From: fliep Date: Fri, 22 May 2009 15:38:14 +0000 (+0000) Subject: Added Nitfol and Frotz source code. X-Git-Tag: v0.9~394 X-Git-Url: https://git.stderr.nl/gitweb?a=commitdiff_plain;h=b1f1dc50b22b30c4d7176e1ff7c0805e80fe0724;p=projects%2Fchimara%2Fchimara.git Added Nitfol and Frotz source code. --- diff --git a/Makefile.am b/Makefile.am index c03385d..76d43cd 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ ## Process this file with automake to produce Makefile.in ## Created by Anjuta -SUBDIRS = src po docs +SUBDIRS = src interpreters docs po chimaradocdir = ${prefix}/doc/chimara dist_chimaradoc_DATA = \ diff --git a/configure.ac b/configure.ac index 191b583..6f2c579 100644 --- a/configure.ac +++ b/configure.ac @@ -76,6 +76,9 @@ Makefile chimara.pc chimara-plugin.pc src/Makefile +interpreters/Makefile +interpreters/frotz/Makefile +interpreters/nitfol/Makefile docs/Makefile docs/reference/Makefile docs/reference/version.xml diff --git a/interpreters/Makefile.am b/interpreters/Makefile.am new file mode 100644 index 0000000..e1407a3 --- /dev/null +++ b/interpreters/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=nitfol frotz diff --git a/interpreters/frotz/AUTHORS b/interpreters/frotz/AUTHORS new file mode 100644 index 0000000..9e4139d --- /dev/null +++ b/interpreters/frotz/AUTHORS @@ -0,0 +1,32 @@ +Original Frotz reference code: + Stefan Jokisch + +Original Unix port: + Galen Hazelwood + +New Frotz reference code: + Jim Dunleavy + David Griffith + +New Unix port: + David Griffith + +V6 semi-support: + Alembic Petrofsky + +OSS sound support (from xfrotz 2.32.1): + Daniel Schepler + + +Thanks also to those who posted to rec.arts.int-fiction feedback on what I +was doing with Unix Frotz, people who checked the betas for bugs, and sent +in patches. These include, but are not limited to: + +Torbjorn Anderson, Timo Korvola, Martin Frost, Mihail Milushev, David +Picton, Chris Sullivan, Leonard Richardson, Stephen Kitt, Paul E Coad, +Paul Janzen, Brad Town, Jason C Penney, Denis Hirschfeldt, Jacob Nevins, +Matteo De Luigi, Steven Frank, Thomas Troeger, David Kinder, and others +that I've forgotten. + +Michael Edmonson (author of Rezrov) and Evin Robertson (author of Nitfol) +deserve recognition for the ideas that I've borrowed from their programs. diff --git a/interpreters/frotz/COPYING b/interpreters/frotz/COPYING new file mode 100644 index 0000000..a43ea21 --- /dev/null +++ b/interpreters/frotz/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/interpreters/frotz/Makefile.am b/interpreters/frotz/Makefile.am new file mode 100644 index 0000000..cd54897 --- /dev/null +++ b/interpreters/frotz/Makefile.am @@ -0,0 +1,14 @@ +PLUGIN_LIBTOOL_FLAGS=-module -avoid-version -export-symbols-regex "^glk_main$$" + +pkglib_LTLIBRARIES = frotz.la +frotz_la_SOURCES = buffer.c err.c fastmem.c files.c input.c main.c math.c \ + object.c process.c quetzal.c random.c redirect.c sound.c stream.c table.c \ + text.c variable.c glkscreen.c glkmisc.c frotz.h glkfrotz.h glkio.h setup.h +frotz_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) + +# TODO: Remove this later, see issue #6 +frotz_la_CFLAGS = -I../../src + +frotzdocdir = $(datadir)/doc/$(PACKAGE)/frotz +dist_frotzdoc_DATA = AUTHORS COPYING README TODO + diff --git a/interpreters/frotz/README b/interpreters/frotz/README new file mode 100644 index 0000000..970146b --- /dev/null +++ b/interpreters/frotz/README @@ -0,0 +1,79 @@ +GlkFrotz v2.43 - An interpreter for most Infocom and other Z-machine games. +Glk port created and maintained by Tor Andersson. + +Read more on the web page: + +http://ghostscript.com/~tor/software/gargoyle/frotz.html + +Requires one non-standard Glk function. Add it to your Glk before +you try to compile, unless you are using Gargoyle: + + char * garglk_fileref_get_name(frefid_t fref); + +--- Original readme --- + +FROTZ V2.43 - An interpreter for all Infocom and other Z-machine games. +Complies with standard 1.0 of Graham Nelson's specification. + +Originally written by Stefan Jokisch in 1995-1997. +Ported to Unix by Galen Hazelwood. +Reference code and Unix port currently maintained by David Griffith. + +- Compiles and runs on most common flavors of Unix, both open source and not. +- Plays all Z-code games including V6. +- Old-style sound support through OSS driver. +- Config files. +- Configurable error checking. +- Default use of the Quetzal file format. Command line option to use the + old format. There are several differences between the old-style save + format at Quetzal such that converting from old-style and Quetzal is + difficult if not impossible. This also means you can't restore an + old-style save and then save your game in Quetzal. +- Optional speech synthesis and recognition through FLITE and Sphinx. +- Distributed under the GNU Public License. + + +For information on what Interactive Fiction is and how to play it, see the +file "HOW_TO_PLAY". + +For installation information, see the file "INSTALL". + +For information on the speech synthesis and recognition capabilities of +Frotz, see the file "SPEECH". + +For update history, see the file "Changelog". + +For information on known bugs in Frotz, see the file "BUGS". + +For bug reports, check the Unix Frotz website to see if there's a new +release. If not, send your bug report to me at dgriffi@cs.csubak.edu. + +For information on porting Frotz to new platforms, see the file "PORTING". + +For those who are involved in creating and distributing binary packages +containing Frotz and including Frotz in BSD-style ports/pkgsrc trees, +see the file "PACKAGING". + +The latest information on Unix Frotz is available at the Unix Frotz +homepage at http://www.cs.csubak.edu/~dgriffi/proj/frotz/. + +The latest release of Unix Frotz is available from the Interactive +Fiction Archive at: +http://www.ifarchive.org/if-archive/infocom/interpreters/ + frotz/frotz-.tar.gz +and +ftp://ftp.ifarchive.org/if-archive/infocom/interpreters/ + frotz/frotz-.tar.gz + +The Interactive Fiction Archive has several mirrors which may be better +choices depending on where you live. Here is a partial list. + +http://www.ifarchive.org/ (USA, Pittsburgh, PA) +http://mirror.ifarchive.org/ (USA) +ftp://ftp.ifarchive.com/if-archive/ (USA, Los Angeles, CA) +ftp://ftp.guetech.org/if-archive/ (USA, Bremerton, WA) +ftp://ftp.plover.net/if-archive/ (USA, Chicago, IL) +ftp://ftp.funet.fi/pub/misc/if-archive/ (Finland) +http://www.planetmirror.com/pub/if-archive/ (Australia) +ftp://ftp.planetmirror.com/pub/if-archive/ (Australia) + diff --git a/interpreters/frotz/TODO b/interpreters/frotz/TODO new file mode 100644 index 0000000..bc19a4d --- /dev/null +++ b/interpreters/frotz/TODO @@ -0,0 +1,4 @@ +workaround buggy games that draw too wide status bars: + save text for line 0 into buffer + compress middle spaces if it overflows, and reprint line 0 + diff --git a/interpreters/frotz/buffer.c b/interpreters/frotz/buffer.c new file mode 100644 index 0000000..f3a721e --- /dev/null +++ b/interpreters/frotz/buffer.c @@ -0,0 +1,153 @@ +/* buffer.c - Text buffering and word wrapping + * Copyright (c) 2003 Tor Andersson -- zapped! + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +extern void stream_char (zchar); +extern void stream_word (const zchar *); +extern void stream_new_line (void); + +static zchar buffer[TEXT_BUFFER_SIZE]; +static int bufpos = 0; + +static zchar prev_c = 0; + +/* + * flush_buffer + * + * Copy the contents of the text buffer to the output streams. + * + */ + +void flush_buffer (void) +{ + static bool locked = FALSE; + + /* Make sure we stop when flush_buffer is called from flush_buffer. + Note that this is difficult to avoid as we might print a newline + during flush_buffer, which might cause a newline interrupt, that + might execute any arbitrary opcode, which might flush the buffer. */ + + if (locked || bufpos == 0) + return; + + /* Send the buffer to the output streams */ + + buffer[bufpos] = 0; + + + locked = TRUE; + + stream_word (buffer); + +#ifdef SPEECH_OUTPUT + os_speech_output(buffer); +#endif + + locked = FALSE; + + /* Reset the buffer */ + + bufpos = 0; + prev_c = 0; + +}/* flush_buffer */ + +/* + * print_char + * + * High level output function. + * + */ + +void print_char (zchar c) +{ + static bool flag = FALSE; + + if (message || ostream_memory || enable_buffering) { + + if (!flag) { + + /* Characters 0 and ZC_RETURN are special cases */ + + if (c == ZC_RETURN) + { new_line (); return; } + if (c == 0) + return; + + /* Flush the buffer before a whitespace or after a hyphen */ + + if (c == ' ' || c == ZC_INDENT || c == ZC_GAP || (prev_c == '-' && c != '-')) + + + flush_buffer (); + + /* Set the flag if this is part one of a style or font change */ + + if (c == ZC_NEW_FONT || c == ZC_NEW_STYLE) + flag = TRUE; + + /* Remember the current character code */ + + prev_c = c; + + } else flag = FALSE; + + /* Insert the character into the buffer */ + + buffer[bufpos++] = c; + + if (bufpos == TEXT_BUFFER_SIZE) + runtime_error (ERR_TEXT_BUF_OVF); + + } else stream_char (c); + +}/* print_char */ + +/* + * new_line + * + * High level newline function. + * + */ + +void new_line (void) +{ + + flush_buffer (); stream_new_line (); + +}/* new_line */ + + +/* + * init_buffer + * + * Initialize buffer variables. + * + */ + +void init_buffer(void) +{ + memset(buffer, 0, sizeof (zchar) * TEXT_BUFFER_SIZE); + bufpos = 0; + prev_c = 0; +} + diff --git a/interpreters/frotz/err.c b/interpreters/frotz/err.c new file mode 100644 index 0000000..61ca78c --- /dev/null +++ b/interpreters/frotz/err.c @@ -0,0 +1,154 @@ +/* err.c - Runtime error reporting functions + * Written by Jim Dunleavy + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* Define stuff for stricter Z-code error checking, for the generic + Unix/DOS/etc terminal-window interface. Feel free to change the way + player prefs are specified, or replace report_zstrict_error() + completely if you want to change the way errors are reported. */ + +/* int err_report_mode = ERR_DEFAULT_REPORT_MODE; */ + +static int error_count[ERR_NUM_ERRORS]; + +static char *err_messages[] = { + "Text buffer overflow", + "Store out of dynamic memory", + "Division by zero", + "Illegal object", + "Illegal attribute", + "No such property", + "Stack overflow", + "Call to illegal address", + "Call to non-routine", + "Stack underflow", + "Illegal opcode", + "Bad stack frame", + "Jump to illegal address", + "Can't save while in interrupt", + "Nesting stream #3 too deep", + "Illegal window", + "Illegal window property", + "Print at illegal address", + "@jin called with object 0", + "@get_child called with object 0", + "@get_parent called with object 0", + "@get_sibling called with object 0", + "@get_prop_addr called with object 0", + "@get_prop called with object 0", + "@put_prop called with object 0", + "@clear_attr called with object 0", + "@set_attr called with object 0", + "@test_attr called with object 0", + "@move_object called moving object 0", + "@move_object called moving into object 0", + "@remove_object called with object 0", + "@get_next_prop called with object 0" +}; + +static void print_long (unsigned long value, int base); + +/* + * init_err + * + * Initialise error reporting. + * + */ + +void init_err (void) +{ + int i; + + /* Initialize the counters. */ + + for (i = 0; i < ERR_NUM_ERRORS; i++) + error_count[i] = 0; +} + +/* + * runtime_error + * + * An error has occurred. Ignore it, pass it to os_fatal or report + * it according to err_report_mode. + * + * errnum : Numeric code for error (1 to ERR_NUM_ERRORS) + * + */ + +void runtime_error (int errnum) +{ + int wasfirst; + + if (errnum <= 0 || errnum > ERR_NUM_ERRORS) + return; + + if (f_setup.err_report_mode == ERR_REPORT_FATAL + || (!f_setup.ignore_errors && errnum <= ERR_MAX_FATAL)) { + flush_buffer (); + os_fatal (err_messages[errnum - 1]); + return; + } + + wasfirst = (error_count[errnum - 1] == 0); + error_count[errnum - 1]++; + + if ((f_setup.err_report_mode == ERR_REPORT_ALWAYS) + || (f_setup.err_report_mode == ERR_REPORT_ONCE && wasfirst)) { + long pc; + + GET_PC (pc); + print_string ("Warning: "); + print_string (err_messages[errnum - 1]); + print_string (" (PC = "); + print_long (pc, 16); + print_char (')'); + + if (f_setup.err_report_mode == ERR_REPORT_ONCE) { + print_string (" (will ignore further occurrences)"); + } else { + print_string (" (occurence "); + print_long (error_count[errnum - 1], 10); + print_char (')'); + } + new_line (); + } + +} /* report_error */ + +/* + * print_long + * + * Print an unsigned 32bit number in decimal or hex. + * + */ + +static void print_long (unsigned long value, int base) +{ + unsigned long i; + char c; + + for (i = (base == 10 ? 1000000000 : 0x10000000); i != 0; i /= base) + if (value >= i || i == 1) { + c = (value / i) % base; + print_char (c + (c <= 9 ? '0' : 'a' - 10)); + } + +}/* print_long */ diff --git a/interpreters/frotz/fastmem.c b/interpreters/frotz/fastmem.c new file mode 100644 index 0000000..113c50c --- /dev/null +++ b/interpreters/frotz/fastmem.c @@ -0,0 +1,1061 @@ +/* fastmem.c - Memory related functions (fast version without virtual memory) + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* + * New undo mechanism added by Jim Dunleavy + */ + +/* + * Glk and Blorb support added by Tor Andersson. + */ + +#include "frotz.h" + +#include "glk.h" +#include "glkio.h" +#include "glkstart.h" +#include "gi_blorb.h" + +extern void seed_random (int); +extern void restart_screen (void); +extern void refresh_text_style (void); +extern void call (zword, int, zword *, int); +extern void split_window (zword); +extern void script_open (void); +extern void script_close (void); + +extern zword save_quetzal (FILE *, FILE *, int); +extern zword restore_quetzal (FILE *, FILE *, int); + +extern void erase_window (zword); + +extern void (*op0_opcodes[]) (void); +extern void (*op1_opcodes[]) (void); +extern void (*op2_opcodes[]) (void); +extern void (*var_opcodes[]) (void); + +zbyte *zmp = NULL; +zbyte *pcp = NULL; + +static FILE *story_fp = NULL; +static size_t blorb_ofs = 0; +static size_t blorb_len = 0; + +/* + * Data for the undo mechanism. + * This undo mechanism is based on the scheme used in Evin Robertson's + * Nitfol interpreter. + * Undo blocks are stored as differences between states. + */ + +typedef struct undo_struct undo_t; +struct undo_struct { + undo_t *next; + undo_t *prev; + long pc; + long diff_size; + zword frame_count; + zword stack_size; + zword frame_offset; + /* undo diff and stack data follow */ +}; + +static undo_t *first_undo = NULL, *last_undo = NULL, *curr_undo = NULL; +static zbyte *undo_mem = NULL, *prev_zmp, *undo_diff; + +static int undo_count = 0; + +/* + * get_header_extension + * + * Read a value from the header extension (former mouse table). + * + */ + +zword get_header_extension (int entry) +{ + zword addr; + zword val; + + if (h_extension_table == 0 || entry > hx_table_size) + return 0; + + addr = h_extension_table + 2 * entry; + LOW_WORD (addr, val); + + return val; + +}/* get_header_extension */ + +/* + * set_header_extension + * + * Set an entry in the header extension (former mouse table). + * + */ + +void set_header_extension (int entry, zword val) +{ + zword addr; + + if (h_extension_table == 0 || entry > hx_table_size) + return; + + addr = h_extension_table + 2 * entry; + SET_WORD (addr, val); + +}/* set_header_extension */ + +/* + * restart_header + * + * Set all header fields which hold information about the interpreter. + * + */ + +void restart_header (void) +{ + zword screen_x_size; + zword screen_y_size; + zbyte font_x_size; + zbyte font_y_size; + + int i; + + SET_BYTE (H_CONFIG, h_config); + SET_WORD (H_FLAGS, h_flags); + + if (h_version >= V4) { + SET_BYTE (H_INTERPRETER_NUMBER, h_interpreter_number); + SET_BYTE (H_INTERPRETER_VERSION, h_interpreter_version); + SET_BYTE (H_SCREEN_ROWS, h_screen_rows); + SET_BYTE (H_SCREEN_COLS, h_screen_cols); + } + + /* It's less trouble to use font size 1x1 for V5 games, especially + because of a bug in the unreleased German version of "Zork 1" */ + + if (h_version != V6) { + screen_x_size = (zword) h_screen_cols; + screen_y_size = (zword) h_screen_rows; + font_x_size = 1; + font_y_size = 1; + } else { + screen_x_size = h_screen_width; + screen_y_size = h_screen_height; + font_x_size = h_font_width; + font_y_size = h_font_height; + } + + if (h_version >= V5) { + SET_WORD (H_SCREEN_WIDTH, screen_x_size); + SET_WORD (H_SCREEN_HEIGHT, screen_y_size); + SET_BYTE (H_FONT_HEIGHT, font_y_size); + SET_BYTE (H_FONT_WIDTH, font_x_size); + SET_BYTE (H_DEFAULT_BACKGROUND, h_default_background); + SET_BYTE (H_DEFAULT_FOREGROUND, h_default_foreground); + } + + if (h_version == V6) + for (i = 0; i < 8; i++) + storeb ((zword) (H_USER_NAME + i), h_user_name[i]); + + SET_BYTE (H_STANDARD_HIGH, h_standard_high); + SET_BYTE (H_STANDARD_LOW, h_standard_low); + +}/* restart_header */ + +/* + * init_memory + * + * Allocate memory and load the story file. + * + */ + +void init_memory (void) +{ + long size; + zword addr; + unsigned n; + int i, j; + + static struct { + enum story story_id; + zword release; + zbyte serial[6]; + } records[] = { + { SHERLOCK, 21, "871214" }, + { SHERLOCK, 26, "880127" }, + { BEYOND_ZORK, 47, "870915" }, + { BEYOND_ZORK, 49, "870917" }, + { BEYOND_ZORK, 51, "870923" }, + { BEYOND_ZORK, 57, "871221" }, + { ZORK_ZERO, 296, "881019" }, + { ZORK_ZERO, 366, "890323" }, + { ZORK_ZERO, 383, "890602" }, + { ZORK_ZERO, 393, "890714" }, + { SHOGUN, 292, "890314" }, + { SHOGUN, 295, "890321" }, + { SHOGUN, 311, "890510" }, + { SHOGUN, 322, "890706" }, + { ARTHUR, 54, "890606" }, + { ARTHUR, 63, "890622" }, + { ARTHUR, 74, "890714" }, + { JOURNEY, 26, "890316" }, + { JOURNEY, 30, "890322" }, + { JOURNEY, 77, "890616" }, + { JOURNEY, 83, "890706" }, + { LURKING_HORROR, 203, "870506" }, + { LURKING_HORROR, 219, "870912" }, + { LURKING_HORROR, 221, "870918" }, + { UNKNOWN, 0, "------" } + }; + + /* Open story file */ + { + giblorb_map_t *map; + giblorb_result_t res; + char magic[4] = "XXXX"; + strid_t file; + + if ((file = glkunix_stream_open_pathname(story_name, 0, 0)) == NULL) + os_fatal ("Cannot open story file"); + + fread(magic, 1, 4, file); + + if (!memcmp(magic, "FORM", 4)) + { + if (giblorb_set_resource_map(file)) + os_fatal("This Blorb file seems to be invalid."); + + map = giblorb_get_resource_map(); + + if (giblorb_load_resource(map, giblorb_method_FilePos, + &res, giblorb_ID_Exec, 0)) + os_fatal("This Blorb file does not contain an executable chunk."); + if (res.chunktype != giblorb_make_id('Z','C','O','D')) + os_fatal("This Blorb file contains an executable chunk, but it is not a Z-code file."); + + story_fp = file; + blorb_ofs = res.data.startpos; + blorb_len = res.length; + } + else + { + story_fp = file; + blorb_ofs = 0; + fseek(story_fp, 0, SEEK_END); + blorb_len = ftell(story_fp); + } + + } + + if (blorb_len < 64) + os_fatal("This file is too small to be a Z-code file."); + + /* Allocate memory for story header */ + + if ((zmp = (zbyte *) malloc (64)) == NULL) + os_fatal ("Out of memory"); + + /* Load header into memory */ + + fseek(story_fp, blorb_ofs, 0); + + if (fread (zmp, 1, 64, story_fp) != 64) + os_fatal ("Story file read error"); + + /* Copy header fields to global variables */ + + LOW_BYTE (H_VERSION, h_version); + + if (h_version < V1 || h_version > V8) + os_fatal ("Unknown Z-code version"); + + if (h_version == V6) + os_fatal ("Cannot play Z-code version 6"); + + LOW_BYTE (H_CONFIG, h_config); + + if (h_version == V3 && (h_config & CONFIG_BYTE_SWAPPED)) + os_fatal ("Byte swapped story file"); + + LOW_WORD (H_RELEASE, h_release); + LOW_WORD (H_RESIDENT_SIZE, h_resident_size); + LOW_WORD (H_START_PC, h_start_pc); + LOW_WORD (H_DICTIONARY, h_dictionary); + LOW_WORD (H_OBJECTS, h_objects); + LOW_WORD (H_GLOBALS, h_globals); + LOW_WORD (H_DYNAMIC_SIZE, h_dynamic_size); + LOW_WORD (H_FLAGS, h_flags); + + for (i = 0, addr = H_SERIAL; i < 6; i++, addr++) + LOW_BYTE (addr, h_serial[i]); + + /* Auto-detect buggy story files that need special fixes */ + + story_id = UNKNOWN; + + for (i = 0; records[i].story_id != UNKNOWN; i++) { + + if (h_release == records[i].release) { + + for (j = 0; j < 6; j++) + if (h_serial[j] != records[i].serial[j]) + goto no_match; + + story_id = records[i].story_id; + + } + +no_match: ; /* null statement */ + + } + + LOW_WORD (H_ABBREVIATIONS, h_abbreviations); + LOW_WORD (H_FILE_SIZE, h_file_size); + + /* Calculate story file size in bytes */ + + if (h_file_size != 0) { + + story_size = (long) 2 * h_file_size; + + if (h_version >= V4) + story_size *= 2; + if (h_version >= V6) + story_size *= 2; + + } else { /* some old games lack the file size entry */ + + story_size = blorb_len; + } + + LOW_WORD (H_CHECKSUM, h_checksum); + LOW_WORD (H_ALPHABET, h_alphabet); + LOW_WORD (H_FUNCTIONS_OFFSET, h_functions_offset); + LOW_WORD (H_STRINGS_OFFSET, h_strings_offset); + LOW_WORD (H_TERMINATING_KEYS, h_terminating_keys); + LOW_WORD (H_EXTENSION_TABLE, h_extension_table); + + /* Zork Zero Macintosh doesn't have the graphics flag set */ + + if (story_id == ZORK_ZERO && h_release == 296) + h_flags |= GRAPHICS_FLAG; + + /* Adjust opcode tables */ + + if (h_version <= V4) { + op0_opcodes[0x09] = z_pop; + op1_opcodes[0x0f] = z_not; + } else { + op0_opcodes[0x09] = z_catch; + op1_opcodes[0x0f] = z_call_n; + } + + /* Allocate memory for story data */ + + if ((zmp = (zbyte *) realloc (zmp, story_size)) == NULL) + os_fatal ("Out of memory"); + + /* Load story file in chunks of 32KB */ + + n = 0x8000; + + for (size = 64; size < story_size; size += n) { + + if (story_size - size < 0x8000) + n = (unsigned) (story_size - size); + + SET_PC (size); + + if (fread (pcp, 1, n, story_fp) != n) + os_fatal ("Story file read error"); + + } + + /* Read header extension table */ + + hx_table_size = get_header_extension (HX_TABLE_SIZE); + hx_unicode_table = get_header_extension (HX_UNICODE_TABLE); + +}/* init_memory */ + +/* + * init_undo + * + * Allocate memory for multiple undo. It is important not to occupy + * all the memory available, since the IO interface may need memory + * during the game, e.g. for loading sounds or pictures. + * + */ + +void init_undo (void) +{ + void *reserved; + + reserved = NULL; /* makes compilers shut up */ + + if (reserve_mem != 0) { + if ((reserved = malloc (reserve_mem)) == NULL) + return; + } + + /* Allocate h_dynamic_size bytes for previous dynamic zmp state + + 1.5 h_dynamic_size for Quetzal diff + 2. */ + undo_mem = malloc ((h_dynamic_size * 5) / 2 + 2); + if (undo_mem != NULL) { + prev_zmp = undo_mem; + undo_diff = undo_mem + h_dynamic_size; + memcpy (prev_zmp, zmp, h_dynamic_size); + } else + f_setup.undo_slots = 0; + + if (reserve_mem != 0) + free (reserved); + +}/* init_undo */ + +/* + * free_undo + * + * Free count undo blocks from the beginning of the undo list. + * + */ + +static void free_undo (int count) +{ + undo_t *p; + + if (count > undo_count) + count = undo_count; + while (count--) { + p = first_undo; + if (curr_undo == first_undo) + curr_undo = curr_undo->next; + first_undo = first_undo->next; + free (p); + undo_count--; + } + if (first_undo) + first_undo->prev = NULL; + else + last_undo = NULL; +}/* free_undo */ + +/* + * reset_memory + * + * Close the story file and deallocate memory. + * + */ + +void reset_memory (void) +{ + if (story_fp) + fclose (story_fp); + story_fp = NULL; + blorb_ofs = 0; + blorb_len = 0; + + if (undo_mem) { + free_undo (undo_count); + free (undo_mem); + } + + undo_mem = NULL; + undo_count = 0; + + if (zmp) + free (zmp); + zmp = NULL; +}/* reset_memory */ + +/* + * storeb + * + * Write a byte value to the dynamic Z-machine memory. + * + */ + +void storeb (zword addr, zbyte value) +{ + + if (addr >= h_dynamic_size) + runtime_error (ERR_STORE_RANGE); + + if (addr == H_FLAGS + 1) { /* flags register is modified */ + + h_flags &= ~(SCRIPTING_FLAG | FIXED_FONT_FLAG); + h_flags |= value & (SCRIPTING_FLAG | FIXED_FONT_FLAG); + + if (value & SCRIPTING_FLAG) { + if (!ostream_script) + script_open (); + } else { + if (ostream_script) + script_close (); + } + + /* TOR - glkified / refresh_text_style (); */ + + } + + SET_BYTE (addr, value); + +}/* storeb */ + +/* + * storew + * + * Write a word value to the dynamic Z-machine memory. + * + */ + +void storew (zword addr, zword value) +{ + + storeb ((zword) (addr + 0), hi (value)); + storeb ((zword) (addr + 1), lo (value)); + +}/* storew */ + +/* + * z_restart, re-load dynamic area, clear the stack and set the PC. + * + * no zargs used + * + */ + +void z_restart (void) +{ + static bool first_restart = TRUE; + + flush_buffer (); + + os_restart_game (RESTART_BEGIN); + + seed_random (0); + + if (!first_restart) { + + fseek (story_fp, blorb_ofs, SEEK_SET); + + if (fread (zmp, 1, h_dynamic_size, story_fp) != h_dynamic_size) + os_fatal ("Story file read error"); + + } else first_restart = FALSE; + + restart_header (); + restart_screen (); + + sp = fp = stack + STACK_SIZE; + frame_count = 0; + + if (h_version != V6) { + + long pc = (long) h_start_pc; + SET_PC (pc); + + } else call (h_start_pc, 0, NULL, 0); + + os_restart_game (RESTART_END); + +}/* z_restart */ + +/* + * z_restore, restore [a part of] a Z-machine state from disk + * + * zargs[0] = address of area to restore (optional) + * zargs[1] = number of bytes to restore + * zargs[2] = address of suggested file name + * + */ + +void z_restore (void) +{ + FILE *gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Get the file name */ + + /* Open auxilary file */ + + if ((gfp = frotzopenprompt(FILE_LOAD_AUX)) == NULL) + goto finished; + + /* Load auxilary file */ + + success = fread (zmp + zargs[0], 1, zargs[1], gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + long pc; + zword release; + zword addr; + int i; + + /* Open game file */ + + if ((gfp = frotzopenprompt(FILE_RESTORE)) == NULL) + goto finished; + + if (f_setup.save_quetzal) { + success = restore_quetzal (gfp, story_fp, blorb_ofs); + + } else { + /* Load game file */ + + release = (unsigned) fgetc (gfp) << 8; + release |= fgetc (gfp); + + (void) fgetc (gfp); + (void) fgetc (gfp); + + /* Check the release number */ + + if (release == h_release) { + + pc = (long) fgetc (gfp) << 16; + pc |= (unsigned) fgetc (gfp) << 8; + pc |= fgetc (gfp); + + SET_PC (pc); + + sp = stack + (fgetc (gfp) << 8); + sp += fgetc (gfp); + fp = stack + (fgetc (gfp) << 8); + fp += fgetc (gfp); + + for (i = (int) (sp - stack); i < STACK_SIZE; i++) { + stack[i] = (unsigned) fgetc (gfp) << 8; + stack[i] |= fgetc (gfp); + } + + fseek (story_fp, blorb_ofs, SEEK_SET); + + for (addr = 0; addr < h_dynamic_size; addr++) { + int skip = fgetc (gfp); + for (i = 0; i < skip; i++) + zmp[addr++] = fgetc (story_fp); + zmp[addr] = fgetc (gfp); + (void) fgetc (story_fp); + } + + /* Check for errors */ + + if (ferror (gfp) || ferror (story_fp) || addr != h_dynamic_size) + success = -1; + else + + /* Success */ + + success = 2; + + } else print_string ("Invalid save file\n"); + } + + if ((short) success >= 0) { + + /* Close game file */ + + fclose (gfp); + + if ((short) success > 0) { + zbyte old_screen_rows; + zbyte old_screen_cols; + + /* In V3, reset the upper window. */ + if (h_version == V3) + split_window (0); + + LOW_BYTE (H_SCREEN_ROWS, old_screen_rows); + LOW_BYTE (H_SCREEN_COLS, old_screen_cols); + + /* Reload cached header fields. */ + restart_header (); + + /* + * Since QUETZAL files may be saved on many different machines, + * the screen sizes may vary a lot. Erasing the status window + * seems to cover up most of the resulting badness. + */ + if (h_version > V3 && h_version != V6 + && (h_screen_rows != old_screen_rows + || h_screen_cols != old_screen_cols)) + erase_window (1); + } + } else + os_fatal ("Error reading save file"); + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); + +}/* z_restore */ + +/* + * mem_diff + * + * Set diff to a Quetzal-like difference between a and b, + * copying a to b as we go. It is assumed that diff points to a + * buffer which is large enough to hold the diff. + * mem_size is the number of bytes to compare. + * Returns the number of bytes copied to diff. + * + */ + +static long mem_diff (zbyte *a, zbyte *b, zword mem_size, zbyte *diff) +{ + unsigned size = mem_size; + zbyte *p = diff; + unsigned j; + zbyte c; + + for (;;) { + for (j = 0; size > 0 && (c = *a++ ^ *b++) == 0; j++) + size--; + if (size == 0) break; + size--; + if (j > 0x8000) { + *p++ = 0; + *p++ = 0xff; + *p++ = 0xff; + j -= 0x8000; + } + if (j > 0) { + *p++ = 0; + j--; + if (j <= 0x7f) { + *p++ = j; + } else { + *p++ = (j & 0x7f) | 0x80; + *p++ = (j & 0x7f80) >> 7; + } + } + *p++ = c; + *(b - 1) ^= c; + } + return p - diff; +}/* mem_diff */ + +/* + * mem_undiff + * + * Applies a quetzal-like diff to dest + * + */ + +static void mem_undiff (zbyte *diff, long diff_length, zbyte *dest) +{ + zbyte c; + + while (diff_length) { + c = *diff++; + diff_length--; + if (c == 0) { + unsigned runlen; + + if (!diff_length) + return; /* Incomplete run */ + runlen = *diff++; + diff_length--; + if (runlen & 0x80) { + if (!diff_length) + return; /* Incomplete extended run */ + c = *diff++; + diff_length--; + runlen = (runlen & 0x7f) | (((unsigned) c) << 7); + } + + dest += runlen + 1; + } else { + *dest++ ^= c; + } + } +}/* mem_undiff */ + +/* + * restore_undo + * + * This function does the dirty work for z_restore_undo. + * + */ + +int restore_undo (void) +{ + + if (f_setup.undo_slots == 0) /* undo feature unavailable */ + + return -1; + + if (curr_undo == NULL) /* no saved game state */ + + return 0; + + /* undo possible */ + + memcpy (zmp, prev_zmp, h_dynamic_size); + SET_PC (curr_undo->pc); + sp = stack + STACK_SIZE - curr_undo->stack_size; + fp = stack + curr_undo->frame_offset; + frame_count = curr_undo->frame_count; + mem_undiff ((zbyte *) (curr_undo + 1), curr_undo->diff_size, prev_zmp); + memcpy (sp, (zbyte *)(curr_undo + 1) + curr_undo->diff_size, + curr_undo->stack_size * sizeof (*sp)); + + curr_undo = curr_undo->prev; + + restart_header (); + + return 2; + +}/* restore_undo */ + +/* + * z_restore_undo, restore a Z-machine state from memory. + * + * no zargs used + * + */ + +void z_restore_undo (void) +{ + + store ((zword) restore_undo ()); + +}/* z_restore_undo */ + +/* + * z_save, save [a part of] the Z-machine state to disk. + * + * zargs[0] = address of memory area to save (optional) + * zargs[1] = number of bytes to save + * zargs[2] = address of suggested file name + * + */ + +void z_save (void) +{ + FILE *gfp; + + zword success = 0; + + if (zargc != 0) { + + /* Open auxilary file */ + + if ((gfp = frotzopenprompt (FILE_SAVE_AUX)) == NULL) + goto finished; + + /* Write auxilary file */ + + success = fwrite (zmp + zargs[0], zargs[1], 1, gfp); + + /* Close auxilary file */ + + fclose (gfp); + + } else { + + long pc; + zword addr; + zword nsp, nfp; + int skip; + int i; + + /* Open game file */ + + if ((gfp = frotzopenprompt (FILE_SAVE)) == NULL) + goto finished; + + if (f_setup.save_quetzal) { + success = save_quetzal (gfp, story_fp, blorb_ofs); + } else { + /* Write game file */ + + fputc ((int) hi (h_release), gfp); + fputc ((int) lo (h_release), gfp); + fputc ((int) hi (h_checksum), gfp); + fputc ((int) lo (h_checksum), gfp); + + GET_PC (pc) + + fputc ((int) (pc >> 16) & 0xff, gfp); + fputc ((int) (pc >> 8) & 0xff, gfp); + fputc ((int) (pc) & 0xff, gfp); + + nsp = (int) (sp - stack); + nfp = (int) (fp - stack); + + fputc ((int) hi (nsp), gfp); + fputc ((int) lo (nsp), gfp); + fputc ((int) hi (nfp), gfp); + fputc ((int) lo (nfp), gfp); + + for (i = nsp; i < STACK_SIZE; i++) { + fputc ((int) hi (stack[i]), gfp); + fputc ((int) lo (stack[i]), gfp); + } + + fseek (story_fp, blorb_ofs, SEEK_SET); + + for (addr = 0, skip = 0; addr < h_dynamic_size; addr++) + if (zmp[addr] != fgetc (story_fp) || skip == 255 || addr + 1 == h_dynamic_size) { + fputc (skip, gfp); + fputc (zmp[addr], gfp); + skip = 0; + } else skip++; + } + + /* Close game file and check for errors */ + + if (fclose (gfp) == EOF || ferror (story_fp)) { + print_string ("Error writing save file\n"); + goto finished; + } + + /* Success */ + + success = 1; + + } + +finished: + + if (h_version <= V3) + branch (success); + else + store (success); + +}/* z_save */ + +/* + * save_undo + * + * This function does the dirty work for z_save_undo. + * + */ + +int save_undo (void) +{ + long diff_size; + zword stack_size; + undo_t *p; + + if (f_setup.undo_slots == 0) /* undo feature unavailable */ + return -1; + + /* save undo possible */ + + while (last_undo != curr_undo) { + p = last_undo; + last_undo = last_undo->prev; + free (p); + undo_count--; + } + if (last_undo) + last_undo->next = NULL; + else + first_undo = NULL; + + if (undo_count == f_setup.undo_slots) + free_undo (1); + + diff_size = mem_diff (zmp, prev_zmp, h_dynamic_size, undo_diff); + stack_size = stack + STACK_SIZE - sp; + do { + p = malloc (sizeof (undo_t) + diff_size + stack_size * sizeof (*sp)); + if (p == NULL) + free_undo (1); + } while (!p && undo_count); + if (p == NULL) + return -1; + GET_PC (p->pc) + p->frame_count = frame_count; + p->diff_size = diff_size; + p->stack_size = stack_size; + p->frame_offset = fp - stack; + memcpy (p + 1, undo_diff, diff_size); + memcpy ((zbyte *)(p + 1) + diff_size, sp, stack_size * sizeof (*sp)); + + if (!first_undo) { + p->prev = NULL; + first_undo = p; + } else { + last_undo->next = p; + p->prev = last_undo; + } + p->next = NULL; + curr_undo = last_undo = p; + undo_count++; + return 1; + +}/* save_undo */ + +/* + * z_save_undo, save the current Z-machine state for a future undo. + * + * no zargs used + * + */ + +void z_save_undo (void) +{ + + store ((zword) save_undo ()); + +}/* z_save_undo */ + +/* + * z_verify, check the story file integrity. + * + * no zargs used + * + */ + +void z_verify (void) +{ + zword checksum = 0; + long i; + + /* Sum all bytes in story file except header bytes */ + + fseek (story_fp, blorb_ofs + 64, SEEK_SET); + + for (i = 64; i < story_size; i++) + checksum += fgetc (story_fp); + + /* Branch if the checksums are equal */ + + branch (checksum == h_checksum); + +}/* z_verify */ diff --git a/interpreters/frotz/files.c b/interpreters/frotz/files.c new file mode 100644 index 0000000..6ed8c33 --- /dev/null +++ b/interpreters/frotz/files.c @@ -0,0 +1,525 @@ +/* files.c - Transscription, recording and playback + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" +#include "glk.h" +#include "glkio.h" + +extern void set_more_prompts (bool); + +extern bool is_terminator (zchar); + +extern bool read_yes_or_no (const char *); + +static int script_width = 0; + +static FILE *sfp = NULL; +static FILE *rfp = NULL; +static FILE *pfp = NULL; + +/* + * script_open + * + * Open the transscript file. 'AMFV' makes this more complicated as it + * turns transscription on/off several times to exclude some text from + * the transscription file. This wasn't a problem for the original V4 + * interpreters which always sent transscription to the printer, but it + * means a problem to modern interpreters that offer to open a new file + * every time transscription is turned on. Our solution is to append to + * the old transscription file in V1 to V4, and to ask for a new file + * name in V5+. + * + * Alas, we cannot do this, since glk cannot give us the filename + * to reopen it again, and I dont want to mess with filerefs here. + * + */ + +void script_open (void) +{ + static bool script_valid = FALSE; + + h_flags &= ~SCRIPTING_FLAG; + +#if 0 + if (h_version >= V5 || !script_valid) { + if (!os_read_file_name (new_name, script_name, FILE_SCRIPT)) + goto done; + } + + /* Opening in "at" mode doesn't work for script_erase_input... */ + + if ((sfp = fopen (sfp = fopen (script_name, "r+t")) != NULL || (sfp = fopen (script_name, "w+t")) != NULL) { + +#endif + + if ((sfp = frotzopenprompt(FILE_SCRIPT)) != NULL) + { + fseek (sfp, 0, SEEK_END); + + h_flags |= SCRIPTING_FLAG; + + script_valid = TRUE; + ostream_script = TRUE; + + script_width = 0; + + } else print_string ("Cannot open file\n"); + +/* done: */ + + SET_WORD (H_FLAGS, h_flags) + +}/* script_open */ + +/* + * script_close + * + * Stop transscription. + * + */ + +void script_close (void) +{ + + h_flags &= ~SCRIPTING_FLAG; + SET_WORD (H_FLAGS, h_flags) + + fclose (sfp); ostream_script = FALSE; + +}/* script_close */ + +/* + * script_new_line + * + * Write a newline to the transscript file. + * + */ + +void script_new_line (void) +{ + + if (fputc ('\n', sfp) == EOF) + script_close (); + + script_width = 0; + +}/* script_new_line */ + +/* + * script_char + * + * Write a single character to the transscript file. + * + */ + +void script_char (zchar c) +{ + + if (c == ZC_INDENT && script_width != 0) + c = ' '; + + if (c == ZC_INDENT) + { script_char (' '); script_char (' '); script_char (' '); return; } + if (c == ZC_GAP) + { script_char (' '); script_char (' '); return; } + + fputc (c, sfp); script_width++; + +}/* script_char */ + +/* + * script_word + * + * Write a string to the transscript file. + * + */ + +void script_word (const zchar *s) +{ + int width; + int i; + + if (*s == ZC_INDENT && script_width != 0) + script_char (*s++); + + for (i = 0, width = 0; s[i] != 0; i++) + + if (s[i] == ZC_NEW_STYLE || s[i] == ZC_NEW_FONT) + i++; + else if (s[i] == ZC_GAP) + width += 3; + else if (s[i] == ZC_INDENT) + width += 2; + else + width += 1; + + if (f_setup.script_cols != 0 && script_width + width > f_setup.script_cols) { + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + s++; + + script_new_line (); + + } + + for (i = 0; s[i] != 0; i++) + + if (s[i] == ZC_NEW_FONT || s[i] == ZC_NEW_STYLE) + i++; + else + script_char (s[i]); + +}/* script_word */ + +/* + * script_write_input + * + * Send an input line to the transscript file. + * + */ + +void script_write_input (const zchar *buf, zchar key) +{ + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + if (f_setup.script_cols != 0 && script_width + width > f_setup.script_cols) + script_new_line (); + + for (i = 0; buf[i] != 0; i++) + script_char (buf[i]); + + if (key == ZC_RETURN) + script_new_line (); + +}/* script_write_input */ + +/* + * script_erase_input + * + * Remove an input line from the transscript file. + * + */ + +void script_erase_input (const zchar *buf) +{ + int width; + int i; + + for (i = 0, width = 0; buf[i] != 0; i++) + width++; + + fseek (sfp, -width, SEEK_CUR); script_width -= width; + +}/* script_erase_input */ + +/* + * script_mssg_on + * + * Start sending a "debugging" message to the transscript file. + * + */ + +void script_mssg_on (void) +{ + + if (script_width != 0) + script_new_line (); + + script_char (ZC_INDENT); + +}/* script_mssg_on */ + +/* + * script_mssg_off + * + * Stop writing a "debugging" message. + * + */ + +void script_mssg_off (void) +{ + + script_new_line (); + +}/* script_mssg_off */ + +/* + * record_open + * + * Open a file to record the player's input. + * + */ + +void record_open (void) +{ + if ((rfp = frotzopenprompt(FILE_RECORD)) != NULL) + ostream_record = TRUE; + else + print_string ("Cannot open file\n"); +} + +/* + * record_close + * + * Stop recording the player's input. + * + */ + +void record_close (void) +{ + + fclose (rfp); ostream_record = FALSE; + +}/* record_close */ + +/* + * record_code + * + * Helper function for record_char. + * + */ + +static void record_code (int c, bool force_encoding) +{ + + if (force_encoding || c == '[' || c < 0x20 || c > 0x7e) { + + int i; + + fputc ('[', rfp); + + for (i = 10000; i != 0; i /= 10) + if (c >= i || i == 1) + fputc ('0' + (c / i) % 10, rfp); + + fputc (']', rfp); + + } else fputc (c, rfp); + +}/* record_code */ + +/* + * record_char + * + * Write a character to the command file. + * + */ + +static void record_char (zchar c) +{ + + if (c != ZC_RETURN) { + if (c < ZC_HKEY_MIN || c > ZC_HKEY_MAX) { + record_code (translate_to_zscii (c), FALSE); + if (c == ZC_SINGLE_CLICK || c == ZC_DOUBLE_CLICK) { + record_code (mouse_x, TRUE); + record_code (mouse_y, TRUE); + } + } else record_code (1000 + c - ZC_HKEY_MIN, TRUE); + } + +}/* record_char */ + +/* + * record_write_key + * + * Copy a keystroke to the command file. + * + */ + +void record_write_key (zchar key) +{ + + record_char (key); + + if (fputc ('\n', rfp) == EOF) + record_close (); + +}/* record_write_key */ + +/* + * record_write_input + * + * Copy a line of input to a command file. + * + */ + +void record_write_input (const zchar *buf, zchar key) +{ + zchar c; + + while ((c = *buf++) != 0) + record_char (c); + + record_char (key); + + if (fputc ('\n', rfp) == EOF) + record_close (); + +}/* record_write_input */ + +/* + * replay_open + * + * Open a file of commands for playback. + * + */ + +void replay_open (void) +{ + if ((pfp = frotzopenprompt(FILE_PLAYBACK)) != NULL) + istream_replay = TRUE; + else + print_string ("Cannot open file\n"); +} + +/* + * replay_close + * + * Stop playback of commands. + * + */ + +void replay_close (void) +{ + fclose (pfp); istream_replay = FALSE; +} + +/* + * replay_code + * + * Helper function for replay_key and replay_line. + * + */ + +static int replay_code (void) +{ + int c; + + if ((c = fgetc (pfp)) == '[') { + + int c2; + + c = 0; + + while ((c2 = fgetc (pfp)) != EOF && c2 >= '0' && c2 <= '9') + c = 10 * c + c2 - '0'; + + return (c2 == ']') ? c : EOF; + + } else return c; + +}/* replay_code */ + +/* + * replay_char + * + * Read a character from the command file. + * + */ + +static zchar replay_char (void) +{ + int c; + + if ((c = replay_code ()) != EOF) { + + if (c != '\n') { + + if (c < 1000) { + + c = translate_from_zscii (c); + + if (c == ZC_SINGLE_CLICK || c == ZC_DOUBLE_CLICK) { + mouse_x = replay_code (); + mouse_y = replay_code (); + } + + return c; + + } else return ZC_HKEY_MIN + c - 1000; + } + + ungetc ('\n', pfp); + + return ZC_RETURN; + + } else return ZC_BAD; + +}/* replay_char */ + +/* + * replay_read_key + * + * Read a keystroke from a command file. + * + */ + +zchar replay_read_key (void) +{ + zchar key; + + key = replay_char (); + + if (fgetc (pfp) != '\n') { + + replay_close (); + return ZC_BAD; + + } else return key; + +}/* replay_read_key */ + +/* + * replay_read_input + * + * Read a line of input from a command file. + * + */ + +zchar replay_read_input (zchar *buf) +{ + zchar c; + + for (;;) { + + c = replay_char (); + + if (c == ZC_BAD || is_terminator (c)) + break; + + *buf++ = c; + + } + + *buf = 0; + + if (fgetc (pfp) != '\n') { + + replay_close (); + return ZC_BAD; + + } else return c; + +}/* replay_read_input */ diff --git a/interpreters/frotz/frotz.h b/interpreters/frotz/frotz.h new file mode 100644 index 0000000..ba800fc --- /dev/null +++ b/interpreters/frotz/frotz.h @@ -0,0 +1,615 @@ +/* + * frotz.h + * + * Global declarations and definitions + * + */ + +typedef int bool; + +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#include +#include + +typedef unsigned char zbyte; +typedef unsigned short zword; + +enum story +{ + BEYOND_ZORK, + SHERLOCK, + ZORK_ZERO, + SHOGUN, + ARTHUR, + JOURNEY, + LURKING_HORROR, + UNKNOWN +}; + +typedef unsigned char zchar; + +/*** Constants that may be set at compile time ***/ + +#ifndef MAX_UNDO_SLOTS +#define MAX_UNDO_SLOTS 500 +#endif +#ifndef MAX_FILE_NAME +#define MAX_FILE_NAME 80 +#endif +#ifndef TEXT_BUFFER_SIZE +#define TEXT_BUFFER_SIZE 200 +#endif +#ifndef INPUT_BUFFER_SIZE +#define INPUT_BUFFER_SIZE 200 +#endif +#ifndef STACK_SIZE +#define STACK_SIZE 60000 +#endif + +#ifndef DEFAULT_SAVE_NAME +#define DEFAULT_SAVE_NAME "story.sav" +#endif +#ifndef DEFAULT_SCRIPT_NAME +#define DEFAULT_SCRIPT_NAME "story.scr" +#endif +#ifndef DEFAULT_COMMAND_NAME +#define DEFAULT_COMMAND_NAME "story.rec" +#endif +#ifndef DEFAULT_AUXILARY_NAME +#define DEFAULT_AUXILARY_NAME "story.aux" +#endif +#ifndef DEFAULT_SAVE_DIR /* DG */ +#define DEFAULT_SAVE_DIR ".frotz-saves" +#endif + +/*** Story file header format ***/ + +#define H_VERSION 0 +#define H_CONFIG 1 +#define H_RELEASE 2 +#define H_RESIDENT_SIZE 4 +#define H_START_PC 6 +#define H_DICTIONARY 8 +#define H_OBJECTS 10 +#define H_GLOBALS 12 +#define H_DYNAMIC_SIZE 14 +#define H_FLAGS 16 +#define H_SERIAL 18 +#define H_ABBREVIATIONS 24 +#define H_FILE_SIZE 26 +#define H_CHECKSUM 28 +#define H_INTERPRETER_NUMBER 30 +#define H_INTERPRETER_VERSION 31 +#define H_SCREEN_ROWS 32 +#define H_SCREEN_COLS 33 +#define H_SCREEN_WIDTH 34 +#define H_SCREEN_HEIGHT 36 +#define H_FONT_HEIGHT 38 /* this is the font width in V5 */ +#define H_FONT_WIDTH 39 /* this is the font height in V5 */ +#define H_FUNCTIONS_OFFSET 40 +#define H_STRINGS_OFFSET 42 +#define H_DEFAULT_BACKGROUND 44 +#define H_DEFAULT_FOREGROUND 45 +#define H_TERMINATING_KEYS 46 +#define H_LINE_WIDTH 48 +#define H_STANDARD_HIGH 50 +#define H_STANDARD_LOW 51 +#define H_ALPHABET 52 +#define H_EXTENSION_TABLE 54 +#define H_USER_NAME 56 + +#define HX_TABLE_SIZE 0 +#define HX_MOUSE_X 1 +#define HX_MOUSE_Y 2 +#define HX_UNICODE_TABLE 3 + +/*** Various Z-machine constants ***/ + +#define V1 1 +#define V2 2 +#define V3 3 +#define V4 4 +#define V5 5 +#define V6 6 +#define V7 7 +#define V8 8 + +#define CONFIG_BYTE_SWAPPED 0x01 /* Story file is byte swapped - V3 */ +#define CONFIG_TIME 0x02 /* Status line displays time - V3 */ +#define CONFIG_TWODISKS 0x04 /* Story file occupied two disks - V3 */ +#define CONFIG_TANDY 0x08 /* Tandy licensed game - V3 */ +#define CONFIG_NOSTATUSLINE 0x10 /* Interpr can't support status lines - V3 */ +#define CONFIG_SPLITSCREEN 0x20 /* Interpr supports split screen mode - V3 */ +#define CONFIG_PROPORTIONAL 0x40 /* Interpr uses proportional font - V3 */ + +#define CONFIG_COLOUR 0x01 /* Interpr supports colour - V5+ */ +#define CONFIG_PICTURES 0x02 /* Interpr supports pictures - V6 */ +#define CONFIG_BOLDFACE 0x04 /* Interpr supports boldface style - V4+ */ +#define CONFIG_EMPHASIS 0x08 /* Interpr supports emphasis style - V4+ */ +#define CONFIG_FIXED 0x10 /* Interpr supports fixed width style - V4+ */ +#define CONFIG_SOUND 0x20 /* Interpr supports sound - V6 */ + +#define CONFIG_TIMEDINPUT 0x80 /* Interpr supports timed input - V4+ */ + +#define SCRIPTING_FLAG 0x0001 /* Outputting to transscription file - V1+ */ +#define FIXED_FONT_FLAG 0x0002 /* Use fixed width font - V3+ */ +#define REFRESH_FLAG 0x0004 /* Refresh the screen - V6 */ +#define GRAPHICS_FLAG 0x0008 /* Game wants to use graphics - V5+ */ +#define OLD_SOUND_FLAG 0x0010 /* Game wants to use sound effects - V3 */ +#define UNDO_FLAG 0x0010 /* Game wants to use UNDO feature - V5+ */ +#define MOUSE_FLAG 0x0020 /* Game wants to use a mouse - V5+ */ +#define COLOUR_FLAG 0x0040 /* Game wants to use colours - V5+ */ +#define SOUND_FLAG 0x0080 /* Game wants to use sound effects - V5+ */ +#define MENU_FLAG 0x0100 /* Game wants to use menus - V6 */ + +#define INTERP_DEFAULT 0 +#define INTERP_DEC_20 1 +#define INTERP_APPLE_IIE 2 +#define INTERP_MACINTOSH 3 +#define INTERP_AMIGA 4 +#define INTERP_ATARI_ST 5 +#define INTERP_MSDOS 6 +#define INTERP_CBM_128 7 +#define INTERP_CBM_64 8 +#define INTERP_APPLE_IIC 9 +#define INTERP_APPLE_IIGS 10 +#define INTERP_TANDY 11 + +#define BLACK_COLOUR 2 +#define RED_COLOUR 3 +#define GREEN_COLOUR 4 +#define YELLOW_COLOUR 5 +#define BLUE_COLOUR 6 +#define MAGENTA_COLOUR 7 +#define CYAN_COLOUR 8 +#define WHITE_COLOUR 9 +#define GREY_COLOUR 10 /* INTERP_MSDOS only */ +#define LIGHTGREY_COLOUR 10 /* INTERP_AMIGA only */ +#define MEDIUMGREY_COLOUR 11 /* INTERP_AMIGA only */ +#define DARKGREY_COLOUR 12 /* INTERP_AMIGA only */ + +#define REVERSE_STYLE 1 +#define BOLDFACE_STYLE 2 +#define EMPHASIS_STYLE 4 +#define FIXED_WIDTH_STYLE 8 + +#define TEXT_FONT 1 +#define PICTURE_FONT 2 +#define GRAPHICS_FONT 3 +#define FIXED_WIDTH_FONT 4 + +#define BEEP_HIGH 1 +#define BEEP_LOW 2 + +/*** Constants for os_restart_game */ + +#define RESTART_BEGIN 0 +#define RESTART_WPROP_SET 1 +#define RESTART_END 2 + +/*** Character codes ***/ + +#define ZC_TIME_OUT 0x00 +#define ZC_NEW_STYLE 0x01 +#define ZC_NEW_FONT 0x02 +#define ZC_BACKSPACE 0x08 +#define ZC_INDENT 0x09 +#define ZC_GAP 0x0b +#define ZC_RETURN 0x0d +#define ZC_HKEY_MIN 0x0e +#define ZC_HKEY_RECORD 0x0e +#define ZC_HKEY_PLAYBACK 0x0f +#define ZC_HKEY_SEED 0x10 +#define ZC_HKEY_UNDO 0x11 +#define ZC_HKEY_RESTART 0x12 +#define ZC_HKEY_QUIT 0x13 +#define ZC_HKEY_DEBUG 0x14 +#define ZC_HKEY_HELP 0x15 +#define ZC_HKEY_MAX 0x15 +#define ZC_ESCAPE 0x1b +#define ZC_ASCII_MIN 0x20 +#define ZC_ASCII_MAX 0x7e +#define ZC_BAD 0x7f +#define ZC_ARROW_MIN 0x81 +#define ZC_ARROW_UP 0x81 +#define ZC_ARROW_DOWN 0x82 +#define ZC_ARROW_LEFT 0x83 +#define ZC_ARROW_RIGHT 0x84 +#define ZC_ARROW_MAX 0x84 +#define ZC_FKEY_MIN 0x85 +#define ZC_FKEY_MAX 0x90 +#define ZC_NUMPAD_MIN 0x91 +#define ZC_NUMPAD_MAX 0x9a +#define ZC_SINGLE_CLICK 0x9b +#define ZC_DOUBLE_CLICK 0x9c +#define ZC_MENU_CLICK 0x9d +#define ZC_LATIN1_MIN 0xa0 +#define ZC_LATIN1_MAX 0xff + +/*** File types ***/ + +#define FILE_RESTORE 0 +#define FILE_SAVE 1 +#define FILE_SCRIPT 2 +#define FILE_PLAYBACK 3 +#define FILE_RECORD 4 +#define FILE_LOAD_AUX 5 +#define FILE_SAVE_AUX 6 + +/*** Data access macros ***/ + +#define SET_BYTE(addr,v) { zmp[addr] = v; } +#define LOW_BYTE(addr,v) { v = zmp[addr]; } +#define CODE_BYTE(v) { v = *pcp++; } + +#if defined (AMIGA) + +extern zbyte *pcp; +extern zbyte *zmp; + +#define lo(v) ((zbyte *)&v)[1] +#define hi(v) ((zbyte *)&v)[0] + +#define SET_WORD(addr,v) { zmp[addr] = hi(v); zmp[addr+1] = lo(v); } +#define LOW_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; } +#define HIGH_WORD(addr,v) { hi(v) = zmp[addr]; lo(v) = zmp[addr+1]; } +#define CODE_WORD(v) { hi(v) = *pcp++; lo(v) = *pcp++; } +#define GET_PC(v) { v = pcp - zmp; } +#define SET_PC(v) { pcp = zmp + v; } + +#endif + +/* A bunch of x86 assembly code previously appeared here. */ + +#if !defined (AMIGA) && !defined (MSDOS_16BIT) + +extern zbyte *pcp; +extern zbyte *zmp; + +#define lo(v) (v & 0xff) +#define hi(v) (v >> 8) + +#define SET_WORD(addr,v) { zmp[addr] = hi(v); zmp[addr+1] = lo(v); } +#define LOW_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; } +#define HIGH_WORD(addr,v) { v = ((zword) zmp[addr] << 8) | zmp[addr+1]; } +#define CODE_WORD(v) { v = ((zword) pcp[0] << 8) | pcp[1]; pcp += 2; } +#define GET_PC(v) { v = pcp - zmp; } +#define SET_PC(v) { pcp = zmp + v; } + +#endif + + +/*** Story file header data ***/ + +extern zbyte h_version; +extern zbyte h_config; +extern zword h_release; +extern zword h_resident_size; +extern zword h_start_pc; +extern zword h_dictionary; +extern zword h_objects; +extern zword h_globals; +extern zword h_dynamic_size; +extern zword h_flags; +extern zbyte h_serial[6]; +extern zword h_abbreviations; +extern zword h_file_size; +extern zword h_checksum; +extern zbyte h_interpreter_number; +extern zbyte h_interpreter_version; +extern zbyte h_screen_rows; +extern zbyte h_screen_cols; +extern zword h_screen_width; +extern zword h_screen_height; +extern zbyte h_font_height; +extern zbyte h_font_width; +extern zword h_functions_offset; +extern zword h_strings_offset; +extern zbyte h_default_background; +extern zbyte h_default_foreground; +extern zword h_terminating_keys; +extern zword h_line_width; +extern zbyte h_standard_high; +extern zbyte h_standard_low; +extern zword h_alphabet; +extern zword h_extension_table; +extern zbyte h_user_name[8]; + +extern zword hx_table_size; +extern zword hx_mouse_x; +extern zword hx_mouse_y; +extern zword hx_unicode_table; + +/*** Various data ***/ + +extern char *story_name; + +extern enum story story_id; +extern long story_size; + +extern zword stack[STACK_SIZE]; +extern zword *sp; +extern zword *fp; +extern zword frame_count; + +extern zword zargs[8]; +extern int zargc; + +extern bool ostream_screen; +extern bool ostream_script; +extern bool ostream_memory; +extern bool ostream_record; +extern bool istream_replay; +extern bool message; + +extern int cwin; +extern int mwin; + +extern int mouse_x; +extern int mouse_y; + +extern bool enable_wrapping; +extern bool enable_scripting; +extern bool enable_scrolling; +extern bool enable_buffering; + + +extern char *option_zcode_path; /* dg */ + +extern long reserve_mem; + + +/*** Z-machine opcodes ***/ + +void z_add (void); +void z_and (void); +void z_art_shift (void); +void z_buffer_mode (void); +void z_call_n (void); +void z_call_s (void); +void z_catch (void); +void z_check_arg_count (void); +void z_check_unicode (void); +void z_clear_attr (void); +void z_copy_table (void); +void z_dec (void); +void z_dec_chk (void); +void z_div (void); +void z_draw_picture (void); +void z_encode_text (void); +void z_erase_line (void); +void z_erase_picture (void); +void z_erase_window (void); +void z_get_child (void); +void z_get_cursor (void); +void z_get_next_prop (void); +void z_get_parent (void); +void z_get_prop (void); +void z_get_prop_addr (void); +void z_get_prop_len (void); +void z_get_sibling (void); +void z_get_wind_prop (void); +void z_inc (void); +void z_inc_chk (void); +void z_input_stream (void); +void z_insert_obj (void); +void z_je (void); +void z_jg (void); +void z_jin (void); +void z_jl (void); +void z_jump (void); +void z_jz (void); +void z_load (void); +void z_loadb (void); +void z_loadw (void); +void z_log_shift (void); +void z_make_menu (void); +void z_mod (void); +void z_mouse_window (void); +void z_move_window (void); +void z_mul (void); +void z_new_line (void); +void z_nop (void); +void z_not (void); +void z_or (void); +void z_output_stream (void); +void z_picture_data (void); +void z_picture_table (void); +void z_piracy (void); +void z_pop (void); +void z_pop_stack (void); +void z_print (void); +void z_print_addr (void); +void z_print_char (void); +void z_print_form (void); +void z_print_num (void); +void z_print_obj (void); +void z_print_paddr (void); +void z_print_ret (void); +void z_print_table (void); +void z_print_unicode (void); +void z_pull (void); +void z_push (void); +void z_push_stack (void); +void z_put_prop (void); +void z_put_wind_prop (void); +void z_quit (void); +void z_random (void); +void z_read (void); +void z_read_char (void); +void z_read_mouse (void); +void z_remove_obj (void); +void z_restart (void); +void z_restore (void); +void z_restore_undo (void); +void z_ret (void); +void z_ret_popped (void); +void z_rfalse (void); +void z_rtrue (void); +void z_save (void); +void z_save_undo (void); +void z_scan_table (void); +void z_scroll_window (void); +void z_set_attr (void); +void z_set_font (void); +void z_set_colour (void); +void z_set_cursor (void); +void z_set_margins (void); +void z_set_window (void); +void z_set_text_style (void); +void z_show_status (void); +void z_sound_effect (void); +void z_split_window (void); +void z_store (void); +void z_storeb (void); +void z_storew (void); +void z_sub (void); +void z_test (void); +void z_test_attr (void); +void z_throw (void); +void z_tokenise (void); +void z_verify (void); +void z_window_size (void); +void z_window_style (void); + +/* Definitions for error handling functions and error codes. */ + +/* extern int err_report_mode; */ + +void init_err (void); +void runtime_error (int); + +/* Error codes */ +#define ERR_TEXT_BUF_OVF 1 /* Text buffer overflow */ +#define ERR_STORE_RANGE 2 /* Store out of dynamic memory */ +#define ERR_DIV_ZERO 3 /* Division by zero */ +#define ERR_ILL_OBJ 4 /* Illegal object */ +#define ERR_ILL_ATTR 5 /* Illegal attribute */ +#define ERR_NO_PROP 6 /* No such property */ +#define ERR_STK_OVF 7 /* Stack overflow */ +#define ERR_ILL_CALL_ADDR 8 /* Call to illegal address */ +#define ERR_CALL_NON_RTN 9 /* Call to non-routine */ +#define ERR_STK_UNDF 10 /* Stack underflow */ +#define ERR_ILL_OPCODE 11 /* Illegal opcode */ +#define ERR_BAD_FRAME 12 /* Bad stack frame */ +#define ERR_ILL_JUMP_ADDR 13 /* Jump to illegal address */ +#define ERR_SAVE_IN_INTER 14 /* Can't save while in interrupt */ +#define ERR_STR3_NESTING 15 /* Nesting stream #3 too deep */ +#define ERR_ILL_WIN 16 /* Illegal window */ +#define ERR_ILL_WIN_PROP 17 /* Illegal window property */ +#define ERR_ILL_PRINT_ADDR 18 /* Print at illegal address */ +#define ERR_MAX_FATAL 18 + +/* Less serious errors */ +#define ERR_JIN_0 19 /* @jin called with object 0 */ +#define ERR_GET_CHILD_0 20 /* @get_child called with object 0 */ +#define ERR_GET_PARENT_0 21 /* @get_parent called with object 0 */ +#define ERR_GET_SIBLING_0 22 /* @get_sibling called with object 0 */ +#define ERR_GET_PROP_ADDR_0 23 /* @get_prop_addr called with object 0 */ +#define ERR_GET_PROP_0 24 /* @get_prop called with object 0 */ +#define ERR_PUT_PROP_0 25 /* @put_prop called with object 0 */ +#define ERR_CLEAR_ATTR_0 26 /* @clear_attr called with object 0 */ +#define ERR_SET_ATTR_0 27 /* @set_attr called with object 0 */ +#define ERR_TEST_ATTR_0 28 /* @test_attr called with object 0 */ +#define ERR_MOVE_OBJECT_0 29 /* @move_object called moving object 0 */ +#define ERR_MOVE_OBJECT_TO_0 30 /* @move_object called moving into object 0 */ +#define ERR_REMOVE_OBJECT_0 31 /* @remove_object called with object 0 */ +#define ERR_GET_NEXT_PROP_0 32 /* @get_next_prop called with object 0 */ +#define ERR_NUM_ERRORS (32) + +/* There are four error reporting modes: never report errors; + report only the first time a given error type occurs; report + every time an error occurs; or treat all errors as fatal + errors, killing the interpreter. I strongly recommend + "report once" as the default. But you can compile in a + different default by changing the definition of + ERR_DEFAULT_REPORT_MODE. In any case, the player can + specify a report mode on the command line by typing "-Z 0" + through "-Z 3". */ + +#define ERR_REPORT_NEVER (0) +#define ERR_REPORT_ONCE (1) +#define ERR_REPORT_ALWAYS (2) +#define ERR_REPORT_FATAL (3) + +#define ERR_DEFAULT_REPORT_MODE ERR_REPORT_NEVER + + +/*** Various global functions ***/ + +/* MacOSX libm defines this */ +#define init_process frotz_process + +void init_process(void); +void init_sound(void); + +zchar translate_from_zscii (zbyte); +zbyte translate_to_zscii (zchar); + +void init_buffer(void); +void flush_buffer(void); +void new_line (void); +void print_char (zchar); +void print_num (zword); +void print_object (zword); +void print_string (const char *); + +void stream_mssg_on (void); +void stream_mssg_off (void); + +void ret (zword); +void store (zword); +void branch (bool); + +void storeb (zword, zbyte); +void storew (zword, zword); + +/*** Interface functions ***/ + +void os_beep (int); +int os_char_width (zchar); +void os_display_char (zchar); +void os_display_string (const zchar *); + +void os_draw_picture (int, int, int); +void os_erase_area (int, int, int, int); + +void os_fatal (char *); +void os_finish_with_sample (int); + +int os_font_data (int, int *, int *); +void os_init_screen (void); +void os_more_prompt (void); +int os_peek_colour (void); +int os_picture_data (int, int *, int *); + +void os_prepare_sample (int); +void os_process_arguments (int, char *[]); +int os_random_seed (void); +int os_read_file_name (char *, const char *, int); +zchar os_read_key (int, int); +zchar os_read_line (int, zchar *, int, int, int); +void os_reset_screen (void); +void os_restart_game (int); + +void os_scroll_area (int, int, int, int, int); +void os_set_colour (int, int); +void os_set_cursor (int, int); +void os_set_font (int); + +void os_set_text_style (int); + +void os_start_sample (int, int, int, zword); +void os_stop_sample (int); +int os_string_width (const zchar *); +void os_init_setup (void); +int os_speech_output(const zchar *); + +#include "setup.h" diff --git a/interpreters/frotz/glkfrotz.h b/interpreters/frotz/glkfrotz.h new file mode 100644 index 0000000..0f8ff4b --- /dev/null +++ b/interpreters/frotz/glkfrotz.h @@ -0,0 +1,40 @@ +/* glk-frotz.h + * + * Frotz os functions for the Glk library version 0.6.1. + */ + +#include "frotz.h" + +#include +#include +#include +#include +#include +#include + +#include "glk.h" + +extern int curr_status_ht; +extern int mach_status_ht; + +extern winid_t gos_status; +extern winid_t gos_upper; +extern winid_t gos_lower; +extern winid_t gos_curwin; +extern int gos_linepending; +extern char *gos_linebuf; +extern winid_t gos_linewin; + +extern schanid_t gos_channel; + +/* from ../common/setup.h */ +extern f_setup_t f_setup; + +/* From input.c. */ +bool is_terminator (zchar); + +/* from glkstuff */ +void gos_update_width(void); +void gos_cancel_pending_line(void); +void reset_status_ht(void); + diff --git a/interpreters/frotz/glkio.h b/interpreters/frotz/glkio.h new file mode 100644 index 0000000..b580d75 --- /dev/null +++ b/interpreters/frotz/glkio.h @@ -0,0 +1,44 @@ +/* glkio.h -- make stdio calls use glk i/o instead */ + +#undef FILE +typedef struct glk_stream_struct FILE; + +#undef EOF +#define EOF (-1) + +#undef ungetc +#define ungetc(f,c) + +#undef fclose +#define fclose(f) (glk_stream_close(f, NULL), 0) +#undef ferror +#define ferror(f) (0) /* No ferror() equivalent */ +#undef fgetc +#define fgetc(f) (glk_get_char_stream(f)) +#undef fgets +#define fgets(a, n, f) (glk_get_line_stream(f, a, n)) +#undef fread +#define fread(a,s,n,f) (glk_get_buffer_stream(f, (char *)a, s*n)) +#undef fwrite +#define fwrite(a,s,n,f) (glk_put_buffer_stream(f, (char *)a, s*n), 0) +#undef fprintf +#define fprintf(f,s,a) (glk_put_string_stream(f, a), 0) +#undef fputc +#define fputc(c, f) (glk_put_char_stream(f, (unsigned char)(c)), 0) +#undef fputs +#define fputs(s, f) (glk_put_buffer_stream(f, s, strlen(s)), 0) +#undef ftell +#define ftell(f) (glk_stream_get_position(f)) +#undef fseek +#define fseek(f, p, m) (glk_stream_set_position(f, p, m), 0) + +#undef SEEK_SET +#define SEEK_SET seekmode_Start +#undef SEEK_CUR +#define SEEK_CUR seekmode_Current +#undef SEEK_END +#define SEEK_END seekmode_End + +FILE *frotzopenprompt(int flag); +FILE *frotzopen(char *filename, int flag); + diff --git a/interpreters/frotz/glkmisc.c b/interpreters/frotz/glkmisc.c new file mode 100644 index 0000000..5f20ee7 --- /dev/null +++ b/interpreters/frotz/glkmisc.c @@ -0,0 +1,484 @@ +/* glkstuff.c -- non-screen related glk stuff */ + +#include "glkfrotz.h" + +#define VERSION "2.43" + +f_setup_t f_setup; + +int curr_status_ht = 0; +int mach_status_ht = 0; + +winid_t gos_upper = NULL; +winid_t gos_lower = NULL; +winid_t gos_curwin = NULL; + +int gos_linepending = 0; +char *gos_linebuf = NULL; +winid_t gos_linewin = NULL; + +schanid_t gos_channel = NULL; + +#define INFORMATION ""\ +"An interpreter for Infocom and other Z-Machine games.\n"\ +"Complies with standard 1.0 of Graham Nelson's specification.\n"\ +"Plays Z-code versions 1-5 and 8.\n"\ +"\n"\ +"Syntax: frotz [options] story-file\n"\ +" -a watch attribute setting\n"\ +" -A watch attribute testing\n"\ +" -i ignore fatal errors\n"\ +" -I # interpreter number\n"\ +" -o watch object movement\n"\ +" -O watch object locating\n"\ +" -P alter piracy opcode\n"\ +" -Q use old-style save format\n"\ +" -s # random number seed value\n"\ +" -S # transscript width\n"\ +" -t set Tandy bit\n"\ +" -u # slots for multiple undo\n"\ +" -x expand abbreviations g/x/z\n" + +/* A unix-like getopt, but with the names changed to avoid any problems. */ +static int zoptind = 1; +static int zoptopt = 0; +static char *zoptarg = NULL; +static int zgetopt (int argc, char *argv[], const char *options) +{ + static int pos = 1; + const char *p; + if (zoptind >= argc || argv[zoptind][0] != '-' || argv[zoptind][1] == 0) + return EOF; + zoptopt = argv[zoptind][pos++]; + zoptarg = NULL; + if (argv[zoptind][pos] == 0) + { + pos = 1; + zoptind++; + } + p = strchr (options, zoptopt); + if (zoptopt == ':' || p == NULL) + { + fputs ("illegal option -- ", stderr); + goto error; + } + else if (p[1] == ':') + { + if (zoptind >= argc) { + fputs ("option requires an argument -- ", stderr); + goto error; + } else { + zoptarg = argv[zoptind]; + if (pos != 1) + zoptarg += pos; + pos = 1; zoptind++; + } + } + return zoptopt; +error: + fputc (zoptopt, stderr); + fputc ('\n', stderr); + return '?'; +} + +static int user_random_seed = -1; +static int user_tandy_bit = 0; +static char *graphics_filename = NULL; + +void os_process_arguments(int argc, char *argv[]) +{ + int c; + +#ifdef GARGLK + garglk_set_program_name("Frotz " VERSION); + garglk_set_program_info( + "Glk Frotz " VERSION "\n" + "Original Frotz by Stefan Jokisch\n" + "Unix port by Jim Dunleavy and David Griffith\n" + "Glk port by Tor Andersson\n"); +#endif + + /* Parse the options */ + do { + c = zgetopt(argc, argv, "aAiI:oOPQs:S:tu:xZ:"); + switch (c) + { + case 'a': f_setup.attribute_assignment = 1; break; + case 'A': f_setup.attribute_testing = 1; break; + case 'i': f_setup.ignore_errors = 1; break; + case 'I': f_setup.interpreter_number = atoi(zoptarg); break; + case 'o': f_setup.object_movement = 1; break; + case 'O': f_setup.object_locating = 1; break; + case 'P': f_setup.piracy = 1; break; + case 'Q': f_setup.save_quetzal = 0; break; + case 's': user_random_seed = atoi(zoptarg); break; + case 'S': f_setup.script_cols = atoi(zoptarg); break; + case 't': user_tandy_bit = 1; break; + case 'u': f_setup.undo_slots = atoi(zoptarg); break; + case 'x': f_setup.expand_abbreviations = 1; break; + case 'Z': f_setup.err_report_mode = atoi(zoptarg); + if ((f_setup.err_report_mode < ERR_REPORT_NEVER) || + (f_setup.err_report_mode > ERR_REPORT_FATAL)) + f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE; + break; + } + } while (c != EOF); + + if (((argc - zoptind) != 1) && ((argc - zoptind) != 2)) + { + winid_t win; + char buf[256]; + win = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + glk_set_window(win); + glk_put_string("FROTZ V" VERSION " -- Glk 0.6.1 interface.\n"); + glk_put_string(INFORMATION); + sprintf(buf, + " -Z # error checking mode (default = %d)\n" + " %d = don't report errors. " + "%d = report first error.\n" + " %d = report all errors. " + "%d = exit after any error.\n", + ERR_DEFAULT_REPORT_MODE, ERR_REPORT_NEVER, + ERR_REPORT_ONCE, ERR_REPORT_ALWAYS, ERR_REPORT_FATAL); + glk_put_string(buf); + glk_exit(); + } + else + { + char *s; + + story_name = argv[zoptind++]; + if (zoptind < argc) + graphics_filename = argv[zoptind++]; + + #ifdef GARGLK + s = strrchr(story_name, '\\'); + if (!s) s = strrchr(story_name, '/'); + garglk_set_story_name(s ? s + 1 : story_name); + #endif + } +} + +void os_init_screen(void) +{ + glui32 width, height; + + /* + * Init glk stuff + */ + + glk_stylehint_set(wintype_TextGrid, style_User1, stylehint_ReverseColor, 1); + + + gos_lower = glk_window_open(0, 0, 0, wintype_TextGrid, 0); + if (!gos_lower) + gos_lower = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + glk_window_get_size(gos_lower, &width, &height); + glk_window_close(gos_lower, NULL); + + gos_lower = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + gos_upper = glk_window_open(gos_lower, + winmethod_Above | winmethod_Fixed, + 0, + wintype_TextGrid, 0); + + gos_channel = NULL; + + glk_set_window(gos_lower); + gos_curwin = gos_lower; + + /* + * Icky magic bit setting + */ + + if (h_version == V3 && user_tandy_bit) + h_config |= CONFIG_TANDY; + + if (h_version == V3 && gos_upper) + h_config |= CONFIG_SPLITSCREEN; + + if (h_version == V3 && !gos_upper) + h_config |= CONFIG_NOSTATUSLINE; + + if (h_version >= V4) + h_config |= CONFIG_BOLDFACE | CONFIG_EMPHASIS | + CONFIG_FIXED | CONFIG_TIMEDINPUT; + + if (h_version >= V5) + h_flags &= ~(GRAPHICS_FLAG | MOUSE_FLAG | MENU_FLAG); + + if ((h_version >= 5) && (h_flags & SOUND_FLAG)) + h_flags |= SOUND_FLAG; + + if ((h_version == 3) && (h_flags & OLD_SOUND_FLAG)) + h_flags |= OLD_SOUND_FLAG; + + if ((h_version == 6) && (f_setup.sound != 0)) + h_config |= CONFIG_SOUND; + + if (h_version >= V5 && (h_flags & UNDO_FLAG)) + if (f_setup.undo_slots == 0) + h_flags &= ~UNDO_FLAG; + + h_screen_cols = width; + h_screen_rows = height; + + h_screen_height = h_screen_rows; + h_screen_width = h_screen_cols; + + h_font_width = 1; + h_font_height = 1; + + /* Must be after screen dimensions are computed. */ + if (h_version == V6) { + h_flags &= ~GRAPHICS_FLAG; + } + + /* Use the ms-dos interpreter number for v6, because that's the + * kind of graphics files we understand. Otherwise, use DEC. */ + h_interpreter_number = h_version == 6 ? INTERP_MSDOS : INTERP_DEC_20; + if (f_setup.interpreter_number > 0) + h_interpreter_number = f_setup.interpreter_number; + h_interpreter_version = 'F'; + { + /* Set these per spec 8.3.2. */ + h_default_foreground = WHITE_COLOUR; + h_default_background = BLACK_COLOUR; + if (h_flags & COLOUR_FLAG) h_flags &= ~COLOUR_FLAG; + } +} + +int os_random_seed (void) +{ + if (user_random_seed == -1) + /* Use the epoch as seed value */ + return (time(0) & 0x7fff); + return user_random_seed; +} + +void os_restart_game (int stage) {} + +void os_fatal (char *s) +{ + if (!gos_lower) + gos_lower = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + + glk_set_window(gos_lower); + glk_set_style(style_Normal); + glk_put_string("\n\nFatal error: "); + glk_put_string(s); + glk_put_string("\n"); + glk_exit(); +} + +void os_init_setup(void) +{ + f_setup.attribute_assignment = 0; + f_setup.attribute_testing = 0; + f_setup.context_lines = 0; + f_setup.object_locating = 0; + f_setup.object_movement = 0; + f_setup.left_margin = 0; + f_setup.right_margin = 0; + f_setup.ignore_errors = 0; + f_setup.interpreter_number = 0; + f_setup.piracy = 0; + f_setup.undo_slots = MAX_UNDO_SLOTS; + f_setup.expand_abbreviations = 0; + f_setup.script_cols = 80; + f_setup.save_quetzal = 1; + f_setup.sound = 1; + f_setup.err_report_mode = ERR_DEFAULT_REPORT_MODE; +} + +void gos_cancel_pending_line(void) +{ + event_t ev; + glk_cancel_line_event(gos_linewin, &ev); + gos_linebuf[ev.val1] = '\0'; + gos_linepending = 0; +} + +zchar os_read_key (int timeout, bool show_cursor) +{ + event_t ev; + winid_t win = gos_curwin ? gos_curwin : gos_lower; + + if (gos_linepending) + gos_cancel_pending_line(); + + glk_request_char_event(win); + if (timeout != 0) + glk_request_timer_events(timeout * 100); + + while (1) + { + glk_select(&ev); + if (ev.type == evtype_Arrange) { + gos_update_height(); + gos_update_width(); + } + else if (ev.type == evtype_Timer) + { + glk_cancel_char_event(win); + glk_request_timer_events(0); + return ZC_TIME_OUT; + } + else if (ev.type == evtype_CharInput) + break; + } + + glk_request_timer_events(0); + + if (gos_upper && mach_status_ht < curr_status_ht) + reset_status_ht(); + curr_status_ht = 0; + + switch (ev.val1) + { + case keycode_Escape: return ZC_ESCAPE; + case keycode_PageUp: return ZC_ARROW_MIN; + case keycode_PageDown: return ZC_ARROW_MAX; + case keycode_Left: return ZC_ARROW_LEFT; + case keycode_Right: return ZC_ARROW_RIGHT; + case keycode_Up: return ZC_ARROW_UP; + case keycode_Down: return ZC_ARROW_DOWN; + case keycode_Return: return ZC_RETURN; + case keycode_Delete: return ZC_BACKSPACE; + case keycode_Tab: return ZC_INDENT; + default: + return ev.val1; + } +} + +zchar os_read_line (int max, zchar *buf, int timeout, int width, int continued) +{ + event_t ev; + winid_t win = gos_curwin ? gos_curwin : gos_lower; + + if (!continued && gos_linepending) + gos_cancel_pending_line(); + + if (!continued || !gos_linepending) + { + glk_request_line_event(win, buf, max - 1, strlen(buf)); + if (timeout != 0) + glk_request_timer_events(timeout * 100); + } + + gos_linepending = 0; + + while (1) + { + glk_select(&ev); + if (ev.type == evtype_Arrange) { + gos_update_height(); + gos_update_width(); + } + else if (ev.type == evtype_Timer) + { + gos_linewin = win; + gos_linepending = 1; + gos_linebuf = buf; + return ZC_TIME_OUT; + } + else if (ev.type == evtype_LineInput) + break; + } + + glk_request_timer_events(0); + buf[ev.val1] = '\0'; + + if (gos_upper && mach_status_ht < curr_status_ht) + reset_status_ht(); + curr_status_ht = 0; + + return ZC_RETURN; +} + +zword os_read_mouse(void) +{ + /* NOT IMPLEMENTED */ + return 0; +} + +static glui32 flag2usage(int flag) +{ + switch (flag) + { + case FILE_RESTORE: + return fileusage_SavedGame | fileusage_BinaryMode; + case FILE_SAVE: + return fileusage_SavedGame | fileusage_BinaryMode; + case FILE_SCRIPT: + return fileusage_Transcript | fileusage_TextMode; + case FILE_PLAYBACK: + return fileusage_InputRecord | fileusage_TextMode; + case FILE_RECORD: + return fileusage_InputRecord | fileusage_TextMode; + case FILE_LOAD_AUX: + return fileusage_Data | fileusage_BinaryMode; + case FILE_SAVE_AUX: + return fileusage_Data | fileusage_BinaryMode; + } + return 0; +} + +static glui32 flag2mode(int flag) +{ + switch (flag) + { + case FILE_RESTORE: + return filemode_Read; + case FILE_SAVE: + return filemode_Write; + case FILE_SCRIPT: + return filemode_ReadWrite; /* append really, but with erase option */ + case FILE_PLAYBACK: + return filemode_Read; + case FILE_RECORD: + return filemode_Write; + case FILE_LOAD_AUX: + return filemode_Read; + case FILE_SAVE_AUX: + return filemode_Write; + } + return filemode_ReadWrite; +} + +strid_t frotzopenprompt(int flag) +{ + frefid_t fref; + strid_t stm; + glui32 gusage = flag2usage(flag); + glui32 gmode = flag2mode(flag); + + fref = glk_fileref_create_by_prompt(gusage, gmode, 0); + if (fref == NULL) + return NULL; + + stm = glk_stream_open_file(fref, gmode, 0); + + glk_fileref_destroy(fref); + + return stm; +} + +strid_t frotzopen(char *filename, int flag) +{ + frefid_t fref; + strid_t stm; + glui32 gusage = flag2usage(flag); + glui32 gmode = flag2mode(flag); + + fref = glk_fileref_create_by_name(gusage, filename, 0); + if (!fref) + return NULL; + + stm = glk_stream_open_file(fref, gmode, 0); + + glk_fileref_destroy(fref); + + return stm; +} + diff --git a/interpreters/frotz/glkscreen.c b/interpreters/frotz/glkscreen.c new file mode 100644 index 0000000..4545124 --- /dev/null +++ b/interpreters/frotz/glkscreen.c @@ -0,0 +1,767 @@ +/* screen.c - Generic screen manipulation + * + * Copyright (c) 2005 Tor Andersson -- Glk-ified and V6-disabled + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "glkfrotz.h" + +static unsigned char statusline[256]; +static int oldstyle = 0; +static int curstyle = 0; +static int upperstyle = 0; +static int lowerstyle = 0; +static int cury = 1; +static int curx = 1; + +/* To make the common code happy */ + +int os_char_width (zchar z) +{ + return 1; +} + +int os_string_width (const zchar *s) +{ + int width = 0; + zchar c; + while ((c = *s++) != 0) + if (c == ZC_NEW_STYLE || c == ZC_NEW_FONT) + s++; + else + width += os_char_width(c); + return width; +} + +void os_prepare_sample (int a) +{ + glk_sound_load_hint(a, 1); +} + +void os_finish_with_sample (int a) +{ + glk_sound_load_hint(a, 0); +} + +/* + * os_start_sample + * + * Play the given sample at the given volume (ranging from 1 to 8 and + * 255 meaning a default volume). The sound is played once or several + * times in the background (255 meaning forever). In Z-code 3 the + * repeats value is always 0 and the number of repeats is taken from + * the sound file itself. The end_of_sound function is called as soon + * as the sound finishes. + * + */ + +void os_start_sample (int number, int volume, int repeats, zword eos) +{ + int vol; + + if (!gos_channel) + { + gos_channel = glk_schannel_create(0); + if (!gos_channel) + return; + } + + switch (volume) + { + case 1: vol = 0x02000; break; + case 2: vol = 0x04000; break; + case 3: vol = 0x06000; break; + case 4: vol = 0x08000; break; + case 5: vol = 0x0a000; break; + case 6: vol = 0x0c000; break; + case 7: vol = 0x0e000; break; + case 8: vol = 0x10000; break; + default: vol = 0x20000; break; + } + + /* we dont do repeating or eos-callback for now... */ + glk_schannel_play_ext(gos_channel, number, 1, 0); + glk_schannel_set_volume(gos_channel, vol); +} + +void os_stop_sample (int a) +{ + if (!gos_channel) + return; + glk_schannel_stop(gos_channel); +} + +void os_beep (int volume) +{ +} + +void gos_update_width(void) +{ + glui32 width; + if (gos_upper) + { + glk_window_get_size(gos_upper, &width, NULL); + h_screen_cols = width; + SET_BYTE(H_SCREEN_COLS, width); + if (curx > width) + { + glk_window_move_cursor(gos_upper, 0, cury-1); + curx = 1; + } + } +} + +void gos_update_height(void) +{ + glui32 height_upper; + glui32 height_lower; + if (gos_curwin) + { + glk_window_get_size(gos_upper, NULL, &height_upper); + glk_window_get_size(gos_lower, NULL, &height_lower); + h_screen_rows = height_upper + height_lower + 1; + SET_BYTE(H_SCREEN_ROWS, h_screen_rows); + } +} + +void reset_status_ht(void) +{ + glui32 height; + if (gos_upper) + { + glk_window_get_size(gos_upper, NULL, &height); + if (mach_status_ht != height) + glk_window_set_arrangement( + glk_window_get_parent(gos_upper), + winmethod_Above | winmethod_Fixed, + mach_status_ht, NULL); + } +} + +void erase_window (int w) +{ + if (w == 0) + glk_window_clear(gos_lower); + else if (gos_upper) + { + memset(statusline, ' ', sizeof statusline); + glk_window_clear(gos_upper); + reset_status_ht(); + curr_status_ht = 0; + } +} + +void split_window (int lines) +{ + if (!gos_upper) + return; + /* The top line is always set for V1 to V3 games */ + if (h_version < V4) + lines++; + + if (lines > curr_status_ht) + { + glui32 height; + + glk_window_get_size(gos_upper, NULL, &height); + if (lines != height) + glk_window_set_arrangement( + glk_window_get_parent(gos_upper), + winmethod_Above | winmethod_Fixed, + lines, NULL); + curr_status_ht = lines; + } + mach_status_ht = lines; + if (cury > lines) + { + glk_window_move_cursor(gos_upper, 0, 0); + curx = cury = 1; + } + gos_update_width(); + + if (h_version == V3) + glk_window_clear(gos_upper); +} + +void restart_screen (void) +{ + erase_window(0); + erase_window(1); + split_window(0); +} + +/* + * statusline overflowed the window size ... bad game! + * so ... split status text into regions, reformat and print anew. + */ + +void packspaces(unsigned char *src, unsigned char *dst) +{ + int killing = 0; + while (*src) + { + if (*src == ' ') + killing++; + else + killing = 0; + if (killing > 2) + src++; + else + *dst++ = *src++; + } + *dst = 0; +} + +void smartstatusline (void) +{ + unsigned char packed[256]; + unsigned char buf[256]; + unsigned char *a, *b, *c, *d; + int roomlen, scorelen, scoreofs; + int len; + + statusline[curx - 1] = 0; /* terminate! */ + + packspaces(statusline, packed); + //strcpy(packed, statusline); + len = strlen(packed); + + a = packed; + while (a[0] == ' ') + a ++; + + b = a; + while (b[0] != 0 && !(b[0] == ' ' && b[1] == ' ')) + b ++; + + c = b; + while (c[0] == ' ') + c ++; + + d = packed + len - 1; + while (d[0] == ' ' && d > c) + d --; + if (d[0] != ' ' && d[0] != 0) + d ++; + if (d < c) + d = c; + + //printf("smart '%s'\n", packed); + //printf("smart %d %d %d %d\n",a-packed,b-packed,c-packed,d-packed); + + roomlen = b - a; + scorelen = d - c; + scoreofs = h_screen_cols - scorelen - 2; + + memset(buf, ' ', h_screen_cols); + memcpy(buf + 1 + scoreofs, c, scorelen); + memcpy(buf + 1, a, roomlen); + if (roomlen >= scoreofs) + buf[roomlen + 1] = '|'; + + glk_window_move_cursor(gos_upper, 0, 0); + glk_set_style(style_User1); + glk_put_buffer(buf, h_screen_cols); + glk_window_move_cursor(gos_upper, cury - 1, curx - 1); +} + +void screen_char (zchar c) +{ + if (gos_linepending && (gos_curwin == gos_linewin)) + { + gos_cancel_pending_line(); + if (gos_curwin == gos_upper) + { + curx = 1; + cury ++; + } + if (c == '\n') + return; + } + + if (gos_upper && gos_curwin == gos_upper) { + if (cury > mach_status_ht) { + mach_status_ht = cury; + reset_status_ht(); + } + } + + /* check fixed flag in header, game can change it at whim */ + if (gos_curwin == gos_lower) + { + static int forcefix = -1; + int curfix = h_flags & FIXED_FONT_FLAG; + if (forcefix != curfix) + { + forcefix = curfix; + zargs[0] = 0xf000; /* tickle tickle! */ + z_set_text_style(); + } + } + + if (gos_upper && gos_curwin == gos_upper) + { + if (c == '\n' || c == ZC_RETURN) { + glk_put_char('\n'); + curx = 1; + cury ++; + } + else { + if (cury == 1) + { + if (curx < sizeof statusline) + statusline[curx - 1] = c; + curx++; + if (curx <= h_screen_cols) + glk_put_char(c); + else + smartstatusline(); + } + else + { + glk_put_char(c); + curx++; + if (curx > h_screen_cols) { + curx = 1; + cury++; + } + } + } + } + else if (gos_curwin == gos_lower) + { + if (c == ZC_RETURN) + glk_put_char('\n'); + else glk_put_char(c); + } +} + +void screen_new_line (void) +{ + screen_char('\n'); +} + +void screen_word (const zchar *s) +{ + zchar c; + while ((c = *s++) != 0) + if (c == ZC_NEW_FONT) + s++; + else if (c == ZC_NEW_STYLE) + s++; + else + screen_char (c); +} + +void screen_mssg_on (void) +{ + if (gos_curwin == gos_lower) + { + oldstyle = curstyle; + glk_set_style(style_Preformatted); + glk_put_string("\n "); + } +} + +void screen_mssg_off (void) +{ + if (gos_curwin == gos_lower) + { + glk_put_char('\n'); + zargs[0] = 0; + z_set_text_style(); + zargs[0] = oldstyle; + z_set_text_style(); + } +} + +/* + * z_buffer_mode, turn text buffering on/off. + * + * zargs[0] = new text buffering flag (0 or 1) + * + */ + +void z_buffer_mode (void) +{ +} + +/* + * z_erase_line, erase the line starting at the cursor position. + * + * zargs[0] = 1 + #units to erase (1 clears to the end of the line) + * + */ + +void z_erase_line (void) +{ + int i; + + if (gos_upper && gos_curwin == gos_upper) + { + for (i = 0; i < h_screen_cols + 1 - curx; i++) + glk_put_char(' '); + glk_window_move_cursor(gos_curwin, curx - 1, cury - 1); + } +} + +/* + * z_erase_window, erase a window or the screen to background colour. + * + * zargs[0] = window (-3 current, -2 screen, -1 screen & unsplit) + * + */ + +void z_erase_window (void) +{ + short w = zargs[0]; + if (w == -2) + { + if (gos_upper) + glk_window_clear(gos_upper); + glk_window_clear(gos_lower); + } + if (w == -1) + { + if (gos_upper) + glk_window_clear(gos_upper); + glk_window_clear(gos_lower); + split_window(0); + } + if (w == 0) + glk_window_clear(gos_lower); + if (w == 1 && gos_upper) + glk_window_clear(gos_upper); +} + +/* + * z_get_cursor, write the cursor coordinates into a table. + * + * zargs[0] = address to write information to + * + */ + +void z_get_cursor (void) +{ + storew ((zword) (zargs[0] + 0), cury); + storew ((zword) (zargs[0] + 2), curx); +} + +/* + * z_print_table, print ASCII text in a rectangular area. + * + * zargs[0] = address of text to be printed + * zargs[1] = width of rectangular area + * zargs[2] = height of rectangular area (optional) + * zargs[3] = number of char's to skip between lines (optional) + * + */ + +void z_print_table (void) +{ + zword addr = zargs[0]; + zword x; + int i, j; + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 1; + if (zargc < 4) + zargs[3] = 0; + + /* Write text in width x height rectangle */ + + x = curx; + + for (i = 0; i < zargs[2]; i++) { + + if (i != 0) { + cury += 1; + curx = x; + } + + for (j = 0; j < zargs[1]; j++) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + print_char (c); + } + + addr += zargs[3]; + } +} + +/* + * z_set_colour, set the foreground and background colours. + * + * zargs[0] = foreground colour + * zargs[1] = background colour + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_colour (void) +{ + int zfore = zargs[0]; + int zback = zargs[1]; + + if (!(zfore == 0 && zback == 0)) + garglk_set_zcolors(zfore, zback); +} + +/* + * z_set_font, set the font for text output and store the previous font. + * + * zargs[0] = number of font or 0 to keep current font + * + */ + +void z_set_font (void) +{ +} + +/* + * z_set_cursor, set the cursor position or turn the cursor on/off. + * + * zargs[0] = y-coordinate or -2/-1 for cursor on/off + * zargs[1] = x-coordinate + * zargs[2] = window (-3 is the current one, optional) + * + */ + +void z_set_cursor (void) +{ + cury = zargs[0]; + curx = zargs[1]; + if (gos_upper) + glk_window_move_cursor(gos_upper, curx - 1, cury - 1); +} + +/* + * z_set_text_style, set the style for text output. + * + * zargs[0] = style flags to set or 0 to reset text style + * + */ + +void z_set_text_style (void) +{ + int style; + + if (zargs[0] == 0) + curstyle = 0; + else if (zargs[0] != 0xf000) /* not tickle time */ + curstyle |= zargs[0]; + + if (h_flags & FIXED_FONT_FLAG) + style = curstyle | FIXED_WIDTH_STYLE; + else + style = curstyle; + + if (gos_linepending && gos_curwin == gos_linewin) + return; + + if (style & REVERSE_STYLE) + { + if (gos_curwin == gos_upper && gos_upper) { + glk_set_style(style_User1); + } + garglk_set_reversevideo(TRUE); + } + else if (style & FIXED_WIDTH_STYLE) + glk_set_style(style_Preformatted); + else if (style & BOLDFACE_STYLE && style & EMPHASIS_STYLE) + glk_set_style(style_Alert); + else if (style & BOLDFACE_STYLE) + glk_set_style(style_Subheader); + else if (style & EMPHASIS_STYLE) + glk_set_style(style_Emphasized); + else + glk_set_style(style_Normal); + + if (curstyle == 0) { + garglk_set_reversevideo(FALSE); + } +} + +/* + * z_set_window, select the current window. + * + * zargs[0] = window to be selected (-3 is the current one) + * + */ + +void z_set_window (void) +{ + int win = zargs[0]; + + if (gos_curwin == gos_lower) + lowerstyle = curstyle; + else + upperstyle = curstyle; + + if (win == 0) + { + glk_set_window(gos_lower); + gos_curwin = gos_lower; + curstyle = lowerstyle; + } + else + { + if (gos_upper) + glk_set_window(gos_upper); + gos_curwin = gos_upper; + curstyle = upperstyle; + } + + if (win == 0) + enable_scripting = TRUE; + else + enable_scripting = FALSE; +} + +/* + * z_show_status, display the status line for V1 to V3 games. + * + * no zargs used + * + */ + +static void pad_status_line (int column) +{ + int spaces; + spaces = (h_screen_cols + 1 - curx) - column; + while (spaces-- > 0) + print_char(' '); +} + +void z_show_status (void) +{ + zword global0; + zword global1; + zword global2; + zword addr; + + bool brief = FALSE; + + if (!gos_upper) + return; + + /* One V5 game (Wishbringer Solid Gold) contains this opcode by + accident, so just return if the version number does not fit */ + + if (h_version >= V4) + return; + + /* Read all relevant global variables from the memory of the + Z-machine into local variables */ + + addr = h_globals; + LOW_WORD (addr, global0) + addr += 2; + LOW_WORD (addr, global1) + addr += 2; + LOW_WORD (addr, global2) + + /* Move to top of the status window, and print in reverse style. */ + + glk_set_window(gos_upper); + gos_curwin = gos_upper; + + curx = cury = 1; + glk_window_move_cursor(gos_upper, 0, 0); + glk_set_style(style_User1); + + /* If the screen width is below 55 characters then we have to use + the brief status line format */ + + if (h_screen_cols < 55) + brief = TRUE; + + /* Print the object description for the global variable 0 */ + + print_char (' '); + print_object (global0); + + /* A header flag tells us whether we have to display the current + time or the score/moves information */ + + if (h_config & CONFIG_TIME) { /* print hours and minutes */ + + zword hours = (global1 + 11) % 12 + 1; + + pad_status_line (brief ? 15 : 20); + + print_string ("Time: "); + + if (hours < 10) + print_char (' '); + print_num (hours); + + print_char (':'); + + if (global2 < 10) + print_char ('0'); + print_num (global2); + + print_char (' '); + + print_char ((global1 >= 12) ? 'p' : 'a'); + print_char ('m'); + + } else { /* print score and moves */ + + pad_status_line (brief ? 15 : 30); + + print_string (brief ? "S: " : "Score: "); + print_num (global1); + + pad_status_line (brief ? 8 : 14); + + print_string (brief ? "M: " : "Moves: "); + print_num (global2); + + } + + /* Pad the end of the status line with spaces */ + + pad_status_line (0); + + /* Return to the lower window */ + + glk_set_window(gos_lower); + gos_curwin = gos_lower; +} + +/* + * z_split_window, split the screen into an upper (1) and lower (0) window. + * + * zargs[0] = height of upper window in screen units (V6) or #lines + * + */ + +void z_split_window (void) +{ + split_window(zargs[0]); +} + diff --git a/interpreters/frotz/input.c b/interpreters/frotz/input.c new file mode 100644 index 0000000..975830c --- /dev/null +++ b/interpreters/frotz/input.c @@ -0,0 +1,298 @@ +/* input.c - High level input functions + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +extern int save_undo (void); + +extern zchar stream_read_key (zword, zword); +extern zchar stream_read_input (int, zchar *, zword, zword, bool); + +extern void tokenise_line (zword, zword, zword, bool); + +/* + * is_terminator + * + * Check if the given key is an input terminator. + * + */ + +bool is_terminator (zchar key) +{ + + if (key == ZC_TIME_OUT) + return TRUE; + if (key == ZC_RETURN) + return TRUE; + if (key >= ZC_HKEY_MIN && key <= ZC_HKEY_MAX) + return TRUE; + + if (h_terminating_keys != 0) + + if (key >= ZC_ARROW_MIN && key <= ZC_MENU_CLICK) { + + zword addr = h_terminating_keys; + zbyte c; + + do { + LOW_BYTE (addr, c) + if (c == 255 || key == translate_from_zscii (c)) + return TRUE; + addr++; + } while (c != 0); + + } + + return FALSE; + +}/* is_terminator */ + +/* + * z_make_menu, add or remove a menu and branch if successful. + * + * zargs[0] = number of menu + * zargs[1] = table of menu entries or 0 to remove menu + * + */ + +void z_make_menu (void) +{ + + /* This opcode was only used for the Macintosh version of Journey. + It controls menus with numbers greater than 2 (menus 0, 1 and 2 + are system menus). Frotz doesn't implement menus yet. */ + + branch (FALSE); + +}/* z_make_menu */ + +/* + * read_yes_or_no + * + * Ask the user a question; return true if the answer is yes. + * + */ + +bool read_yes_or_no (const char *s) +{ + zchar key; + + print_string (s); + print_string ("? (y/n) >"); + + key = stream_read_key (0, 0); + + if (key == 'y' || key == 'Y') { + print_string ("y\n"); + return TRUE; + } else { + print_string ("n\n"); + return FALSE; + } + +}/* read_yes_or_no */ + +/* + * read_string + * + * Read a string from the current input stream. + * + */ + +void read_string (int max, zchar *buffer) +{ + zchar key; + + buffer[0] = 0; + + do { + + key = stream_read_input (max, buffer, 0, 0, FALSE); + + } while (key != ZC_RETURN); + +}/* read_string */ + +/* + * read_number + * + * Ask the user to type in a number and return it. + * + */ + +int read_number (void) +{ + zchar buffer[6]; + int value = 0; + int i; + + read_string (5, buffer); + + for (i = 0; buffer[i] != 0; i++) + if (buffer[i] >= '0' && buffer[i] <= '9') + value = 10 * value + buffer[i] - '0'; + + return value; + +}/* read_number */ + +/* + * z_read, read a line of input and (in V5+) store the terminating key. + * + * zargs[0] = address of text buffer + * zargs[1] = address of token buffer + * zargs[2] = timeout in tenths of a second (optional) + * zargs[3] = packed address of routine to be called on timeout + * + */ + +void z_read (void) +{ + zchar buffer[INPUT_BUFFER_SIZE]; + zword addr; + zchar key; + zbyte max, size; + zbyte c; + int i; + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + + /* Get maximum input size */ + + addr = zargs[0]; + + LOW_BYTE (addr, max) + + if (h_version <= V4) + max--; + + if (max >= INPUT_BUFFER_SIZE) + max = INPUT_BUFFER_SIZE - 1; + + /* Get initial input size */ + + if (h_version >= V5) { + addr++; + LOW_BYTE (addr, size) + } else size = 0; + + /* Copy initial input to local buffer */ + + for (i = 0; i < size; i++) { + addr++; + LOW_BYTE (addr, c) + buffer[i] = translate_from_zscii (c); + } + + buffer[i] = 0; + + /* Draw status line for V1 to V3 games */ + + if (h_version <= V3) + z_show_status (); + + /* Read input from current input stream */ + + key = stream_read_input ( + max, buffer, /* buffer and size */ + zargs[2], /* timeout value */ + zargs[3], /* timeout routine */ + h_version == V6); /* no script in V6 */ + + if (key == ZC_BAD) + return; + + /* Perform save_undo for V1 to V4 games */ + + if (h_version <= V4) + save_undo (); + + /* Copy local buffer back to dynamic memory */ + + for (i = 0; buffer[i] != 0; i++) { + + if (key == ZC_RETURN) { + + if (buffer[i] >= 'A' && buffer[i] <= 'Z') + buffer[i] += 'a' - 'A'; + if (buffer[i] >= 0xc0 && buffer[i] <= 0xde && buffer[i] != 0xd7) + buffer[i] += 0x20; + + } + + storeb ((zword) (zargs[0] + ((h_version <= V4) ? 1 : 2) + i), translate_to_zscii (buffer[i])); + + } + + /* Add null character (V1-V4) or write input length into 2nd byte */ + + if (h_version <= V4) + storeb ((zword) (zargs[0] + 1 + i), 0); + else + storeb ((zword) (zargs[0] + 1), i); + + /* Tokenise line if a token buffer is present */ + + if (key == ZC_RETURN && zargs[1] != 0) + tokenise_line (zargs[0], zargs[1], 0, FALSE); + + /* Store key */ + + if (h_version >= V5) + store (translate_to_zscii (key)); + +}/* z_read */ + +/* + * z_read_char, read and store a key. + * + * zargs[0] = input device (must be 1) + * zargs[1] = timeout in tenths of a second (optional) + * zargs[2] = packed address of routine to be called on timeout + * + */ + +void z_read_char (void) +{ + zchar key; + + /* Supply default arguments */ + + if (zargc < 2) + zargs[1] = 0; + + /* Read input from the current input stream */ + + key = stream_read_key ( + zargs[1], /* timeout value */ + zargs[2]); /* timeout routine */ + + if (key == ZC_BAD) + return; + + /* Store key */ + + store (translate_to_zscii (key)); + +}/* z_read_char */ + diff --git a/interpreters/frotz/main.c b/interpreters/frotz/main.c new file mode 100644 index 0000000..a403c13 --- /dev/null +++ b/interpreters/frotz/main.c @@ -0,0 +1,218 @@ +/* main.c - Frotz V2.40 main function + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +/* + * This is an interpreter for Infocom V1 to V6 games. It also supports + * the recently defined V7 and V8 games. Please report bugs to + * + * s.jokisch@avu.de + * + */ + +#include "frotz.h" + +#ifndef MSDOS_16BIT +#define cdecl +#endif + +extern void interpret (void); +extern void init_memory (void); +extern void init_undo (void); +extern void reset_memory (void); + +/* Story file name, id number and size */ + +char *story_name = 0; + +enum story story_id = UNKNOWN; +long story_size = 0; + +/* Story file header data */ + +zbyte h_version = 0; +zbyte h_config = 0; +zword h_release = 0; +zword h_resident_size = 0; +zword h_start_pc = 0; +zword h_dictionary = 0; +zword h_objects = 0; +zword h_globals = 0; +zword h_dynamic_size = 0; +zword h_flags = 0; +zbyte h_serial[6] = { 0, 0, 0, 0, 0, 0 }; +zword h_abbreviations = 0; +zword h_file_size = 0; +zword h_checksum = 0; +zbyte h_interpreter_number = 0; +zbyte h_interpreter_version = 0; +zbyte h_screen_rows = 0; +zbyte h_screen_cols = 0; +zword h_screen_width = 0; +zword h_screen_height = 0; +zbyte h_font_height = 1; +zbyte h_font_width = 1; +zword h_functions_offset = 0; +zword h_strings_offset = 0; +zbyte h_default_background = 0; +zbyte h_default_foreground = 0; +zword h_terminating_keys = 0; +zword h_line_width = 0; +zbyte h_standard_high = 1; +zbyte h_standard_low = 0; +zword h_alphabet = 0; +zword h_extension_table = 0; +zbyte h_user_name[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + +zword hx_table_size = 0; +zword hx_mouse_x = 0; +zword hx_mouse_y = 0; +zword hx_unicode_table = 0; + +/* Stack data */ + +zword stack[STACK_SIZE]; +zword *sp = 0; +zword *fp = 0; +zword frame_count = 0; + +/* IO streams */ + +bool ostream_screen = TRUE; +bool ostream_script = FALSE; +bool ostream_memory = FALSE; +bool ostream_record = FALSE; +bool istream_replay = FALSE; +bool message = FALSE; + +/* Current window and mouse data */ + +int cwin = 0; +int mwin = 0; + +int mouse_y = 0; +int mouse_x = 0; + +/* Window attributes */ + +bool enable_wrapping = FALSE; +bool enable_scripting = TRUE; +bool enable_scrolling = FALSE; +bool enable_buffering = FALSE; + +/* User options */ + +/* +int option_attribute_assignment = 0; +int option_attribute_testing = 0; +int option_context_lines = 0; +int option_object_locating = 0; +int option_object_movement = 0; +int option_left_margin = 0; +int option_right_margin = 0; +int option_ignore_errors = 0; +int option_piracy = 0; +int option_undo_slots = MAX_UNDO_SLOTS; +int option_expand_abbreviations = 0; +int option_script_cols = 80; +int option_save_quetzal = 1; +*/ + +int option_sound = 1; +char *option_zcode_path; + + +/* Size of memory to reserve (in bytes) */ + +long reserve_mem = 0; + +/* + * z_piracy, branch if the story file is a legal copy. + * + * no zargs used + * + */ + +void z_piracy (void) +{ + + branch (!f_setup.piracy); + +}/* z_piracy */ + +/* + * main + * + * Prepare and run the game. + * + */ + +#include "glk.h" +#include "glkstart.h" + +static int myargc; +static char **myargv; + +glkunix_argumentlist_t glkunix_arguments[] = +{ +{ "-a", glkunix_arg_NoValue, "-a: watch attribute setting" }, +{ "-A", glkunix_arg_NoValue, "-A: watch attribute testing" }, +{ "-i", glkunix_arg_NoValue, "-i: ignore fatal errors" }, +{ "-o", glkunix_arg_NoValue, "-o: watch object movement" }, +{ "-O", glkunix_arg_NoValue, "-O: watch object locating" }, +{ "-P", glkunix_arg_NoValue, "-P: alter piracy opcode" }, +{ "-Q", glkunix_arg_NoValue, "-Q: use old-style save format" }, +{ "-t", glkunix_arg_NoValue, "-t: set Tandy bit" }, +{ "-x", glkunix_arg_NoValue, "-x: expand abbreviations g/x/z" }, +{ "-I", glkunix_arg_NumberValue, "-I: interpreter number" }, +{ "-s", glkunix_arg_NumberValue, "-s: random number seed value" }, +{ "-S", glkunix_arg_NumberValue, "-S: transcript width" }, +{ "-u", glkunix_arg_NumberValue, "-u: slots for multiple undo" }, +{ "-Z", glkunix_arg_NumberValue, "-Z: error checking mode" }, +{ "", glkunix_arg_ValueFollows, "filename: The game file to load." }, +{ NULL, glkunix_arg_End, NULL } +}; + +int glkunix_startup_code(glkunix_startup_t *data) +{ + myargc = data->argc; + myargv = data->argv; + + os_init_setup (); + os_process_arguments (myargc, myargv); + + init_buffer (); + init_err (); + init_memory (); + init_process (); + init_sound (); + + os_init_screen (); + + init_undo (); + z_restart (); + return TRUE; +} + +void glk_main (void) +{ + interpret (); + reset_memory (); +} + diff --git a/interpreters/frotz/math.c b/interpreters/frotz/math.c new file mode 100644 index 0000000..5ff5163 --- /dev/null +++ b/interpreters/frotz/math.c @@ -0,0 +1,261 @@ +/* math.c - Arithmetic, compare and logical opcodes + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* + * z_add, 16bit addition. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_add (void) +{ + + store ((zword) ((short) zargs[0] + (short) zargs[1])); + +}/* z_add */ + +/* + * z_and, bitwise AND operation. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_and (void) +{ + + store ((zword) (zargs[0] & zargs[1])); + +}/* z_and */ + +/* + * z_art_shift, arithmetic SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right + * + */ + +void z_art_shift (void) +{ + + if ((short) zargs[1] > 0) + store ((zword) ((short) zargs[0] << (short) zargs[1])); + else + store ((zword) ((short) zargs[0] >> - (short) zargs[1])); + +}/* z_art_shift */ + +/* + * z_div, signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_div (void) +{ + + if (zargs[1] == 0) + runtime_error (ERR_DIV_ZERO); + + store ((zword) ((short) zargs[0] / (short) zargs[1])); + +}/* z_div */ + +/* + * z_je, branch if the first value equals any of the following. + * + * zargs[0] = first value + * zargs[1] = second value (optional) + * ... + * zargs[3] = fourth value (optional) + * + */ + +void z_je (void) +{ + + branch ( + zargc > 1 && (zargs[0] == zargs[1] || ( + zargc > 2 && (zargs[0] == zargs[2] || ( + zargc > 3 && (zargs[0] == zargs[3])))))); + +}/* z_je */ + +/* + * z_jg, branch if the first value is greater than the second. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_jg (void) +{ + + branch ((short) zargs[0] > (short) zargs[1]); + +}/* z_jg */ + +/* + * z_jl, branch if the first value is less than the second. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_jl (void) +{ + + branch ((short) zargs[0] < (short) zargs[1]); + +}/* z_jl */ + +/* + * z_jz, branch if value is zero. + * + * zargs[0] = value + * + */ + +void z_jz (void) +{ + + branch ((short) zargs[0] == 0); + +}/* z_jz */ + +/* + * z_log_shift, logical SHIFT operation. + * + * zargs[0] = value + * zargs[1] = #positions to shift left (positive) or right (negative) + * + */ + +void z_log_shift (void) +{ + + if ((short) zargs[1] > 0) + store ((zword) (zargs[0] << (short) zargs[1])); + else + store ((zword) (zargs[0] >> - (short) zargs[1])); + +}/* z_log_shift */ + +/* + * z_mod, remainder after signed 16bit division. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_mod (void) +{ + + if (zargs[1] == 0) + runtime_error (ERR_DIV_ZERO); + + store ((zword) ((short) zargs[0] % (short) zargs[1])); + +}/* z_mod */ + +/* + * z_mul, 16bit multiplication. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_mul (void) +{ + + store ((zword) ((short) zargs[0] * (short) zargs[1])); + +}/* z_mul */ + +/* + * z_not, bitwise NOT operation. + * + * zargs[0] = value + * + */ + +void z_not (void) +{ + + store ((zword) ~zargs[0]); + +}/* z_not */ + +/* + * z_or, bitwise OR operation. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_or (void) +{ + + store ((zword) (zargs[0] | zargs[1])); + +}/* z_or */ + +/* + * z_sub, 16bit substraction. + * + * zargs[0] = first value + * zargs[1] = second value + * + */ + +void z_sub (void) +{ + + store ((zword) ((short) zargs[0] - (short) zargs[1])); + +}/* z_sub */ + +/* + * z_test, branch if all the flags of a bit mask are set in a value. + * + * zargs[0] = value to be examined + * zargs[1] = bit mask + * + */ + +void z_test (void) +{ + + branch ((zargs[0] & zargs[1]) == zargs[1]); + +}/* z_test */ diff --git a/interpreters/frotz/object.c b/interpreters/frotz/object.c new file mode 100644 index 0000000..22ffd08 --- /dev/null +++ b/interpreters/frotz/object.c @@ -0,0 +1,1003 @@ +/* object.c - Object manipulation opcodes + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#define MAX_OBJECT 2000 + +#define O1_PARENT 4 +#define O1_SIBLING 5 +#define O1_CHILD 6 +#define O1_PROPERTY_OFFSET 7 +#define O1_SIZE 9 + +#define O4_PARENT 6 +#define O4_SIBLING 8 +#define O4_CHILD 10 +#define O4_PROPERTY_OFFSET 12 +#define O4_SIZE 14 + +/* + * object_address + * + * Calculate the address of an object. + * + */ + +static zword object_address (zword obj) +{ +/* zchar obj_num[10]; */ + + /* Check object number */ + + if (obj > ((h_version <= V3) ? 255 : MAX_OBJECT)) { + print_string("@Attempt to address illegal object "); + print_num(obj); + print_string(". This is normally fatal."); + new_line(); + runtime_error (ERR_ILL_OBJ); + } + + /* Return object address */ + + if (h_version <= V3) + return h_objects + ((obj - 1) * O1_SIZE + 62); + else + return h_objects + ((obj - 1) * O4_SIZE + 126); + +}/* object_address */ + +/* + * object_name + * + * Return the address of the given object's name. + * + */ + +zword object_name (zword object) +{ + zword obj_addr; + zword name_addr; + + obj_addr = object_address (object); + + /* The object name address is found at the start of the properties */ + + if (h_version <= V3) + obj_addr += O1_PROPERTY_OFFSET; + else + obj_addr += O4_PROPERTY_OFFSET; + + LOW_WORD (obj_addr, name_addr) + + return name_addr; + +}/* object_name */ + +/* + * first_property + * + * Calculate the start address of the property list associated with + * an object. + * + */ + +static zword first_property (zword obj) +{ + zword prop_addr; + zbyte size; + + /* Fetch address of object name */ + + prop_addr = object_name (obj); + + /* Get length of object name */ + + LOW_BYTE (prop_addr, size) + + /* Add name length to pointer */ + + return prop_addr + 1 + 2 * size; + +}/* first_property */ + +/* + * next_property + * + * Calculate the address of the next property in a property list. + * + */ + +static zword next_property (zword prop_addr) +{ + zbyte value; + + /* Load the current property id */ + + LOW_BYTE (prop_addr, value) + prop_addr++; + + /* Calculate the length of this property */ + + if (h_version <= V3) + value >>= 5; + else if (!(value & 0x80)) + value >>= 6; + else { + + LOW_BYTE (prop_addr, value) + value &= 0x3f; + + if (value == 0) value = 64; /* demanded by Spec 1.0 */ + + } + + /* Add property length to current property pointer */ + + return prop_addr + value + 1; + +}/* next_property */ + +/* + * unlink_object + * + * Unlink an object from its parent and siblings. + * + */ + +static void unlink_object (zword object) +{ + zword obj_addr; + zword parent_addr; + zword sibling_addr; + + if (object == 0) { + runtime_error (ERR_REMOVE_OBJECT_0); + return; + } + + obj_addr = object_address (object); + + if (h_version <= V3) { + + zbyte parent; + zbyte younger_sibling; + zbyte older_sibling; + zbyte zero = 0; + + /* Get parent of object, and return if no parent */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + if (!parent) + return; + + /* Get (older) sibling of object and set both parent and sibling + pointers to 0 */ + + SET_BYTE (obj_addr, zero) + obj_addr += O1_SIBLING - O1_PARENT; + LOW_BYTE (obj_addr, older_sibling) + SET_BYTE (obj_addr, zero) + + /* Get first child of parent (the youngest sibling of the object) */ + + parent_addr = object_address (parent) + O1_CHILD; + LOW_BYTE (parent_addr, younger_sibling) + + /* Remove object from the list of siblings */ + + if (younger_sibling == object) + SET_BYTE (parent_addr, older_sibling) + else { + do { + sibling_addr = object_address (younger_sibling) + O1_SIBLING; + LOW_BYTE (sibling_addr, younger_sibling) + } while (younger_sibling != object); + SET_BYTE (sibling_addr, older_sibling) + } + + } else { + + zword parent; + zword younger_sibling; + zword older_sibling; + zword zero = 0; + + /* Get parent of object, and return if no parent */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + if (!parent) + return; + + /* Get (older) sibling of object and set both parent and sibling + pointers to 0 */ + + SET_WORD (obj_addr, zero) + obj_addr += O4_SIBLING - O4_PARENT; + LOW_WORD (obj_addr, older_sibling) + SET_WORD (obj_addr, zero) + + /* Get first child of parent (the youngest sibling of the object) */ + + parent_addr = object_address (parent) + O4_CHILD; + LOW_WORD (parent_addr, younger_sibling) + + /* Remove object from the list of siblings */ + + if (younger_sibling == object) + SET_WORD (parent_addr, older_sibling) + else { + do { + sibling_addr = object_address (younger_sibling) + O4_SIBLING; + LOW_WORD (sibling_addr, younger_sibling) + } while (younger_sibling != object); + SET_WORD (sibling_addr, older_sibling) + } + + } + +}/* unlink_object */ + +/* + * z_clear_attr, clear an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to be cleared + * + */ + +void z_clear_attr (void) +{ + zword obj_addr; + zbyte value; + + if (story_id == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error (ERR_ILL_ATTR); + + /* If we are monitoring attribute assignment display a short note */ + + if (f_setup.attribute_assignment) { + stream_mssg_on (); + print_string ("@clear_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_CLEAR_ATTR_0); + return; + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Clear attribute bit */ + + LOW_BYTE (obj_addr, value) + value &= ~(0x80 >> (zargs[1] & 7)); + SET_BYTE (obj_addr, value) + +}/* z_clear_attr */ + +/* + * z_jin, branch if the first object is inside the second. + * + * zargs[0] = first object + * zargs[1] = second object + * + */ + +void z_jin (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (f_setup.object_locating) { + stream_mssg_on (); + print_string ("@jin "); + print_object (zargs[0]); + print_string (" "); + print_object (zargs[1]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_JIN_0); + branch (0 == zargs[1]); + return; + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte parent; + + /* Get parent id from object */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + + /* Branch if the parent is obj2 */ + + branch (parent == zargs[1]); + + } else { + + zword parent; + + /* Get parent id from object */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + + /* Branch if the parent is obj2 */ + + branch (parent == zargs[1]); + + } + +}/* z_jin */ + +/* + * z_get_child, store the child of an object. + * + * zargs[0] = object + * + */ + +void z_get_child (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (f_setup.object_locating) { + stream_mssg_on (); + print_string ("@get_child "); + print_object (zargs[0]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_GET_CHILD_0); + store (0); + branch (FALSE); + return; + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte child; + + /* Get child id from object */ + + obj_addr += O1_CHILD; + LOW_BYTE (obj_addr, child) + + /* Store child id and branch */ + + store (child); + branch (child); + + } else { + + zword child; + + /* Get child id from object */ + + obj_addr += O4_CHILD; + LOW_WORD (obj_addr, child) + + /* Store child id and branch */ + + store (child); + branch (child); + + } + +}/* z_get_child */ + +/* + * z_get_next_prop, store the number of the first or next property. + * + * zargs[0] = object + * zargs[1] = address of current property (0 gets the first property) + * + */ + +void z_get_next_prop (void) +{ + zword prop_addr; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtime_error (ERR_GET_NEXT_PROP_0); + store (0); + return; + } + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + if (zargs[1] != 0) { + + /* Scan down the property list */ + + do { + LOW_BYTE (prop_addr, value) + prop_addr = next_property (prop_addr); + } while ((value & mask) > zargs[1]); + + /* Exit if the property does not exist */ + + if ((value & mask) != zargs[1]) + runtime_error (ERR_NO_PROP); + + } + + /* Return the property id */ + + LOW_BYTE (prop_addr, value) + store ((zword) (value & mask)); + +}/* z_get_next_prop */ + +/* + * z_get_parent, store the parent of an object. + * + * zargs[0] = object + * + */ + +void z_get_parent (void) +{ + zword obj_addr; + + /* If we are monitoring object locating display a short note */ + + if (f_setup.object_locating) { + stream_mssg_on (); + print_string ("@get_parent "); + print_object (zargs[0]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_GET_PARENT_0); + store (0); + return; + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte parent; + + /* Get parent id from object */ + + obj_addr += O1_PARENT; + LOW_BYTE (obj_addr, parent) + + /* Store parent */ + + store (parent); + + } else { + + zword parent; + + /* Get parent id from object */ + + obj_addr += O4_PARENT; + LOW_WORD (obj_addr, parent) + + /* Store parent */ + + store (parent); + + } + +}/* z_get_parent */ + +/* + * z_get_prop, store the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + * + */ + +void z_get_prop (void) +{ + zword prop_addr; + zword wprop_val; + zbyte bprop_val; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtime_error (ERR_GET_PROP_0); + store (0); + return; + } + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + if ((value & mask) == zargs[1]) { /* property found */ + + /* Load property (byte or word sized) */ + + prop_addr++; + + if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) { + + LOW_BYTE (prop_addr, bprop_val) + wprop_val = bprop_val; + + } else LOW_WORD (prop_addr, wprop_val) + + } else { /* property not found */ + + /* Load default value */ + + prop_addr = h_objects + 2 * (zargs[1] - 1); + LOW_WORD (prop_addr, wprop_val) + + } + + /* Store the property value */ + + store (wprop_val); + +}/* z_get_prop */ + +/* + * z_get_prop_addr, store the address of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to be examined + * + */ + +void z_get_prop_addr (void) +{ + zword prop_addr; + zbyte value; + zbyte mask; + + if (zargs[0] == 0) { + runtime_error (ERR_GET_PROP_ADDR_0); + store (0); + return; + } + + if (story_id == BEYOND_ZORK) + if (zargs[0] > MAX_OBJECT) + { store (0); return; } + + /* Property id is in bottom five (six) bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + /* Calculate the property address or return zero */ + + if ((value & mask) == zargs[1]) { + + if (h_version >= V4 && (value & 0x80)) + prop_addr++; + store ((zword) (prop_addr + 1)); + + } else store (0); + +}/* z_get_prop_addr */ + +/* + * z_get_prop_len, store the length of an object property. + * + * zargs[0] = address of property to be examined + * + */ + +void z_get_prop_len (void) +{ + zword addr; + zbyte value; + + /* Back up the property pointer to the property id */ + + addr = zargs[0] - 1; + LOW_BYTE (addr, value) + + /* Calculate length of property */ + + if (h_version <= V3) + value = (value >> 5) + 1; + else if (!(value & 0x80)) + value = (value >> 6) + 1; + else { + + value &= 0x3f; + + if (value == 0) value = 64; /* demanded by Spec 1.0 */ + + } + + /* Store length of property */ + + store (value); + +}/* z_get_prop_len */ + +/* + * z_get_sibling, store the sibling of an object. + * + * zargs[0] = object + * + */ + +void z_get_sibling (void) +{ + zword obj_addr; + + if (zargs[0] == 0) { + runtime_error (ERR_GET_SIBLING_0); + store (0); + branch (FALSE); + return; + } + + obj_addr = object_address (zargs[0]); + + if (h_version <= V3) { + + zbyte sibling; + + /* Get sibling id from object */ + + obj_addr += O1_SIBLING; + LOW_BYTE (obj_addr, sibling) + + /* Store sibling and branch */ + + store (sibling); + branch (sibling); + + } else { + + zword sibling; + + /* Get sibling id from object */ + + obj_addr += O4_SIBLING; + LOW_WORD (obj_addr, sibling) + + /* Store sibling and branch */ + + store (sibling); + branch (sibling); + + } + +}/* z_get_sibling */ + +/* + * z_insert_obj, make an object the first child of another object. + * + * zargs[0] = object to be moved + * zargs[1] = destination object + * + */ + +void z_insert_obj (void) +{ + zword obj1 = zargs[0]; + zword obj2 = zargs[1]; + zword obj1_addr; + zword obj2_addr; + + /* If we are monitoring object movements display a short note */ + + if (f_setup.object_movement) { + stream_mssg_on (); + print_string ("@move_obj "); + print_object (obj1); + print_string (" "); + print_object (obj2); + stream_mssg_off (); + } + + if (obj1 == 0) { + runtime_error (ERR_MOVE_OBJECT_0); + return; + } + + if (obj2 == 0) { + runtime_error (ERR_MOVE_OBJECT_TO_0); + return; + } + + /* Get addresses of both objects */ + + obj1_addr = object_address (obj1); + obj2_addr = object_address (obj2); + + /* Remove object 1 from current parent */ + + unlink_object (obj1); + + /* Make object 1 first child of object 2 */ + + if (h_version <= V3) { + + zbyte child; + + obj1_addr += O1_PARENT; + SET_BYTE (obj1_addr, obj2) + obj2_addr += O1_CHILD; + LOW_BYTE (obj2_addr, child) + SET_BYTE (obj2_addr, obj1) + obj1_addr += O1_SIBLING - O1_PARENT; + SET_BYTE (obj1_addr, child) + + } else { + + zword child; + + obj1_addr += O4_PARENT; + SET_WORD (obj1_addr, obj2) + obj2_addr += O4_CHILD; + LOW_WORD (obj2_addr, child) + SET_WORD (obj2_addr, obj1) + obj1_addr += O4_SIBLING - O4_PARENT; + SET_WORD (obj1_addr, child) + + } + +}/* z_insert_obj */ + +/* + * z_put_prop, set the value of an object property. + * + * zargs[0] = object + * zargs[1] = number of property to set + * zargs[2] = value to set property to + * + */ + +void z_put_prop (void) +{ + zword prop_addr; + zword value; + zbyte mask; + + if (zargs[0] == 0) { + runtime_error (ERR_PUT_PROP_0); + return; + } + + /* Property id is in bottom five or six bits */ + + mask = (h_version <= V3) ? 0x1f : 0x3f; + + /* Load address of first property */ + + prop_addr = first_property (zargs[0]); + + /* Scan down the property list */ + + for (;;) { + LOW_BYTE (prop_addr, value) + if ((value & mask) <= zargs[1]) + break; + prop_addr = next_property (prop_addr); + } + + /* Exit if the property does not exist */ + + if ((value & mask) != zargs[1]) + runtime_error (ERR_NO_PROP); + + /* Store the new property value (byte or word sized) */ + + prop_addr++; + + if ((h_version <= V3 && !(value & 0xe0)) || (h_version >= V4 && !(value & 0xc0))) { + zbyte v = zargs[2]; + SET_BYTE (prop_addr, v) + } else { + zword v = zargs[2]; + SET_WORD (prop_addr, v) + } + +}/* z_put_prop */ + +/* + * z_remove_obj, unlink an object from its parent and siblings. + * + * zargs[0] = object + * + */ + +void z_remove_obj (void) +{ + + /* If we are monitoring object movements display a short note */ + + if (f_setup.object_movement) { + stream_mssg_on (); + print_string ("@remove_obj "); + print_object (zargs[0]); + stream_mssg_off (); + } + + /* Call unlink_object to do the job */ + + unlink_object (zargs[0]); + +}/* z_remove_obj */ + +/* + * z_set_attr, set an object attribute. + * + * zargs[0] = object + * zargs[1] = number of attribute to set + * + */ + +void z_set_attr (void) +{ + zword obj_addr; + zbyte value; + + if (story_id == SHERLOCK) + if (zargs[1] == 48) + return; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error (ERR_ILL_ATTR); + + /* If we are monitoring attribute assignment display a short note */ + + if (f_setup.attribute_assignment) { + stream_mssg_on (); + print_string ("@set_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_SET_ATTR_0); + return; + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Load attribute byte */ + + LOW_BYTE (obj_addr, value) + + /* Set attribute bit */ + + value |= 0x80 >> (zargs[1] & 7); + + /* Store attribute byte */ + + SET_BYTE (obj_addr, value) + +}/* z_set_attr */ + +/* + * z_test_attr, branch if an object attribute is set. + * + * zargs[0] = object + * zargs[1] = number of attribute to test + * + */ + +void z_test_attr (void) +{ + zword obj_addr; + zbyte value; + + if (zargs[1] > ((h_version <= V3) ? 31 : 47)) + runtime_error (ERR_ILL_ATTR); + + /* If we are monitoring attribute testing display a short note */ + + if (f_setup.attribute_testing) { + stream_mssg_on (); + print_string ("@test_attr "); + print_object (zargs[0]); + print_string (" "); + print_num (zargs[1]); + stream_mssg_off (); + } + + if (zargs[0] == 0) { + runtime_error (ERR_TEST_ATTR_0); + branch (FALSE); + return; + } + + /* Get attribute address */ + + obj_addr = object_address (zargs[0]) + zargs[1] / 8; + + /* Load attribute byte */ + + LOW_BYTE (obj_addr, value) + + /* Test attribute */ + + branch (value & (0x80 >> (zargs[1] & 7))); + +}/* z_test_attr */ diff --git a/interpreters/frotz/process.c b/interpreters/frotz/process.c new file mode 100644 index 0000000..e79edf5 --- /dev/null +++ b/interpreters/frotz/process.c @@ -0,0 +1,797 @@ +/* process.c - Interpreter loop and program control + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#ifdef DJGPP +#include "djfrotz.h" +#endif + + +zword zargs[8]; +int zargc; + +static int finished = 0; + +static void __extended__ (void); +static void __illegal__ (void); + +void (*op0_opcodes[0x10]) (void) = { + z_rtrue, + z_rfalse, + z_print, + z_print_ret, + z_nop, + z_save, + z_restore, + z_restart, + z_ret_popped, + z_catch, + z_quit, + z_new_line, + z_show_status, + z_verify, + __extended__, + z_piracy +}; + +void (*op1_opcodes[0x10]) (void) = { + z_jz, + z_get_sibling, + z_get_child, + z_get_parent, + z_get_prop_len, + z_inc, + z_dec, + z_print_addr, + z_call_s, + z_remove_obj, + z_print_obj, + z_ret, + z_jump, + z_print_paddr, + z_load, + z_call_n +}; + +void (*var_opcodes[0x40]) (void) = { + __illegal__, + z_je, + z_jl, + z_jg, + z_dec_chk, + z_inc_chk, + z_jin, + z_test, + z_or, + z_and, + z_test_attr, + z_set_attr, + z_clear_attr, + z_store, + z_insert_obj, + z_loadw, + z_loadb, + z_get_prop, + z_get_prop_addr, + z_get_next_prop, + z_add, + z_sub, + z_mul, + z_div, + z_mod, + z_call_s, + z_call_n, + z_set_colour, + z_throw, + __illegal__, + __illegal__, + __illegal__, + z_call_s, + z_storew, + z_storeb, + z_put_prop, + z_read, + z_print_char, + z_print_num, + z_random, + z_push, + z_pull, + z_split_window, + z_set_window, + z_call_s, + z_erase_window, + z_erase_line, + z_set_cursor, + z_get_cursor, + z_set_text_style, + z_buffer_mode, + z_output_stream, + z_input_stream, + z_sound_effect, + z_read_char, + z_scan_table, + z_not, + z_call_n, + z_call_n, + z_tokenise, + z_encode_text, + z_copy_table, + z_print_table, + z_check_arg_count +}; + +void (*ext_opcodes[0x1d]) (void) = { + z_save, + z_restore, + z_log_shift, + z_art_shift, + z_set_font, + __illegal__, // glkify - z_draw_picture, + __illegal__, // glkify - z_picture_data, + __illegal__, // glkify - z_erase_picture, + __illegal__, // glkify - z_set_margins, + z_save_undo, + z_restore_undo, + z_print_unicode, + z_check_unicode, + __illegal__, + __illegal__, + __illegal__, + __illegal__, // glkify - z_move_window, + __illegal__, // glkify - z_window_size, + __illegal__, // glkify - z_window_style, + __illegal__, // glkify - z_get_wind_prop, + __illegal__, // glkify - z_scroll_window, + z_pop_stack, + __illegal__, // glkify - z_read_mouse, + __illegal__, // glkify - z_mouse_window, + z_push_stack, + __illegal__, // glkify - z_put_wind_prop, + z_print_form, + z_make_menu, + __illegal__, // glkify - z_picture_table +}; + + +/* + * init_process + * + * Initialize process variables. + * + */ + +void init_process (void) +{ + finished = 0; +} /* init_process */ + + +/* + * load_operand + * + * Load an operand, either a variable or a constant. + * + */ + +static void load_operand (zbyte type) +{ + zword value; + + if (type & 2) { /* variable */ + + zbyte variable; + + CODE_BYTE (variable) + + if (variable == 0) + value = *sp++; + else if (variable < 16) + value = *(fp - variable); + else { + zword addr = h_globals + 2 * (variable - 16); + LOW_WORD (addr, value) + } + + } else if (type & 1) { /* small constant */ + + zbyte bvalue; + + CODE_BYTE (bvalue) + value = bvalue; + + } else CODE_WORD (value) /* large constant */ + + zargs[zargc++] = value; + +}/* load_operand */ + +/* + * load_all_operands + * + * Given the operand specifier byte, load all (up to four) operands + * for a VAR or EXT opcode. + * + */ + +static void load_all_operands (zbyte specifier) +{ + int i; + + for (i = 6; i >= 0; i -= 2) { + + zbyte type = (specifier >> i) & 0x03; + + if (type == 3) + break; + + load_operand (type); + + } + +}/* load_all_operands */ + +/* + * interpret + * + * Z-code interpreter main loop + * + */ + +void interpret (void) +{ + do { + + zbyte opcode; + + CODE_BYTE (opcode) + + zargc = 0; + + if (opcode < 0x80) { /* 2OP opcodes */ + + load_operand ((zbyte) (opcode & 0x40) ? 2 : 1); + load_operand ((zbyte) (opcode & 0x20) ? 2 : 1); + + var_opcodes[opcode & 0x1f] (); + + } else if (opcode < 0xb0) { /* 1OP opcodes */ + + load_operand ((zbyte) (opcode >> 4)); + + op1_opcodes[opcode & 0x0f] (); + + } else if (opcode < 0xc0) { /* 0OP opcodes */ + + op0_opcodes[opcode - 0xb0] (); + + } else { /* VAR opcodes */ + + zbyte specifier1; + zbyte specifier2; + + if (opcode == 0xec || opcode == 0xfa) { /* opcodes 0xec */ + CODE_BYTE (specifier1) /* and 0xfa are */ + CODE_BYTE (specifier2) /* call opcodes */ + load_all_operands (specifier1); /* with up to 8 */ + load_all_operands (specifier2); /* arguments */ + } else { + CODE_BYTE (specifier1) + load_all_operands (specifier1); + } + + var_opcodes[opcode - 0xc0] (); + + } + +#if defined(DJGPP) && defined(SOUND_SUPPORT) + if (end_of_sound_flag) + end_of_sound (); +#endif + + } while (finished == 0); + + finished--; + +}/* interpret */ + +/* + * call + * + * Call a subroutine. Save PC and FP then load new PC and initialise + * new stack frame. Note that the caller may legally provide less or + * more arguments than the function actually has. The call type "ct" + * can be 0 (z_call_s), 1 (z_call_n) or 2 (direct call). + * + */ + +void call (zword routine, int argc, zword *args, int ct) +{ + long pc; + zword value; + zbyte count; + int i; + + if (sp - stack < 4) + runtime_error (ERR_STK_OVF); + + GET_PC (pc) + + *--sp = (zword) (pc >> 9); + *--sp = (zword) (pc & 0x1ff); + *--sp = (zword) (fp - stack - 1); + *--sp = (zword) (argc | (ct << (f_setup.save_quetzal ? 12 : 8))); + + fp = sp; + frame_count++; + + /* Calculate byte address of routine */ + + if (h_version <= V3) + pc = (long) routine << 1; + else if (h_version <= V5) + pc = (long) routine << 2; + else if (h_version <= V7) + pc = ((long) routine << 2) + ((long) h_functions_offset << 3); + else /* h_version == V8 */ + pc = (long) routine << 3; + + if (pc >= story_size) + runtime_error (ERR_ILL_CALL_ADDR); + + SET_PC (pc) + + /* Initialise local variables */ + + CODE_BYTE (count) + + if (count > 15) + runtime_error (ERR_CALL_NON_RTN); + if (sp - stack < count) + runtime_error (ERR_STK_OVF); + + if (f_setup.save_quetzal) + fp[0] |= (zword) count << 8; /* Save local var count for Quetzal. */ + + value = 0; + + for (i = 0; i < count; i++) { + + if (h_version <= V4) /* V1 to V4 games provide default */ + CODE_WORD (value) /* values for all local variables */ + + *--sp = (zword) ((argc-- > 0) ? args[i] : value); + + } + + /* Start main loop for direct calls */ + + if (ct == 2) + interpret (); + +}/* call */ + +/* + * ret + * + * Return from the current subroutine and restore the previous stack + * frame. The result may be stored (0), thrown away (1) or pushed on + * the stack (2). In the latter case a direct call has been finished + * and we must exit the interpreter loop. + * + */ + +void ret (zword value) +{ + long pc; + int ct; + + if (sp > fp) + runtime_error (ERR_STK_UNDF); + + sp = fp; + + ct = *sp++ >> (f_setup.save_quetzal ? 12 : 8); + frame_count--; + fp = stack + 1 + *sp++; + pc = *sp++; + pc = ((long) *sp++ << 9) | pc; + + SET_PC (pc) + + /* Handle resulting value */ + + if (ct == 0) + store (value); + if (ct == 2) + *--sp = value; + + /* Stop main loop for direct calls */ + + if (ct == 2) + finished++; + +}/* ret */ + +/* + * branch + * + * Take a jump after an instruction based on the flag, either true or + * false. The branch can be short or long; it is encoded in one or two + * bytes respectively. When bit 7 of the first byte is set, the jump + * takes place if the flag is true; otherwise it is taken if the flag + * is false. When bit 6 of the first byte is set, the branch is short; + * otherwise it is long. The offset occupies the bottom 6 bits of the + * first byte plus all the bits in the second byte for long branches. + * Uniquely, an offset of 0 means return false, and an offset of 1 is + * return true. + * + */ + +void branch (bool flag) +{ + long pc; + zword offset; + zbyte specifier; + zbyte off1; + zbyte off2; + + CODE_BYTE (specifier) + + off1 = specifier & 0x3f; + + if (!flag) + specifier ^= 0x80; + + if (!(specifier & 0x40)) { /* it's a long branch */ + + if (off1 & 0x20) /* propagate sign bit */ + off1 |= 0xc0; + + CODE_BYTE (off2) + + offset = (off1 << 8) | off2; + + } else offset = off1; /* it's a short branch */ + + if (specifier & 0x80) { + + if (offset > 1) { /* normal branch */ + + GET_PC (pc) + pc += (short) offset - 2; + SET_PC (pc) + + } else ret (offset); /* special case, return 0 or 1 */ + } + +}/* branch */ + +/* + * store + * + * Store an operand, either as a variable or pushed on the stack. + * + */ + +void store (zword value) +{ + zbyte variable; + + CODE_BYTE (variable) + + if (variable == 0) + *--sp = value; + else if (variable < 16) + *(fp - variable) = value; + else { + zword addr = h_globals + 2 * (variable - 16); + SET_WORD (addr, value) + } + +}/* store */ + +/* + * direct_call + * + * Call the interpreter loop directly. This is necessary when + * + * - a sound effect has been finished + * - a read instruction has timed out + * - a newline countdown has hit zero + * + * The interpreter returns the result value on the stack. + * + */ + +int direct_call (zword addr) +{ + zword saved_zargs[8]; + int saved_zargc; + int i; + + /* Calls to address 0 return false */ + + if (addr == 0) + return 0; + + /* Save operands and operand count */ + + for (i = 0; i < 8; i++) + saved_zargs[i] = zargs[i]; + + saved_zargc = zargc; + + /* Call routine directly */ + + call (addr, 0, 0, 2); + + /* Restore operands and operand count */ + + for (i = 0; i < 8; i++) + zargs[i] = saved_zargs[i]; + + zargc = saved_zargc; + + /* Resulting value lies on top of the stack */ + + return (short) *sp++; + +}/* direct_call */ + +/* + * __extended__ + * + * Load and execute an extended opcode. + * + */ + +static void __extended__ (void) +{ + zbyte opcode; + zbyte specifier; + + CODE_BYTE (opcode) + CODE_BYTE (specifier) + + load_all_operands (specifier); + + if (opcode < 0x1d) /* extended opcodes from 0x1d on */ + ext_opcodes[opcode] (); /* are reserved for future spec' */ + +}/* __extended__ */ + +/* + * __illegal__ + * + * Exit game because an unknown opcode has been hit. + * + */ + +static void __illegal__ (void) +{ + + runtime_error (ERR_ILL_OPCODE); + +}/* __illegal__ */ + +/* + * z_catch, store the current stack frame for later use with z_throw. + * + * no zargs used + * + */ + +void z_catch (void) +{ + + store (f_setup.save_quetzal ? frame_count : (zword) (fp - stack)); + +}/* z_catch */ + +/* + * z_throw, go back to the given stack frame and return the given value. + * + * zargs[0] = value to return + * zargs[1] = stack frame + * + */ + +void z_throw (void) +{ + + if (f_setup.save_quetzal) { + if (zargs[1] > frame_count) + runtime_error (ERR_BAD_FRAME); + + /* Unwind the stack a frame at a time. */ + for (; frame_count > zargs[1]; --frame_count) + fp = stack + 1 + fp[1]; + } else { + if (zargs[1] > STACK_SIZE) + runtime_error (ERR_BAD_FRAME); + + fp = stack + zargs[1]; + } + + ret (zargs[0]); + +}/* z_throw */ + +/* + * z_call_n, call a subroutine and discard its result. + * + * zargs[0] = packed address of subroutine + * zargs[1] = first argument (optional) + * ... + * zargs[7] = seventh argument (optional) + * + */ + +void z_call_n (void) +{ + + if (zargs[0] != 0) + call (zargs[0], zargc - 1, zargs + 1, 1); + +}/* z_call_n */ + +/* + * z_call_s, call a subroutine and store its result. + * + * zargs[0] = packed address of subroutine + * zargs[1] = first argument (optional) + * ... + * zargs[7] = seventh argument (optional) + * + */ + +void z_call_s (void) +{ + + if (zargs[0] != 0) + call (zargs[0], zargc - 1, zargs + 1, 0); + else + store (0); + +}/* z_call_s */ + +/* + * z_check_arg_count, branch if subroutine was called with >= n arg's. + * + * zargs[0] = number of arguments + * + */ + +void z_check_arg_count (void) +{ + + if (fp == stack + STACK_SIZE) + branch (zargs[0] == 0); + else + branch (zargs[0] <= (*fp & 0xff)); + +}/* z_check_arg_count */ + +/* + * z_jump, jump unconditionally to the given address. + * + * zargs[0] = PC relative address + * + */ + +void z_jump (void) +{ + long pc; + + GET_PC (pc) + + pc += (short) zargs[0] - 2; + + if (pc >= story_size) + runtime_error (ERR_ILL_JUMP_ADDR); + + SET_PC (pc) + +}/* z_jump */ + +/* + * z_nop, no operation. + * + * no zargs used + * + */ + +void z_nop (void) +{ + + /* Do nothing */ + +}/* z_nop */ + +/* + * z_quit, stop game and exit interpreter. + * + * no zargs used + * + */ + +void z_quit (void) +{ + + finished = 9999; + +}/* z_quit */ + +/* + * z_ret, return from a subroutine with the given value. + * + * zargs[0] = value to return + * + */ + +void z_ret (void) +{ + + ret (zargs[0]); + +}/* z_ret */ + +/* + * z_ret_popped, return from a subroutine with a value popped off the stack. + * + * no zargs used + * + */ + +void z_ret_popped (void) +{ + + ret (*sp++); + +}/* z_ret_popped */ + +/* + * z_rfalse, return from a subroutine with false (0). + * + * no zargs used + * + */ + +void z_rfalse (void) +{ + + ret (0); + +}/* z_rfalse */ + +/* + * z_rtrue, return from a subroutine with true (1). + * + * no zargs used + * + */ + +void z_rtrue (void) +{ + + ret (1); + +}/* z_rtrue */ diff --git a/interpreters/frotz/quetzal.c b/interpreters/frotz/quetzal.c new file mode 100644 index 0000000..e41a57b --- /dev/null +++ b/interpreters/frotz/quetzal.c @@ -0,0 +1,539 @@ +/* quetzal.c - Saving and restoring of Quetzal files. + * Written by Martin Frost + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" +#include "glk.h" +#include "glkio.h" + +#define get_c fgetc +#define put_c fputc + +typedef unsigned long zlong; + +/* + * This is used only by save_quetzal. It probably should be allocated + * dynamically rather than statically. + */ + +static zword frames[STACK_SIZE/4+1]; + +/* + * ID types. + */ + +#define makeid(a,b,c,d) ((zlong) (((a)<<24) | ((b)<<16) | ((c)<<8) | (d))) + +#define ID_FORM makeid ('F','O','R','M') +#define ID_IFZS makeid ('I','F','Z','S') +#define ID_IFhd makeid ('I','F','h','d') +#define ID_UMem makeid ('U','M','e','m') +#define ID_CMem makeid ('C','M','e','m') +#define ID_Stks makeid ('S','t','k','s') +#define ID_ANNO makeid ('A','N','N','O') + +/* + * Various parsing states within restoration. + */ + +#define GOT_HEADER 0x01 +#define GOT_STACK 0x02 +#define GOT_MEMORY 0x04 +#define GOT_NONE 0x00 +#define GOT_ALL 0x07 +#define GOT_ERROR 0x80 + +/* + * Macros used to write the files. + */ + +#define write_byte(fp,b) (put_c (b, fp) != EOF) +#define write_bytx(fp,b) write_byte (fp, (b) & 0xFF) +#define write_word(fp,w) \ + (write_bytx (fp, (w) >> 8) && write_bytx (fp, (w))) +#define write_long(fp,l) \ + (write_bytx (fp, (l) >> 24) && write_bytx (fp, (l) >> 16) && \ + write_bytx (fp, (l) >> 8) && write_bytx (fp, (l))) +#define write_chnk(fp,id,len) \ + (write_long (fp, (id)) && write_long (fp, (len))) +#define write_run(fp,run) \ + (write_byte (fp, 0) && write_byte (fp, (run))) + +/* Read one word from file; return TRUE if OK. */ +static bool read_word (FILE *f, zword *result) +{ + int a, b; + + if ((a = get_c (f)) == EOF) return FALSE; + if ((b = get_c (f)) == EOF) return FALSE; + + *result = ((zword) a << 8) | (zword) b; + return TRUE; +} + +/* Read one long from file; return TRUE if OK. */ +static bool read_long (FILE *f, zlong *result) +{ + int a, b, c, d; + + if ((a = get_c (f)) == EOF) return FALSE; + if ((b = get_c (f)) == EOF) return FALSE; + if ((c = get_c (f)) == EOF) return FALSE; + if ((d = get_c (f)) == EOF) return FALSE; + + *result = ((zlong) a << 24) | ((zlong) b << 16) | + ((zlong) c << 8) | (zlong) d; + return TRUE; +} + +/* + * Restore a saved game using Quetzal format. Return 2 if OK, 0 if an error + * occurred before any damage was done, -1 on a fatal error. + */ + +zword restore_quetzal (FILE *svf, FILE *stf, int blorb_ofs) +{ + zlong ifzslen, currlen, tmpl; + zlong pc; + zword i, tmpw; + zword fatal = 0; /* Set to -1 when errors must be fatal. */ + zbyte skip, progress = GOT_NONE; + int x, y; + + /* Check it's really an `IFZS' file. */ + if (!read_long (svf, &tmpl) + || !read_long (svf, &ifzslen) + || !read_long (svf, &currlen)) return 0; + if (tmpl != ID_FORM || currlen != ID_IFZS) + { + print_string ("This is not a saved game file!\n"); + return 0; + } + if ((ifzslen & 1) || ifzslen<4) /* Sanity checks. */ return 0; + ifzslen -= 4; + + /* Read each chunk and process it. */ + while (ifzslen > 0) + { + /* Read chunk header. */ + if (ifzslen < 8) /* Couldn't contain a chunk. */ return 0; + if (!read_long (svf, &tmpl) + || !read_long (svf, &currlen)) return 0; + ifzslen -= 8; /* Reduce remaining by size of header. */ + + /* Handle chunk body. */ + if (ifzslen < currlen) /* Chunk goes past EOF?! */ return 0; + skip = currlen & 1; + ifzslen -= currlen + (zlong) skip; + + switch (tmpl) + { + /* `IFhd' header chunk; must be first in file. */ + case ID_IFhd: + if (progress & GOT_HEADER) + { + print_string ("Save file has two IFZS chunks!\n"); + return fatal; + } + progress |= GOT_HEADER; + if (currlen < 13 + || !read_word (svf, &tmpw)) return fatal; + if (tmpw != h_release) + progress = GOT_ERROR; + + for (i=H_SERIAL; i STACK_SIZE) + { + print_string ("Save-file has too much stack (and I can't cope).\n"); + return fatal; + } + currlen -= 8; + if (currlen < tmpw*2) return fatal; + for (i=0; i 0; + currlen -= 8, ++frame_count) + { + if (currlen < 8) return fatal; + if (sp - stack < 4) /* No space for frame. */ + { + print_string ("Save-file has too much stack (and I can't cope).\n"); + return fatal; + } + + /* Read PC, procedure flag and formal param count. */ + if (!read_long (svf, &tmpl)) return fatal; + y = (int) (tmpl & 0x0F); /* Number of formals. */ + tmpw = y << 8; + + /* Read result variable. */ + if ((x = get_c (svf)) == EOF) return fatal; + + /* Check the procedure flag... */ + if (tmpl & 0x10) + { + tmpw |= 0x1000; /* It's a procedure. */ + tmpl >>= 8; /* Shift to get PC value. */ + } + else + { + /* Functions have type 0, so no need to or anything. */ + tmpl >>= 8; /* Shift to get PC value. */ + --tmpl; /* Point at result byte. */ + /* Sanity check on result variable... */ + if (zmp[tmpl] != (zbyte) x) + { + print_string ("Save-file has wrong variable number on stack (possibly wrong game version?)\n"); + return fatal; + } + } + *--sp = (zword) (tmpl >> 9); /* High part of PC */ + *--sp = (zword) (tmpl & 0x1FF); /* Low part of PC */ + *--sp = (zword) (fp - stack - 1); /* FP */ + + /* Read and process argument mask. */ + if ((x = get_c (svf)) == EOF) return fatal; + ++x; /* Should now be a power of 2 */ + for (i=0; i<8; ++i) + if (x & (1< 0; --currlen) + { + if ((x = get_c (svf)) == EOF) return fatal; + if (x == 0) /* Start run. */ + { + /* Check for bogus run. */ + if (currlen < 2) + { + print_string ("File contains bogus `CMem' chunk.\n"); + for (; currlen > 0; --currlen) + (void) get_c (svf); /* Skip rest. */ + currlen = 1; + i = 0xFFFF; + break; /* Keep going; may be a `UMem' too. */ + } + /* Copy story file to memory during the run. */ + --currlen; + if ((x = get_c (svf)) == EOF) return fatal; + for (; x >= 0 && i h_dynamic_size) + { + print_string ("warning: `CMem' chunk too long!\n"); + for (; currlen > 1; --currlen) + (void) get_c (svf); /* Skip rest. */ + break; /* Keep going; there may be a `UMem' too. */ + } + } + /* If chunk is short, assume a run. */ + for (; i 0) + { + for (; j > 0x100; j -= 0x100) + { + if (!write_run (svf, 0xFF)) return 0; + cmemlen += 2; + } + if (!write_run (svf, j-1)) return 0; + cmemlen += 2; + j = 0; + } + /* Any runs are now written. Write this (nonzero) byte. */ + if (!write_byte (svf, (zbyte) c)) return 0; + ++cmemlen; + } + } + /* + * Reached end of dynamic memory. We ignore any unwritten run there may be + * at this point. + */ + if (cmemlen & 1) /* Chunk length must be even. */ + if (!write_byte (svf, 0)) return 0; + + /* Write `Stks' chunk. You are not expected to understand this. ;) */ + if ((stkspos = ftell (svf)) < 0) return 0; + if (!write_chnk (svf, ID_Stks, 0)) return 0; + + /* + * We construct a list of frame indices, most recent first, in `frames'. + * These indices are the offsets into the `stack' array of the word before + * the first word pushed in each frame. + */ + frames[0] = sp - stack; /* The frame we'd get by doing a call now. */ + for (i = fp - stack + 4, n=0; i < STACK_SIZE+4; i = stack[i-3] + 5) + frames[++n] = i; + + /* + * All versions other than V6 can use evaluation stack outside a function + * context. We write a faked stack frame (most fields zero) to cater for + * this. + */ + if (h_version != V6) + { + for (i=0; i<6; ++i) + if (!write_byte (svf, 0)) return 0; + nstk = STACK_SIZE - frames[n]; + if (!write_word (svf, nstk)) return 0; + for (j=STACK_SIZE-1; j >= frames[n]; --j) + if (!write_word (svf, stack[j])) return 0; + stkslen = 8 + 2*nstk; + } + + /* Write out the rest of the stack frames. */ + for (i=n; i>0; --i) + { + p = stack + frames[i] - 4; /* Points to call frame. */ + nvars = (p[0] & 0x0F00) >> 8; + nargs = p[0] & 0x00FF; + nstk = frames[i] - frames[i-1] - nvars - 4; + pc = ((zlong) p[3] << 9) | p[2]; + + switch (p[0] & 0xF000) /* Check type of call. */ + { + case 0x0000: /* Function. */ + var = zmp[pc]; + pc = ((pc + 1) << 8) | nvars; + break; + case 0x1000: /* Procedure. */ + var = 0; + pc = (pc << 8) | 0x10 | nvars; /* Set procedure flag. */ + break; + /* case 0x2000: */ + default: + runtime_error (ERR_SAVE_IN_INTER); + return 0; + } + if (nargs != 0) + nargs = (1 << nargs) - 1; /* Make args into bitmap. */ + + /* Write the main part of the frame... */ + if (!write_long (svf, pc) + || !write_byte (svf, var) + || !write_byte (svf, nargs) + || !write_word (svf, nstk)) return 0; + + /* Write the variables and eval stack. */ + for (j=0, --p; j> 16) & 0x7fff; + } + + store ((zword) (result % zargs[0] + 1)); + + } + +}/* z_random */ diff --git a/interpreters/frotz/redirect.c b/interpreters/frotz/redirect.c new file mode 100644 index 0000000..239e9c4 --- /dev/null +++ b/interpreters/frotz/redirect.c @@ -0,0 +1,173 @@ +/* redirect.c - Output redirection to Z-machine memory + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#define MAX_NESTING 16 + +/* TOR: glkify -- this is for V6 only */ +static zword get_max_width (zword win) { return 80; } + +static int depth = -1; + +static struct { + zword xsize; + zword table; + zword width; + zword total; +} redirect[MAX_NESTING]; + +/* + * memory_open + * + * Begin output redirection to the memory of the Z-machine. + * + */ + +void memory_open (zword table, zword xsize, bool buffering) +{ + + if (++depth < MAX_NESTING) { + + if (!buffering) + xsize = 0xffff; + if (buffering && (short) xsize <= 0) + xsize = get_max_width ((zword) (- (short) xsize)); + + storew (table, 0); + + redirect[depth].table = table; + redirect[depth].width = 0; + redirect[depth].total = 0; + redirect[depth].xsize = xsize; + + ostream_memory = TRUE; + + } else runtime_error (ERR_STR3_NESTING); + +}/* memory_open */ + +/* + * memory_new_line + * + * Redirect a newline to the memory of the Z-machine. + * + */ + +void memory_new_line (void) +{ + zword size; + zword addr; + + redirect[depth].total += redirect[depth].width; + redirect[depth].width = 0; + + addr = redirect[depth].table; + + LOW_WORD (addr, size) + addr += 2; + + if (redirect[depth].xsize != 0xffff) { + + redirect[depth].table = addr + size; + size = 0; + + } else storeb ((zword) (addr + (size++)), 13); + + storew (redirect[depth].table, size); + +}/* memory_new_line */ + +/* + * memory_word + * + * Redirect a string of characters to the memory of the Z-machine. + * + */ + +void memory_word (const zchar *s) +{ + zword size; + zword addr; + zchar c; + + if (h_version == V6) { + + int width = os_string_width (s); + + if (redirect[depth].xsize != 0xffff) + + if (redirect[depth].width + width > redirect[depth].xsize) { + + if (*s == ' ' || *s == ZC_INDENT || *s == ZC_GAP) + width = os_string_width (++s); + + memory_new_line (); + + } + + redirect[depth].width += width; + + } + + addr = redirect[depth].table; + + LOW_WORD (addr, size) + addr += 2; + + while ((c = *s++) != 0) + storeb ((zword) (addr + (size++)), translate_to_zscii (c)); + + storew (redirect[depth].table, size); + +}/* memory_word */ + +/* + * memory_close + * + * End of output redirection. + * + */ + +void memory_close (void) +{ + + if (depth >= 0) { + + if (redirect[depth].xsize != 0xffff) + memory_new_line (); + + if (h_version == V6) { + + h_line_width = (redirect[depth].xsize != 0xffff) ? + redirect[depth].total : redirect[depth].width; + + SET_WORD (H_LINE_WIDTH, h_line_width) + + } + + if (depth == 0) + ostream_memory = FALSE; + + depth--; + + } + +}/* memory_close */ diff --git a/interpreters/frotz/setup.h b/interpreters/frotz/setup.h new file mode 100644 index 0000000..a9b9360 --- /dev/null +++ b/interpreters/frotz/setup.h @@ -0,0 +1,67 @@ +/* + * Various status thingies for the interpreter and interface. + * + */ + +typedef struct frotz_setup_struct { + int attribute_assignment; /* done */ + int attribute_testing; /* done */ + int context_lines; /* done */ + int object_locating; /* done */ + int object_movement; /* done */ + int left_margin; /* done */ + int right_margin; /* done */ + int ignore_errors; /* done */ + int interpreter_number; /* Just dumb frotz now */ + int piracy; /* done */ + int undo_slots; /* done */ + int expand_abbreviations; /* done */ + int script_cols; /* done */ + int save_quetzal; /* done */ + int sound; /* done */ + int err_report_mode; /* done */ +} f_setup_t; + +extern f_setup_t f_setup; + + +typedef struct zcode_header_struct { + zbyte h_version; + zbyte h_config; + zword h_release; + zword h_resident_size; + zword h_start_pc; + zword h_dictionary; + zword h_objects; + zword h_globals; + zword h_dynamic_size; + zword h_flags; + zbyte h_serial[6]; + zword h_abbreviations; + zword h_file_size; + zword h_checksum; + zbyte h_interpreter_number; + zbyte h_interpreter_version; + zbyte h_screen_rows; + zbyte h_screen_cols; + zword h_screen_width; + zword h_screen_height; + zbyte h_font_height; + zbyte h_font_width; + zword h_functions_offset; + zword h_strings_offset; + zbyte h_default_background; + zbyte h_default_foreground; + zword h_terminating_keys; + zword h_line_width; + zbyte h_standard_high; + zbyte h_standard_low; + zword h_alphabet; + zword h_extension_table; + zbyte h_user_name[8]; + + zword hx_table_size; + zword hx_mouse_x; + zword hx_mouse_y; + zword hx_unicode_table; +} z_header_t; diff --git a/interpreters/frotz/sound.c b/interpreters/frotz/sound.c new file mode 100644 index 0000000..26d3835 --- /dev/null +++ b/interpreters/frotz/sound.c @@ -0,0 +1,204 @@ +/* sound.c - Sound effect function + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +#ifdef DJGPP +#include "djfrotz.h" +#endif + +#define EFFECT_PREPARE 1 +#define EFFECT_PLAY 2 +#define EFFECT_STOP 3 +#define EFFECT_FINISH_WITH 4 + +extern int direct_call (zword); + +static zword routine = 0; + +static int next_sample = 0; +static int next_volume = 0; + +static bool locked = FALSE; +static bool playing = FALSE; + +/* + * init_sound + * + * Initialize sound variables. + * + */ + +void init_sound (void) +{ + locked = FALSE; + playing = FALSE; +} /* init_sound */ + + +/* + * start_sample + * + * Call the IO interface to play a sample. + * + */ + +static void start_sample (int number, int volume, int repeats, zword eos) +{ + + static zbyte lh_repeats[] = { + 0x00, 0x00, 0x00, 0x01, 0xff, + 0x00, 0x01, 0x01, 0x01, 0x01, + 0xff, 0x01, 0x01, 0xff, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff + }; + + if (story_id == LURKING_HORROR) + repeats = lh_repeats[number]; + + os_start_sample (number, volume, repeats, eos); + + routine = eos; + playing = TRUE; + +}/* start_sample */ + +/* + * start_next_sample + * + * Play a sample that has been delayed until the previous sound effect has + * finished. This is necessary for two samples in The Lurking Horror that + * immediately follow other samples. + * + */ + +static void start_next_sample (void) +{ + + if (next_sample != 0) + start_sample (next_sample, next_volume, 0, 0); + + next_sample = 0; + next_volume = 0; + +}/* start_next_sample */ + +/* + * end_of_sound + * + * Call the Z-code routine which was given as the last parameter of + * a sound_effect call. This function may be called from a hardware + * interrupt (which requires extremely careful programming). + * + */ + +void end_of_sound (void) +{ + +#if defined(DJGPP) && defined(SOUND_SUPPORT) + end_of_sound_flag = 0; +#endif + + playing = FALSE; + + if (!locked) { + + if (story_id == LURKING_HORROR) + start_next_sample (); + + direct_call (routine); + + } + +}/* end_of_sound */ + +/* + * z_sound_effect, load / play / stop / discard a sound effect. + * + * zargs[0] = number of bleep (1 or 2) or sample + * zargs[1] = operation to perform (samples only) + * zargs[2] = repeats and volume (play sample only) + * zargs[3] = end-of-sound routine (play sample only, optional) + * + * Note: Volumes range from 1 to 8, volume 255 is the default volume. + * Repeats are stored in the high byte, 255 is infinite loop. + * + */ + +void z_sound_effect (void) +{ + zword number = zargs[0]; + zword effect = zargs[1]; + zword volume = zargs[2]; + + /* By default play sound 1 at volume 8 */ + if (zargc < 1) + number = 1; + if (zargc < 2) + effect = EFFECT_PLAY; + if (zargc < 3) + volume = 8; + + if (number >= 3 || number == 0) { + + locked = TRUE; + + if (story_id == LURKING_HORROR && (number == 9 || number == 16)) { + + if (effect == EFFECT_PLAY) { + + next_sample = number; + next_volume = volume; + + locked = FALSE; + + if (!playing) + start_next_sample (); + + } else locked = FALSE; + + return; + + } + + playing = FALSE; + + switch (effect) { + + case EFFECT_PREPARE: + os_prepare_sample (number); + break; + case EFFECT_PLAY: + start_sample (number, lo (volume), hi (volume), (zargc == 4) ? zargs[3] : 0); + break; + case EFFECT_STOP: + os_stop_sample (number); + break; + case EFFECT_FINISH_WITH: + os_finish_with_sample (number); + break; + + } + + locked = FALSE; + + } else os_beep (number); + +}/* z_sound_effect */ diff --git a/interpreters/frotz/stream.c b/interpreters/frotz/stream.c new file mode 100644 index 0000000..c7be749 --- /dev/null +++ b/interpreters/frotz/stream.c @@ -0,0 +1,351 @@ +/* stream.c - IO stream implementation + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* glkify */ +zchar console_read_input (int max, zchar *buf, zword timeout, bool continued) +{ + return os_read_line(max, buf, timeout, max, continued); +} +zchar console_read_key (zword timeout) +{ + return os_read_key(timeout, 0); +} + +extern bool validate_click (void); + +extern void replay_open (void); +extern void replay_close (void); +extern void memory_open (zword, zword, bool); +extern void memory_close (void); +extern void record_open (void); +extern void record_close (void); +extern void script_open (void); +extern void script_close (void); + +extern void memory_word (const zchar *); +extern void memory_new_line (void); +extern void record_write_key (zchar); +extern void record_write_input (const zchar *, zchar); +extern void script_char (zchar); +extern void script_word (const zchar *); +extern void script_new_line (void); +extern void script_write_input (const zchar *, zchar); +extern void script_erase_input (const zchar *); +extern void script_mssg_on (void); +extern void script_mssg_off (void); +extern void screen_char (zchar); +extern void screen_word (const zchar *); +extern void screen_new_line (void); +extern void screen_write_input (const zchar *, zchar); +extern void screen_erase_input (const zchar *); +extern void screen_mssg_on (void); +extern void screen_mssg_off (void); + +extern zchar replay_read_key (void); +extern zchar replay_read_input (zchar *); +extern zchar console_read_key (zword); +extern zchar console_read_input (int, zchar *, zword, bool); + +extern int direct_call (zword); + +/* + * stream_mssg_on + * + * Start printing a "debugging" message. + * + */ + +void stream_mssg_on (void) +{ + + flush_buffer (); + + if (ostream_screen) + screen_mssg_on (); + if (ostream_script && enable_scripting) + script_mssg_on (); + + message = TRUE; + +}/* stream_mssg_on */ + +/* + * stream_mssg_off + * + * Stop printing a "debugging" message. + * + */ + +void stream_mssg_off (void) +{ + + flush_buffer (); + + if (ostream_screen) + screen_mssg_off (); + if (ostream_script && enable_scripting) + script_mssg_off (); + + message = FALSE; + +}/* stream_mssg_off */ + +/* + * z_output_stream, open or close an output stream. + * + * zargs[0] = stream to open (positive) or close (negative) + * zargs[1] = address to redirect output to (stream 3 only) + * zargs[2] = width of redirected output (stream 3 only, optional) + * + */ + +void z_output_stream (void) +{ + + flush_buffer (); + + switch ((short) zargs[0]) { + + case 1: ostream_screen = TRUE; + break; + case -1: ostream_screen = FALSE; + break; + case 2: if (!ostream_script) script_open (); + break; + case -2: if (ostream_script) script_close (); + break; + case 3: memory_open (zargs[1], zargs[2], zargc >= 3); + break; + case -3: memory_close (); + break; + case 4: if (!ostream_record) record_open (); + break; + case -4: if (ostream_record) record_close (); + break; + + } + +}/* z_output_stream */ + +/* + * stream_char + * + * Send a single character to the output stream. + * + */ + +void stream_char (zchar c) +{ + + if (ostream_screen) + screen_char (c); + if (ostream_script && enable_scripting) + script_char (c); + +}/* stream_char */ + +/* + * stream_word + * + * Send a string of characters to the output streams. + * + */ + +void stream_word (const zchar *s) +{ + + if (ostream_memory && !message) + + memory_word (s); + + else { + + if (ostream_screen) + screen_word (s); + if (ostream_script && enable_scripting) + script_word (s); + + } + +}/* stream_word */ + +/* + * stream_new_line + * + * Send a newline to the output streams. + * + */ + +void stream_new_line (void) +{ + + if (ostream_memory && !message) + + memory_new_line (); + + else { + + if (ostream_screen) + screen_new_line (); + if (ostream_script && enable_scripting) + script_new_line (); + + } + +}/* stream_new_line */ + +/* + * z_input_stream, select an input stream. + * + * zargs[0] = input stream to be selected + * + */ + +void z_input_stream (void) +{ + + flush_buffer (); + + if (zargs[0] == 0 && istream_replay) + replay_close (); + if (zargs[0] == 1 && !istream_replay) + replay_open (); + +}/* z_input_stream */ + +/* + * stream_read_key + * + * Read a single keystroke from the current input stream. + * + */ + +zchar stream_read_key ( zword timeout, zword routine ) +{ + zchar key = ZC_BAD; + + flush_buffer (); + + /* Read key from current input stream */ + +continue_input: + + do { + + if (istream_replay) + key = replay_read_key (); + else + key = console_read_key (timeout); + + } while (key == ZC_BAD); + + /* Verify mouse clicks */ + +/* glkify + if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK) + if (!validate_click ()) + goto continue_input; +*/ + + /* Copy key to the command file */ + + if (ostream_record && !istream_replay) + record_write_key (key); + + /* Handle timeouts */ + + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + /* Return key */ + + return key; + +}/* stream_read_key */ + +/* + * stream_read_input + * + * Read a line of input from the current input stream. + * + */ + +zchar stream_read_input ( int max, zchar *buf, + zword timeout, zword routine, + bool no_scripting ) +{ + zchar key = ZC_BAD; + + flush_buffer (); + + /* Remove initial input from the transscript file or from the screen */ + + if (ostream_script && enable_scripting && !no_scripting) + script_erase_input (buf); +//glkify if (istream_replay) +//glkify screen_erase_input (buf); + + /* Read input line from current input stream */ + +continue_input: + + do { + + if (istream_replay) + key = replay_read_input (buf); + else + key = console_read_input (max, buf, timeout, key != ZC_BAD); + + } while (key == ZC_BAD); + + /* Verify mouse clicks */ + +/* glkify + if (key == ZC_SINGLE_CLICK || key == ZC_DOUBLE_CLICK) + if (!validate_click ()) + goto continue_input; +*/ + + /* Copy input line to the command file */ + + if (ostream_record && !istream_replay) + record_write_input (buf, key); + + /* Handle timeouts */ + + if (key == ZC_TIME_OUT) + if (direct_call (routine) == 0) + goto continue_input; + + /* Copy input line to transscript file or to the screen */ + + if (ostream_script && enable_scripting && !no_scripting) + script_write_input (buf, key); +//glkify if (istream_replay) +//glkify screen_write_input (buf, key); + + /* Return terminating key */ + + return key; + +}/* stream_read_input */ diff --git a/interpreters/frotz/table.c b/interpreters/frotz/table.c new file mode 100644 index 0000000..eb3a163 --- /dev/null +++ b/interpreters/frotz/table.c @@ -0,0 +1,193 @@ +/* table.c - Table handling opcodes + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* + * z_copy_table, copy a table or fill it with zeroes. + * + * zargs[0] = address of table + * zargs[1] = destination address or 0 for fill + * zargs[2] = size of table + * + * Note: Copying is safe even when source and destination overlap; but + * if zargs[1] is negative the table _must_ be copied forwards. + * + */ + +void z_copy_table (void) +{ + zword addr; + zword size = zargs[2]; + zbyte value; + int i; + + if (zargs[1] == 0) /* zero table */ + + for (i = 0; i < size; i++) + storeb ((zword) (zargs[0] + i), 0); + + else if ((short) size < 0 || zargs[0] > zargs[1]) /* copy forwards */ + + for (i = 0; i < (((short) size < 0) ? - (short) size : size); i++) { + addr = zargs[0] + i; + LOW_BYTE (addr, value) + storeb ((zword) (zargs[1] + i), value); + } + + else /* copy backwards */ + + for (i = size - 1; i >= 0; i--) { + addr = zargs[0] + i; + LOW_BYTE (addr, value) + storeb ((zword) (zargs[1] + i), value); + } + +}/* z_copy_table */ + +/* + * z_loadb, store a value from a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + * + */ + +void z_loadb (void) +{ + zword addr = zargs[0] + zargs[1]; + zbyte value; + + LOW_BYTE (addr, value) + + store (value); + +}/* z_loadb */ + +/* + * z_loadw, store a value from a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry to store + * + */ + +void z_loadw (void) +{ + zword addr = zargs[0] + 2 * zargs[1]; + zword value; + + LOW_WORD (addr, value) + + store (value); + +}/* z_loadw */ + +/* + * z_scan_table, find and store the address of a target within a table. + * + * zargs[0] = target value to be searched for + * zargs[1] = address of table + * zargs[2] = number of table entries to check value against + * zargs[3] = type of table (optional, defaults to 0x82) + * + * Note: The table is a word array if bit 7 of zargs[3] is set; otherwise + * it's a byte array. The lower bits hold the address step. + * + */ + +void z_scan_table (void) +{ + zword addr = zargs[1]; + int i; + + /* Supply default arguments */ + + if (zargc < 4) + zargs[3] = 0x82; + + /* Scan byte or word array */ + + for (i = 0; i < zargs[2]; i++) { + + if (zargs[3] & 0x80) { /* scan word array */ + + zword wvalue; + + LOW_WORD (addr, wvalue) + + if (wvalue == zargs[0]) + goto finished; + + } else { /* scan byte array */ + + zbyte bvalue; + + LOW_BYTE (addr, bvalue) + + if (bvalue == zargs[0]) + goto finished; + + } + + addr += zargs[3] & 0x7f; + + } + + addr = 0; + +finished: + + store (addr); + branch (addr); + +}/* z_scan_table */ + +/* + * z_storeb, write a byte into a table of bytes. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + * + */ + +void z_storeb (void) +{ + + storeb ((zword) (zargs[0] + zargs[1]), zargs[2]); + +}/* z_storeb */ + +/* + * z_storew, write a word into a table of words. + * + * zargs[0] = address of table + * zargs[1] = index of table entry + * zargs[2] = value to be written + * + */ + +void z_storew (void) +{ + + storew ((zword) (zargs[0] + 2 * zargs[1]), zargs[2]); + +}/* z_storew */ diff --git a/interpreters/frotz/text.c b/interpreters/frotz/text.c new file mode 100644 index 0000000..8145cfe --- /dev/null +++ b/interpreters/frotz/text.c @@ -0,0 +1,1109 @@ +/* text.c - Text manipulation functions + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +enum string_type { + LOW_STRING, ABBREVIATION, HIGH_STRING, EMBEDDED_STRING, VOCABULARY +}; + +extern zword object_name (zword); + +static zchar decoded[10]; +static zword encoded[3]; + +/* + * According to Matteo De Luigi , + * 0xab and 0xbb were in each other's proper positions. + * Sat Apr 21, 2001 + */ +static zchar zscii_to_latin1[] = { + 0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xbb, + 0xab, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9, + 0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3, + 0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0, + 0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4, + 0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5, + 0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5, + 0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0, + 0xa3, 0x00, 0x00, 0xa1, 0xbf +}; + +/* + * translate_from_zscii + * + * Map a ZSCII character onto the ISO Latin-1 alphabet. + * + */ + +zchar translate_from_zscii (zbyte c) +{ + + if (c == 0xfc) + return ZC_MENU_CLICK; + if (c == 0xfd) + return ZC_DOUBLE_CLICK; + if (c == 0xfe) + return ZC_SINGLE_CLICK; + + if (c >= 0x9b && story_id != BEYOND_ZORK) { + + if (hx_unicode_table != 0) { /* game has its own Unicode table */ + + zbyte N; + + LOW_BYTE (hx_unicode_table, N) + + if (c - 0x9b < N) { + + zword addr = hx_unicode_table + 1 + 2 * (c - 0x9b); + zword unicode; + + LOW_WORD (addr, unicode) + + return (unicode < 0x100) ? (zchar) unicode : '?'; + + } else return '?'; + + } else /* game uses standard set */ + + if (c <= 0xdf) { + + if (c == 0xdc || c == 0xdd) /* Oe and oe ligatures */ + return '?'; /* are not ISO-Latin 1 */ + + return zscii_to_latin1[c - 0x9b]; + + } else return '?'; + } + + return c; + +}/* translate_from_zscii */ + +/* + * translate_to_zscii + * + * Map an ISO Latin-1 character onto the ZSCII alphabet. + * + */ + +zbyte translate_to_zscii (zchar c) +{ + int i; + + if (c == ZC_SINGLE_CLICK) + return 0xfe; + if (c == ZC_DOUBLE_CLICK) + return 0xfd; + if (c == ZC_MENU_CLICK) + return 0xfc; + + if (c >= ZC_LATIN1_MIN) { + + if (hx_unicode_table != 0) { /* game has its own Unicode table */ + + zbyte N; + int i; + + LOW_BYTE (hx_unicode_table, N) + + for (i = 0x9b; i < 0x9b + N; i++) { + + zword addr = hx_unicode_table + 1 + 2 * (i - 0x9b); + zword unicode; + + LOW_WORD (addr, unicode) + + if (c == unicode) + return (zbyte) i; + + } + + return '?'; + + } else { /* game uses standard set */ + + for (i = 0x9b; i <= 0xdf; i++) + if (c == zscii_to_latin1[i - 0x9b]) + return (zbyte) i; + + return '?'; + + } + } + + if (c == 0) /* Safety thing from David Kinder */ + c = '?'; /* regarding his Unicode patches */ + /* Sept 15, 2002 */ + + return c; + +}/* translate_to_zscii */ + +/* + * alphabet + * + * Return a character from one of the three character sets. + * + */ + +static zchar alphabet (int set, int index) +{ + + if (h_alphabet != 0) { /* game uses its own alphabet */ + + zbyte c; + + zword addr = h_alphabet + 26 * set + index; + LOW_BYTE (addr, c) + + return translate_from_zscii (c); + + } else /* game uses default alphabet */ + + if (set == 0) + return 'a' + index; + else if (set == 1) + return 'A' + index; + else if (h_version == V1) + return " 0123456789.,!?_#'\"/\\<-:()"[index]; + else + return " ^0123456789.,!?_#'\"/\\-:()"[index]; + +}/* alphabet */ + +/* + * load_string + * + * Copy a ZSCII string from the memory to the global "decoded" string. + * + */ + +static void load_string (zword addr, zword length) +{ + int resolution = (h_version <= V3) ? 2 : 3; + int i = 0; + + while (i < 3 * resolution) + + if (i < length) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + decoded[i++] = translate_from_zscii (c); + + } else decoded[i++] = 0; + +}/* load_string */ + +/* + * encode_text + * + * Encode the Unicode text in the global "decoded" string then write + * the result to the global "encoded" array. (This is used to look up + * words in the dictionary.) Up to V3 the vocabulary resolution is + * two, since V4 it is three words. Because each word contains three + * Z-characters, that makes six or nine Z-characters respectively. + * Longer words are chopped to the proper size, shorter words are are + * padded out with 5's. For word completion we pad with 0s and 31s, + * the minimum and maximum Z-characters. + * + */ + +static void encode_text (int padding) +{ + static zchar again[] = { 'a', 'g', 'a', 'i', 'n', 0 }; + static zchar examine[] = { 'e', 'x', 'a', 'm', 'i', 'n', 'e', 0 }; + static zchar wait[] = { 'w', 'a', 'i', 't', 0 }; + + zbyte zchars[12]; + const zchar *ptr = decoded; + zchar c; + int resolution = (h_version <= V3) ? 2 : 3; + int i = 0; + + /* Expand abbreviations that some old Infocom games lack */ + + if (f_setup.expand_abbreviations) + + if (padding == 0x05 && decoded[1] == 0) + + switch (decoded[0]) { + case 'g': ptr = again; break; + case 'x': ptr = examine; break; + case 'z': ptr = wait; break; + } + + /* Translate string to a sequence of Z-characters */ + + while (i < 3 * resolution) + + if ((c = *ptr++) != 0) { + + int index, set; + zbyte c2; + + /* Search character in the alphabet */ + + for (set = 0; set < 3; set++) + for (index = 0; index < 26; index++) + if (c == alphabet (set, index)) + goto letter_found; + + /* Character not found, store its ZSCII value */ + + c2 = translate_to_zscii (c); + + zchars[i++] = 5; + zchars[i++] = 6; + zchars[i++] = c2 >> 5; + zchars[i++] = c2 & 0x1f; + + continue; + + letter_found: + + /* Character found, store its index */ + + if (set != 0) + zchars[i++] = ((h_version <= V2) ? 1 : 3) + set; + + zchars[i++] = index + 6; + + } else zchars[i++] = padding; + + /* Three Z-characters make a 16bit word */ + + for (i = 0; i < resolution; i++) + + encoded[i] = + (zchars[3 * i + 0] << 10) | + (zchars[3 * i + 1] << 5) | + (zchars[3 * i + 2]); + + encoded[resolution - 1] |= 0x8000; + +}/* encode_text */ + +/* + * z_check_unicode, test if a unicode character can be read and printed. + * + * zargs[0] = Unicode + * + */ + +void z_check_unicode (void) +{ + zword c = zargs[0]; + + if (c >= 0x20 && c <= 0x7e) + store (3); + else if (c == 0xa0) + store (1); + else if (c >= 0xa1 && c <= 0xff) + store (3); + else + store (0); + +}/* z_check_unicode */ + +/* + * z_encode_text, encode a ZSCII string for use in a dictionary. + * + * zargs[0] = address of text buffer + * zargs[1] = length of ASCII string + * zargs[2] = offset of ASCII string within the text buffer + * zargs[3] = address to store encoded text in + * + * This is a V5+ opcode and therefore the dictionary resolution must be + * three 16bit words. + * + */ + +void z_encode_text (void) +{ + int i; + + load_string ((zword) (zargs[0] + zargs[2]), zargs[1]); + + encode_text (0x05); + + for (i = 0; i < 3; i++) + storew ((zword) (zargs[3] + 2 * i), encoded[i]); + +}/* z_encode_text */ + +/* + * decode_text + * + * Convert encoded text to Unicode. The encoded text consists of 16bit + * words. Every word holds 3 Z-characters (5 bits each) plus a spare + * bit to mark the last word. The Z-characters translate to ZSCII by + * looking at the current current character set. Some select another + * character set, others refer to abbreviations. + * + * There are several different string types: + * + * LOW_STRING - from the lower 64KB (byte address) + * ABBREVIATION - from the abbreviations table (word address) + * HIGH_STRING - from the end of the memory map (packed address) + * EMBEDDED_STRING - from the instruction stream (at PC) + * VOCABULARY - from the dictionary (byte address) + * + * The last type is only used for word completion. + * + */ + +#define outchar(c) if (st==VOCABULARY) *ptr++=c; else print_char(c) + +static void decode_text (enum string_type st, zword addr) +{ + zchar *ptr; + long byte_addr; + zchar c2; + zword code; + zbyte c, prev_c = 0; + int shift_state = 0; + int shift_lock = 0; + int status = 0; + + ptr = NULL; /* makes compilers shut up */ + byte_addr = 0; + + /* Calculate the byte address if necessary */ + + if (st == ABBREVIATION) + + byte_addr = (long) addr << 1; + + else if (st == HIGH_STRING) { + + if (h_version <= V3) + byte_addr = (long) addr << 1; + else if (h_version <= V5) + byte_addr = (long) addr << 2; + else if (h_version <= V7) + byte_addr = ((long) addr << 2) + ((long) h_strings_offset << 3); + else /* h_version == V8 */ + byte_addr = (long) addr << 3; + + if (byte_addr >= story_size) + runtime_error (ERR_ILL_PRINT_ADDR); + + } + + /* Loop until a 16bit word has the highest bit set */ + + if (st == VOCABULARY) + ptr = decoded; + + do { + + int i; + + /* Fetch the next 16bit word */ + + if (st == LOW_STRING || st == VOCABULARY) { + LOW_WORD (addr, code) + addr += 2; + } else if (st == HIGH_STRING || st == ABBREVIATION) { + HIGH_WORD (byte_addr, code) + byte_addr += 2; + } else + CODE_WORD (code) + + /* Read its three Z-characters */ + + for (i = 10; i >= 0; i -= 5) { + + zword abbr_addr; + zword ptr_addr; + + c = (code >> i) & 0x1f; + + switch (status) { + + case 0: /* normal operation */ + + if (shift_state == 2 && c == 6) + status = 2; + + else if (h_version == V1 && c == 1) + new_line (); + + else if (h_version >= V2 && shift_state == 2 && c == 7) + new_line (); + + else if (c >= 6) + outchar (alphabet (shift_state, c - 6)); + + else if (c == 0) + outchar (' '); + + else if (h_version >= V2 && c == 1) + status = 1; + + else if (h_version >= V3 && c <= 3) + status = 1; + + else { + + shift_state = (shift_lock + (c & 1) + 1) % 3; + + if (h_version <= V2 && c >= 4) + shift_lock = shift_state; + + break; + + } + + shift_state = shift_lock; + + break; + + case 1: /* abbreviation */ + + ptr_addr = h_abbreviations + 64 * (prev_c - 1) + 2 * c; + + LOW_WORD (ptr_addr, abbr_addr) + decode_text (ABBREVIATION, abbr_addr); + + status = 0; + break; + + case 2: /* ZSCII character - first part */ + + status = 3; + break; + + case 3: /* ZSCII character - second part */ + + c2 = translate_from_zscii ((prev_c << 5) | c); + outchar (c2); + + status = 0; + break; + + } + + prev_c = c; + + } + + } while (!(code & 0x8000)); + + if (st == VOCABULARY) + *ptr = 0; + +}/* decode_text */ + +#undef outchar + +/* + * z_new_line, print a new line. + * + * no zargs used + * + */ + +void z_new_line (void) +{ + + new_line (); + +}/* z_new_line */ + +/* + * z_print, print a string embedded in the instruction stream. + * + * no zargs used + * + */ + +void z_print (void) +{ + + decode_text (EMBEDDED_STRING, 0); + +}/* z_print */ + +/* + * z_print_addr, print a string from the lower 64KB. + * + * zargs[0] = address of string to print + * + */ + +void z_print_addr (void) +{ + + decode_text (LOW_STRING, zargs[0]); + +}/* z_print_addr */ + +/* + * z_print_char print a single ZSCII character. + * + * zargs[0] = ZSCII character to be printed + * + */ + +void z_print_char (void) +{ + + print_char (translate_from_zscii (zargs[0])); + +}/* z_print_char */ + +/* + * z_print_form, print a formatted table. + * + * zargs[0] = address of formatted table to be printed + * + */ + +void z_print_form (void) +{ + zword count; + zword addr = zargs[0]; + + bool first = TRUE; + + for (;;) { + + LOW_WORD (addr, count) + addr += 2; + + if (count == 0) + break; + + if (!first) + new_line (); + + while (count--) { + + zbyte c; + + LOW_BYTE (addr, c) + addr++; + + print_char (translate_from_zscii (c)); + + } + + first = FALSE; + + } + +}/* z_print_form */ + +/* + * print_num + * + * Print a signed 16bit number. + * + */ + +void print_num (zword value) +{ + int i; + + /* Print sign */ + + if ((short) value < 0) { + print_char ('-'); + value = - (short) value; + } + + /* Print absolute value */ + + for (i = 10000; i != 0; i /= 10) + if (value >= i || i == 1) + print_char ('0' + (value / i) % 10); + +}/* print_num */ + +/* + * z_print_num, print a signed number. + * + * zargs[0] = number to print + * + */ + +void z_print_num (void) +{ + + print_num (zargs[0]); + +}/* z_print_num */ + +/* + * print_object + * + * Print an object description. + * + */ + +void print_object (zword object) +{ + zword addr = object_name (object); + zword code = 0x94a5; + zbyte length; + + LOW_BYTE (addr, length) + addr++; + + if (length != 0) + LOW_WORD (addr, code) + + if (code == 0x94a5) { /* encoded text 0x94a5 == empty string */ + + print_string ("object#"); /* supply a generic name */ + print_num (object); /* for anonymous objects */ + + } else decode_text (LOW_STRING, addr); + +}/* print_object */ + +/* + * z_print_obj, print an object description. + * + * zargs[0] = number of object to be printed + * + */ + +void z_print_obj (void) +{ + + print_object (zargs[0]); + +}/* z_print_obj */ + +/* + * z_print_paddr, print the string at the given packed address. + * + * zargs[0] = packed address of string to be printed + * + */ + +void z_print_paddr (void) +{ + + decode_text (HIGH_STRING, zargs[0]); + +}/* z_print_paddr */ + +/* + * z_print_ret, print the string at PC, print newline then return true. + * + * no zargs used + * + */ + +void z_print_ret (void) +{ + + decode_text (EMBEDDED_STRING, 0); + new_line (); + ret (1); + +}/* z_print_ret */ + +/* + * print_string + * + * Print a string of ASCII characters. + * + */ + +void print_string (const char *s) +{ + char c; + + while ((c = *s++) != 0) + + if (c == '\n') + new_line (); + else + print_char (c); + +}/* print_string */ + +/* + * z_print_unicode + * + * zargs[0] = Unicode + * + */ + +void z_print_unicode (void) +{ + + print_char ((zargs[0] <= 0xff) ? zargs[0] : '?'); + +}/* z_print_unicode */ + +/* + * lookup_text + * + * Scan a dictionary searching for the given word. The first argument + * can be + * + * 0x00 - find the first word which is >= the given one + * 0x05 - find the word which exactly matches the given one + * 0x1f - find the last word which is <= the given one + * + * The return value is 0 if the search fails. + * + */ + +static zword lookup_text (int padding, zword dct) +{ + zword entry_addr; + zword entry_count; + zword entry; + zword addr; + zbyte entry_len; + zbyte sep_count; + int resolution = (h_version <= V3) ? 2 : 3; + int entry_number; + int lower, upper; + int i; + bool sorted; + + encode_text (padding); + + LOW_BYTE (dct, sep_count) /* skip word separators */ + dct += 1 + sep_count; + LOW_BYTE (dct, entry_len) /* get length of entries */ + dct += 1; + LOW_WORD (dct, entry_count) /* get number of entries */ + dct += 2; + + if ((short) entry_count < 0) { /* bad luck, entries aren't sorted */ + + entry_count = - (short) entry_count; + sorted = FALSE; + + } else sorted = TRUE; /* entries are sorted */ + + lower = 0; + upper = entry_count - 1; + + while (lower <= upper) { + + if (sorted) /* binary search */ + entry_number = (lower + upper) / 2; + else /* linear search */ + entry_number = lower; + + entry_addr = dct + entry_number * entry_len; + + /* Compare word to dictionary entry */ + + addr = entry_addr; + + for (i = 0; i < resolution; i++) { + LOW_WORD (addr, entry) + if (encoded[i] != entry) + goto continuing; + addr += 2; + } + + return entry_addr; /* exact match found, return now */ + + continuing: + + if (sorted) /* binary search */ + + if (encoded[i] > entry) + lower = entry_number + 1; + else + upper = entry_number - 1; + + else lower++; /* linear search */ + + } + + /* No exact match has been found */ + + if (padding == 0x05) + return 0; + + entry_number = (padding == 0x00) ? lower : upper; + + if (entry_number == -1 || entry_number == entry_count) + return 0; + + return dct + entry_number * entry_len; + +}/* lookup_text */ + +/* + * tokenise_text + * + * Translate a single word to a token and append it to the token + * buffer. Every token consists of the address of the dictionary + * entry, the length of the word and the offset of the word from + * the start of the text buffer. Unknown words cause empty slots + * if the flag is set (such that the text can be scanned several + * times with different dictionaries); otherwise they are zero. + * + */ + +static void tokenise_text (zword text, zword length, zword from, zword parse, zword dct, bool flag) +{ + zword addr; + zbyte token_max, token_count; + + LOW_BYTE (parse, token_max) + parse++; + LOW_BYTE (parse, token_count) + + if (token_count < token_max) { /* sufficient space left for token? */ + + storeb (parse++, token_count + 1); + + load_string ((zword) (text + from), length); + + addr = lookup_text (0x05, dct); + + if (addr != 0 || !flag) { + + parse += 4 * token_count; + + storew ((zword) (parse + 0), addr); + storeb ((zword) (parse + 2), length); + storeb ((zword) (parse + 3), from); + + } + + } + +}/* tokenise_text */ + +/* + * tokenise_line + * + * Split an input line into words and translate the words to tokens. + * + */ + +void tokenise_line (zword text, zword token, zword dct, bool flag) +{ + zword addr1; + zword addr2; + zbyte length; + zbyte c; + + length = 0; /* makes compilers shut up */ + + /* Use standard dictionary if the given dictionary is zero */ + + if (dct == 0) + dct = h_dictionary; + + /* Remove all tokens before inserting new ones */ + + storeb ((zword) (token + 1), 0); + + /* Move the first pointer across the text buffer searching for the + beginning of a word. If this succeeds, store the position in a + second pointer. Move the first pointer searching for the end of + the word. When it is found, "tokenise" the word. Continue until + the end of the buffer is reached. */ + + addr1 = text; + addr2 = 0; + + if (h_version >= V5) { + addr1++; + LOW_BYTE (addr1, length) + } + + do { + + zword sep_addr; + zbyte sep_count; + zbyte separator; + + /* Fetch next ZSCII character */ + + addr1++; + + if (h_version >= V5 && addr1 == text + 2 + length) + c = 0; + else + LOW_BYTE (addr1, c) + + /* Check for separator */ + + sep_addr = dct; + + LOW_BYTE (sep_addr, sep_count) + sep_addr++; + + do { + + LOW_BYTE (sep_addr, separator) + sep_addr++; + + } while (c != separator && --sep_count != 0); + + /* This could be the start or the end of a word */ + + if (sep_count == 0 && c != ' ' && c != 0) { + + if (addr2 == 0) + addr2 = addr1; + + } else if (addr2 != 0) { + + tokenise_text ( + text, + (zword) (addr1 - addr2), + (zword) (addr2 - text), + token, dct, flag ); + + addr2 = 0; + + } + + /* Translate separator (which is a word in its own right) */ + + if (sep_count != 0) + + tokenise_text ( + text, + (zword) (1), + (zword) (addr1 - text), + token, dct, flag ); + + } while (c != 0); + +}/* tokenise_line */ + +/* + * z_tokenise, make a lexical analysis of a ZSCII string. + * + * zargs[0] = address of string to analyze + * zargs[1] = address of token buffer + * zargs[2] = address of dictionary (optional) + * zargs[3] = set when unknown words cause empty slots (optional) + * + */ + +void z_tokenise (void) +{ + + /* Supply default arguments */ + + if (zargc < 3) + zargs[2] = 0; + if (zargc < 4) + zargs[3] = 0; + + /* Call tokenise_line to do the real work */ + + tokenise_line (zargs[0], zargs[1], zargs[2], zargs[3] != 0); + +}/* z_tokenise */ + +/* + * completion + * + * Scan the vocabulary to complete the last word on the input line + * (similar to "tcsh" under Unix). The return value is + * + * 2 ==> completion is impossible + * 1 ==> completion is ambiguous + * 0 ==> completion is successful + * + * The function also returns a string in its second argument. In case + * of 2, the string is empty; in case of 1, the string is the longest + * extension of the last word on the input line that is common to all + * possible completions (for instance, if the last word on the input + * is "fo" and its only possible completions are "follow" and "folly" + * then the string is "ll"); in case of 0, the string is an extension + * to the last word that results in the only possible completion. + * + */ + +int completion (const zchar *buffer, zchar *result) +{ + zword minaddr; + zword maxaddr; + zchar *ptr; + zchar c; + int len; + int i; + + *result = 0; + + /* Copy last word to "decoded" string */ + + len = 0; + + while ((c = *buffer++) != 0) + + if (c != ' ') { + + if (len < 9) + decoded[len++] = c; + + } else len = 0; + + decoded[len] = 0; + + /* Search the dictionary for first and last possible extensions */ + + minaddr = lookup_text (0x00, h_dictionary); + maxaddr = lookup_text (0x1f, h_dictionary); + + if (minaddr == 0 || maxaddr == 0 || minaddr > maxaddr) + return 2; + + /* Copy first extension to "result" string */ + + decode_text (VOCABULARY, minaddr); + + ptr = result; + + for (i = len; (c = decoded[i]) != 0; i++) + *ptr++ = c; + *ptr = 0; + + /* Merge second extension with "result" string */ + + decode_text (VOCABULARY, maxaddr); + + for (i = len, ptr = result; (c = decoded[i]) != 0; i++, ptr++) + if (*ptr != c) break; + *ptr = 0; + + /* Search was ambiguous or successful */ + + return (minaddr == maxaddr) ? 0 : 1; + +}/* completion */ diff --git a/interpreters/frotz/variable.c b/interpreters/frotz/variable.c new file mode 100644 index 0000000..3e5c6e0 --- /dev/null +++ b/interpreters/frotz/variable.c @@ -0,0 +1,304 @@ +/* variable.c - Variable and stack related opcodes + * Copyright (c) 1995-1997 Stefan Jokisch + * + * This file is part of Frotz. + * + * Frotz 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. + * + * Frotz 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include "frotz.h" + +/* + * z_dec, decrement a variable. + * + * zargs[0] = variable to decrement + * + */ + +void z_dec (void) +{ + zword value; + + if (zargs[0] == 0) + (*sp)--; + else if (zargs[0] < 16) + (*(fp - zargs[0]))--; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value--; + SET_WORD (addr, value) + } + +}/* z_dec */ + +/* + * z_dec_chk, decrement a variable and branch if now less than value. + * + * zargs[0] = variable to decrement + * zargs[1] = value to check variable against + * + */ + +void z_dec_chk (void) +{ + zword value; + + if (zargs[0] == 0) + value = --(*sp); + else if (zargs[0] < 16) + value = --(*(fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value--; + SET_WORD (addr, value) + } + + branch ((short) value < (short) zargs[1]); + +}/* z_dec_chk */ + +/* + * z_inc, increment a variable. + * + * zargs[0] = variable to increment + * + */ + +void z_inc (void) +{ + zword value; + + if (zargs[0] == 0) + (*sp)++; + else if (zargs[0] < 16) + (*(fp - zargs[0]))++; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value++; + SET_WORD (addr, value) + } + +}/* z_inc */ + +/* + * z_inc_chk, increment a variable and branch if now greater than value. + * + * zargs[0] = variable to increment + * zargs[1] = value to check variable against + * + */ + +void z_inc_chk (void) +{ + zword value; + + if (zargs[0] == 0) + value = ++(*sp); + else if (zargs[0] < 16) + value = ++(*(fp - zargs[0])); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + value++; + SET_WORD (addr, value) + } + + branch ((short) value > (short) zargs[1]); + +}/* z_inc_chk */ + +/* + * z_load, store the value of a variable. + * + * zargs[0] = variable to store + * + */ + +void z_load (void) +{ + zword value; + + if (zargs[0] == 0) + value = *sp; + else if (zargs[0] < 16) + value = *(fp - zargs[0]); + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + LOW_WORD (addr, value) + } + + store (value); + +}/* z_load */ + +/* + * z_pop, pop a value off the game stack and discard it. + * + * no zargs used + * + */ + +void z_pop (void) +{ + + sp++; + +}/* z_pop */ + +/* + * z_pop_stack, pop n values off the game or user stack and discard them. + * + * zargs[0] = number of values to discard + * zargs[1] = address of user stack (optional) + * + */ + +void z_pop_stack (void) +{ + + if (zargc == 2) { /* it's a user stack */ + + zword size; + zword addr = zargs[1]; + + LOW_WORD (addr, size) + + size += zargs[0]; + storew (addr, size); + + } else sp += zargs[0]; /* it's the game stack */ + +}/* z_pop_stack */ + +/* + * z_pull, pop a value off... + * + * a) ...the game or a user stack and store it (V6) + * + * zargs[0] = address of user stack (optional) + * + * b) ...the game stack and write it to a variable (other than V6) + * + * zargs[0] = variable to write value to + * + */ + +void z_pull (void) +{ + zword value; + + if (h_version != V6) { /* not a V6 game, pop stack and write */ + + value = *sp++; + + if (zargs[0] == 0) + *sp = value; + else if (zargs[0] < 16) + *(fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD (addr, value) + } + + } else { /* it's V6, but is there a user stack? */ + + if (zargc == 1) { /* it's a user stack */ + + zword size; + zword addr = zargs[0]; + + LOW_WORD (addr, size) + + size++; + storew (addr, size); + + addr += 2 * size; + LOW_WORD (addr, value) + + } else value = *sp++; /* it's the game stack */ + + store (value); + + } + +}/* z_pull */ + +/* + * z_push, push a value onto the game stack. + * + * zargs[0] = value to push onto the stack + * + */ + +void z_push (void) +{ + + *--sp = zargs[0]; + +}/* z_push */ + +/* + * z_push_stack, push a value onto a user stack then branch if successful. + * + * zargs[0] = value to push onto the stack + * zargs[1] = address of user stack + * + */ + +void z_push_stack (void) +{ + zword size; + zword addr = zargs[1]; + + LOW_WORD (addr, size) + + if (size != 0) { + + storew ((zword) (addr + 2 * size), zargs[0]); + + size--; + storew (addr, size); + + } + + branch (size); + +}/* z_push_stack */ + +/* + * z_store, write a value to a variable. + * + * zargs[0] = variable to be written to + * zargs[1] = value to write + * + */ + +void z_store (void) +{ + zword value = zargs[1]; + + if (zargs[0] == 0) + *sp = value; + else if (zargs[0] < 16) + *(fp - zargs[0]) = value; + else { + zword addr = h_globals + 2 * (zargs[0] - 16); + SET_WORD (addr, value) + } + +}/* z_store */ diff --git a/interpreters/nitfol/COPYING b/interpreters/nitfol/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/interpreters/nitfol/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/interpreters/nitfol/ChangeLog b/interpreters/nitfol/ChangeLog new file mode 100644 index 0000000..3a966d9 --- /dev/null +++ b/interpreters/nitfol/ChangeLog @@ -0,0 +1,93 @@ +0.5 + Fixed segfault loading non-infix symbols (reported by David Picton) + Made /globals use symbols instead of G## when possible + Made automapper ignore superfluous exits + Fixed problems when automap, replay, and @read_char are used together + Removed 512 byte @read limit + Wrapped (m/re)alloc to test for malloc(0) and Out Of Memory + Made showstuff detect when printing an error causes another error + Stop repeating typo corrections in ambiguous commands while automapping + Fixed sound volume bug (thanks DorianX) + Sped up automapper and made it not break redo + Improved status line + Fixed undo/redo for games without native undo + Catches unknown extended opcodes + Disabled symbol-guessing for games without a property table + added ~/.nitfolrc support + Use ANSI string functions when they're available + Added alias support + Catch bug in Inform to reduce chances of overflowing input buffer + Fixed memory leak in malformed 'or's and function calls + Removed shift/reduce conflicts in yacc grammar + Added fake locations for bent passages in automapper + Improved forced movement in automapper + Added automapper cycle and interference detection + Added /ralias and empty alias support + Made n_malloc free old undo slots if out of memory + Added -mapsym option + Resize upper window (height) upon illegal @set_cursor calls + Made automapper hide non-fatal errors while exploring + Lots of automapper bugfixes and improvements + Fixed @get_next_prop + Removed frefid_t usage + Fixed multiple undo corruption (reported by David Picton) + Minor @print_table fixes + Font 3 to ASCII conversion implemented (see enablefont3 in globals.c) + Minor undo fixes (reported by David Picton) + Made @restore not clear the screen + Minor header loading changes + Added short circuiting for &&, ||, 'or' grammar + Moved map into separate configurable window + Added stack depth limit option + Extended restore fix + Detect out-of-range word writing (reported by Paul David Doherty) + Detect object tree looping with @insert_obj (ask Amilia for elephant) + Fixed various problems revealed by crashme + +0.4 + Added -expand command-line flag per request of David Picton + No longer overwrites Inform version number with userid + Makefile changes + Added script recording/playback (and appropriate debugger commands) + Fixed @remove to clear sibling of removed object + Added -help, -version command-line options + More header fixes + Made /find case insensitive + Fixed screen size calculation on restore + Fixed /restart and /restore + Fixed -transcript, and various minor transcripting changes + Added /run as a synonym for /restart + Added savefile->gamefile searching + Sound channel creation fixes (reported by DorianX) + Macintosh compilation and startup fixes (thanks zarf) + Upgraded my bison, so included inform.c doesn't require alloca + MACS and UNIX IntD chunks added to specify gamefile in saved games + A couple nasty off-by-one memory allocation bugs fixed (yay efence) + Doesn't glk_window_open in glk_main if it doesn't have to + Added symbol guessing for sourceless inform 6.10+ games + Further postponed automapping improvements + +0.3 + Makefile no longer depends on GNU Make features + Removed op_obj.h cleverness + Fixed some glk-specific initialization problems + Added more debugging commands and inform operators + Added debugging boolean type with "true" and "false" + Object handling is now more strict (but not enough) + Fixed out-of-range string printing problems + Fixed up header setting code + Added infinite undo/redo + texinfo documentation written + Added preliminary automapping support + Compressed save file writing + Header stuff is better but not yet perfect + Lots of bug fixes + +0.2 + Added source-level debugger + Major i/o redesign/rewrite/wrapping + Added blorb, sound, graphics support + Added glk-specific initialization with perl cleverness + +0.1 + Initial release diff --git a/interpreters/nitfol/INSTALL b/interpreters/nitfol/INSTALL new file mode 100644 index 0000000..cd07beb --- /dev/null +++ b/interpreters/nitfol/INSTALL @@ -0,0 +1,38 @@ +As far as I know, nitfol contains mostly ANSI C, so most recent C +compilers should compile it fine. If you have any problems, let me +know. + +If you have make, just use the supplied Makefile. You may wish to +edit it to specify options and locations of libraries. Type 'make +target', where target is one of: xnitfol, termnitfol, cheapnitfol, +dosnitfol, glktdnitfol, winnitfol, mudnitfol, or mpcmudnitfol, +depending on which Glk library you wish to link with. Typing +'make all' will make the first three of these. + +If you don't have make, compile automap.c solve.c infix.c debug.c +inform.c quetzal.c undo.c op_call.c decode.c errmesg.c globals.c iff.c +init.c main.c io.c z_io.c op_jmp.c op_math.c op_save.c op_table.c +op_v6.c oplist.c stack.c zscii.c tokenise.c struct.c objects.c +portfunc.c hash.c, one of graphics.c or no_graph.c, one of blorb.c or +no_blorb.c, and one of sound.c or no_snd.c, depending on whether your +Glk library supports graphics, Blorb, and/or sound. If your Glk +library needs startup code, link in startunix.c, startwin.c, +startdos.c, or startmac.c as appropriate. Compile time options are +determined by the definition or lack thereof of macros, so either pass +them to your compiler or put them in the beginning of nitfol.h (like +#define SMART_TOKENISER). Look in the file 'Makefile' for a list of +these options. Link together the compiled c files with your Glk +library to produce an executable. If you make a project file to do +your compilation for a platform lacking make, please send it to me so +I can include it in future releases. + +Under djgpp, you'll need to remove the -ansi option for startunix.c / +startdos.c to compile (non-insane suggestions to get it to define DIR +properly with -ansi would be appreciated). + +If you're on a platform that doesn't normally come with compilers, +uploading the result to gmd may be appreciated by the compiler-less +(if no one else has done so). + +If something doesn't doesn't work, please complain. + diff --git a/interpreters/nitfol/Makefile.am b/interpreters/nitfol/Makefile.am new file mode 100644 index 0000000..e52ddf8 --- /dev/null +++ b/interpreters/nitfol/Makefile.am @@ -0,0 +1,26 @@ +# TODO: There are a lot more documentation files in the Nitfol distribution + +PLUGIN_LIBTOOL_FLAGS=-module -avoid-version -export-symbols-regex "^glk_main$$" + +GRAPHICS = no_graph.c +# GRAPHICS = graphics.c +BLORB = blorb.c +# BLORB = no_blorb.c +SOUND = no_snd.c +# SOUND = sound.c + +pkglib_LTLIBRARIES = nitfol.la +nitfol_la_SOURCES = automap.c solve.c infix.c debug.c inform.c quetzal.c \ + undo.c op_call.c decode.c errmesg.c globals.c iff.c init.c main.c io.c \ + z_io.c op_jmp.c op_math.c op_save.c op_table.c op_v6.c oplist.c stack.c \ + zscii.c tokenise.c struct.c objects.c portfunc.c hash.c \ + $(GRAPHICS) \ + $(BLORB) \ + $(SOUND) +nitfol_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS) + +# TODO: Remove this later, see issue #6 +nitfol_la_CFLAGS = -I../../src + +nitfoldocdir = $(datadir)/doc/$(PACKAGE)/nitfol +dist_nitfoldoc_DATA = ChangeLog COPYING INSTALL README diff --git a/interpreters/nitfol/automap.c b/interpreters/nitfol/automap.c new file mode 100644 index 0000000..3322f9f --- /dev/null +++ b/interpreters/nitfol/automap.c @@ -0,0 +1,1076 @@ +/* automap.c: main automapping code + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ + +#include "nitfol.h" + +#ifdef DEBUGGING + +struct dirinfo { + const char *name; + int deltax, deltay; + char symbol; + char oneway; +}; + +static const struct dirinfo dirways[] = { + { "n", 0, -1, '|', '^' }, + { "s", 0, 1, '|', 'v' }, + { "w", -1, 0, '-', '<' }, + { "e", 1, 0, '-', '>' }, + { "nw", -1, -1, '\\', '^' }, + { "se", 1, 1, '\\', 'v' }, + { "ne", 1, -1, '/', '^' }, + { "sw", -1, 1, '/', 'v' }, + { "up", 0, 0, 0, 0 }, + { "down", 0, 0, 0, 0 }, + { "wait", 0, 0, 0, 0 } +}; + +#define NUM_EXITS (sizeof(dirways) / sizeof(*dirways)) +#define REVERSE_DIR(dir) (dir ^ 1) + +#define NUM_DIRS (NUM_EXITS - 3) +#define NUM_WALK (NUM_EXITS - 1) + +#define DIR_UP (NUM_DIRS + 0) +#define DIR_DOWN (NUM_DIRS + 1) +#define DIR_WAIT (NUM_DIRS + 2) + +char *roomsymbol = NULL; + +#define ROOM_SYMBOL(is_player, is_up, is_down, is_real) (is_real ? roomsymbol[(is_up != 0) | ((is_down != 0) << 1) | ((is_player != 0) << 2)] : roomsymbol[8]) + + +typedef struct edge edge; +typedef struct loc_node loc_node; + +struct edge { + loc_node *dest[2]; /* Two endpoints of passage */ + BOOL is_oneway; /* Oneway passages are always created dest[0]--->dest[1] */ + BOOL touched; + int min_length; + int guess_length; +}; + +struct loc_node { + zword number; + BOOL found, real, touched; + edge *outgoing[NUM_DIRS]; /* Drawn map connections */ + loc_node *exits[NUM_EXITS]; /* Actual connections */ + glui32 dist; /* For automap_find_path */ +}; + + +typedef struct edgelist edgelist; +struct edgelist { + edgelist *next; + edge *node; +}; + +static edgelist *all_edges; + +static edge *automap_new_edge(loc_node *src, loc_node *dest, BOOL is_oneway) +{ + edgelist newedge; + newedge.node = n_malloc(sizeof(edge)); + newedge.node->dest[0] = src; + newedge.node->dest[1] = dest; + newedge.node->is_oneway = is_oneway; + newedge.node->touched = FALSE; + newedge.node->min_length = is_oneway ? 4 : 2; + newedge.node->guess_length = is_oneway ? 4 : 2; + LEadd(all_edges, newedge); + return newedge.node; +} + + +static void automap_remove_edge(edge *e) +{ + unsigned n, i; + edgelist *p, *t; + if(e == NULL) + return; + for(n = 0; n < 2; n++) { + loc_node *thisdest = e->dest[n]; + if(thisdest) + for(i = 0; i < NUM_DIRS; i++) { + if(thisdest->outgoing[i] == e) + thisdest->outgoing[i] = NULL; + } + } + LEsearchremove(all_edges, p, t, p->node == e, n_free(p->node)); +} + + +static void automap_edges_untouch(void) +{ + edgelist *p; + for(p = all_edges; p; p=p->next) { + p->node->touched = FALSE; + } +} + + +static void automap_edges_mindist(void) +{ + edgelist *p; + for(p = all_edges; p; p=p->next) { + int len = p->node->is_oneway ? 4 : 2; + p->node->min_length = p->node->guess_length = len; + } +} + + +static hash_table rooms; +static char *loc_exp; +static zwinid automap_win; + + +void automap_kill(void) +{ + mymap_kill(); + n_free(loc_exp); + loc_exp = NULL; + LEdestruct(all_edges, n_free(all_edges->node)); + n_hash_free_table(&rooms, n_free); + z_kill_window(automap_win); +} + + +BOOL automap_init(int numobj, const char *location_exp) +{ + automap_kill(); + + if(!roomsymbol) + roomsymbol = n_strdup("*udb@UDB+"); + + if(location_exp) + loc_exp = n_strdup(location_exp); + + n_hash_construct_table(&rooms, numobj / 2); + + automap_win = z_split_screen(wintype_TextGrid, + automap_split | winmethod_Fixed, + automap_draw_callback, automap_mouse_callback); + return TRUE; +} + + +static loc_node *room_find(glui32 location, BOOL is_real) +{ + const char *preface = is_real ? "" : "fake"; + const char *key = n_static_number(preface, location); + return (loc_node *) n_hash_lookup(key, &rooms); +} + + +static loc_node *room_find_or_create(glui32 location, BOOL is_real) +{ + loc_node *r; + const char *preface = is_real ? "" : "fake"; + const char *key = n_static_number(preface, location); + r = (loc_node *) n_hash_lookup(key, &rooms); + if(r == NULL) { + unsigned n; + r = (loc_node *) n_malloc(sizeof(loc_node)); + r->number = location; + r->found = FALSE; + r->real = is_real; + r->touched = FALSE; + for(n = 0; n < NUM_EXITS; n++) { + r->exits[n] = NULL; + if(n < NUM_DIRS) + r->outgoing[n] = NULL; + } + n_hash_insert(key, r, &rooms); + } + return r; +} + + +static void room_remove(loc_node *room) +{ + unsigned n; + if(room) { + const char *preface = room->real ? "" : "fake"; + for(n = 0; n < NUM_DIRS; n++) + automap_remove_edge(room->outgoing[n]); + n_free(n_hash_del(n_static_number(preface, room->number), &rooms)); + } +} + + + +typedef struct automap_path automap_path; +struct automap_path { + automap_path *next; + loc_node *loc; /* A location */ + int dir; /* And the direction we're going from it */ +}; + +typedef struct interlist interlist; +struct interlist { + interlist *next; + + loc_node *a, *b; +}; + + +static BOOL mymap_plot(int x, int y, char symbol, loc_node *node); +static edge *automap_get_edge(loc_node *location, int dir); +static void automap_calc_location(loc_node *location, loc_node *last, + int x, int y); +static automap_path *automap_find_path(loc_node *location, loc_node *dest, + BOOL by_walking); +static int automap_edge_oneway(loc_node *location, int dir); +static loc_node *automap_edge_follow(loc_node *location, int dir); + + +static interlist *interferences = NULL; + +static void automap_forget_interference(void) +{ + LEdestroy(interferences); +} + +static void automap_remember_interference(loc_node *a, loc_node *b) +{ + /* interlist *p; + LEsearch(interferences, p, (p->a==a && p->b==b) || (p->a==b && p->b==a)); + if(!p) {*/ + interlist newnode; + newnode.a = a; + newnode.b = b; + LEadd(interferences, newnode); + /* }*/ +} + + +static int automap_find_and_count_interference(loc_node *center) +{ + interlist *i; + int count; + + automap_cycles_fill_values(); + automap_forget_interference(); + mymap_reinit(); + n_hash_enumerate(&rooms, make_untouched); + automap_edges_untouch(); + automap_calc_location(center, NULL, 0, 0); + + count = 0; + for(i = interferences; i; i=i->next) + count++; + + return count; +} + + +/* Returns TRUE if it improved any */ +static BOOL automap_increase_along_path(automap_path *path, int oldcount, + loc_node *center, int effort) +{ + automap_path *p; + int exploring; + int explore_max = effort > 1; + if(!effort) + return FALSE; + + /* Takes two passes at trying to improve the situation. + The first time (!exploring), it tries increasing the length of each + edge along the path, observing the results and then undoing the increase. + If it was able to improve anything, it returns with the best improvement. + Otherwise it tries increasing the length of each edge and calling itself; + If its child is able to improve things, then it returns with both + lengthenings in effect. */ + + for(exploring = 0; exploring <= explore_max; exploring++) { + edge *best_edge = NULL; + int best_count = oldcount; + int smallest_new = 10000; + + for(p = path; p; p=p->next) { + int newcount; + edge *e = automap_get_edge(p->loc, p->dir); + int old_min_length = e->min_length; + int old_guess_length = e->guess_length; + + if(p->next && p->next->loc != automap_edge_follow(p->loc, p->dir)) + n_show_error(E_SYSTEM, "path doesn't follow itself", 0); + + e->guess_length += 2; + e->min_length = e->guess_length; + + if(!exploring) { + newcount = automap_find_and_count_interference(center); + if(newcount < best_count + || (newcount == best_count && newcount < oldcount + && e->min_length < smallest_new)) { + best_edge = e; + best_count = newcount; + smallest_new = e->min_length; + } + } else { + if(automap_increase_along_path(p, oldcount, center, effort-1)) + return TRUE; + } + + e->min_length = old_min_length; + e->guess_length = old_guess_length; + } + + if(!exploring && best_edge) { + best_edge->guess_length += 2; + best_edge->min_length = best_edge->guess_length; + automap_find_and_count_interference(center); + return TRUE; + } + } + return FALSE; +} + + +/* Returns true if all interferences have been resolved */ +static BOOL automap_resolve_interference(loc_node *center, int effort) +{ + int skip_interferences = 0; + int n; + + while(interferences) { + interlist *oldinter = interferences; + interlist *i; + automap_path *path; + int oldcount; + + oldcount = 0; + for(i = oldinter; i; i=i->next) + oldcount++; + + if(skip_interferences >= oldcount) + return FALSE; + + i = oldinter; + for(n = 0; n < skip_interferences; n++) + i=i->next; + + path = automap_find_path(i->a, i->b, FALSE); + if(!path) + return FALSE; + + interferences = NULL; + + if(!automap_increase_along_path(path, oldcount, center, effort)) + skip_interferences++; + + LEdestroy(oldinter); + LEdestroy(path); + } + return TRUE; +} + + +static void automap_set_virtual_connection(loc_node *location, int d, + loc_node *dest, BOOL is_oneway) +{ + if(location->outgoing[d]) { + int way = automap_edge_oneway(location, d); + if(dest || way != 2) + automap_remove_edge(location->outgoing[d]); + } + + if(dest) { + edge *p = automap_new_edge(location, dest, is_oneway); + + location->outgoing[d] = p; + + if(dest->outgoing[REVERSE_DIR(d)]) + automap_remove_edge(dest->outgoing[REVERSE_DIR(d)]); + dest->outgoing[REVERSE_DIR(d)] = p; + } +} + + +static void automap_set_connection(int location, int d, int dest, BOOL is_real) +{ + loc_node *r, *t; + + r = room_find_or_create(location, is_real); + t = room_find_or_create(dest, is_real); + + if(t == r) + t = NULL; + + r->exits[d] = t; +} + + +static edge *automap_get_edge(loc_node *location, int dir) +{ + return location->outgoing[dir]; +} + + +static loc_node *automap_edge_follow(loc_node *location, int dir) +{ + if(location->outgoing[dir] == NULL) + return NULL; + + if(location->outgoing[dir]->dest[0] == location) + return location->outgoing[dir]->dest[1]; + if(location->outgoing[dir]->dest[1] == location) + return location->outgoing[dir]->dest[0]; + + n_show_error(E_SYSTEM, "edge isn't connected to what it should be", 0); + return NULL; +} + + +static int automap_edge_length(loc_node *location, int dir) +{ + return location->outgoing[dir]->guess_length; +} + + +/* Returns 0 if not oneway, 1 if oneway in the given direction, and 2 if + oneway in the other direction */ +static int automap_edge_oneway(loc_node *location, int dir) +{ + if(location->outgoing[dir] == NULL) + return 0; + if(location->outgoing[dir]->dest[0] == location) + return location->outgoing[dir]->is_oneway; + return (location->outgoing[dir]->is_oneway) << 1; +} + + +static BOOL automap_draw_edge(loc_node *location, int dir, int *x, int *y) +{ + int deltax, deltay; + int s; + int len; + int oneway; + edge *e = automap_get_edge(location, dir); + + if(e->touched) + return TRUE; + e->touched = TRUE; + + deltax = dirways[dir].deltax; + deltay = dirways[dir].deltay; + len = automap_edge_length(location, dir); + oneway = automap_edge_oneway(location, dir); + + *x += deltax; + *y += deltay; + + if(oneway) + len--; + + if(oneway == 2) { + mymap_plot(*x, *y, dirways[REVERSE_DIR(dir)].oneway, location); + *x += deltax; + *y += deltay; + } + + for(s = 1; s < len; s++) { + mymap_plot(*x, *y, dirways[dir].symbol, location); + *x += deltax; + *y += deltay; + } + + if(oneway == 1) { + mymap_plot(*x, *y, dirways[dir].oneway, location); + *x += deltax; + *y += deltay; + } + return TRUE; +} + + +static void automap_adjust_length(loc_node *location, int dir, int newlen) +{ + location->outgoing[dir]->min_length = newlen; +} + + +static int mapwidth; +static int mapheight; + +static char *mymap = NULL; +static loc_node **mymapnode = NULL; + +static char mymap_read(int x, int y) +{ + x += mapwidth / 2; y += mapheight / 2; + if(x < 0 || x >= mapwidth || y < 0 || y >= mapheight) + return ' '; + return mymap[x + y * mapheight]; +} + + +static BOOL mymap_plot(int x, int y, char symbol, loc_node *node) +{ + BOOL status = TRUE; + char *dest; + x += mapwidth / 2; y += mapheight / 2; + if(x < 0 || x >= mapwidth || y < 0 || y >= mapheight) + return status; + dest = &mymap[x + y * mapwidth]; + if(*dest != ' ') { + if((*dest=='/' && symbol=='\\') || (*dest=='\\' && symbol=='/')) + symbol = 'X'; + else if((*dest=='-' && symbol=='|') || (*dest=='|' && symbol=='-')) + symbol = '+'; + else + status = FALSE; + } else { + if(mymapnode[x + y * mapwidth]) + status = FALSE; + } + if(status) { + *dest = symbol; + mymapnode[x + y * mapwidth] = node; + } else { + loc_node *interfere = mymapnode[x + y * mapwidth]; + automap_remember_interference(node, interfere); + } + return status; +} + + +void mymap_init(int width, int height) +{ + int i; + int max; + mapwidth = width * 2; + mapheight = height * 2; + max = mapwidth * mapheight; + n_free(mymap); + n_free(mymapnode); + mymap = (char *) n_malloc(max); + mymapnode = (loc_node **) n_malloc(max * sizeof(*mymapnode)); + for(i = 0; i < max; i++) { + mymap[i] = ' '; + mymapnode[i] = NULL; + } +} + + +int automap_get_height(void) +{ + return mapheight / 2; +} + + +void mymap_reinit(void) +{ + mymap_init(mapwidth/2, mapheight/2); +} + + +void mymap_kill(void) +{ + n_free(mymap); + mymap = NULL; + n_free(mymapnode); + mymapnode = NULL; +} + + +static int xoffset, yoffset; + +static void mymap_draw(void) +{ + int x, y; + int firsty, firstx, lasty, lastx; + int height, width; + + firsty = mapheight; firstx = mapwidth; + lasty = 0; lastx = 0; + for(y = 0; y < mapheight; y++) { + for(x = 0; x < mapwidth; x++) + if(mymap[x + y * mapwidth] != ' ') { + if(y < firsty) + firsty = y; + if(y > lasty) + lasty = y; + if(x < firstx) + firstx = x; + if(x > lastx) + lastx = x; + } + } + + height = lasty - firsty; width = lastx - firstx; + + xoffset = firstx + (width - mapwidth/2) / 2; + yoffset = firsty + (height - mapheight/2) / 2; + + if(yoffset >= mapheight/2) + yoffset = mapheight/2 - 1; + if(yoffset <= 1) + yoffset = 2; + if(xoffset >= mapwidth/2) + xoffset = mapwidth/2 - 1; + if(xoffset <= 1) + xoffset = 2; + + for(y = 0; y < mapheight/2; y++) { + for(x = 0; x < mapwidth/2; x++) + glk_put_char(mymap[x+xoffset + (y+yoffset) * mapwidth]); + } +} + +static glui32 selected_room_number = 0; + +static void automap_write_loc(int x, int y) +{ + loc_node *room; + selected_room_number = 0; + x += xoffset; y += yoffset; + if(x < 0 || x >= mapwidth || y < 0 || y >= mapheight) + return; + room = mymapnode[x + y * mapwidth]; + if(!room || !room->found || !room->real) + return; + selected_room_number = room->number; +} + + +glui32 automap_draw_callback(winid_t win, glui32 width, glui32 height) +{ + if(win == NULL) + return automap_size; + + mymap_init(width, height); + automap_set_locations(automap_location); + + glk_stream_set_current(glk_window_get_stream(win)); + mymap_draw(); + + if(selected_room_number) { + offset short_name_off = object_name(selected_room_number); + glk_window_move_cursor(win, 0, 0); + + if(short_name_off) + decodezscii(short_name_off, w_glk_put_char); + else + w_glk_put_string(""); + w_glk_put_string(" ("); + g_print_number(selected_room_number); + glk_put_char(')'); + } + return automap_size; +} + + +BOOL automap_mouse_callback(BOOL is_char_event, + winid_t win, glui32 x, glui32 y) +{ + automap_write_loc(x, y); + return FALSE; +} + + +static void automap_calc_location(loc_node *location, loc_node *last, + int x, int y) +{ + unsigned i; + char symbol; + loc_node *is_up, *is_down; + + if(!location) + return; + + if(location->touched) + return; + location->touched = TRUE; + + /* Make sure unfound locations are blanked */ + if(!location->found) { + mymap_plot(x, y, ' ', location); + return; + } + + + /* Don't draw up/down exits if there's a normal passage leading that way */ + is_up = location->exits[DIR_UP]; + is_down = location->exits[DIR_DOWN]; + for(i = 0; i < NUM_DIRS; i++) { + loc_node *thisdest = automap_edge_follow(location, i); + if(thisdest && !thisdest->real) + thisdest = location->exits[i]; + if(thisdest == is_up) + is_up = 0; + if(thisdest == is_down) + is_down = 0; + } + + symbol = ROOM_SYMBOL((x==0 && y==0), is_up, is_down, location->real); + + mymap_plot(x, y, symbol, location); + + for(i = 0; i < NUM_DIRS; i++) { + loc_node *thisdest = automap_edge_follow(location, i); + if(thisdest && thisdest != last) { + int destx = x; + int desty = y; + automap_draw_edge(location, i, &destx, &desty); + automap_calc_location(thisdest, location, destx, desty); + } + } +} + + +/* Returns magic cookies to identify fake locations */ +static glui32 automap_get_cookie(void) { + /* FIXME: When the glui32 wraps around Bad Things will happen if we return a + cookie still in use. Should reissue cookies to everyone when we wrap + around. */ + static glui32 cookie = 0; + return cookie++; +} + + +static void automap_calc_exits(loc_node *location, int depth) +{ + unsigned i, n; + loc_node *proposed[NUM_DIRS]; /* Store proposed edges here */ + BOOL is_oneway[NUM_DIRS]; + + /* Remove fake locations */ + for(i = 0; i < NUM_DIRS; i++) { + loc_node *curdest = automap_edge_follow(location, i); + if(curdest && !curdest->real) + room_remove(curdest); + } + + /* Default to things going the way they actually do */ + for(i = 0; i < NUM_DIRS; i++) { + proposed[i] = location->exits[i]; + is_oneway[i] = FALSE; + } + + /* Get rid of superfluous exits */ + for(i = 0; i < NUM_DIRS; i++) { + if(proposed[i]) { + for(n = i+1; n < NUM_DIRS; n++) { + if(proposed[n] == proposed[i]) { + if(proposed[i]->exits[REVERSE_DIR(n)] == location) { + proposed[i] = NULL; + break; + } + if(proposed[i]->exits[REVERSE_DIR(i)] == location) + proposed[n] = NULL; + } + } + } + } + + /* Handle forced movement */ + for(i = 0; i < NUM_DIRS; i++) { + if(proposed[i] && proposed[i] == location->exits[DIR_WAIT]) { + if(proposed[i]->exits[REVERSE_DIR(i)] != location) + proposed[i] = NULL; + } + } + + /* Check for one way and bent passages */ + for(i = 0; i < NUM_DIRS; i++) { + if(proposed[i] && proposed[i]->found + && proposed[i]->exits[REVERSE_DIR(i)] != location) { + is_oneway[i] = TRUE; + for(n = 0; n < NUM_DIRS; n++) { + if(n != i && proposed[i]->exits[n] == location) { + loc_node *newnode = room_find_or_create(automap_get_cookie(), FALSE); + + is_oneway[i] = FALSE; + newnode->found = TRUE; + + automap_set_virtual_connection(proposed[i], n, newnode, FALSE); + proposed[i] = newnode; + } + } + } + } + + /* If it's a one way passage, but there are up/down exits connecting the two, + ignore the passage */ + for(i = 0; i < NUM_DIRS; i++) { + if(is_oneway[i] && proposed[i] + && ((location->exits[DIR_UP] == proposed[i] + && proposed[i]->exits[DIR_DOWN] == location) + || (location->exits[DIR_DOWN] == proposed[i] + && proposed[i]->exits[DIR_UP] == location))) { + proposed[i] = 0; + is_oneway[i] = FALSE; + } + } + + /* Create the proposed passages */ + for(i = 0; i < NUM_DIRS; i++) + automap_set_virtual_connection(location, i, proposed[i], is_oneway[i]); + + /* Explore neighbors */ + if(depth) { + for(i = 0; i < NUM_DIRS; i++) + automap_calc_exits(location->exits[i], depth-1); + } +} + + +#define INFINITY 1000000L + +static void make_distant(const char *unused_key, void *r) +{ + loc_node *t = (loc_node *) r; + t->dist = INFINITY; +} + + +static void automap_calc_distances(loc_node *location, glui32 distance, + BOOL by_walking) +{ + unsigned i; + unsigned maxdir = by_walking ? NUM_EXITS : NUM_DIRS; + if(location->dist < distance) + return; + location->dist = distance; + for(i = 0; i < maxdir; i++) { + loc_node *thisdest; + if(by_walking) + thisdest = location->exits[i]; + else + thisdest = automap_edge_follow(location, i); + + if(thisdest) + automap_calc_distances(thisdest, distance+1, by_walking); + } +} + + +static automap_path *automap_find_path(loc_node *location, loc_node *dest, + BOOL by_walking) +{ + automap_path *path = NULL; + automap_path *rev; + automap_path newnode; + loc_node *p; + + /* Find the distances of all nodes from dest */ + n_hash_enumerate(&rooms, make_distant); + automap_calc_distances(dest, 0, by_walking); + + /* If dest isn't reachable, location's distance will still be infinite */ + if(location->dist == INFINITY) + return NULL; + + /* At each step, go toward a nearer node 'till we're there */ + p = location; + while(p != dest) { + unsigned i; + unsigned best_dir; + glui32 best_dist = INFINITY; + loc_node *best_node = NULL; + unsigned maxdir = by_walking ? NUM_EXITS : NUM_DIRS; + for(i = 0; i < maxdir; i++) { + loc_node *thisdest; + if(by_walking) + thisdest = p->exits[i]; + else + thisdest = automap_edge_follow(p, i); + + if(thisdest && thisdest->dist < best_dist) { + best_dir = i; + best_dist = thisdest->dist; + best_node = thisdest; + } + } + if(!best_node) { + n_show_error(E_SYSTEM, "couldn't find path there", 0); + return NULL; + } + newnode.loc = p; + newnode.dir = best_dir; + LEadd(path, newnode); + p = best_node; + } + + rev = NULL; + while(path) { + LEadd(rev, *path); + LEremove(path); + } + + return rev; +} + + +static void automap_find_cycles(loc_node *location, automap_path *curpath) +{ + unsigned i; + location->touched = TRUE; + for(i = 0; i < NUM_DIRS; i++) { + loc_node *thisdest = automap_edge_follow(location, i); + if(thisdest && thisdest->found) { + automap_path newnode; + newnode.dir = i; + newnode.loc = location; + LEadd(curpath, newnode); + + if(thisdest->touched) { /* Found a cycle! */ + int cyclelength = 0; + automap_path *p; + cycleequation *cycle = NULL; + cycleequation newcycle; + for(p = curpath; p; p=p->next) { + int dir = p->dir; + newcycle.var = &(p->loc->outgoing[dir]->guess_length); + newcycle.min = &(p->loc->outgoing[dir]->min_length); + newcycle.xcoefficient = dirways[dir].deltax; + newcycle.ycoefficient = dirways[dir].deltay; + LEadd(cycle, newcycle); + + cyclelength++; + if(p->loc == thisdest) { /* Found the relevant endpoint */ + if(cyclelength <= 2) /* Ignore two nodes going to each other */ + LEdestroy(cycle); + else + automap_add_cycle(cycle); /* automap_add_cycle gets ownership */ + break; + } + } + if(!p) { /* The cycle had already been found */ + LEdestroy(cycle); + } + } else { + automap_find_cycles(thisdest, curpath); + } + LEremove(curpath); + } + } +} + + +static void automap_calc_cycles(loc_node *start) +{ + automap_delete_cycles(); + automap_find_cycles(start, NULL); + automap_cycle_elimination(); + automap_cycles_fill_values(); +} + + +void make_untouched(const char *unused_key, void *r) +{ + loc_node *t = (loc_node *) r; + t->touched = FALSE; +} + + +void automap_set_locations(int center) +{ + loc_node *r; + + r = room_find(center, TRUE); + + n_hash_enumerate(&rooms, make_untouched); + automap_edges_mindist(); + automap_calc_cycles(r); + + n_hash_enumerate(&rooms, make_untouched); + automap_forget_interference(); + mymap_reinit(); + automap_edges_untouch(); + automap_calc_location(r, NULL, 0, 0); + + automap_resolve_interference(r, 2); +} + + +static int automap_dir = NUM_EXITS; +static BOOL automap_explored = FALSE; +zword automap_location = 0; + + +/* Returns a direction it wants you to explore in. Take the direction and + call automap_unexplore, which'll take you back to the @read. + If it returns NULL, we're finished; get player input */ +const char *automap_explore(void) +{ + if(automap_explored) { + n_show_error(E_SAVE, "tried to explore when we just did so", automap_explored); + return NULL; + } + + if(!loc_exp) + return NULL; + + if(automap_dir == NUM_EXITS) { + fast_saveundo(); + automap_location = evaluate_expression(loc_exp, stack_get_depth()).v; + automap_dir = 0; + } else { + automap_dir++; + if(automap_dir == NUM_EXITS) { + loc_node *r = room_find(automap_location, TRUE); + r->found = TRUE; + automap_calc_exits(r, 0); + allow_saveundo = TRUE; + allow_output = TRUE; + return NULL; + } + } + + allow_saveundo = FALSE; + allow_output = FALSE; + automap_explored = TRUE; + + return dirways[automap_dir].name; +} + + +/* Undoes the work of automap_explore - call whenever a turn is 'over' */ +BOOL automap_unexplore(void) +{ + zword dest; + + if(!automap_explored || !loc_exp) + return FALSE; + automap_explored = FALSE; + + dest = evaluate_expression(loc_exp, stack_get_depth()).v; + + automap_set_connection(automap_location, automap_dir, dest, TRUE); + + fast_restoreundo(); + return TRUE; +} + +#else + +char *roomsymbol = NULL; + +BOOL automap_unexplore(void) +{ + return FALSE; +} + +#endif diff --git a/interpreters/nitfol/automap.h b/interpreters/nitfol/automap.h new file mode 100644 index 0000000..ef03490 --- /dev/null +++ b/interpreters/nitfol/automap.h @@ -0,0 +1,34 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i automap.c' */ +#ifndef CFH_AUTOMAP_H +#define CFH_AUTOMAP_H + +/* From `automap.c': */ + +#ifdef DEBUGGING +extern char * roomsymbol; +void automap_kill (void); +BOOL automap_init (int numobj , const char *location_exp ); +void mymap_init (int width , int height ); +int automap_get_height (void); +void mymap_reinit (void); +void mymap_kill (void); +glui32 automap_draw_callback (winid_t win , glui32 width , glui32 height ); +BOOL automap_mouse_callback (BOOL is_char_event , winid_t win , glui32 x , glui32 y ); +void make_untouched (const char *unused_key , void *r ); +void automap_set_locations (int center ); +extern zword automap_location; +const char * automap_explore (void); +BOOL automap_unexplore (void); + +#else +extern char * roomsymbol; +BOOL automap_unexplore (void); + +#endif + +#endif /* CFH_AUTOMAP_H */ diff --git a/interpreters/nitfol/binary.h b/interpreters/nitfol/binary.h new file mode 100644 index 0000000..5a27103 --- /dev/null +++ b/interpreters/nitfol/binary.h @@ -0,0 +1,45 @@ +#define b0000 0 +#define b0001 1 +#define b0010 2 +#define b0011 3 +#define b0100 4 +#define b0101 5 +#define b0110 6 +#define b0111 7 +#define b1000 8 +#define b1001 9 +#define b1010 10 +#define b1011 11 +#define b1100 12 +#define b1101 13 +#define b1110 14 +#define b1111 15 + +#define b00000000 0x00 +#define b00000001 0x01 +#define b00000010 0x02 +#define b00000011 0x03 +#define b00000100 0x04 +#define b00000111 0x07 +#define b00001000 0x08 +#define b00001111 0x0f +#define b00010000 0x10 +#define b00011111 0x1f +#define b00100000 0x20 +#define b00111111 0x3f +#define b01000000 0x40 +#define b01111111 0x7f +#define b10000000 0x80 +#define b10111111 0xbf +#define b11000000 0xc0 +#define b11011111 0xdf +#define b11100000 0xe0 +#define b11101111 0xef +#define b11110000 0xf0 +#define b11110111 0xf7 +#define b11111000 0xf8 +#define b11111011 0xfb +#define b11111100 0xfc +#define b11111101 0xfd +#define b11111110 0xfe +#define b11111111 0xff diff --git a/interpreters/nitfol/blorb.c b/interpreters/nitfol/blorb.c new file mode 100644 index 0000000..aedb96f --- /dev/null +++ b/interpreters/nitfol/blorb.c @@ -0,0 +1,34 @@ +#include "nitfol.h" +#include "gi_blorb.h" + +/* Link this in only if your glk supports blorb */ + +giblorb_err_t wrap_gib_create_map(strid_t file, giblorb_map_t **newmap) +{ + return giblorb_create_map(file, newmap); +} + + +giblorb_err_t wrap_gib_destroy_map(giblorb_map_t *map) +{ + return giblorb_destroy_map(map); +} + + +giblorb_err_t wrap_gib_load_resource(giblorb_map_t *map, glui32 method, giblorb_result_t *res, glui32 usage, glui32 resnum) +{ + return giblorb_load_resource(map, method, res, usage, resnum); +} + + +giblorb_err_t wrap_gib_count_resources(giblorb_map_t *map, glui32 usage, glui32 *num, glui32 *min, glui32 *max) +{ + return giblorb_count_resources(map, usage, num, min, max); +} + + +giblorb_err_t wrap_gib_set_resource_map(strid_t file) +{ + return giblorb_set_resource_map(file); +} + diff --git a/interpreters/nitfol/copying.awk b/interpreters/nitfol/copying.awk new file mode 100644 index 0000000..15bb686 --- /dev/null +++ b/interpreters/nitfol/copying.awk @@ -0,0 +1,58 @@ +BEGIN { + FS="\""; + print "/* ==> Do not modify this file!! It is created automatically"; + print " by copying.awk. Modify copying.awk instead. <== */"; + print ""; + print "#ifdef DEBUGGING"; + print ""; + print "#include \"nitfol.h\""; + print ""; + print "void show_copying(void)"; + print "{"; + print " infix_print_fixed_string("; +} +NR == 1,/^[ ]*NO WARRANTY[ ]*$/ { + if ($0 ~ / /) + { + print " \"\\n\\n\""; + } + else if ($0 !~ /^[ ]*NO WARRANTY[ ]*$/) + { + printf " \""; + for (i = 1; i <= NF; i++) + { + gsub("\011", " ", $i); + printf "%s", $i; + } + printf "\\n\"\n"; + } +} +/^[ ]*NO WARRANTY[ ]*$/ { + print " );"; + print "}"; + print ""; + print "void show_warranty(void)"; + print "{"; + print " infix_print_fixed_string("; +} +/^[ ]*NO WARRANTY[ ]*$/, /^[ ]*END OF TERMS AND CONDITIONS[ ]*$/{ + if (! ($0 ~ /^[ ]*END OF TERMS AND CONDITIONS[ ]*$/)) + { + printf " \""; + for (i = 1; i <= NF; i++) + { + gsub("\011", " ", $i); + printf "%s", $i; + } + printf "\\n\"\n", $NF; + } +} +END { + print " );"; + print "}"; + print ""; + print "#endif"; +} + + + diff --git a/interpreters/nitfol/copying.c b/interpreters/nitfol/copying.c new file mode 100644 index 0000000..183a320 --- /dev/null +++ b/interpreters/nitfol/copying.c @@ -0,0 +1,299 @@ +/* ==> Do not modify this file!! It is created automatically + by copying.awk. Modify copying.awk instead. <== */ + +#ifdef DEBUGGING + +#include "nitfol.h" + +void show_copying(void) +{ + infix_print_fixed_string( + " GNU GENERAL PUBLIC LICENSE\n" + " Version 2, June 1991\n" + "\n" + " Copyright (C) 1989, 1991 Free Software Foundation, Inc.\n" + " 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA\n" + " Everyone is permitted to copy and distribute verbatim copies\n" + " of this license document, but changing it is not allowed.\n" + "\n" + " Preamble\n" + "\n" + " The licenses for most software are designed to take away your\n" + "freedom to share and change it. By contrast, the GNU General Public\n" + "License is intended to guarantee your freedom to share and change free\n" + "software--to make sure the software is free for all its users. This\n" + "General Public License applies to most of the Free Software\n" + "Foundation's software and to any other program whose authors commit to\n" + "using it. (Some other Free Software Foundation software is covered by\n" + "the GNU Library General Public License instead.) You can apply it to\n" + "your programs, too.\n" + "\n" + " When we speak of free software, we are referring to freedom, not\n" + "price. Our General Public Licenses are designed to make sure that you\n" + "have the freedom to distribute copies of free software (and charge for\n" + "this service if you wish), that you receive source code or can get it\n" + "if you want it, that you can change the software or use pieces of it\n" + "in new free programs; and that you know you can do these things.\n" + "\n" + " To protect your rights, we need to make restrictions that forbid\n" + "anyone to deny you these rights or to ask you to surrender the rights.\n" + "These restrictions translate to certain responsibilities for you if you\n" + "distribute copies of the software, or if you modify it.\n" + "\n" + " For example, if you distribute copies of such a program, whether\n" + "gratis or for a fee, you must give the recipients all the rights that\n" + "you have. You must make sure that they, too, receive or can get the\n" + "source code. And you must show them these terms so they know their\n" + "rights.\n" + "\n" + " We protect your rights with two steps: (1) copyright the software, and\n" + "(2) offer you this license which gives you legal permission to copy,\n" + "distribute and/or modify the software.\n" + "\n" + " Also, for each author's protection and ours, we want to make certain\n" + "that everyone understands that there is no warranty for this free\n" + "software. If the software is modified by someone else and passed on, we\n" + "want its recipients to know that what they have is not the original, so\n" + "that any problems introduced by others will not reflect on the original\n" + "authors' reputations.\n" + "\n" + " Finally, any free program is threatened constantly by software\n" + "patents. We wish to avoid the danger that redistributors of a free\n" + "program will individually obtain patent licenses, in effect making the\n" + "program proprietary. To prevent this, we have made it clear that any\n" + "patent must be licensed for everyone's free use or not licensed at all.\n" + "\n" + " The precise terms and conditions for copying, distribution and\n" + "modification follow.\n" + "\n\n" + " GNU GENERAL PUBLIC LICENSE\n" + " TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n" + "\n" + " 0. This License applies to any program or other work which contains\n" + "a notice placed by the copyright holder saying it may be distributed\n" + "under the terms of this General Public License. The Program, below,\n" + "refers to any such program or work, and a work based on the Program\n" + "means either the Program or any derivative work under copyright law:\n" + "that is to say, a work containing the Program or a portion of it,\n" + "either verbatim or with modifications and/or translated into another\n" + "language. (Hereinafter, translation is included without limitation in\n" + "the term modification.) Each licensee is addressed as you.\n" + "\n" + "Activities other than copying, distribution and modification are not\n" + "covered by this License; they are outside its scope. The act of\n" + "running the Program is not restricted, and the output from the Program\n" + "is covered only if its contents constitute a work based on the\n" + "Program (independent of having been made by running the Program).\n" + "Whether that is true depends on what the Program does.\n" + "\n" + " 1. You may copy and distribute verbatim copies of the Program's\n" + "source code as you receive it, in any medium, provided that you\n" + "conspicuously and appropriately publish on each copy an appropriate\n" + "copyright notice and disclaimer of warranty; keep intact all the\n" + "notices that refer to this License and to the absence of any warranty;\n" + "and give any other recipients of the Program a copy of this License\n" + "along with the Program.\n" + "\n" + "You may charge a fee for the physical act of transferring a copy, and\n" + "you may at your option offer warranty protection in exchange for a fee.\n" + "\n" + " 2. You may modify your copy or copies of the Program or any portion\n" + "of it, thus forming a work based on the Program, and copy and\n" + "distribute such modifications or work under the terms of Section 1\n" + "above, provided that you also meet all of these conditions:\n" + "\n" + " a) You must cause the modified files to carry prominent notices\n" + " stating that you changed the files and the date of any change.\n" + "\n" + " b) You must cause any work that you distribute or publish, that in\n" + " whole or in part contains or is derived from the Program or any\n" + " part thereof, to be licensed as a whole at no charge to all third\n" + " parties under the terms of this License.\n" + "\n" + " c) If the modified program normally reads commands interactively\n" + " when run, you must cause it, when started running for such\n" + " interactive use in the most ordinary way, to print or display an\n" + " announcement including an appropriate copyright notice and a\n" + " notice that there is no warranty (or else, saying that you provide\n" + " a warranty) and that users may redistribute the program under\n" + " these conditions, and telling the user how to view a copy of this\n" + " License. (Exception: if the Program itself is interactive but\n" + " does not normally print such an announcement, your work based on\n" + " the Program is not required to print an announcement.)\n" + "\n\n" + "These requirements apply to the modified work as a whole. If\n" + "identifiable sections of that work are not derived from the Program,\n" + "and can be reasonably considered independent and separate works in\n" + "themselves, then this License, and its terms, do not apply to those\n" + "sections when you distribute them as separate works. But when you\n" + "distribute the same sections as part of a whole which is a work based\n" + "on the Program, the distribution of the whole must be on the terms of\n" + "this License, whose permissions for other licensees extend to the\n" + "entire whole, and thus to each and every part regardless of who wrote it.\n" + "\n" + "Thus, it is not the intent of this section to claim rights or contest\n" + "your rights to work written entirely by you; rather, the intent is to\n" + "exercise the right to control the distribution of derivative or\n" + "collective works based on the Program.\n" + "\n" + "In addition, mere aggregation of another work not based on the Program\n" + "with the Program (or with a work based on the Program) on a volume of\n" + "a storage or distribution medium does not bring the other work under\n" + "the scope of this License.\n" + "\n" + " 3. You may copy and distribute the Program (or a work based on it,\n" + "under Section 2) in object code or executable form under the terms of\n" + "Sections 1 and 2 above provided that you also do one of the following:\n" + "\n" + " a) Accompany it with the complete corresponding machine-readable\n" + " source code, which must be distributed under the terms of Sections\n" + " 1 and 2 above on a medium customarily used for software interchange; or,\n" + "\n" + " b) Accompany it with a written offer, valid for at least three\n" + " years, to give any third party, for a charge no more than your\n" + " cost of physically performing source distribution, a complete\n" + " machine-readable copy of the corresponding source code, to be\n" + " distributed under the terms of Sections 1 and 2 above on a medium\n" + " customarily used for software interchange; or,\n" + "\n" + " c) Accompany it with the information you received as to the offer\n" + " to distribute corresponding source code. (This alternative is\n" + " allowed only for noncommercial distribution and only if you\n" + " received the program in object code or executable form with such\n" + " an offer, in accord with Subsection b above.)\n" + "\n" + "The source code for a work means the preferred form of the work for\n" + "making modifications to it. For an executable work, complete source\n" + "code means all the source code for all modules it contains, plus any\n" + "associated interface definition files, plus the scripts used to\n" + "control compilation and installation of the executable. However, as a\n" + "special exception, the source code distributed need not include\n" + "anything that is normally distributed (in either source or binary\n" + "form) with the major components (compiler, kernel, and so on) of the\n" + "operating system on which the executable runs, unless that component\n" + "itself accompanies the executable.\n" + "\n" + "If distribution of executable or object code is made by offering\n" + "access to copy from a designated place, then offering equivalent\n" + "access to copy the source code from the same place counts as\n" + "distribution of the source code, even though third parties are not\n" + "compelled to copy the source along with the object code.\n" + "\n\n" + " 4. You may not copy, modify, sublicense, or distribute the Program\n" + "except as expressly provided under this License. Any attempt\n" + "otherwise to copy, modify, sublicense or distribute the Program is\n" + "void, and will automatically terminate your rights under this License.\n" + "However, parties who have received copies, or rights, from you under\n" + "this License will not have their licenses terminated so long as such\n" + "parties remain in full compliance.\n" + "\n" + " 5. You are not required to accept this License, since you have not\n" + "signed it. However, nothing else grants you permission to modify or\n" + "distribute the Program or its derivative works. These actions are\n" + "prohibited by law if you do not accept this License. Therefore, by\n" + "modifying or distributing the Program (or any work based on the\n" + "Program), you indicate your acceptance of this License to do so, and\n" + "all its terms and conditions for copying, distributing or modifying\n" + "the Program or works based on it.\n" + "\n" + " 6. Each time you redistribute the Program (or any work based on the\n" + "Program), the recipient automatically receives a license from the\n" + "original licensor to copy, distribute or modify the Program subject to\n" + "these terms and conditions. You may not impose any further\n" + "restrictions on the recipients' exercise of the rights granted herein.\n" + "You are not responsible for enforcing compliance by third parties to\n" + "this License.\n" + "\n" + " 7. If, as a consequence of a court judgment or allegation of patent\n" + "infringement or for any other reason (not limited to patent issues),\n" + "conditions are imposed on you (whether by court order, agreement or\n" + "otherwise) that contradict the conditions of this License, they do not\n" + "excuse you from the conditions of this License. If you cannot\n" + "distribute so as to satisfy simultaneously your obligations under this\n" + "License and any other pertinent obligations, then as a consequence you\n" + "may not distribute the Program at all. For example, if a patent\n" + "license would not permit royalty-free redistribution of the Program by\n" + "all those who receive copies directly or indirectly through you, then\n" + "the only way you could satisfy both it and this License would be to\n" + "refrain entirely from distribution of the Program.\n" + "\n" + "If any portion of this section is held invalid or unenforceable under\n" + "any particular circumstance, the balance of the section is intended to\n" + "apply and the section as a whole is intended to apply in other\n" + "circumstances.\n" + "\n" + "It is not the purpose of this section to induce you to infringe any\n" + "patents or other property right claims or to contest validity of any\n" + "such claims; this section has the sole purpose of protecting the\n" + "integrity of the free software distribution system, which is\n" + "implemented by public license practices. Many people have made\n" + "generous contributions to the wide range of software distributed\n" + "through that system in reliance on consistent application of that\n" + "system; it is up to the author/donor to decide if he or she is willing\n" + "to distribute software through any other system and a licensee cannot\n" + "impose that choice.\n" + "\n" + "This section is intended to make thoroughly clear what is believed to\n" + "be a consequence of the rest of this License.\n" + "\n\n" + " 8. If the distribution and/or use of the Program is restricted in\n" + "certain countries either by patents or by copyrighted interfaces, the\n" + "original copyright holder who places the Program under this License\n" + "may add an explicit geographical distribution limitation excluding\n" + "those countries, so that distribution is permitted only in or among\n" + "countries not thus excluded. In such case, this License incorporates\n" + "the limitation as if written in the body of this License.\n" + "\n" + " 9. The Free Software Foundation may publish revised and/or new versions\n" + "of the General Public License from time to time. Such new versions will\n" + "be similar in spirit to the present version, but may differ in detail to\n" + "address new problems or concerns.\n" + "\n" + "Each version is given a distinguishing version number. If the Program\n" + "specifies a version number of this License which applies to it and any\n" + "later version, you have the option of following the terms and conditions\n" + "either of that version or of any later version published by the Free\n" + "Software Foundation. If the Program does not specify a version number of\n" + "this License, you may choose any version ever published by the Free Software\n" + "Foundation.\n" + "\n" + " 10. If you wish to incorporate parts of the Program into other free\n" + "programs whose distribution conditions are different, write to the author\n" + "to ask for permission. For software which is copyrighted by the Free\n" + "Software Foundation, write to the Free Software Foundation; we sometimes\n" + "make exceptions for this. Our decision will be guided by the two goals\n" + "of preserving the free status of all derivatives of our free software and\n" + "of promoting the sharing and reuse of software generally.\n" + "\n" + ); +} + +void show_warranty(void) +{ + infix_print_fixed_string( + " NO WARRANTY\n" + "\n" + " 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\n" + "FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN\n" + "OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\n" + "PROVIDE THE PROGRAM AS IS WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\n" + "OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\n" + "MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS\n" + "TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE\n" + "PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\n" + "REPAIR OR CORRECTION.\n" + "\n" + " 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\n" + "WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\n" + "REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\n" + "INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\n" + "OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\n" + "TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\n" + "YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\n" + "PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\n" + "POSSIBILITY OF SUCH DAMAGES.\n" + "\n" + ); +} + +#endif diff --git a/interpreters/nitfol/copying.h b/interpreters/nitfol/copying.h new file mode 100644 index 0000000..5cba65f --- /dev/null +++ b/interpreters/nitfol/copying.h @@ -0,0 +1,18 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i copying.c' */ +#ifndef CFH_COPYING_H +#define CFH_COPYING_H + +/* From `copying.c': */ + +#ifdef DEBUGGING +void show_copying (void); +void show_warranty (void); + +#endif + +#endif /* CFH_COPYING_H */ diff --git a/interpreters/nitfol/crashme.inf b/interpreters/nitfol/crashme.inf new file mode 100644 index 0000000..f2c7bf8 --- /dev/null +++ b/interpreters/nitfol/crashme.inf @@ -0,0 +1,97 @@ +! Self-modifying reproducing Z-code. +! +! Generates random junk and sees how the interpreter behaves. If it's clever +! it shouldn't die except for @quit and stack overflow. +! +! inform \$MAX_STATIC_DATA=40000 crashme.inf +! +! Written by Evin Robertson 1999. Placed in public domain. + +Array randstuff -> 32767; +Array checkfix -> 257; + +Global replay = 0; + +Array filename string "CRASHME.MEM"; + +#iftrue (#version_number <= 3); +Constant Granularity = 2; +Constant FileMult = 2; +#endif; + +#iftrue (#version_number >= 4 && #version_number <= 5); +Constant Granularity = 4; +Constant FileMult = 4; +#endif; + +#iftrue (#version_number >= 6 && #version_number <= 7); +Constant Granularity = 4; +Constant FileMult = 8; +#endif; + +#iftrue (#version_number == 8); +Constant Granularity = 8; +Constant FileMult = 8; +#endif; + + +[ Main i a g r c l t game_size checksum; + game_size = FileMult * (0-->13); ! game size + + r = randstuff % Granularity; + if(r) + r = Granularity - r; + + a = randstuff + r; + c = a / Granularity; + l = 32767 - r; + + if(replay) { + print "You are running crashme's output. This will repeat the test run which generated this output.^"; + } else { + print "This program generates random Z-code which is run to test the robustness of your Z-machine interpreter. Most likely this will infinite loop. Do not run if you can't kill your interpreter when it is tightly looping.^Will attempt to write CRASHME.MEM which should be a valid Z-machine game which contains the same code that will be run.^"; + } + print "Press 'q' to abort.^"; + @read_char 1 -> i; + if(i == 'Q' or 'q') + @quit; + + replay++; + + + if(replay == 1) { + for(i=0: i < l: i++) + a->i = random(256) - 1; + for(i=0: i < 13: i++) + a->i = (emptyfunc * Granularity)->i; + + checksum = 0; + for(i=0: i < l: i++) + checksum = checksum + a->i; + g = 0-->6; ! globals location + for(i=0: i < 480: i++) + checksum = checksum + g->i; + + for(i=0: i < 257: i++) { + if(-255 <= checksum && checksum <= 0) { + checkfix->i = -checksum; + break; + } else { + checkfix->i = 255; + checksum = checksum + 255; + } + } + #IFV5; @save 0 game_size filename -> t; #ENDIF; + } + + ! reduce the chances that it'll attempt to write over the same file + for(i=0: i <= 11: i++) + filename->i = 0; + + @call_vn c; + print "^Done.^"; +]; + +[ emptyfunc; + print "starting...^"; +]; diff --git a/interpreters/nitfol/dbg_help.h b/interpreters/nitfol/dbg_help.h new file mode 100644 index 0000000..a60a648 --- /dev/null +++ b/interpreters/nitfol/dbg_help.h @@ -0,0 +1,58 @@ +static name_token command_help[] = { + { BREAKPOINTS, "List breakpoints.\nAn argument specifies a specific breakpoint to list." }, + { QUIT, "Exit nitfol." }, + { LANGUAGE, "Show the current source language." }, + { COND, "Set a condition for an existing breakpoint." }, + { RESTORE, "Restore a saved game." }, + { BREAK, "Set a breakpoint.\nAn 'if' clause specifies a condition." }, + { STEPI, "Step exactly one instruction.\nAn argument specifies a repeat count." }, + { RESTART, "Restart the game." }, + { OBJECT_TREE, "Display the object tree.\nAn argument says which object to use as the root of the tree." }, + { DISABLE_DISPLAY, "Temporarily disable an automatic display." }, + { SELECT_FRAME, "Select a specific stack frame." }, + { ALIAS, "Add an alias" }, + { DOWN_SILENTLY, "Silently select the child of the selected frame.\nAn argument specifies how many frames down to go." }, + { FRAME, "Show the selected stack frame.\nAn argument specifies a stack frame to show." }, + { GIVE, "Give an object an attribute.\nWith a tilde clears the attribute instead of setting it." }, + { SET, "Evaluate an expression without printing its value." }, + { PRINT, "Evaluates an expression and prints the result.\nThis can include function calls." }, + { UP_FRAME, "Select the parent of the selected frame.\nAn argument specifies how many frames up to go." }, + { '#', "Enter a comment" }, + { CONT, "Continue execution.\nAn argument sets the ignore count of the current breakpoint." }, + { DUMPMEM, "Dump memory to a file" }, + { UNDO, "Undo last move (not last debugger command)." }, + { DISPLAY, "Print value of an expression each time the program stops." }, + { MOVE, "Move an object around the object tree." }, + { UP_SILENTLY, "Select the parent of the selected frame silently.\nAn argument specifies how many frames up to go." }, + { COPYING, "Show licensing information." }, + { RECORDOFF, "Stop recording a script." }, + { JUMP, "Continue execution at a new location." }, + { RECORDON, "Start recording a script." }, + { RALIAS, "Add a recursive alias" }, + { LIST_GLOBALS, "List all global variables and their values.\nWith an argument, list all only those with a specific value." }, + { BACKTRACE, "Display the parent functions of the current frame.\nAn argument specifies how many frames back to show.\nIf the argument is negative, start from the first frame instead of the current." }, + { FIND, "Find objects whose shortnames contain a string." }, + { FINISH, "An argument specifies a repeat count." }, + { DOWN_FRAME, "Select the child of the selected frame.\nAn argument specifies how many frames down to go." }, + { IGNORE, "Set the ignore count for a breakpoint." }, + { REPLAYOFF, "Halt replay." }, + { NEXTI, "Step one instruction, stepping over subroutine calls.\nStep a specified number of instructions, stepping over subroutine calls." }, + { HELP, "Print list of commands." }, + { REDO, "Redo undid move. Only works immediately after an 'undo'." }, + { ENABLE_BREAK, "Re-enabled a breakpoint." }, + { UNTIL, "Resume execution until the program reaches a line number greater than the current line." }, + { REPLAY, "Replay a recorded script." }, + { UNALIAS, "Remove an alias" }, + { REMOVE, "Remove an object from the object tree." }, + { INFOSOURCES, "List source files." }, + { DELETE, "Delete a breakpoint." }, + { SYMBOL_FILE, "Load debugging info from a file (usually 'gameinfo.dbg')." }, + { AUTOMAP, "Start automapping" }, + { WARRANTY, "Show warranty information." }, + { DISABLE_BREAK, "Temporarily disable a breakpoint." }, + { UNDISPLAY, "Stop automatically displaying an expression." }, + { ENABLE_DISPLAY, "Re-enable an automatic display." }, + { STEP, "Step through program to a different source line.\nAn argument specifies a repeat count." }, + { INFOSOURCE, "Get information on the current source file." }, + { NEXT, "Step through program, stepping over subroutine calls.\nAn argument specifies a repeat count." } +}; diff --git a/interpreters/nitfol/dbg_help.texi b/interpreters/nitfol/dbg_help.texi new file mode 100644 index 0000000..de1644f --- /dev/null +++ b/interpreters/nitfol/dbg_help.texi @@ -0,0 +1,185 @@ +@item info breakpoints +@itemx info breakpoints @var{num} +List breakpoints. An argument specifies a specific breakpoint to list. + +@item quit +Exit nitfol. + +@item show language +Show the current source language. + +@item condition @var{num} @var{exp} +Set a condition for an existing breakpoint. + +@item restore +Restore a saved game. + +@item break @var{linespec} +@itemx break @var{linespec} if @var{exp} +Set a breakpoint. An @code{if} clause specifies a condition. + +@item stepi +@itemx stepi @var{num} +Step exactly one instruction. An argument specifies a repeat count. + +@item restart +Restart the game. + +@item object-tree +@itemx object-tree @var{exp} +Display the object tree. An argument says which object to use as the root of the tree. + +@item disable display @var{num} +Temporarily disable an automatic display. + +@item select-frame @var{num} +Select a specific stack frame. + +@item alias @var{name} @var{value} +Add an alias + +@item down-silently +@itemx down-silently @var{num} +Silently select the child of the selected frame. An argument specifies how many frames down to go. + +@item frame +@itemx frame @var{num} +Show the selected stack frame. An argument specifies a stack frame to show. + +@item give @var{exp} @var{num} +@itemx give @var{exp} ~ @var{num} +Give an object an attribute. With a tilde clears the attribute instead of setting it. + +@item set @var{exp} +Evaluate an expression without printing its value. + +@item print @var{exp} +Evaluates an expression and prints the result. This can include function calls. + +@item up +@itemx up @var{num} +Select the parent of the selected frame. An argument specifies how many frames up to go. + +@item # comment +Enter a comment + +@item continue +@itemx continue @var{num} +Continue execution. An argument sets the ignore count of the current breakpoint. + +@item dumpmem @var{file} +Dump memory to a file + +@item undo +Undo last move (not last debugger command). + +@item display @var{exp} +Print value of an expression each time the program stops. + +@item move @var{exp} to @var{exp} +Move an object around the object tree. + +@item up-silently +@itemx up-silently @var{num} +Select the parent of the selected frame silently. An argument specifies how many frames up to go. + +@item show copying +Show licensing information. + +@item recording off +Stop recording a script. + +@item jump @var{linespec} +Continue execution at a new location. + +@item recording on +Start recording a script. + +@item ralias @var{name} @var{value} +Add a recursive alias + +@item globals +@itemx globals @var{exp} +List all global variables and their values. With an argument, list all only those with a specific value. + +@item backtrace +@itemx backtrace @var{num} +@itemx backtrace - @var{num} +Display the parent functions of the current frame. An argument specifies how many frames back to show. If the argument is negative, start from the first frame instead of the current. + +@item find +Find objects whose shortnames contain a string. + +@item finish +An argument specifies a repeat count. + +@item down +@itemx down @var{num} +Select the child of the selected frame. An argument specifies how many frames down to go. + +@item ignore @var{num} @var{num} +Set the ignore count for a breakpoint. + +@item replay off +Halt replay. + +@item nexti +@itemx nexti @var{num} +Step one instruction, stepping over subroutine calls. Step a specified number of instructions, stepping over subroutine calls. + +@item help +Print list of commands. + +@item redo +Redo undid move. Only works immediately after an @code{undo}. + +@item enable @var{num} +Re-enabled a breakpoint. + +@item until +Resume execution until the program reaches a line number greater than the current line. + +@item replay +Replay a recorded script. + +@item unalias @var{name} +Remove an alias + +@item remove @var{exp} +Remove an object from the object tree. + +@item info sources +List source files. + +@item delete @var{num} +Delete a breakpoint. + +@item symbol-file @var{file} +Load debugging info from a file (usually @file{gameinfo.dbg}). + +@item automap @var{exp} +Start automapping + +@item show warranty +Show warranty information. + +@item disable @var{num} +Temporarily disable a breakpoint. + +@item undisplay @var{num} +Stop automatically displaying an expression. + +@item enable display @var{num} +Re-enable an automatic display. + +@item step +@itemx step @var{num} +Step through program to a different source line. An argument specifies a repeat count. + +@item info source +Get information on the current source file. + +@item next +@itemx next @var{num} +Step through program, stepping over subroutine calls. An argument specifies a repeat count. + diff --git a/interpreters/nitfol/debug.c b/interpreters/nitfol/debug.c new file mode 100644 index 0000000..2163a76 --- /dev/null +++ b/interpreters/nitfol/debug.c @@ -0,0 +1,548 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +#ifdef HEADER + +#ifndef DEBUGGING + +#define debug_object(o, t) +#define debug_attrib(a, o) + +#endif + +typedef enum { CONT_GO, CONT_STEP, CONT_NEXT, CONT_FINISH, CONT_UNTIL, CONT_STEPI, CONT_NEXTI } Cont_type; + + +#endif + +BOOL enter_debugger = FALSE; + +#ifdef DEBUGGING + +BOOL exit_debugger = FALSE; +infix_file *cur_file; +int cur_line; +int cur_break; +int cur_stack_depth; +int infix_selected_frame; + +static Cont_type debug_cont = CONT_GO; +static int step_count = 0; + +typedef enum { bp_none, bp_break, bp_write_watch, bp_read_watch, bp_access_watch } bptype; + +typedef enum { del, del_at_next_stop, disable, donttouch } bpdisp; + +typedef struct breakpoint breakpoint; + +struct breakpoint { + breakpoint *next; + + int number; + BOOL enabled; + + bptype type; + bpdisp disposition; + + offset PC; + int break_frame; /* If not -1, only break at this depth */ + + infix_file *file; + int line; + + unsigned hit_count; + unsigned ignore_count; /* Don't break until 0 */ + + char *condition; + + char *watch_expression; + int watch_frame; /* Frame at which to evaluate watch; -1 if no locals */ + z_typed watch_value; +}; + + +static breakpoint *breaklist; +static int breaknumber = 1; + + +void set_step(Cont_type t, int count) +{ + debug_cont = t; + step_count = count; + exit_debugger = TRUE; + + do_check_watches = TRUE; + if(debug_cont == CONT_GO && breaklist == NULL) + do_check_watches = FALSE; +} + + +int infix_set_break(offset location) +{ + breakpoint newbreak; + + infix_location cur_location; + + if(location == 0) { + infix_print_string("No code at that location.\n"); + return 0; + } + + newbreak.number = breaknumber++; + newbreak.enabled = TRUE; + newbreak.type = bp_break; + newbreak.disposition = donttouch; + newbreak.PC = location; + newbreak.break_frame = -1; + newbreak.hit_count = 0; + newbreak.ignore_count = 0; + newbreak.condition = NULL; + + LEadd(breaklist, newbreak); + + infix_print_string("Breakpoint "); + infix_print_number(newbreak.number); + infix_print_string(" at "); + infix_print_number(location); + + if(infix_decode_PC(&cur_location, location)) { + infix_print_string(": file "); + infix_print_string(cur_location.file->filename); + infix_print_string(", line "); + infix_print_number(cur_location.line_num); + } + infix_print_string(".\n"); + + do_check_watches = TRUE; + return newbreak.number; +} + +static breakpoint *find_breakpoint(int breaknum) +{ + breakpoint *p; + LEsearch(breaklist, p, p->number == breaknum); + + if(p) + return p; + + infix_print_string("No breakpoint number "); + infix_print_number(breaknum); + infix_print_string(".\n"); + return NULL; +} + + +void infix_delete_breakpoint(int breaknum) +{ + breakpoint *p, *t; + LEsearchremove(breaklist, p, t, p->number == breaknum, n_free(p->condition)); +} + +void infix_set_cond(int breaknum, const char *condition) +{ + breakpoint *p = find_breakpoint(breaknum); + + if(p) { + if(p->condition) { + n_free(p->condition); + } + p->condition = n_strdup(condition); + } +} + +void infix_set_ignore(int breaknum, int count) +{ + breakpoint *p = find_breakpoint(breaknum); + + if(p) + p->ignore_count = count; +} + +void infix_set_break_enabled(int breaknum, BOOL enabled) +{ + breakpoint *p = find_breakpoint(breaknum); + + if(p) + p->enabled = enabled; +} + +static void infix_show_break(breakpoint *b) +{ + infix_print_number(b->number); + infix_print_char(' '); + infix_print_string("breakpoint"); + infix_print_char(' '); + infix_print_string("keep"); + infix_print_char(' '); + if(b->enabled) + infix_print_char('y'); + else + infix_print_char('n'); + infix_print_offset(b->PC); + infix_print_char(' '); + infix_gprint_loc(0, b->PC); + infix_print_char('\n'); +} + +void infix_show_all_breakpoints(void) +{ + breakpoint *p; + if(!breaklist) { + infix_print_string("No breakpoints or watchpoints.\n"); + } else { + for(p = breaklist; p; p=p->next) + infix_show_break(p); + } +} + +void infix_show_breakpoint(int breaknum) +{ + breakpoint *p = find_breakpoint(breaknum); + if(p) { + infix_show_break(p); + } else { + infix_print_string("No such breakpoint or watchpoint.\n"); + } +} + + +typedef struct auto_display auto_display; + +struct auto_display { + auto_display *next; + + int number; + BOOL enabled; + + char *exp; +}; + +static auto_display *displist; +static int dispnumber = 1; + +int infix_auto_display(const char *expression) +{ + auto_display newdisp; + newdisp.number = dispnumber++; + newdisp.enabled = TRUE; + newdisp.exp = n_strdup(expression); + + LEadd(displist, newdisp); + + return newdisp.number; +} + +void perform_displays(void) +{ + auto_display *p; + for(p = displist; p; p=p->next) { + if(p->enabled) { + infix_print_number(p->number); + infix_print_string(": "); + infix_print_string(p->exp); + infix_print_string(" = "); + infix_display(evaluate_expression(p->exp, infix_selected_frame)); + } + } +} + + +static auto_display *find_auto_display(int displaynum) +{ + auto_display *p; + LEsearch(displist, p, p->number == displaynum); + + if(p) + return p; + + infix_print_string("No auto-display number "); + infix_print_number(displaynum); + infix_print_string(".\n"); + return NULL; +} + + +void infix_auto_undisplay(int displaynum) +{ + auto_display *p, *t; + LEsearchremove(displist, p, t, p->number == displaynum, n_free(p->exp)); +} + + +void infix_set_display_enabled(int displaynum, BOOL enabled) +{ + auto_display *p = find_auto_display(displaynum); + + if(p) + p->enabled = enabled; +} + + +const char *debug_decode_number(unsigned number) +{ + const char *name; + z_typed val; + val.v = number; + + val.t = Z_OBJECT; + name = infix_get_name(val); + + if(!name) { + val.t = Z_ROUTINE; + name = infix_get_name(val); + } + if(!name) { + val.t = Z_STRING; + name = infix_get_name(val); + } + return name; +} + + +unsigned opcode_counters[OFFSET_END]; + + +void check_watches(void) +{ + /* This function is called after every instruction, and infix_decode_PC is + relatively expensive, so only get it when we need it */ + infix_location cur_location; + BOOL found_location = FALSE; + + BOOL is_breakpoint = FALSE; + int depth; + BOOL go_debug; + breakpoint *p; + + + switch(debug_cont) { + case CONT_STEP: + if(!infix_decode_PC(&cur_location, oldPC)) + break; + found_location = TRUE; + if(cur_file != cur_location.file || cur_line != cur_location.line_num) + go_debug = TRUE; + break; + + case CONT_STEPI: + go_debug = TRUE; + break; + + case CONT_NEXT: + depth = stack_get_depth(); + if(depth < cur_stack_depth) { + go_debug = TRUE; + } else if(cur_stack_depth == depth) { + if(!infix_decode_PC(&cur_location, oldPC)) + break; + found_location = TRUE; + + if(cur_file != cur_location.file || cur_line != cur_location.line_num) + go_debug = TRUE; + } + break; + + case CONT_NEXTI: + depth = stack_get_depth(); + if(depth <= cur_stack_depth) + go_debug = TRUE; + break; + + case CONT_FINISH: + depth = stack_get_depth(); + if(depth < cur_stack_depth) + go_debug = TRUE; + break; + + case CONT_UNTIL: + depth = stack_get_depth(); + if(depth < cur_stack_depth) { + go_debug = TRUE; + } else if(cur_stack_depth == depth) { + if(!infix_decode_PC(&cur_location, oldPC)) + break; + found_location = TRUE; + + if(cur_file != cur_location.file || cur_line > cur_location.line_num) + go_debug = TRUE; + } + break; + + case CONT_GO: + go_debug = FALSE; + } + + if(go_debug && step_count && --step_count) + go_debug = FALSE; + + for(p = breaklist; p; p=p->next) { + if(p->enabled) { + BOOL break_hit = FALSE; + switch(p->type) { + case bp_none: + case bp_read_watch: + break; + + case bp_break: + break_hit = p->PC == oldPC; + break; + + case bp_write_watch: + case bp_access_watch: + + ; + } + + if(break_hit) { + if(p->ignore_count) { + p->ignore_count--; + } else { + z_typed foo; + if(p->condition) + foo = evaluate_expression(p->condition, infix_selected_frame); + if(!p->condition || foo.v) { + is_breakpoint = TRUE; + go_debug = TRUE; + + p->hit_count++; + + infix_print_string("Breakpoint "); + infix_print_number(p->number); + infix_print_string(", "); + + switch(p->disposition) { + case del: + infix_delete_breakpoint(p->number); + break; + + case disable: + p->enabled = FALSE; + break; + + case del_at_next_stop: + case donttouch: + ; + } + } + } + } + } + } + + + if(go_debug || enter_debugger) { + depth = stack_get_depth(); + + enter_debugger = FALSE; + + if(!found_location) + found_location = infix_decode_PC(&cur_location, oldPC); + + if(found_location) { + cur_file = cur_location.file; + cur_line = cur_location.line_num; + } else { + cur_file = NULL; + cur_line = 0; + } + + if(is_breakpoint || cur_stack_depth != depth) { + infix_gprint_loc(depth, 0); + } else { + infix_file_print_line(cur_file, cur_line); + } + + infix_selected_frame = cur_stack_depth = depth; + + for(p = breaklist; p; p=p->next) { + if(p->disposition == del_at_next_stop) + infix_delete_breakpoint(p->number); + } + + debug_prompt(); + } + + return; +} + + +void debug_prompt(void) +{ + char buffer[513]; + exit_debugger = FALSE; + perform_displays(); + while(!exit_debugger) { + if(db_prompt) + infix_print_string(db_prompt); + else + infix_print_string("(nitfol) "); + infix_get_string(buffer, 512); + process_debug_command(buffer); + } +} + +void infix_select_frame(int num) +{ + if(frame_is_valid(num)) + infix_selected_frame = num; +} + +void infix_show_frame(int frame) +{ + infix_print_char('#'); + infix_print_number(frame); + infix_print_string(" "); + infix_gprint_loc(frame, 0); +} + +void infix_backtrace(int start, int length) +{ + int n; + for(n = start + length - 1; n >= start; n--) { + infix_show_frame(n); + } +} + + + +const char *watchnames[] = { + "reading object", + "move to object", + "moving object" +}; + + + + +void debug_object(zword objectnum, watchinfo type) +{ + /*n_show_debug(E_OBJECT, watchnames[type], objectnum);*/ +} + +void debug_attrib(zword attribnum, zword objectnum) +{ + /*n_show_debug(E_OBJECT, "using attribute", attribnum);*/ +} + + + +#endif + diff --git a/interpreters/nitfol/debug.h b/interpreters/nitfol/debug.h new file mode 100644 index 0000000..0b6bd30 --- /dev/null +++ b/interpreters/nitfol/debug.h @@ -0,0 +1,52 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i debug.c' */ +#ifndef CFH_DEBUG_H +#define CFH_DEBUG_H + +/* From `debug.c': */ + +#ifndef DEBUGGING +#define debug_object(o, t) +#define debug_attrib(a, o) + +#endif +typedef enum { CONT_GO, CONT_STEP, CONT_NEXT, CONT_FINISH, CONT_UNTIL, CONT_STEPI, CONT_NEXTI }Cont_type; +extern BOOL enter_debugger; + +#ifdef DEBUGGING +extern BOOL exit_debugger; +extern infix_file * cur_file; +extern int cur_line; +extern int cur_break; +extern int cur_stack_depth; +extern int infix_selected_frame; +void set_step (Cont_type t , int count ); +int infix_set_break (offset location ); +void infix_delete_breakpoint (int breaknum ); +void infix_set_cond (int breaknum , const char *condition ); +void infix_set_ignore (int breaknum , int count ); +void infix_set_break_enabled (int breaknum , BOOL enabled ); +void infix_show_all_breakpoints (void); +void infix_show_breakpoint (int breaknum ); +int infix_auto_display (const char *expression ); +void perform_displays (void); +void infix_auto_undisplay (int displaynum ); +void infix_set_display_enabled (int displaynum , BOOL enabled ); +const char * debug_decode_number (unsigned number ); +extern unsigned opcode_counters[]; +void check_watches (void); +void debug_prompt (void); +void infix_select_frame (int num ); +void infix_show_frame (int frame ); +void infix_backtrace (int start , int length ); +extern const char * watchnames[]; +void debug_object (zword objectnum , watchinfo type ); +void debug_attrib (zword attribnum , zword objectnum ); + +#endif + +#endif /* CFH_DEBUG_H */ diff --git a/interpreters/nitfol/decode.c b/interpreters/nitfol/decode.c new file mode 100644 index 0000000..7f31eac --- /dev/null +++ b/interpreters/nitfol/decode.c @@ -0,0 +1,164 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +void decode(void) +{ + unsigned optypes; + int maxoperands; + + while(!exit_decoder) { + zbyte opcode = HIBYTE(PC); + + oldPC = PC; + +#ifdef DEBUGGING + if(do_check_watches) + check_watches(); +#endif + + PC++; + +#ifndef NO_TICK + glk_tick(); /* tick tock hickery dock the mouse ran up the clock */ +#endif + + /* Top bits decide opcode/operand encoding */ + switch(opcode >> 4) { + case 0: case 1: /* long 2OP */ + operand[0] = HIBYTE(PC); /* small constant */ + operand[1] = HIBYTE(PC+1); /* small constant */ + numoperands = 2; PC += 2; + opcode += OFFSET_2OP - 0x00; + break; + + + case 2: case 3: /* long 2OP */ + operand[0] = HIBYTE(PC); /* small constant */ + operand[1] = get_var(HIBYTE(PC+1)); /* variable */ + numoperands = 2; PC += 2; + opcode += OFFSET_2OP - 0x20; + break; + + + case 4: case 5: /* long 2OP */ + operand[0] = get_var(HIBYTE(PC)); /* variable */ + operand[1] = HIBYTE(PC+1); /* small constant */ + numoperands = 2; PC += 2; + opcode += OFFSET_2OP - 0x40; + break; + + + case 6: case 7: /* long 2OP */ + operand[0] = get_var(HIBYTE(PC)); /* variable */ + operand[1] = get_var(HIBYTE(PC+1)); /* variable */ + numoperands = 2; PC += 2; + opcode += OFFSET_2OP - 0x60; + break; + + + case 8: /* short 1OP */ + operand[0] = HIWORD(PC); /* large constant */ + numoperands = 1; PC += ZWORD_SIZE; + opcode += OFFSET_1OP - 0x80; + break; + + + case 9: /* short 1OP */ + operand[0] = HIBYTE(PC); /* small constant */ + numoperands = 1; PC += 1; + opcode += OFFSET_1OP - 0x90; + break; + + + case 10: /* short 1OP */ + operand[0] = get_var(HIBYTE(PC)); /* variable */ + numoperands = 1; PC += 1; + opcode += OFFSET_1OP - 0xa0; + break; + + + case 11: /* short 0OP */ + if(opcode != 0xbe) { + numoperands = 0; + opcode += OFFSET_0OP - 0xb0; + break; + } + /* EXTENDED */ + opcode = HIBYTE(PC); /* Get the extended opcode */ + optypes = HIBYTE(PC+1); + PC += 2; + +#ifndef FAST + if(OFFSET_EXT + opcode > OFFSET_END) { + n_show_error(E_INSTR, "unknown extended opcode", opcode); + break; + } +#endif + + for(numoperands = 0; numoperands < 4; numoperands++) { + switch(optypes & (3 << 6)) { /* Look at the highest two bits. */ + case 0 << 6: + operand[numoperands] = HIWORD(PC); PC+=ZWORD_SIZE; break; + case 1 << 6: + operand[numoperands] = HIBYTE(PC); PC++; break; + case 2 << 6: + operand[numoperands] = get_var(HIBYTE(PC)); PC++; break; + default: + goto END_OF_EXTENDED; /* inky says, "use the goto." */ + } + optypes <<= 2; /* Move the next two bits into position. */ + } + END_OF_EXTENDED: + opcode += OFFSET_EXT; + break; + + case 12: case 13: case 14: case 15: /* variable operand count */ + maxoperands = 4; + optypes = ((unsigned) HIBYTE(PC)) << 8; /* Shift left so our loop will */ + /* be the same for both 4 and 8 operands */ + PC++; + + if(opcode == 0xec || opcode == 0xfa) { /* call_vs2 and call_vn2 */ + maxoperands = 8; + optypes |= HIBYTE(PC); /* Fill the bottom 8 bits */ + PC++; + } + + for(numoperands = 0; numoperands < maxoperands; numoperands++) { + switch(optypes & (3 << 14)) { /* Look at the highest two bits. */ + case 0 << 14: + operand[numoperands] = HIWORD(PC); PC+=ZWORD_SIZE; break; + case 1 << 14: + operand[numoperands] = HIBYTE(PC); PC++; break; + case 2 << 14: + operand[numoperands] = get_var(HIBYTE(PC)); PC++; break; + default: + goto END_OF_VARIABLE; + } + optypes <<= 2; /* Move the next two bits into position. */ + } + END_OF_VARIABLE: + opcode += OFFSET_2OP - 0xc0; + break; + } + opcodetable[opcode](); + } +} diff --git a/interpreters/nitfol/decode.h b/interpreters/nitfol/decode.h new file mode 100644 index 0000000..0e9bf2e --- /dev/null +++ b/interpreters/nitfol/decode.h @@ -0,0 +1,13 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i decode.c' */ +#ifndef CFH_DECODE_H +#define CFH_DECODE_H + +/* From `decode.c': */ +void decode (void); + +#endif /* CFH_DECODE_H */ diff --git a/interpreters/nitfol/errmesg.c b/interpreters/nitfol/errmesg.c new file mode 100644 index 0000000..a49332e --- /dev/null +++ b/interpreters/nitfol/errmesg.c @@ -0,0 +1,110 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +#ifdef HEADER +typedef enum { E_INSTR, E_OBJECT, E_STACK, E_MEMORY, E_MATH, E_STRING, + E_OUTPUT, E_SOUND, E_SYSTEM, E_VERSION, E_CORRUPT, E_SAVE, + E_DEBUG } errortypes; +#endif + +static const char *errortypenames[] = { + "instruc", + "object", + "stack", + "memory", + "math", + "string", + "output", + "sound", + "system", + "version", + "corrupt", + "save", + "debug" +}; + + +/* These all feature loop detection, so if error reporting spawns another + error, we won't infinite loop. Hopefully. +*/ + +void n_show_warn(errortypes type, const char *message, offset number) +{ + if(!ignore_errors && allow_output) { + showstuff("WARN", errortypenames[type], message, number); + } +} + +void n_show_port(errortypes type, const char *message, offset number) +{ + if(!ignore_errors && allow_output) { + showstuff("PORT", errortypenames[type], message, number); + } +} + +void n_show_error(errortypes type, const char *message, offset number) +{ + if(!ignore_errors && allow_output) { + showstuff("ERROR", errortypenames[type], message, number); + } +} + +/* showstuff calls n_show_fatal if it's looping uncontrollably, but it + disables its loopiness detector so it can show this fatal error. So + n_show_fatal needs its own loop detection. */ +void n_show_fatal(errortypes type, const char *message, offset number) +{ + static BOOL loopy = FALSE; + if(loopy) { + /* puts("loopy"); */ + glk_exit(); + } + loopy = TRUE; + showstuff("FATAL", errortypenames[type], message, number); + loopy = FALSE; + + glk_exit(); +} + +void n_show_debug(errortypes type, const char *message, offset number) +{ +#ifndef HIDE_DEBUG + static BOOL loopy = FALSE; + if(loopy) + n_show_fatal(E_SYSTEM, "loopy debug error", 0); + loopy = TRUE; + showstuff("E_DEBUG", errortypenames[type], message, number); + loopy = FALSE; +#endif +} + +zword z_range_error(offset p) +{ + if(!ignore_errors) { + static BOOL loopy = FALSE; + if(loopy) + n_show_fatal(E_SYSTEM, "loopy range error", 0); + loopy = TRUE; + showstuff("RANGE", errortypenames[E_MEMORY], "invalid memory access", p); + loopy = FALSE; + } + return 0; +} diff --git a/interpreters/nitfol/errmesg.h b/interpreters/nitfol/errmesg.h new file mode 100644 index 0000000..e7f8496 --- /dev/null +++ b/interpreters/nitfol/errmesg.h @@ -0,0 +1,21 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i errmesg.c' */ +#ifndef CFH_ERRMESG_H +#define CFH_ERRMESG_H + +/* From `errmesg.c': */ +typedef enum { E_INSTR, E_OBJECT, E_STACK, E_MEMORY, E_MATH, E_STRING, + E_OUTPUT, E_SOUND, E_SYSTEM, E_VERSION, E_CORRUPT, E_SAVE, + E_DEBUG }errortypes; +void n_show_warn (errortypes type , const char *message , offset number ); +void n_show_port (errortypes type , const char *message , offset number ); +void n_show_error (errortypes type , const char *message , offset number ); +void n_show_fatal (errortypes type , const char *message , offset number ); +void n_show_debug (errortypes type , const char *message , offset number ); +zword z_range_error (offset p ); + +#endif /* CFH_ERRMESG_H */ diff --git a/interpreters/nitfol/gi_blorb.h b/interpreters/nitfol/gi_blorb.h new file mode 100644 index 0000000..c71b100 --- /dev/null +++ b/interpreters/nitfol/gi_blorb.h @@ -0,0 +1,96 @@ +#ifndef _GI_BLORB_H +#define _GI_BLORB_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* gi_blorb.h: Blorb library layer for Glk API. + gi_blorb version 1.1. + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html + + This file is copyright 1998-1999 by Andrew Plotkin. You may copy, + distribute, and incorporate it into your own programs, by any means + and under any conditions, as long as you do not modify it. You may + also modify this file, incorporate it into your own programs, + and distribute the modified version, as long as you retain a notice + in your program or documentation which mentions my name and the URL + shown above. +*/ + +/* Error type and error codes */ +typedef glui32 giblorb_err_t; +#define giblorb_err_None (0) +#define giblorb_err_CompileTime (1) +#define giblorb_err_Alloc (2) +#define giblorb_err_Read (3) +#define giblorb_err_NotAMap (4) +#define giblorb_err_Format (5) +#define giblorb_err_NotFound (6) + +/* Methods for loading a chunk */ +#define giblorb_method_DontLoad (0) +#define giblorb_method_Memory (1) +#define giblorb_method_FilePos (2) + +/* Four-byte constants */ + +#define giblorb_make_id(c1, c2, c3, c4) \ + (((c1) << 24) | ((c2) << 16) | ((c3) << 8) | (c4)) + +#define giblorb_ID_Snd (giblorb_make_id('S', 'n', 'd', ' ')) +#define giblorb_ID_Exec (giblorb_make_id('E', 'x', 'e', 'c')) +#define giblorb_ID_Pict (giblorb_make_id('P', 'i', 'c', 't')) +#define giblorb_ID_Copyright (giblorb_make_id('(', 'c', ')', ' ')) +#define giblorb_ID_AUTH (giblorb_make_id('A', 'U', 'T', 'H')) +#define giblorb_ID_ANNO (giblorb_make_id('A', 'N', 'N', 'O')) + +/* giblorb_map_t: Holds the complete description of an open Blorb + file. This type is opaque for normal interpreter use. */ +typedef struct giblorb_map_struct giblorb_map_t; + +/* giblorb_result_t: Result when you try to load a chunk. */ +typedef struct giblorb_result_struct { + glui32 chunknum; /* The chunk number (for use in + giblorb_unload_chunk(), etc.) */ + union { + void *ptr; /* A pointer to the data (if you used + giblorb_method_Memory) */ + glui32 startpos; /* The position in the file (if you + used giblorb_method_FilePos) */ + } data; + glui32 length; /* The length of the data */ + glui32 chunktype; /* The type of the chunk. */ +} giblorb_result_t; + +extern giblorb_err_t giblorb_create_map(strid_t file, + giblorb_map_t **newmap); +extern giblorb_err_t giblorb_destroy_map(giblorb_map_t *map); + +extern giblorb_err_t giblorb_load_chunk_by_type(giblorb_map_t *map, + glui32 method, giblorb_result_t *res, glui32 chunktype, + glui32 count); +extern giblorb_err_t giblorb_load_chunk_by_number(giblorb_map_t *map, + glui32 method, giblorb_result_t *res, glui32 chunknum); +extern giblorb_err_t giblorb_unload_chunk(giblorb_map_t *map, + glui32 chunknum); + +extern giblorb_err_t giblorb_load_resource(giblorb_map_t *map, + glui32 method, giblorb_result_t *res, glui32 usage, + glui32 resnum); +extern giblorb_err_t giblorb_count_resources(giblorb_map_t *map, + glui32 usage, glui32 *num, glui32 *min, glui32 *max); + +/* The following functions are part of the Glk library itself, not + the Blorb layer (whose code is in gi_blorb.c). These functions + are necessarily implemented in platform-dependent code. +*/ +extern giblorb_err_t giblorb_set_resource_map(strid_t file); + +#ifdef __cplusplus +} +#endif + +#endif /* _GI_BLORB_H */ diff --git a/interpreters/nitfol/glk.h b/interpreters/nitfol/glk.h new file mode 100644 index 0000000..2c5d58d --- /dev/null +++ b/interpreters/nitfol/glk.h @@ -0,0 +1,426 @@ +#ifndef GLK_H +#define GLK_H + +/* glk.h: Header file for Glk API, version 0.7.0. + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html + + This file is copyright 1998-2004 by Andrew Plotkin. You may copy, + distribute, and incorporate it into your own programs, by any means + and under any conditions, as long as you do not modify it. You may + also modify this file, incorporate it into your own programs, + and distribute the modified version, as long as you retain a notice + in your program or documentation which mentions my name and the URL + shown above. +*/ + +/* You may have to edit the definition of glui32 to make sure it's really a + 32-bit unsigned integer type, and glsi32 to make sure it's really a + 32-bit signed integer type. If they're not, horrible things will happen. */ +#include +#if (USHORT_MAX == 4294967295) +typedef unsigned short glui32; +typedef signed short glsi32; +#elif (UINT_MAX == 4294967295) +typedef unsigned int glui32; +typedef signed int glsi32; +#elif (ULONG_MAX) == 4294967295) +typedef unsigned long glui32; +typedef signed long glsi32; +#else +#error No 32-bit integer type found. +#endif + + +/* These are the compile-time conditionals that reveal various Glk optional + modules. */ +#define GLK_MODULE_UNICODE +#define GLK_MODULE_IMAGE +#define GLK_MODULE_SOUND +#define GLK_MODULE_HYPERLINKS + +/* These types are opaque object identifiers. They're pointers to opaque + C structures, which are defined differently by each library. */ +typedef struct glk_window_struct *winid_t; +typedef struct glk_stream_struct *strid_t; +typedef struct glk_fileref_struct *frefid_t; +typedef struct glk_schannel_struct *schanid_t; + +#define gestalt_Version (0) +#define gestalt_CharInput (1) +#define gestalt_LineInput (2) +#define gestalt_CharOutput (3) +#define gestalt_CharOutput_CannotPrint (0) +#define gestalt_CharOutput_ApproxPrint (1) +#define gestalt_CharOutput_ExactPrint (2) +#define gestalt_MouseInput (4) +#define gestalt_Timer (5) +#define gestalt_Graphics (6) +#define gestalt_DrawImage (7) +#define gestalt_Sound (8) +#define gestalt_SoundVolume (9) +#define gestalt_SoundNotify (10) +#define gestalt_Hyperlinks (11) +#define gestalt_HyperlinkInput (12) +#define gestalt_SoundMusic (13) +#define gestalt_GraphicsTransparency (14) +#define gestalt_Unicode (15) + +#define evtype_None (0) +#define evtype_Timer (1) +#define evtype_CharInput (2) +#define evtype_LineInput (3) +#define evtype_MouseInput (4) +#define evtype_Arrange (5) +#define evtype_Redraw (6) +#define evtype_SoundNotify (7) +#define evtype_Hyperlink (8) + +typedef struct event_struct { + glui32 type; + winid_t win; + glui32 val1, val2; +} event_t; + +#define keycode_Unknown (0xffffffff) +#define keycode_Left (0xfffffffe) +#define keycode_Right (0xfffffffd) +#define keycode_Up (0xfffffffc) +#define keycode_Down (0xfffffffb) +#define keycode_Return (0xfffffffa) +#define keycode_Delete (0xfffffff9) +#define keycode_Escape (0xfffffff8) +#define keycode_Tab (0xfffffff7) +#define keycode_PageUp (0xfffffff6) +#define keycode_PageDown (0xfffffff5) +#define keycode_Home (0xfffffff4) +#define keycode_End (0xfffffff3) +#define keycode_Func1 (0xffffffef) +#define keycode_Func2 (0xffffffee) +#define keycode_Func3 (0xffffffed) +#define keycode_Func4 (0xffffffec) +#define keycode_Func5 (0xffffffeb) +#define keycode_Func6 (0xffffffea) +#define keycode_Func7 (0xffffffe9) +#define keycode_Func8 (0xffffffe8) +#define keycode_Func9 (0xffffffe7) +#define keycode_Func10 (0xffffffe6) +#define keycode_Func11 (0xffffffe5) +#define keycode_Func12 (0xffffffe4) +/* The last keycode is always (0x100000000 - keycode_MAXVAL) */ +#define keycode_MAXVAL (28) + +#define style_Normal (0) +#define style_Emphasized (1) +#define style_Preformatted (2) +#define style_Header (3) +#define style_Subheader (4) +#define style_Alert (5) +#define style_Note (6) +#define style_BlockQuote (7) +#define style_Input (8) +#define style_User1 (9) +#define style_User2 (10) +#define style_NUMSTYLES (11) + +typedef struct stream_result_struct { + glui32 readcount; + glui32 writecount; +} stream_result_t; + +#define wintype_AllTypes (0) +#define wintype_Pair (1) +#define wintype_Blank (2) +#define wintype_TextBuffer (3) +#define wintype_TextGrid (4) +#define wintype_Graphics (5) + +#define winmethod_Left (0x00) +#define winmethod_Right (0x01) +#define winmethod_Above (0x02) +#define winmethod_Below (0x03) +#define winmethod_DirMask (0x0f) + +#define winmethod_Fixed (0x10) +#define winmethod_Proportional (0x20) +#define winmethod_DivisionMask (0xf0) + +#define fileusage_Data (0x00) +#define fileusage_SavedGame (0x01) +#define fileusage_Transcript (0x02) +#define fileusage_InputRecord (0x03) +#define fileusage_TypeMask (0x0f) + +#define fileusage_TextMode (0x100) +#define fileusage_BinaryMode (0x000) + +#define filemode_Write (0x01) +#define filemode_Read (0x02) +#define filemode_ReadWrite (0x03) +#define filemode_WriteAppend (0x05) + +#define seekmode_Start (0) +#define seekmode_Current (1) +#define seekmode_End (2) + +#define stylehint_Indentation (0) +#define stylehint_ParaIndentation (1) +#define stylehint_Justification (2) +#define stylehint_Size (3) +#define stylehint_Weight (4) +#define stylehint_Oblique (5) +#define stylehint_Proportional (6) +#define stylehint_TextColor (7) +#define stylehint_BackColor (8) +#define stylehint_ReverseColor (9) +#define stylehint_NUMHINTS (10) + +#define stylehint_just_LeftFlush (0) +#define stylehint_just_LeftRight (1) +#define stylehint_just_Centered (2) +#define stylehint_just_RightFlush (3) + +/* glk_main() is the top-level function which you define. The Glk library + calls it. */ +extern void glk_main(void); + +extern void glk_exit(void); +extern void glk_set_interrupt_handler(void (*func)(void)); +extern void glk_tick(void); + +extern glui32 glk_gestalt(glui32 sel, glui32 val); +extern glui32 glk_gestalt_ext(glui32 sel, glui32 val, glui32 *arr, + glui32 arrlen); + +extern unsigned char glk_char_to_lower(unsigned char ch); +extern unsigned char glk_char_to_upper(unsigned char ch); + +extern winid_t glk_window_get_root(void); +extern winid_t glk_window_open(winid_t split, glui32 method, glui32 size, + glui32 wintype, glui32 rock); +extern void glk_window_close(winid_t win, stream_result_t *result); +extern void glk_window_get_size(winid_t win, glui32 *widthptr, + glui32 *heightptr); +extern void glk_window_set_arrangement(winid_t win, glui32 method, + glui32 size, winid_t keywin); +extern void glk_window_get_arrangement(winid_t win, glui32 *methodptr, + glui32 *sizeptr, winid_t *keywinptr); +extern winid_t glk_window_iterate(winid_t win, glui32 *rockptr); +extern glui32 glk_window_get_rock(winid_t win); +extern glui32 glk_window_get_type(winid_t win); +extern winid_t glk_window_get_parent(winid_t win); +extern winid_t glk_window_get_sibling(winid_t win); +extern void glk_window_clear(winid_t win); +extern void glk_window_move_cursor(winid_t win, glui32 xpos, glui32 ypos); + +extern strid_t glk_window_get_stream(winid_t win); +extern void glk_window_set_echo_stream(winid_t win, strid_t str); +extern strid_t glk_window_get_echo_stream(winid_t win); +extern void glk_set_window(winid_t win); + +extern strid_t glk_stream_open_file(frefid_t fileref, glui32 fmode, + glui32 rock); +extern strid_t glk_stream_open_memory(char *buf, glui32 buflen, glui32 fmode, + glui32 rock); +extern void glk_stream_close(strid_t str, stream_result_t *result); +extern strid_t glk_stream_iterate(strid_t str, glui32 *rockptr); +extern glui32 glk_stream_get_rock(strid_t str); +extern void glk_stream_set_position(strid_t str, glsi32 pos, glui32 seekmode); +extern glui32 glk_stream_get_position(strid_t str); +extern void glk_stream_set_current(strid_t str); +extern strid_t glk_stream_get_current(void); + +extern void glk_put_char(unsigned char ch); +extern void glk_put_char_stream(strid_t str, unsigned char ch); +extern void glk_put_string(char *s); +extern void glk_put_string_stream(strid_t str, char *s); +extern void glk_put_buffer(char *buf, glui32 len); +extern void glk_put_buffer_stream(strid_t str, char *buf, glui32 len); +extern void glk_set_style(glui32 styl); +extern void glk_set_style_stream(strid_t str, glui32 styl); + +extern glsi32 glk_get_char_stream(strid_t str); +extern glui32 glk_get_line_stream(strid_t str, char *buf, glui32 len); +extern glui32 glk_get_buffer_stream(strid_t str, char *buf, glui32 len); + +extern void glk_stylehint_set(glui32 wintype, glui32 styl, glui32 hint, + glsi32 val); +extern void glk_stylehint_clear(glui32 wintype, glui32 styl, glui32 hint); +extern glui32 glk_style_distinguish(winid_t win, glui32 styl1, glui32 styl2); +extern glui32 glk_style_measure(winid_t win, glui32 styl, glui32 hint, + glui32 *result); + +extern frefid_t glk_fileref_create_temp(glui32 usage, glui32 rock); +extern frefid_t glk_fileref_create_by_name(glui32 usage, char *name, + glui32 rock); +extern frefid_t glk_fileref_create_by_prompt(glui32 usage, glui32 fmode, + glui32 rock); +extern frefid_t glk_fileref_create_from_fileref(glui32 usage, frefid_t fref, + glui32 rock); +extern void glk_fileref_destroy(frefid_t fref); +extern frefid_t glk_fileref_iterate(frefid_t fref, glui32 *rockptr); +extern glui32 glk_fileref_get_rock(frefid_t fref); +extern void glk_fileref_delete_file(frefid_t fref); +extern glui32 glk_fileref_does_file_exist(frefid_t fref); + +extern void glk_select(event_t *event); +extern void glk_select_poll(event_t *event); + +extern void glk_request_timer_events(glui32 millisecs); + +extern void glk_request_line_event(winid_t win, char *buf, glui32 maxlen, + glui32 initlen); +extern void glk_request_char_event(winid_t win); +extern void glk_request_mouse_event(winid_t win); + +extern void glk_cancel_line_event(winid_t win, event_t *event); +extern void glk_cancel_char_event(winid_t win); +extern void glk_cancel_mouse_event(winid_t win); + +#ifdef GLK_MODULE_UNICODE + +extern glui32 glk_buffer_to_lower_case_uni(glui32 *buf, glui32 len, + glui32 numchars); +extern glui32 glk_buffer_to_upper_case_uni(glui32 *buf, glui32 len, + glui32 numchars); +extern glui32 glk_buffer_to_title_case_uni(glui32 *buf, glui32 len, + glui32 numchars, glui32 lowerrest); + +extern void glk_put_char_uni(glui32 ch); +extern void glk_put_string_uni(glui32 *s); +extern void glk_put_buffer_uni(glui32 *buf, glui32 len); +extern void glk_put_char_stream_uni(strid_t str, glui32 ch); +extern void glk_put_string_stream_uni(strid_t str, glui32 *s); +extern void glk_put_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); + +extern glsi32 glk_get_char_stream_uni(strid_t str); +extern glui32 glk_get_buffer_stream_uni(strid_t str, glui32 *buf, glui32 len); +extern glui32 glk_get_line_stream_uni(strid_t str, glui32 *buf, glui32 len); + +extern strid_t glk_stream_open_file_uni(frefid_t fileref, glui32 fmode, + glui32 rock); +extern strid_t glk_stream_open_memory_uni(glui32 *buf, glui32 buflen, + glui32 fmode, glui32 rock); + +extern void glk_request_char_event_uni(winid_t win); +extern void glk_request_line_event_uni(winid_t win, glui32 *buf, + glui32 maxlen, glui32 initlen); + +#endif /* GLK_MODULE_UNICODE */ + +#ifdef GLK_MODULE_IMAGE + +#define imagealign_InlineUp (0x01) +#define imagealign_InlineDown (0x02) +#define imagealign_InlineCenter (0x03) +#define imagealign_MarginLeft (0x04) +#define imagealign_MarginRight (0x05) + +extern glui32 glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2); +extern glui32 glk_image_draw_scaled(winid_t win, glui32 image, + glsi32 val1, glsi32 val2, glui32 width, glui32 height); +extern glui32 glk_image_get_info(glui32 image, glui32 *width, glui32 *height); + +extern void glk_window_flow_break(winid_t win); + +extern void glk_window_erase_rect(winid_t win, + glsi32 left, glsi32 top, glui32 width, glui32 height); +extern void glk_window_fill_rect(winid_t win, glui32 color, + glsi32 left, glsi32 top, glui32 width, glui32 height); +extern void glk_window_set_background_color(winid_t win, glui32 color); + +#endif /* GLK_MODULE_IMAGE */ + +#ifdef GLK_MODULE_SOUND + +extern schanid_t glk_schannel_create(glui32 rock); +extern void glk_schannel_destroy(schanid_t chan); +extern schanid_t glk_schannel_iterate(schanid_t chan, glui32 *rockptr); +extern glui32 glk_schannel_get_rock(schanid_t chan); + +extern glui32 glk_schannel_play(schanid_t chan, glui32 snd); +extern glui32 glk_schannel_play_ext(schanid_t chan, glui32 snd, glui32 repeats, + glui32 notify); +extern void glk_schannel_stop(schanid_t chan); +extern void glk_schannel_set_volume(schanid_t chan, glui32 vol); + +extern void glk_sound_load_hint(glui32 snd, glui32 flag); + +#endif /* GLK_MODULE_SOUND */ + +#ifdef GLK_MODULE_HYPERLINKS + +extern void glk_set_hyperlink(glui32 linkval); +extern void glk_set_hyperlink_stream(strid_t str, glui32 linkval); +extern void glk_request_hyperlink_event(winid_t win); +extern void glk_cancel_hyperlink_event(winid_t win); + +#endif /* GLK_MODULE_HYPERLINKS */ + +/* XXX non-official Glk functions that may or may not exist */ + +#define GARGLK 1 + +extern char* garglk_fileref_get_name(frefid_t fref); + +extern void garglk_set_program_name(const char *name); +extern void garglk_set_program_info(const char *info); +extern void garglk_set_story_name(const char *name); +extern void garglk_set_config(const char *name); + +/* not implemented */ + +#define garglk_font_Roman (0) +#define garglk_font_Italic (1) +#define garglk_font_Bold (2) +#define garglk_font_BoldItalic (3) +#define garglk_font_MonoRoman (4) +#define garglk_font_MonoItalic (5) +#define garglk_font_MonoBold (6) +#define garglk_font_MonoBoldItalic (7) + +#define garglk_color_White (0) +#define garglk_color_Red (1) +#define garglk_color_Green (2) +#define garglk_color_Blue (3) +#define garglk_color_Cyan (4) +#define garglk_color_Magenta (5) +#define garglk_color_Yellow (6) +#define garglk_color_Black (7) + +extern void garglk_set_style_font(glui32 font); +extern void garglk_set_style_stream_font(strid_t str, glui32 font); +extern void garglk_set_style_color(glui32 bg, glui32 fg); +extern void garglk_set_style_stream_color(strid_t str, glui32 bg, glui32 fg); + +/* JM: functions added to support Z-machine features that aren't in the Glk standard */ + +/* garglk_set_line_terminators - amends the current line input request to include terminating + * key codes. any of the specified key codes will terminate input (without printing a newline), + * and the key code will be returned in the event record as val2. */ +extern void garglk_set_line_terminators(winid_t win, const glui32 *keycodes, glui32 numkeycodes); + +/* garglk_unput_string - removes the specified string from the end of the output buffer, if + * indeed it is there. */ +extern void garglk_unput_string(char *str); +extern void garglk_unput_string_uni(glui32 *str); + +#define zcolor_Current (0) +#define zcolor_Default (1) +#define zcolor_Black (2) +#define zcolor_Red (3) +#define zcolor_Green (4) +#define zcolor_Yellow (5) +#define zcolor_Blue (6) +#define zcolor_Magenta (7) +#define zcolor_Cyan (8) +#define zcolor_White (9) +#define zcolor_LightGrey (10) +#define zcolor_MediumGrey (11) +#define zcolor_DarkGrey (12) +#define zcolor_NUMCOLORS (13) + +extern void garglk_set_zcolors(glui32 fg, glui32 bg); +extern void garglk_set_reversevideo(glui32 reverse); + +#endif /* GLK_H */ diff --git a/interpreters/nitfol/glkstart.h b/interpreters/nitfol/glkstart.h new file mode 100644 index 0000000..8835064 --- /dev/null +++ b/interpreters/nitfol/glkstart.h @@ -0,0 +1,70 @@ +/* glkstart.h: Unix-specific header file for GlkTerm, CheapGlk, and XGlk + (Unix implementations of the Glk API). + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html +*/ + +/* This header defines an interface that must be used by program linked + with the various Unix Glk libraries -- at least, the three I wrote. + (I encourage anyone writing a Unix Glk library to use this interface, + but it's not part of the Glk spec.) + + Because Glk is *almost* perfectly portable, this interface *almost* + doesn't have to exist. In practice, it's small. +*/ + +#ifndef GT_START_H +#define GT_START_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* We define our own TRUE and FALSE and NULL, because ANSI + is a strange world. */ +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef NULL +#define NULL 0 +#endif + +#define glkunix_arg_End (0) +#define glkunix_arg_ValueFollows (1) +#define glkunix_arg_NoValue (2) +#define glkunix_arg_ValueCanFollow (3) +#define glkunix_arg_NumberValue (4) + +typedef struct glkunix_argumentlist_struct { + char *name; + int argtype; + char *desc; +} glkunix_argumentlist_t; + +typedef struct glkunix_startup_struct { + int argc; + char **argv; +} glkunix_startup_t; + +/* The list of command-line arguments; this should be defined in your code. */ +extern glkunix_argumentlist_t glkunix_arguments[]; + +/* The external function; this should be defined in your code. */ +extern int glkunix_startup_code(glkunix_startup_t *data); + +/* Some helpful utility functions which the library makes available + to your code. Obviously, this is nonportable; so you should + only call it from glkunix_startup_code(). +*/ +extern strid_t glkunix_stream_open_pathname(char *pathname, glui32 textmode, + glui32 rock); + +#ifdef __cplusplus +} +#endif + +#endif /* GT_START_H */ + diff --git a/interpreters/nitfol/globals.c b/interpreters/nitfol/globals.c new file mode 100644 index 0000000..fa85d2b --- /dev/null +++ b/interpreters/nitfol/globals.c @@ -0,0 +1,119 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +strid_t current_zfile; +glui32 zfile_offset; + +strid_t input_stream1; + +strid_t blorb_file; +glui32 imagecount = 0; + +int using_infix; + +int zversion; +int granularity; +offset rstart; +offset sstart; + +/* Note that a lot of these are set in nitfol.opt, so changing the defaults + here only affects ports without initialization code */ + +const char *username; +BOOL aye_matey = FALSE; +BOOL do_tandy = FALSE; +BOOL do_spell_correct = TRUE; +BOOL do_expand = TRUE; +BOOL do_automap = TRUE; +BOOL fullname = FALSE; +BOOL quiet = TRUE; +BOOL ignore_errors = FALSE; +BOOL auto_save_undo = TRUE; +BOOL auto_save_undo_char = FALSE; + +int interp_num = 2; +char interp_ver = 'N'; + +zbyte *z_memory; + +offset PC; +offset oldPC; + +/* I would kind of like to make these local to the opcode, but doing so makes + a bagillion warning messages about unused operands... */ +int numoperands; +zword operand[16]; + +zword maxobjs; /* Maximum number of objects that could fit */ +zword object_count = 0; /* Objects before first property */ +zword obj_first_prop_addr = 0; /* Location of start of first property */ +zword obj_last_prop_addr = ZWORD_MASK; /* Location of start of last property */ +zword prop_table_start = 0; /* Beginning of property table */ +zword prop_table_end = ZWORD_MASK;/* End of property table */ + +offset total_size; +offset dynamic_size; +offset high_mem_mark; +offset game_size; + +zword z_checksum; /* calculated checksum, not header */ +zword z_globaltable; +zword z_objecttable; +zword z_propdefaults; +zword z_synonymtable; +zword z_dictionary; +zword z_terminators; +zword z_headerext; + +int faked_random_seed = 0; /* If nonzero, use this as a seed instead of time */ + +BOOL in_timer; /* True if we're inside a timer routine */ +BOOL exit_decoder; /* To let the decoder know we're done */ +zword time_ret; /* Get the return value back to the timer */ +BOOL smart_timed = TRUE; /* redraw the prompt */ +BOOL lower_block_quotes; /* put block quotes in lower window */ +BOOL read_abort; /* quickly stop reading */ +BOOL has_done_save_undo; /* the game has done a save_undo since last move */ +BOOL allow_saveundo = TRUE; /* Otherwise, ignore all @save_undo opcodes */ +BOOL allow_output = TRUE; /* Otherwise, ignore all output */ + +BOOL testing_string = FALSE; /* If we're uncertain this is really a string */ +BOOL string_bad = FALSE; /* If it turns out to not be a bad string */ + +BOOL do_check_watches = FALSE; /* Preventing check_watches from being + pointlessly called is a worthwhile speedup */ + +BOOL false_undo = FALSE; /* We just did a fake undo */ + +char *db_prompt; /* Defaults to "(nitfol) " */ +char *search_path; /* Path in which to look for games */ + +int automap_size = 12; +glui32 automap_split = winmethod_Above; + +int stacklimit = 0; + +BOOL enablefont3 = FALSE; /* Enables font3 -> ascii conversion. + Nitfol doesn't claim to support it + even if you set this flag. This + messes SameGame.z5 up, which won't + switch back to font1. */ + diff --git a/interpreters/nitfol/globals.h b/interpreters/nitfol/globals.h new file mode 100644 index 0000000..02ae270 --- /dev/null +++ b/interpreters/nitfol/globals.h @@ -0,0 +1,78 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i globals.c' */ +#ifndef CFH_GLOBALS_H +#define CFH_GLOBALS_H + +/* From `globals.c': */ +extern strid_t current_zfile; +extern glui32 zfile_offset; +extern strid_t input_stream1; +extern strid_t blorb_file; +extern glui32 imagecount; +extern int using_infix; +extern int zversion; +extern int granularity; +extern offset rstart; +extern offset sstart; +extern const char * username; +extern BOOL aye_matey; +extern BOOL do_tandy; +extern BOOL do_spell_correct; +extern BOOL do_expand; +extern BOOL do_automap; +extern BOOL fullname; +extern BOOL quiet; +extern BOOL ignore_errors; +extern BOOL auto_save_undo; +extern BOOL auto_save_undo_char; +extern int interp_num; +extern char interp_ver; +extern zbyte * z_memory; +extern offset PC; +extern offset oldPC; +extern int numoperands; +extern zword operand[]; +extern zword maxobjs; +extern zword object_count; +extern zword obj_first_prop_addr; +extern zword obj_last_prop_addr; +extern zword prop_table_start; +extern zword prop_table_end; +extern offset total_size; +extern offset dynamic_size; +extern offset high_mem_mark; +extern offset game_size; +extern zword z_checksum; +extern zword z_globaltable; +extern zword z_objecttable; +extern zword z_propdefaults; +extern zword z_synonymtable; +extern zword z_dictionary; +extern zword z_terminators; +extern zword z_headerext; +extern int faked_random_seed; +extern BOOL in_timer; +extern BOOL exit_decoder; +extern zword time_ret; +extern BOOL smart_timed; +extern BOOL lower_block_quotes; +extern BOOL read_abort; +extern BOOL has_done_save_undo; +extern BOOL allow_saveundo; +extern BOOL allow_output; +extern BOOL testing_string; +extern BOOL string_bad; +extern BOOL do_check_watches; +extern BOOL false_undo; +extern char * db_prompt; +extern char * search_path; +extern int automap_size; +extern glui32 automap_split; +extern int stacklimit; +extern BOOL enablefont3; + +#endif /* CFH_GLOBALS_H */ diff --git a/interpreters/nitfol/graphics.c b/interpreters/nitfol/graphics.c new file mode 100644 index 0000000..ce81364 --- /dev/null +++ b/interpreters/nitfol/graphics.c @@ -0,0 +1,38 @@ +#include "nitfol.h" + +/* A bunch of trivial wrappers so we can get away with ignoring whether + * during compilation whether or not the glk library supports graphics. + * + * Link this in only if you glk supports graphics */ + + +#ifdef GLK_MODULE_IMAGE + + +glui32 wrap_glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2) +{ + if(!glk_gestalt(gestalt_Graphics, 0)) + return FALSE; + return glk_image_draw(win, image, val1, val2); +} + + +glui32 wrap_glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2, glui32 width, glui32 height) +{ + if(!glk_gestalt(gestalt_Graphics, 0)) + return FALSE; + return glk_image_draw_scaled(win, image, val1, val2, width, height); +} + + +glui32 wrap_glk_image_get_info(glui32 image, glui32 *width, glui32 *height) +{ + if(!glk_gestalt(gestalt_Graphics, 0)) { + *width = 0; *height = 0; + return FALSE; + } + return glk_image_get_info(image, width, height); +} + + +#endif diff --git a/interpreters/nitfol/graphics.h b/interpreters/nitfol/graphics.h new file mode 100644 index 0000000..5cd8f54 --- /dev/null +++ b/interpreters/nitfol/graphics.h @@ -0,0 +1,19 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i graphics.c' */ +#ifndef CFH_GRAPHICS_H +#define CFH_GRAPHICS_H + +/* From `graphics.c': */ + +#ifdef GLK_MODULE_IMAGE +glui32 wrap_glk_image_draw (winid_t win , glui32 image , glsi32 val1 , glsi32 val2 ); +glui32 wrap_glk_image_draw_scaled (winid_t win , glui32 image , glsi32 val1 , glsi32 val2 , glui32 width , glui32 height ); +glui32 wrap_glk_image_get_info (glui32 image , glui32 *width , glui32 *height ); + +#endif + +#endif /* CFH_GRAPHICS_H */ diff --git a/interpreters/nitfol/hash.c b/interpreters/nitfol/hash.c new file mode 100644 index 0000000..55da1a7 --- /dev/null +++ b/interpreters/nitfol/hash.c @@ -0,0 +1,308 @@ +#include "nitfol.h" + + +/* Modified and bugfixed for nitfol by Evin Robertson Sept 10, 1999 */ + + +/* +** public domain code by Jerry Coffin, with improvements by HenkJan Wolthuis. +** +** Tested with Visual C 1.0 and Borland C 3.1. +** Compiles without warnings, and seems like it should be pretty +** portable. +*/ + + +#ifdef HEADER +/* +** A hash table consists of an array of these buckets. Each bucket +** holds a copy of the key, a pointer to the data associated with the +** key, and a pointer to the next bucket that collided with this one, +** if there was one. +*/ + +typedef struct bucket { + char *key; + void *data; + struct bucket *next; +} bucket; + +/* +** This is what you actually declare an instance of to create a table. +** You then call 'construct_table' with the address of this structure, +** and a guess at the size of the table. Note that more nodes than this +** can be inserted in the table, but performance degrades as this +** happens. Performance should still be quite adequate until 2 or 3 +** times as many nodes have been inserted as the table was created with. +*/ + +typedef struct hash_table { + size_t size; + bucket **table; +} hash_table; +#endif + + +/* +** These are used in freeing a table. Perhaps I should code up +** something a little less grungy, but it works, so what the heck. +*/ +typedef void (*my_function)(void *); +static my_function function = NULL; +static hash_table *the_table = NULL; + +/* Initialize the hash_table to the size asked for. Allocates space +** for the correct number of pointers and sets them to NULL. If it +** can't allocate sufficient memory, signals error by setting the size +** of the table to 0. +*/ + +hash_table *n_hash_construct_table(hash_table *table, size_t size) +{ + size_t i; + bucket **temp; + + table -> size = size; + table -> table = (bucket * *)n_malloc(sizeof(bucket *) * size); + temp = table -> table; + + if ( temp == NULL ) + { + table -> size = 0; + return table; + } + + for (i=0;isize; + bucket *ptr; + + /* + ** NULL means this bucket hasn't been used yet. We'll simply + ** allocate space for our new bucket and put our data there, with + ** the table pointing at it. + */ + + if (NULL == (table->table)[val]) + { + (table->table)[val] = (bucket *)n_malloc(sizeof(bucket)); + if (NULL==(table->table)[val]) + return NULL; + + (table->table)[val] -> key = n_strdup(key); + (table->table)[val] -> next = NULL; + (table->table)[val] -> data = data; + return (table->table)[val] -> data; + } + + /* + ** This spot in the table is already in use. See if the current string + ** has already been inserted, and if so, increment its count. + */ + + for (ptr = (table->table)[val];NULL != ptr; ptr = ptr -> next) + if (0 == n_strcmp(key, ptr->key)) + { + void *old_data; + + old_data = ptr->data; + ptr -> data = data; + return old_data; + } + + /* + ** This key must not be in the table yet. We'll add it to the head of + ** the list at this spot in the hash table. Speed would be + ** slightly improved if the list was kept sorted instead. In this case, + ** this code would be moved into the loop above, and the insertion would + ** take place as soon as it was determined that the present key in the + ** list was larger than this one. + */ + + ptr = (bucket *)n_malloc(sizeof(bucket)); + if (NULL==ptr) + return 0; + ptr -> key = n_strdup(key); + ptr -> data = data; + ptr -> next = (table->table)[val]; + (table->table)[val] = ptr; + return data; +} + + +/* +** Look up a key and return the associated data. Returns NULL if +** the key is not in the table. +*/ + +void *n_hash_lookup(const char *key, hash_table *table) +{ + unsigned val = hash(key) % table->size; + bucket *ptr; + + if (NULL == (table->table)[val]) + return NULL; + + for ( ptr = (table->table)[val];NULL != ptr; ptr = ptr->next ) + { + if (0 == n_strcmp(key, ptr -> key ) ) + return ptr->data; + } + return NULL; +} + +/* +** Delete a key from the hash table and return associated +** data, or NULL if not present. +*/ + +void *n_hash_del(const char *key, hash_table *table) +{ + unsigned val = hash(key) % table->size; + void *data; + bucket *ptr, *last = NULL; + + if (NULL == (table->table)[val]) + return NULL; + + /* + ** Traverse the list, keeping track of the previous node in the list. + ** When we find the node to delete, we set the previous node's next + ** pointer to point to the node after ourself instead. We then delete + ** the key from the present node, and return a pointer to the data it + ** contains. + */ + + for (last = NULL, ptr = (table->table)[val]; + NULL != ptr; + last = ptr, ptr = ptr->next) + { + if (0 == n_strcmp(key, ptr -> key)) + { + if (last != NULL ) + { + data = ptr -> data; + last -> next = ptr -> next; + n_free(ptr->key); + n_free(ptr); + return data; + } + + /* + ** If 'last' still equals NULL, it means that we need to + ** delete the first node in the list. This simply consists + ** of putting our own 'next' pointer in the array holding + ** the head of the list. We then dispose of the current + ** node as above. + */ + + else + { + data = ptr->data; + (table->table)[val] = ptr->next; + n_free(ptr->key); + n_free(ptr); + return data; + } + } + } + + /* + ** If we get here, it means we didn't find the item in the table. + ** Signal this by returning NULL. + */ + + return NULL; +} + +/* +** free_table iterates the table, calling this repeatedly to free +** each individual node. This, in turn, calls one or two other +** functions - one to free the storage used for the key, the other +** passes a pointer to the data back to a function defined by the user, +** process the data as needed. +*/ + +static void free_node(const char *key, void *data) +{ + (void) data; + + if (function) + function(n_hash_del(key,the_table)); + else n_hash_del(key,the_table); +} + +/* +** Frees a complete table by iterating over it and freeing each node. +** the second parameter is the address of a function it will call with a +** pointer to the data associated with each node. This function is +** responsible for freeing the data, or doing whatever is needed with +** it. +*/ + +void n_hash_free_table(hash_table *table, void (*func)(void *)) +{ + function = func; + the_table = table; + + n_hash_enumerate( table, free_node); + n_free(table->table); + table->table = NULL; + table->size = 0; + + the_table = NULL; + function = (void (*)(void *))NULL; +} + +/* +** Simply invokes the function given as the second parameter for each +** node in the table, passing it the key and the associated data. +*/ + +void n_hash_enumerate( hash_table *table, void (*func)(const char *, void *)) +{ + unsigned i; + bucket *temp, *next; + + for (i=0; i < table->size; i++) + { + for (temp = (table->table)[i]; temp; temp=next) + { + next = temp->next; /* ecr - temp may be deleted by func */ + func(temp->key, temp->data); + } + } +} diff --git a/interpreters/nitfol/hash.h b/interpreters/nitfol/hash.h new file mode 100644 index 0000000..10171a0 --- /dev/null +++ b/interpreters/nitfol/hash.h @@ -0,0 +1,29 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i hash.c' */ +#ifndef CFH_HASH_H +#define CFH_HASH_H + +/* From `hash.c': */ +typedef struct bucket { + char *key; + void *data; + struct bucket *next; +} +bucket; +typedef struct hash_table { + size_t size; + bucket **table; +} +hash_table; +hash_table * n_hash_construct_table (hash_table *table , size_t size ); +void * n_hash_insert (const char *key , void *data , hash_table *table ); +void * n_hash_lookup (const char *key , hash_table *table ); +void * n_hash_del (const char *key , hash_table *table ); +void n_hash_free_table (hash_table *table , void ( *func ) ( void * ) ); +void n_hash_enumerate (hash_table *table , void ( *func ) ( const char * , void * ) ); + +#endif /* CFH_HASH_H */ diff --git a/interpreters/nitfol/iff.c b/interpreters/nitfol/iff.c new file mode 100644 index 0000000..4fcfe41 --- /dev/null +++ b/interpreters/nitfol/iff.c @@ -0,0 +1,96 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +BOOL iffgetchunk(strid_t stream, char *desttype, glui32 *ulength, + glui32 file_size) +{ + int i; + glui32 c; + unsigned char length[4]; + + c = glk_stream_get_position(stream); + if(c & 1) { + glk_get_char_stream(stream); /* Eat padding */ + c++; + } + + if(glk_get_buffer_stream(stream, desttype, 4) != 4) + return FALSE; + if(glk_get_buffer_stream(stream, (char *) length, 4) != 4) + return FALSE; + + *ulength = MSBdecode4(length); + + for(i = 0; i < 4; i++) + if(desttype[i] < 0x20 || desttype[i] > 0x7e) + return FALSE; + + c += 8; + + return ((c + *ulength) <= file_size); +} + + +BOOL ifffindchunk(strid_t stream, const char *type, glui32 *length, glui32 loc) +{ + char t[4]; + glui32 file_size; + + glk_stream_set_position(stream, 0, seekmode_End); + file_size = glk_stream_get_position(stream); + + glk_stream_set_position(stream, loc, seekmode_Start); + *length = 0; + do { + glk_stream_set_position(stream, *length, seekmode_Current); + if(!iffgetchunk(stream, t, length, file_size)) + return FALSE; + } while((t[0] != type[0]) || (t[1] != type[1]) || + (t[2] != type[2]) || (t[3] != type[3])); + + return TRUE; +} + + +void iffputchunk(strid_t stream, const char *type, glui32 ulength) +{ + glui32 c; + unsigned char length[4]; + + c = glk_stream_get_position(stream); + if(c & 1) + glk_put_char_stream(stream, 0); /* Spew padding */ + + MSBencode4(length, ulength); + + w_glk_put_buffer_stream(stream, type, 4); + w_glk_put_buffer_stream(stream, (char *) length, 4); +} + + +void iffpadend(strid_t stream) +{ + glui32 c; + + c = glk_stream_get_position(stream); + if(c & 1) + glk_put_char_stream(stream, 0); /* Spew padding */ +} diff --git a/interpreters/nitfol/iff.h b/interpreters/nitfol/iff.h new file mode 100644 index 0000000..e2eaa91 --- /dev/null +++ b/interpreters/nitfol/iff.h @@ -0,0 +1,16 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i iff.c' */ +#ifndef CFH_IFF_H +#define CFH_IFF_H + +/* From `iff.c': */ +BOOL iffgetchunk (strid_t stream , char *desttype , glui32 *ulength , glui32 file_size ); +BOOL ifffindchunk (strid_t stream , const char *type , glui32 *length , glui32 loc ); +void iffputchunk (strid_t stream , const char *type , glui32 ulength ); +void iffpadend (strid_t stream ); + +#endif /* CFH_IFF_H */ diff --git a/interpreters/nitfol/infix.c b/interpreters/nitfol/infix.c new file mode 100644 index 0000000..47b208d --- /dev/null +++ b/interpreters/nitfol/infix.c @@ -0,0 +1,1170 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" +#ifdef STDOUT_DEBUG +#include +#endif + + +#ifdef DEBUGGING + + +#ifdef HEADER + +typedef enum { Z_UNKNOWN, Z_BOOLEAN, Z_NUMBER, Z_OBJECT, Z_ROUTINE, Z_STRING, Z_GLOBAL, Z_LOCAL, Z_BYTEARRAY, Z_WORDARRAY, Z_OBJPROP, Z_ATTR, Z_PROP, Z_ARRAY } z_type; + +typedef struct z_typed z_typed; +struct z_typed { + zword v; /* The value stored */ + z_type t; + + zword o, p; /* location of value (if t is appropriate) */ +}; + + +typedef struct { + const char *filename; + strid_t stream; + int num_lines; + glui32 *line_locations; +} infix_file; + +typedef struct { + infix_file *file; + int line_num; + int line_x; + const char *func_name; + unsigned func_num; + offset thisPC; +} infix_location; + +#endif + + +#define EOF_DBR 0 +#define FILE_DBR 1 +#define CLASS_DBR 2 +#define OBJECT_DBR 3 +#define GLOBAL_DBR 4 +#define ATTR_DBR 5 +#define PROP_DBR 6 +#define FAKE_ACTION_DBR 7 +#define ACTION_DBR 8 +#define HEADER_DBR 9 +#define LINEREF_DBR 10 +#define ROUTINE_DBR 11 +#define ARRAY_DBR 12 +#define MAP_DBR 13 +#define ROUTINE_END_DBR 14 +#define MAX_DBR 14 + +static unsigned record_info[][9] = { + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* eof_dbr */ + { 1, 0x8000, 0x8000, 0, 0, 0, 0, 0, 0 }, /* file_dbr */ + { 0x8000, 1, 2, 1, 1, 2, 1, 0, 0 }, /* class_dbr */ + { 2, 0x8000, 1, 2, 1, 1, 2, 1, 0 }, /* object_dbr */ + { 1, 0x8000, 0, 0, 0, 0, 0, 0, 0 }, /* global_dbr */ + { 2, 0x8000, 0, 0, 0, 0, 0, 0, 0 }, /* attr_dbr */ + { 2, 0x8000, 0, 0, 0, 0, 0, 0, 0 }, /* prop_dbr */ + { 2, 0x8000, 0, 0, 0, 0, 0, 0, 0 }, /* fake_action_dbr */ + { 2, 0x8000, 0, 0, 0, 0, 0, 0, 0 }, /* action_dbr */ + { 64, 0, 0, 0, 0, 0, 0, 0, 0 }, /* header_dbr */ + { 2, 2, 0, 0, 0, 0, 0, 0, 0 }, /* lineref_dbr */ + { 2, 1, 2, 1, 3, 0x8000, 0, 0, 0 }, /* routine_dbr */ + { 2, 0x8000, 0, 0, 0, 0, 0, 0, 0 }, /* array_dbr */ + { 0, 0, 0, 0, 0, 0, 0, 0, 0 }, /* map_dbr */ + { 2, 1, 2, 1, 3, 0, 0, 0, 0 } /* routine_end_dbr */ +}; + + +offset infix_get_routine_PC(zword routine) +{ + offset destPC = UNPACKR(routine); + unsigned num_local = HIBYTE(destPC); + destPC++; + if(zversion < 5) + destPC += num_local * ZWORD_SIZE; + return destPC; +} + + +static infix_file infix_load_file_info(const char *filename) +{ + infix_file r; + glsi32 c; + int lines_allocated = 0; + + r.filename = filename; + r.num_lines = 0; + r.line_locations = NULL; + + r.stream = n_file_name(fileusage_Data | fileusage_TextMode, + filemode_Read, filename); + + if(!r.stream) + return r; + + lines_allocated = 256; + r.line_locations = (glui32 *) n_malloc(lines_allocated * sizeof(*r.line_locations)); + r.line_locations[r.num_lines] = 0; + r.num_lines++; + + while((c = glk_get_char_stream(r.stream)) != GLK_EOF) { + if(c == 10) { + if(r.num_lines >= lines_allocated) { + glui32 *new_locations; + + lines_allocated *= 2; + new_locations = (glui32 *) n_realloc(r.line_locations, + lines_allocated * sizeof(*r.line_locations)); + r.line_locations = new_locations; + } + r.line_locations[r.num_lines] = glk_stream_get_position(r.stream); + r.num_lines++; + } + } + return r; +} + + +static void infix_unload_file_info(infix_file *f) +{ + if(f->line_locations) + n_free(f->line_locations); + f->line_locations = 0; + return; +} + + +void infix_file_print_line(infix_file *f, int line) +{ + glsi32 c; + + if(!(f && f->stream && f->num_lines && f->line_locations)) + return; + + if(line <= 0) + line = 1; + if(line > f->num_lines) + line = f->num_lines; + + if(fullname) { /* Running as a subprocess under emacs */ + infix_print_char(26); + infix_print_char(26); + infix_print_string(f->filename); + infix_print_char(':'); + infix_print_number(line); + infix_print_char(':'); + infix_print_number(f->line_locations[line-1] + 0); + infix_print_string(":beg:0x00000000"); + } else { + infix_print_number(line); + infix_print_char('\t'); + + glk_stream_set_position(f->stream, f->line_locations[line-1], seekmode_Start); + + while((c = glk_get_char_stream(f->stream)) != GLK_EOF) { + if(c == 10) + break; + infix_print_char(c); + } + } + + infix_print_char(10); +} + + +static unsigned local_names_info[] = { 0x8000, 0 }; +static unsigned sequence_point_info[] ={ 1, 2, 1, 2, 0 }; +static unsigned map_info[] = { 0x8000, 3, 0 }; + +static glui32 infix_strlen; +static char *infix_stringdata; + +static glui32 infix_add_string(strid_t infix) +{ + glui32 ch; + glui32 return_offset = infix_strlen; + + while((ch = glk_get_char_stream(infix)) != 0) { + if(infix_stringdata) + infix_stringdata[infix_strlen] = ch; + infix_strlen++; + } + + if(infix_stringdata) + infix_stringdata[infix_strlen] = 0; + infix_strlen++; + + return return_offset; +} + +static void infix_add_stringchar(int c) +{ + /* Really inefficient to call realloc for every character, but oh well... */ + infix_stringdata = n_realloc(infix_stringdata, infix_strlen+1); + infix_stringdata[infix_strlen++] = c; +} + +static glui32 infix_add_zstring(zword paddr) +{ + if(paddr) { + glui32 return_offset = infix_strlen; + offset addr = UNPACKS(paddr); + if(addr+2 < total_size) { + decodezscii(addr, infix_add_stringchar); + infix_add_stringchar(0); + return return_offset; + } + } + return 0xffffffffL; +} + + +static infix_file *infix_files; +static unsigned infix_filescount; + +static char **infix_objects; +static unsigned infix_objectscount; + +static char **infix_globals; +static unsigned infix_globalscount; + +typedef struct { + zword address; + const char *name; +} infix_arrayref; + +static infix_arrayref *infix_arrays; +static unsigned infix_arrayscount; + +static char **infix_attrs; +static unsigned infix_attrscount; + +static char **infix_props; +static unsigned infix_propscount; + +typedef struct { + const char *name; + unsigned filenum; + unsigned startline; + unsigned start_x; + offset start_PC; + const char *localnames[15]; + unsigned end_line; + unsigned end_x; + offset end_PC; +} routineref; + +static routineref *infix_routines; +static unsigned infix_routinescount; + +typedef struct { + unsigned routine; + unsigned filenum; + unsigned line; + unsigned x; + offset PC; +} infix_sequence; + +static infix_sequence *infix_linerefs; +static unsigned infix_linerefscount; + + +static offset code_start = 0; +static offset string_start = 0; + +static int infix_compare_linerefs(const void *a, const void *b) +{ + const infix_sequence *A = (const infix_sequence *) a; + const infix_sequence *B = (const infix_sequence *) b; + if(A->PC < B->PC) + return -1; + if(A->PC > B->PC) + return 1; + + if(A->line < B->line) + return -1; + if(A->line > B->line) + return 1; + return 0; +} + +static BOOL infix_load_records(strid_t infix) +{ + unsigned n; + + for(;;) { + glui32 record_type = glk_get_char_stream(infix); + glui32 record_data[64]; + glui32 more_data[4]; + if(record_type > MAX_DBR) { + glk_stream_close(infix, NULL); + n_show_error(E_DEBUG, "unknown record type", record_type); + return FALSE; + } + + fillstruct(infix, record_info[record_type], record_data, infix_add_string); + + switch(record_type) { + case EOF_DBR: + if(infix_linerefs && infix_routines && code_start) { + for(n = 0; n < infix_routinescount; n++) { + infix_routines[n].start_PC += code_start; + infix_routines[n].end_PC += code_start; + } + + n_qsort(infix_linerefs, infix_linerefscount, sizeof(*infix_linerefs), + infix_compare_linerefs); + + for(n = 0; n < infix_linerefscount; n++) + infix_linerefs[n].PC += code_start; + } + return TRUE; + case FILE_DBR: + if(infix_files) { + infix_files[record_data[0]] = infix_load_file_info(infix_stringdata + record_data[2]); + } + if(record_data[0] >= infix_filescount) + infix_filescount = record_data[0] + 1; + break; + case CLASS_DBR: + break; + case OBJECT_DBR: + if(infix_objects) { + infix_objects[record_data[0]] = infix_stringdata + record_data[1]; + } + if(record_data[0] >= infix_objectscount) + infix_objectscount = record_data[0] + 1; + break; + case GLOBAL_DBR: + if(infix_globals) { + infix_globals[record_data[0]] = infix_stringdata + record_data[1]; + } + if(record_data[0] >= infix_globalscount) + infix_globalscount = record_data[0] + 1; + break; + case ARRAY_DBR: + if(infix_arrays) { + } + infix_arrayscount++; + break; + case ATTR_DBR: + if(infix_attrs) { + infix_attrs[record_data[0]] = infix_stringdata + record_data[1]; + } + if(record_data[0] >= infix_attrscount) + infix_attrscount = record_data[0] + 1; + break; + case PROP_DBR: + if(infix_props) { + infix_props[record_data[0]] = infix_stringdata + record_data[1]; + } + if(record_data[0] >= infix_propscount) + infix_propscount = record_data[0] + 1; + break; + case FAKE_ACTION_DBR: + break; + case ACTION_DBR: + break; + case HEADER_DBR: + glk_stream_set_position(current_zfile, 0, seekmode_Start); + for(n = 0; n < 64; n++) { + unsigned c = (unsigned char) glk_get_char_stream(current_zfile); + if(record_data[n] != c) { + n_show_error(E_DEBUG, "infix file does not match current file", n); + return FALSE; + } + } + break; + case ROUTINE_DBR: + if(infix_routines) { + infix_routines[record_data[0]].filenum = record_data[1]; + infix_routines[record_data[0]].startline = record_data[2]; + infix_routines[record_data[0]].start_x = record_data[3]; + infix_routines[record_data[0]].start_PC = record_data[4]; + infix_routines[record_data[0]].name = infix_stringdata + + record_data[5]; + } + + if(infix_routines) + for(n = 0; n < 15; n++) + infix_routines[record_data[0]].localnames[n] = NULL; + + for(n = 0; n < 16; n++) { + if(!glk_get_char_stream(infix)) + break; + if(n == 15) + return FALSE; + glk_stream_set_position(infix, -1, seekmode_Current); + fillstruct(infix, local_names_info, more_data, infix_add_string); + if(infix_routines) { + infix_routines[record_data[0]].localnames[n] = infix_stringdata + + more_data[0]; + } + } + if(record_data[0] >= infix_routinescount) + infix_routinescount = record_data[0] + 1; + break; + case LINEREF_DBR: + for(n = 0; n < record_data[1]; n++) { + fillstruct(infix, sequence_point_info, more_data, NULL); + if(infix_linerefs) { + infix_linerefs[infix_linerefscount].routine = record_data[0]; + infix_linerefs[infix_linerefscount].filenum = more_data[0]; + infix_linerefs[infix_linerefscount].line = more_data[1]; + infix_linerefs[infix_linerefscount].x = more_data[2]; + infix_linerefs[infix_linerefscount].PC = more_data[3] + + infix_routines[record_data[0]].start_PC; + } + infix_linerefscount++; + } + break; + case ROUTINE_END_DBR: + if(infix_routines) { + infix_routines[record_data[0]].end_line = record_data[2]; + infix_routines[record_data[0]].end_x = record_data[3]; + infix_routines[record_data[0]].end_PC = record_data[4]; + } + if(record_data[0] >= infix_routinescount) + infix_routinescount = record_data[0] + 1; + break; + case MAP_DBR: + for(;;) { + if(!glk_get_char_stream(infix)) + break; + glk_stream_set_position(infix, -1, seekmode_Current); + fillstruct(infix, map_info, more_data, infix_add_string); + if(infix_stringdata) { + if(n_strcmp(infix_stringdata + more_data[0], "code area") == 0) + code_start = more_data[1]; + if(n_strcmp(infix_stringdata + more_data[0], "strings area") == 0) + string_start = more_data[1]; + } + } + break; + } + } +} + + +BOOL init_infix(strid_t infix) +{ + if(!infix && (infix_props || infix_attrs)) + return FALSE; + + kill_infix(); + + /* Inform 6.10+ has run-time symbols (techman 9.6) */ + if(!infix && z_memory && prop_table_end + && (z_memory[HD_INFORMVER] == '6' && z_memory[HD_INFORMVER + 2] >= '1')) { + zword addr; + unsigned i; + glui32 *prop_names, *attr_names; + + /* Assume no strings before end of property table */ + string_start = prop_table_end; + + for(addr = prop_table_end+1; LOWORD(addr); addr+=ZWORD_SIZE) + ; + addr+=ZWORD_SIZE; + + infix_propscount = LOWORD(addr) - 1; addr+=ZWORD_SIZE; + prop_names = (glui32 *) n_calloc(sizeof(*prop_names), infix_propscount+1); + for(i = 1; i <= infix_propscount; i++) { + prop_names[i] = infix_add_zstring(LOWORD(addr)); + addr += ZWORD_SIZE; + } + + infix_attrscount = 48; + attr_names = (glui32 *) n_calloc(sizeof(*prop_names), infix_propscount); + for(i = 0; i <= 47; i++) { + attr_names[i] = infix_add_zstring(LOWORD(addr)); + addr += ZWORD_SIZE; + } + + infix_props = (char **) n_calloc(sizeof(*infix_props), infix_propscount+1); + infix_attrs = (char **) n_calloc(sizeof(*infix_attrs), infix_attrscount); + for(i = 1; i <= infix_propscount; i++) { + if(prop_names[i] != 0xffffffffL) + infix_props[i] = infix_stringdata + prop_names[i]; + } + for(i = 0; i <= 47; i++) { + if(attr_names[i] != 0xffffffffL) + infix_attrs[i] = infix_stringdata + attr_names[i]; + } + + n_free(prop_names); + n_free(attr_names); + + return TRUE; + } + + if(!infix) + return FALSE; + + glk_stream_set_position(infix, 0, seekmode_Start); + if((glk_get_char_stream(infix) != 0xDE) || + (glk_get_char_stream(infix) != 0xBF) || + (glk_get_char_stream(infix) != 0x00) || + (glk_get_char_stream(infix) != 0x00)) { + glk_stream_close(infix, NULL); + n_show_error(E_DEBUG, "unknown version or not an infix file", 0); + return FALSE; + } + + /* ignore inform version number */ + glk_stream_set_position(infix, 6, seekmode_Start); + + /* Calculate space requirements */ + infix_load_records(infix); + + /* Malloc required memory */ + infix_stringdata = (char *) n_calloc(sizeof(*infix_stringdata), + infix_strlen); + infix_strlen = 0; + infix_files = (infix_file *) n_calloc(sizeof(*infix_files), + infix_filescount); + infix_filescount = 0; + infix_objects = (char **) n_calloc(sizeof(*infix_objects), + infix_objectscount); + infix_objectscount = 0; + infix_globals = (char **) n_calloc(sizeof(*infix_globals), + infix_globalscount); + infix_globalscount = 0; + infix_attrs = (char **) n_calloc(sizeof(*infix_attrs), + infix_attrscount); + infix_attrscount = 0; + infix_props = (char **) n_calloc(sizeof(*infix_props), + infix_propscount); + infix_propscount = 0; + infix_routines = (routineref *) n_calloc(sizeof(*infix_routines), + infix_routinescount); + infix_routinescount= 0; + infix_linerefs = (infix_sequence *) n_calloc(sizeof(*infix_linerefs), + infix_linerefscount); + infix_linerefscount= 0; + + glk_stream_set_position(infix, 6, seekmode_Start); + infix_load_records(infix); + + return TRUE; +} + + +void kill_infix(void) +{ + unsigned i; + + n_free(infix_stringdata); + infix_stringdata = 0; + infix_strlen = 0; + + if(infix_files) { + for(i = 0; i < infix_filescount; i++) + infix_unload_file_info(&infix_files[i]); + n_free(infix_files); + } + infix_files = 0; + infix_filescount = 0; + + n_free(infix_objects); + infix_objects = 0; + infix_objectscount = 0; + + n_free(infix_globals); + infix_globals = 0; + infix_globalscount = 0; + + n_free(infix_attrs); + infix_attrs = 0; + infix_attrscount = 0; + + n_free(infix_props); + infix_props = 0; + infix_propscount = 0; + + n_free(infix_routines); + infix_routines = 0; + infix_routinescount = 0; + + n_free(infix_linerefs); + infix_linerefs = 0; + infix_linerefscount = 0; +} + + +void infix_print_znumber(zword blah) +{ + set_glk_stream_current(); + g_print_znumber(blah); +} + +void infix_print_offset(zword blah) +{ + set_glk_stream_current(); + g_print_number(blah); +} + +void infix_print_number(zword blah) +{ + set_glk_stream_current(); + g_print_number(blah); +} + +void infix_print_char(int blah) +{ + if(blah == 13) + blah = 10; +#ifdef STDOUT_DEBUG + fputc(blah, stdout); +#else + set_glk_stream_current(); + /* We don't do a style-set because of bugs in zarf Glks */ + glk_put_char((unsigned char) blah); +#endif +} + +void infix_print_fixed_char(int blah) +{ + if(blah == 13) + blah = 10; +#ifdef STDOUT_DEBUG + fputc(blah, stdout); +#else + set_glk_stream_current(); + glk_set_style(style_Preformatted); + glk_put_char((unsigned char) blah); + glk_set_style(style_Normal); +#endif +} + +void infix_print_string(const char *blah) +{ + while(*blah) + infix_print_char(*blah++); +} + +void infix_print_fixed_string(const char *blah) +{ + while(*blah) + infix_print_fixed_char(*blah++); +} + +void infix_get_string(char *dest, int maxlen) +{ +#ifdef STDOUT_DEBUG + fgets(dest, maxlen, stdin); +#else + unsigned char t; + int len; + len = z_read(0, dest, maxlen - 1, 0, 0, 0, 0, &t); + dest[len] = 0; +#endif +} + +void infix_get_val(z_typed *val) +{ + switch(val->t) { + case Z_LOCAL: val->v = frame_get_var(val->o, val->p + 1); break; + case Z_GLOBAL: val->v = get_var(val->o + 0x10); break; + case Z_BYTEARRAY: val->v = LOBYTE(val->o + val->p); break; + case Z_WORDARRAY: val->v = LOWORD(val->o + ZWORD_SIZE * val->p); break; + case Z_OBJPROP: val->v = infix_get_prop(val->o, val->p); break; + default: ; + } +} + +void infix_assign(z_typed *dest, zword val) +{ + switch(dest->t) { + case Z_LOCAL: frame_set_var(dest->o, dest->p + 1, val); break; + case Z_BYTEARRAY: LOBYTEwrite(dest->o + dest->p, val); break; + case Z_WORDARRAY: LOWORDwrite(dest->o + ZWORD_SIZE * dest->p, val); break; + case Z_OBJPROP: infix_put_prop(dest->o, dest->p, val); break; + default: infix_print_string("assigning to non-lvalue\n"); break; + } + dest->v = val; +} + +void infix_display(z_typed val) +{ + unsigned n, i; + const char *name; + + switch(val.t) { + default: + infix_print_znumber(val.v); + break; + + case Z_BOOLEAN: + if(val.v) + infix_print_string("true"); + else + infix_print_string("false"); + break; + + case Z_UNKNOWN: + val.t = Z_NUMBER; + infix_display(val); + + name = debug_decode_number(val.v); + + if(name) { + infix_print_char(' '); + infix_print_char('('); + infix_print_string(name); + infix_print_char(')'); + } + break; + + case Z_OBJECT: + infix_object_display(val.v); + break; + + case Z_STRING: + infix_print_char('\"'); + decodezscii(UNPACKS(val.v), infix_print_char); + infix_print_char('\"'); + break; + + case Z_ROUTINE: + if(!infix_routines) + return; + + for(i = 0; i < infix_routinescount; i++) { + if(infix_routines[i].start_PC == UNPACKR(val.v)) { + + infix_print_char('{'); + for(n = 0; n < 15; n++) { + if(infix_routines[i].localnames[n]) { + infix_print_string(infix_routines[i].localnames[n]); + if(n < 14 && infix_routines[i].localnames[n+1]) + infix_print_string(", "); + } + } + infix_print_string("} "); + + infix_print_offset(infix_routines[i].start_PC); + infix_print_string(" <"); + infix_print_string(infix_routines[i].name); + infix_print_char('>'); + break; + } + } + } + infix_print_char('\n'); +} + + +int infix_find_file(infix_file **dest, const char *name) +{ + unsigned n; + if(infix_files) { + for(n = 0; n < infix_filescount; n++) { + if(infix_files[n].filename) { + unsigned len = n_strlen(infix_files[n].filename); + if(len && + n_strncasecmp(infix_files[n].filename, name, len) == 0 && + (name[len] == ' ' || name[len] == ':' || name[len] == 0)) { + *dest = &infix_files[n]; + return len; + } + } + } + } + return 0; +} + + +BOOL infix_find_symbol(z_typed *val, const char *name, int len) +{ + unsigned n; + if(infix_routines) { + infix_location location; + if(infix_decode_PC(&location, frame_get_PC(infix_selected_frame))) { + for(n = 0; n < 15; n++) { + if(n_strmatch(infix_routines[location.func_num].localnames[n], name, len)) { + val->t = Z_LOCAL; + val->o = infix_selected_frame; + val->p = n; + infix_get_val(val); + return TRUE; + } + } + } + } + if(infix_objects) + for(n = 0; n < infix_objectscount; n++) { + if(n_strmatch(infix_objects[n], name, len)) { + val->t = Z_OBJECT; + val->v = n; + return TRUE; + } + } + if(infix_globals) + for(n = 0; n < infix_globalscount; n++) { + if(n_strmatch(infix_globals[n], name, len)) { + val->t = Z_WORDARRAY; + val->o = z_globaltable; + val->p = n; + infix_get_val(val); + return TRUE; + } + } + if(infix_arrays) + for(n = 0; n < infix_arrayscount; n++) { + if(n_strmatch(infix_arrays[n].name, name, len)) { + val->t = Z_NUMBER; + val->v = n; + return TRUE; + } + } + if(infix_attrs) + for(n = 0; n < infix_attrscount; n++) { + if(n_strmatch(infix_attrs[n], name, len)) { + val->t = Z_NUMBER; + val->v = n; + return TRUE; + } + } + if(infix_props) + for(n = 0; n < infix_propscount; n++) { + if(n_strmatch(infix_props[n], name, len)) { + val->t = Z_NUMBER; + val->v = n; + return TRUE; + } + } + if(infix_routines) + for(n = 0; n < infix_routinescount; n++) { + if(n_strmatch(infix_routines[n].name, name, len)) { + val->t = Z_ROUTINE; + val->v = PACKR(infix_routines[n].start_PC); + return TRUE; + } + } + return FALSE; +} + +static char infix_temp_string_buffer[45]; +static unsigned infix_temp_strlen; + +static void infix_temp_string_build(int ch) +{ + infix_temp_string_buffer[infix_temp_strlen] = ch; + infix_temp_strlen++; + if(infix_temp_strlen > 40) { + infix_temp_strlen = 43; + infix_temp_string_buffer[40] = '.'; + infix_temp_string_buffer[41] = '.'; + infix_temp_string_buffer[42] = '.'; + } +} + + +const char *infix_get_name(z_typed val) +{ + unsigned i; + if(!infix_stringdata) + return NULL; + switch(val.t) { + case Z_GLOBAL: + if(val.o < infix_globalscount) + return infix_globals[val.o]; + break; + case Z_OBJECT: + if(val.v < infix_objectscount) + return infix_objects[val.v]; + break; + case Z_ATTR: + if(val.v < infix_attrscount) + return infix_attrs[val.v]; + break; + case Z_PROP: + if(val.v < infix_propscount) + return infix_props[val.v]; + break; + case Z_ROUTINE: + for(i = 0; i < infix_routinescount; i++) { + if(infix_routines[i].start_PC == UNPACKR(val.v)) + return infix_routines[i].name; + } + break; + case Z_STRING: + if(UNPACKS(val.v) < string_start) + break; + if(string_start < code_start && UNPACKS(val.v) >= code_start) + break; + if(UNPACKS(val.v) >= total_size) + break; + + /* Assume every string except the first is preceded by zeroed padding until + an end-of-string marker */ + if(UNPACKS(val.v) != string_start) { + offset addr = UNPACKS(val.v) - 2; + zword s; + while((s = HIWORD(addr)) == 0) + addr -= 2; + if(!(s & 0x8000)) + break; + } + + infix_temp_string_buffer[0] = '\"'; + infix_temp_strlen = 1; + testing_string = TRUE; string_bad = FALSE; + decodezscii(UNPACKS(val.v), infix_temp_string_build); + testing_string = FALSE; + if(string_bad) + break; + infix_temp_string_buffer[infix_temp_strlen] = '\"'; + infix_temp_string_buffer[infix_temp_strlen + 1] = 0; + return infix_temp_string_buffer; + case Z_ARRAY: + if(val.v < infix_arrayscount) + return infix_arrays[val.v].name; + break; + + default: ; + } + return NULL; +} + + +/* We search through linerefs very often so use binary search for speed */ + +static infix_sequence *infix_search_linerefs(offset thisPC) +{ + unsigned n; + int top = 0; + int bottom = infix_linerefscount-1; + int middle = (top + bottom) / 2; + + if(!infix_linerefs) + return NULL; + + do { + middle = (top + bottom) / 2; + if(thisPC < infix_linerefs[middle].PC) + bottom = middle - 1; + else if(thisPC > infix_linerefs[middle].PC) + top = middle + 1; + else + break; + } while(top <= bottom); + + /* If the PC is in the middle of a line, we want to display that line. In + this case, PC will be greater than infix_linerefs[middle].PC, so just let + it go. Otherwise, we want to look at the previous lineref, so subtract + one (or more) + */ + + while(middle && thisPC < infix_linerefs[middle].PC) + middle--; + + + /* Make sure PC is inside the function the lineref says it is; if so, then + we're done */ + + n = infix_linerefs[middle].routine; + if(thisPC >= infix_routines[n].start_PC && + thisPC <= infix_routines[n].end_PC) + return &infix_linerefs[middle]; + + return NULL; +} + + +BOOL infix_decode_PC(infix_location *dest, offset thisPC) +{ + infix_sequence *n = infix_search_linerefs(thisPC); + + if(!n) { /* No sequence point found - return a function */ + unsigned i; + + if(!infix_routines) + return FALSE; + + for(i = 0; i < infix_routinescount; i++) { + if(thisPC >= infix_routines[i].start_PC && + thisPC <= infix_routines[i].end_PC) { + + routineref *r = &infix_routines[i]; + dest->file = &infix_files[r->filenum]; + dest->line_num = r->startline; + dest->line_x = r->start_x; + dest->func_name = r->name; + dest->func_num = i; + dest->thisPC = r->start_PC; + + return TRUE; + } + } + + /* Not in a function. Give up. */ + return FALSE; + } + + + dest->file = &infix_files[n->filenum]; + dest->line_num = n->line; + dest->line_x = n->x; + dest->func_name = infix_routines[n->routine].name; + dest->func_num = n->routine; + dest->thisPC = n->PC; + + return TRUE; +} + + +BOOL infix_decode_fileloc(infix_location *dest, const char *filename, + unsigned line_num) +{ + unsigned n; + if(!infix_linerefs) + return FALSE; + for(n = 0; n < infix_linerefscount; n++) { + if(infix_linerefs[n].line == line_num && + n_strcmp(infix_files[infix_linerefs[n].filenum].filename, filename) == 0) { + + dest->file = &infix_files[infix_linerefs[n].filenum]; + dest->line_num = infix_linerefs[n].line; + dest->line_x = infix_linerefs[n].x; + dest->func_name = infix_routines[infix_linerefs[n].routine].name; + dest->func_num = infix_linerefs[n].routine; + dest->thisPC = infix_linerefs[n].PC; + return TRUE; + } + } + dest->thisPC = 0; + return FALSE; +} + + +BOOL infix_decode_func_name(infix_location *dest, const char *file_name, + const char *func_name) +{ + unsigned n; + if(!infix_linerefs) + return FALSE; + for(n = 0; n < infix_linerefscount; n++) { + if(n_strcmp(infix_files[infix_linerefs[n].filenum].filename, file_name) == 0) { + if(!file_name || n_strcmp(infix_routines[infix_linerefs[n].filenum].name, + func_name) == 0) { + + dest->file = &infix_files[infix_linerefs[n].filenum]; + dest->line_num = infix_linerefs[n].line; + dest->line_x = infix_linerefs[n].x; + dest->func_name = infix_routines[infix_linerefs[n].routine].name; + dest->func_num = infix_linerefs[n].routine; + dest->thisPC = infix_linerefs[n].PC; + return TRUE; + } + } + } + return FALSE; +} + + +void infix_gprint_loc(int frame, offset thisPC) +{ + infix_location boo; + offset loc; + BOOL found_frame; + unsigned numlocals; + unsigned n; + + if(!thisPC) { + loc = frame_get_PC(frame); + numlocals = stack_get_numlocals(frame); + } else { + loc = thisPC; + numlocals = 0; + } + + found_frame = infix_decode_PC(&boo, loc); + + infix_print_offset(loc); + + if(found_frame) { + infix_print_string(" in "); + infix_print_string(boo.func_name); + } + + if(!thisPC) { + infix_print_string(" ("); + + for(n = 0; n < numlocals; n++) { + const char *name; + if(n) + infix_print_string(", "); + if(found_frame) { + infix_print_string(infix_routines[boo.func_num].localnames[n]); + infix_print_char('='); + } + infix_print_znumber(frame_get_var(frame, n + 1)); + name = debug_decode_number(frame_get_var(frame, n + 1)); + if(name) { + infix_print_char(' '); + infix_print_string(name); + } + } + + infix_print_string(")"); + } + + if(found_frame) { + infix_print_string(" at "); + if(boo.file->filename) + infix_print_string(boo.file->filename); + else + infix_print_string(""); + infix_print_char(':'); + infix_print_number(boo.line_num); + } + + infix_print_char('\n'); + + if(found_frame && !thisPC) + infix_file_print_line(boo.file, boo.line_num); +} + + +void infix_list_files(void) +{ + unsigned i; + for(i = 0; i < infix_filescount; i++) { + if(i) + infix_print_string(", "); + infix_print_string(infix_files[i].filename); + } +} + +#else + +BOOL init_infix(strid_t unused) +{ + n_show_error(E_DEBUG, "debugging code not compiled in", 0); + return FALSE; +} + + +#endif + diff --git a/interpreters/nitfol/infix.h b/interpreters/nitfol/infix.h new file mode 100644 index 0000000..4292d63 --- /dev/null +++ b/interpreters/nitfol/infix.h @@ -0,0 +1,68 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i infix.c' */ +#ifndef CFH_INFIX_H +#define CFH_INFIX_H + +/* From `infix.c': */ + + +#ifdef DEBUGGING +typedef enum { Z_UNKNOWN, Z_BOOLEAN, Z_NUMBER, Z_OBJECT, Z_ROUTINE, Z_STRING, Z_GLOBAL, Z_LOCAL, Z_BYTEARRAY, Z_WORDARRAY, Z_OBJPROP, Z_ATTR, Z_PROP, Z_ARRAY }z_type; +typedef struct z_typed z_typed; +struct z_typed { + zword v; + z_type t; + + zword o, p; +} +; +typedef struct { + const char *filename; + strid_t stream; + int num_lines; + glui32 *line_locations; +} +infix_file; +typedef struct { + infix_file *file; + int line_num; + int line_x; + const char *func_name; + unsigned func_num; + offset thisPC; +} +infix_location; +offset infix_get_routine_PC (zword routine ); +void infix_file_print_line (infix_file *f , int line ); +BOOL init_infix (strid_t infix ); +void kill_infix (void); +void infix_print_znumber (zword blah ); +void infix_print_offset (zword blah ); +void infix_print_number (zword blah ); +void infix_print_char (int blah ); +void infix_print_fixed_char (int blah ); +void infix_print_string (const char *blah ); +void infix_print_fixed_string (const char *blah ); +void infix_get_string (char *dest , int maxlen ); +void infix_get_val (z_typed *val ); +void infix_assign (z_typed *dest , zword val ); +void infix_display (z_typed val ); +int infix_find_file (infix_file **dest , const char *name ); +BOOL infix_find_symbol (z_typed *val , const char *name , int len ); +const char * infix_get_name (z_typed val ); +BOOL infix_decode_PC (infix_location *dest , offset thisPC ); +BOOL infix_decode_fileloc (infix_location *dest , const char *filename , unsigned line_num ); +BOOL infix_decode_func_name (infix_location *dest , const char *file_name , const char *func_name ); +void infix_gprint_loc (int frame , offset thisPC ); +void infix_list_files (void); + +#else +BOOL init_infix (strid_t unused ); + +#endif + +#endif /* CFH_INFIX_H */ diff --git a/interpreters/nitfol/inform.c b/interpreters/nitfol/inform.c new file mode 100644 index 0000000..db615a7 --- /dev/null +++ b/interpreters/nitfol/inform.c @@ -0,0 +1,2592 @@ +/* A Bison parser, made from inform.y + by GNU bison 1.35. */ + +#define YYBISON 1 /* Identify Bison output. */ + +# define NUM 257 +# define DFILE 258 +# define CONDITION 259 +# define ALIAS 260 +# define RALIAS 261 +# define UNALIAS 262 +# define DUMPMEM 263 +# define AUTOMAP 264 +# define HELP 265 +# define UNDO 266 +# define REDO 267 +# define LANGUAGE 268 +# define INFOSOURCE 269 +# define INFOSOURCES 270 +# define COPYING 271 +# define WARRANTY 272 +# define PRINT 273 +# define SET 274 +# define MOVE 275 +# define TO 276 +# define GIVE 277 +# define REMOVE 278 +# define JUMP 279 +# define CONT 280 +# define STEP 281 +# define NEXT 282 +# define UNTIL 283 +# define STEPI 284 +# define NEXTI 285 +# define FINISH 286 +# define BREAK 287 +# define DELETE 288 +# define IF 289 +# define COND 290 +# define IGNORE 291 +# define BREAKPOINTS 292 +# define RESTORE 293 +# define RESTART 294 +# define QUIT 295 +# define RECORDON 296 +# define RECORDOFF 297 +# define REPLAY 298 +# define REPLAYOFF 299 +# define SYMBOL_FILE 300 +# define FRAME 301 +# define SELECT_FRAME 302 +# define BACKTRACE 303 +# define UP_FRAME 304 +# define DOWN_FRAME 305 +# define UP_SILENTLY 306 +# define DOWN_SILENTLY 307 +# define DISPLAY 308 +# define UNDISPLAY 309 +# define DISABLE_DISPLAY 310 +# define ENABLE_DISPLAY 311 +# define DISABLE_BREAK 312 +# define ENABLE_BREAK 313 +# define OBJECT_TREE 314 +# define FIND 315 +# define LIST_GLOBALS 316 +# define BTRUE 317 +# define BFALSE 318 +# define NOTHING 319 +# define PARENT 320 +# define CHILD 321 +# define SIBLING 322 +# define CHILDREN 323 +# define RANDOM 324 +# define ANDAND 325 +# define OROR 326 +# define NOTNOT 327 +# define OR 328 +# define BYTEARRAY 329 +# define WORDARRAY 330 +# define precNEG 331 +# define NUMBER 332 +# define OBJECT 333 +# define ROUTINE 334 +# define STRING 335 +# define GLOBAL 336 +# define LOCAL 337 +# define INCREMENT 338 +# define DECREMENT 339 +# define PROPADDR 340 +# define PROPLENGTH 341 +# define SUPERCLASS 342 + +#line 1 "inform.y" + +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at ecr+@andrew.cmu.edu +*/ + +#include "nitfol.h" +#include + +/* bison uses str* functions; make it use n_str* instead... */ +#ifndef n_strcat +#define strcat(d, s) n_strcat(d, s) +#endif +#ifndef n_strlen +#define strlen(s) n_strlen(s) +#endif +#ifndef n_strcpy +#define strcpy(d, s) n_strcpy(d, s) +#endif + + +#ifdef DEBUGGING + + typedef struct zword_list zword_list; + struct zword_list { + zword_list *next; + zword item; + }; + + typedef struct cond_list cond_list; + struct cond_list { + cond_list *next; + zword val; + BOOL (*condfunc)(zword a, zword b); + BOOL opposite; + }; + + cond_list *condlist; + + static z_typed z_t(z_typed a, z_typed b, zword v); + + static const char *lex_expression; + static int lex_offset; + + static const char *lex_tail(void) { + const char *t = lex_expression + lex_offset; + while(*t == ' ') + t++; + lex_offset = n_strlen(lex_expression); + return t; + } + + static z_typed inform_result; + + static int yylex(void); + static void yyerror(const char *s); + static void inform_help(void); + + int ignoreeffects; + +#define YYERROR_VERBOSE + +/* +#define YYDEBUG 1 +*/ + + +#line 84 "inform.y" +#ifndef YYSTYPE +typedef union { + glui32 pcoffset; + infix_file *filenum; + z_typed val; + + zword_list *zlist; + + struct { + BOOL (*condfunc)(zword a, zword b); + BOOL opposite; + } cond; + + BOOL flag; +} yystype; +# define YYSTYPE yystype +# define YYSTYPE_IS_TRIVIAL 1 +#endif +#ifndef YYDEBUG +# define YYDEBUG 0 +#endif + + + +#define YYFINAL 208 +#define YYFLAG -32768 +#define YYNTBASE 104 + +/* YYTRANSLATE(YYLEX) -- Bison token number corresponding to YYLEX. */ +#define YYTRANSLATE(x) ((unsigned)(x) <= 342 ? yytranslate[x] : 114) + +/* YYTRANSLATE[YYLEX] -- Bison token number corresponding to YYLEX. */ +static const char yytranslate[] = +{ + 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 101, 2, 81, 82, 2, + 98, 103, 79, 77, 71, 78, 99, 80, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 102, 2, + 2, 72, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 83, 2, 84, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 69, 70, 73, 74, 75, 76, 85, + 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 100 +}; + +#if YYDEBUG +static const short yyprhs[] = +{ + 0, 0, 1, 3, 5, 7, 9, 11, 13, 15, + 17, 19, 21, 23, 25, 27, 29, 31, 33, 35, + 38, 41, 43, 46, 49, 52, 57, 59, 62, 64, + 66, 69, 73, 78, 81, 84, 86, 89, 91, 94, + 96, 99, 101, 103, 106, 108, 111, 113, 116, 120, + 123, 127, 130, 132, 135, 138, 141, 143, 145, 147, + 149, 151, 153, 156, 159, 161, 164, 166, 169, 171, + 174, 176, 179, 181, 184, 188, 190, 193, 196, 200, + 203, 205, 209, 210, 214, 216, 218, 222, 226, 227, + 232, 234, 236, 238, 240, 244, 249, 254, 259, 264, + 269, 274, 275, 280, 281, 286, 289, 293, 297, 301, + 305, 309, 313, 317, 320, 324, 328, 331, 334, 337, + 340, 343, 347, 351, 355, 358, 361, 364, 367, 370, + 373 +}; +static const short yyrhs[] = +{ + -1, 101, 0, 9, 0, 6, 0, 7, 0, 8, + 0, 10, 0, 11, 0, 40, 0, 39, 0, 42, + 0, 43, 0, 44, 0, 45, 0, 41, 0, 12, + 0, 13, 0, 46, 0, 19, 108, 0, 20, 108, + 0, 54, 0, 55, 3, 0, 56, 3, 0, 57, + 3, 0, 21, 108, 22, 108, 0, 60, 0, 60, + 108, 0, 61, 0, 62, 0, 62, 108, 0, 23, + 108, 3, 0, 23, 108, 84, 3, 0, 24, 108, + 0, 25, 105, 0, 26, 0, 26, 3, 0, 27, + 0, 27, 3, 0, 28, 0, 28, 3, 0, 29, + 0, 30, 0, 30, 3, 0, 31, 0, 31, 3, + 0, 32, 0, 33, 105, 0, 33, 105, 35, 0, + 36, 3, 0, 37, 3, 3, 0, 34, 3, 0, + 38, 0, 38, 3, 0, 58, 3, 0, 59, 3, + 0, 14, 0, 15, 0, 16, 0, 17, 0, 18, + 0, 47, 0, 47, 3, 0, 48, 3, 0, 50, + 0, 50, 3, 0, 52, 0, 52, 3, 0, 51, + 0, 51, 3, 0, 53, 0, 53, 3, 0, 49, + 0, 49, 3, 0, 49, 78, 3, 0, 3, 0, + 77, 3, 0, 78, 3, 0, 4, 102, 3, 0, + 79, 3, 0, 111, 0, 106, 76, 111, 0, 0, + 111, 71, 107, 0, 111, 0, 109, 0, 108, 71, + 111, 0, 108, 71, 109, 0, 0, 111, 5, 110, + 106, 0, 3, 0, 64, 0, 63, 0, 65, 0, + 111, 72, 111, 0, 66, 98, 108, 103, 0, 67, + 98, 108, 103, 0, 68, 98, 108, 103, 0, 69, + 98, 108, 103, 0, 70, 98, 108, 103, 0, 111, + 98, 107, 103, 0, 0, 111, 73, 112, 111, 0, + 0, 111, 74, 113, 111, 0, 75, 111, 0, 111, + 77, 111, 0, 111, 78, 111, 0, 111, 79, 111, + 0, 111, 80, 111, 0, 111, 81, 111, 0, 111, + 82, 111, 0, 111, 83, 111, 0, 84, 111, 0, + 111, 85, 111, 0, 111, 86, 111, 0, 78, 111, + 0, 94, 111, 0, 111, 94, 0, 95, 111, 0, + 111, 95, 0, 111, 96, 111, 0, 111, 97, 111, + 0, 111, 99, 111, 0, 88, 111, 0, 89, 111, + 0, 90, 111, 0, 91, 111, 0, 92, 111, 0, + 93, 111, 0, 98, 108, 103, 0 +}; + +#endif + +#if YYDEBUG +/* YYRLINE[YYN] -- source line where rule number YYN was defined. */ +static const short yyrline[] = +{ + 0, 130, 132, 134, 142, 144, 146, 148, 150, 152, + 154, 165, 167, 169, 171, 173, 175, 182, 189, 198, + 200, 202, 204, 206, 208, 210, 212, 214, 216, 221, + 239, 255, 257, 259, 261, 263, 265, 267, 269, 271, + 273, 275, 277, 279, 281, 283, 285, 287, 289, 291, + 293, 295, 297, 299, 301, 303, 305, 307, 309, 311, + 313, 315, 317, 319, 321, 323, 325, 327, 329, 331, + 333, 335, 337, 339, 341, 349, 350, 351, 352, 353, + 357, 364, 377, 378, 382, 383, 384, 385, 389, 389, + 394, 396, 398, 400, 403, 406, 408, 410, 412, 415, + 425, 443, 443, 445, 445, 447, 450, 452, 454, 456, + 458, 460, 462, 464, 467, 469, 472, 475, 477, 479, + 481, 484, 486, 489, 497, 499, 501, 503, 505, 507, + 509 +}; +#endif + + +#if (YYDEBUG) || defined YYERROR_VERBOSE + +/* YYTNAME[TOKEN_NUM] -- String name of the token TOKEN_NUM. */ +static const char *const yytname[] = +{ + "$", "error", "$undefined.", "NUM", "DFILE", "CONDITION", "ALIAS", + "RALIAS", "UNALIAS", "DUMPMEM", "AUTOMAP", "HELP", "UNDO", "REDO", + "LANGUAGE", "INFOSOURCE", "INFOSOURCES", "COPYING", "WARRANTY", "PRINT", + "SET", "MOVE", "TO", "GIVE", "REMOVE", "JUMP", "CONT", "STEP", "NEXT", + "UNTIL", "STEPI", "NEXTI", "FINISH", "BREAK", "DELETE", "IF", "COND", + "IGNORE", "BREAKPOINTS", "RESTORE", "RESTART", "QUIT", "RECORDON", + "RECORDOFF", "REPLAY", "REPLAYOFF", "SYMBOL_FILE", "FRAME", + "SELECT_FRAME", "BACKTRACE", "UP_FRAME", "DOWN_FRAME", "UP_SILENTLY", + "DOWN_SILENTLY", "DISPLAY", "UNDISPLAY", "DISABLE_DISPLAY", + "ENABLE_DISPLAY", "DISABLE_BREAK", "ENABLE_BREAK", "OBJECT_TREE", + "FIND", "LIST_GLOBALS", "BTRUE", "BFALSE", "NOTHING", "PARENT", "CHILD", + "SIBLING", "CHILDREN", "RANDOM", "','", "'='", "ANDAND", "OROR", + "NOTNOT", "OR", "'+'", "'-'", "'*'", "'/'", "'%'", "'&'", "'|'", "'~'", + "BYTEARRAY", "WORDARRAY", "precNEG", "NUMBER", "OBJECT", "ROUTINE", + "STRING", "GLOBAL", "LOCAL", "INCREMENT", "DECREMENT", "PROPADDR", + "PROPLENGTH", "'('", "'.'", "SUPERCLASS", "'#'", "':'", "')'", "input", + "linespec", "orlist", "arglist", "commaexp", "condexp", "@1", "exp", + "@2", "@3", 0 +}; +#endif + +/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */ +static const short yyr1[] = +{ + 0, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, + 104, 104, 104, 104, 104, 105, 105, 105, 105, 105, + 106, 106, 107, 107, 108, 108, 108, 108, 110, 109, + 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, + 111, 112, 111, 113, 111, 111, 111, 111, 111, 111, + 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, + 111, 111, 111, 111, 111, 111, 111, 111, 111, 111, + 111 +}; + +/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ +static const short yyr2[] = +{ + 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, + 2, 1, 2, 2, 2, 4, 1, 2, 1, 1, + 2, 3, 4, 2, 2, 1, 2, 1, 2, 1, + 2, 1, 1, 2, 1, 2, 1, 2, 3, 2, + 3, 2, 1, 2, 2, 2, 1, 1, 1, 1, + 1, 1, 2, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 3, 1, 2, 2, 3, 2, + 1, 3, 0, 3, 1, 1, 3, 3, 0, 4, + 1, 1, 1, 1, 3, 4, 4, 4, 4, 4, + 4, 0, 4, 0, 4, 2, 3, 3, 3, 3, + 3, 3, 3, 2, 3, 3, 2, 2, 2, 2, + 2, 3, 3, 3, 2, 2, 2, 2, 2, 2, + 3 +}; + +/* YYDEFACT[S] -- default rule to reduce with in state S when YYTABLE + doesn't specify something else to do. Zero means the default is an + error. */ +static const short yydefact[] = +{ + 1, 4, 5, 6, 3, 7, 8, 16, 17, 56, + 57, 58, 59, 60, 0, 0, 0, 0, 0, 0, + 35, 37, 39, 41, 42, 44, 46, 0, 0, 0, + 0, 52, 10, 9, 15, 11, 12, 13, 14, 18, + 61, 0, 72, 64, 68, 66, 70, 21, 0, 0, + 0, 0, 0, 26, 28, 29, 2, 90, 92, 91, + 93, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 19, 85, + 84, 20, 0, 0, 33, 75, 0, 0, 0, 0, + 34, 36, 38, 40, 43, 45, 47, 51, 49, 0, + 53, 62, 63, 73, 0, 65, 69, 67, 71, 22, + 23, 24, 54, 55, 27, 30, 0, 0, 0, 0, + 0, 105, 116, 113, 124, 125, 126, 127, 128, 129, + 117, 119, 0, 0, 88, 0, 101, 103, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 118, 120, 0, + 0, 82, 0, 0, 31, 0, 0, 76, 77, 79, + 48, 50, 74, 0, 0, 0, 0, 0, 130, 87, + 86, 0, 94, 0, 0, 106, 107, 108, 109, 110, + 111, 112, 114, 115, 121, 122, 0, 0, 123, 25, + 32, 78, 95, 96, 97, 98, 99, 89, 80, 102, + 104, 100, 82, 0, 83, 81, 0, 0, 0 +}; + +static const short yydefgoto[] = +{ + 206, 90, 197, 186, 78, 79, 171, 80, 173, 174 +}; + +static const short yypact[] = +{ + 136,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768, + -32768,-32768,-32768,-32768, 155, 155, 155, 155, 155, 15, + 28, 33, 34,-32768, 44, 45,-32768, 15, 46, 47, + 48, 55,-32768,-32768,-32768,-32768,-32768,-32768,-32768,-32768, + 56, 57, 13, 58, 60, 69, 71,-32768, 72, 83, + 92, 93, 94, 155,-32768, 155,-32768,-32768,-32768,-32768, + -32768, -59, 1, 2, 8, 9, 155, 155, 155, 155, + 155, 155, 155, 155, 155, 155, 155, 155, 37,-32768, + 194, 37, -7, 14, 37,-32768, 18, 107, 126, 133, + -32768,-32768,-32768,-32768,-32768,-32768, 102,-32768,-32768, 168, + -32768,-32768,-32768,-32768, 199,-32768,-32768,-32768,-32768,-32768, + -32768,-32768,-32768,-32768, 37, 37, 155, 155, 155, 155, + 155, 36, -29, 115, -29, -29, -29, -29, -29, -29, + 29, 29, -51, 155,-32768, 155,-32768,-32768, 155, 155, + 155, 155, 155, 155, 155, 155, 155,-32768,-32768, 155, + 155, 155, 155, 155,-32768, 200, 201,-32768,-32768,-32768, + -32768,-32768,-32768, -50, -49, -48, -47, -46,-32768,-32768, + 194, 155, 251, 155, 155, -53, -53, 115, 115, 115, + 115, 115, -29, -29, -64, -64, 103, 223,-32768, 37, + -32768,-32768,-32768,-32768,-32768,-32768,-32768, 63, 251, 36, + 36,-32768, 155, 155,-32768, 251, 205, 207,-32768 +}; + +static const short yypgoto[] = +{ + -32768, 181,-32768, 24, -15, 82,-32768, -62,-32768,-32768 +}; + + +#define YYLAST 350 + + +static const short yytable[] = +{ + 81, 82, 83, 84, 121, 122, 123, 124, 125, 126, + 127, 128, 129, 130, 131, 153, 103, 154, 85, 86, + 133, 133, 133, 133, 133, 133, 140, 141, 142, 143, + 144, 91, 145, 146, 151, 152, 92, 93, 114, 116, + 115, 147, 148, 149, 150, 151, 152, 94, 95, 97, + 98, 99, 168, 192, 193, 194, 195, 196, 100, 101, + 102, 105, 132, 106, 133, 147, 148, 149, 150, 151, + 152, 170, 107, 172, 108, 109, 175, 176, 177, 178, + 179, 180, 181, 182, 183, 133, 110, 184, 185, 187, + 188, 104, 87, 88, 89, 111, 112, 113, 155, 117, + 118, 163, 164, 165, 166, 167, 119, 120, 133, 198, + 157, 199, 200, 138, 139, 140, 141, 142, 143, 144, + 156, 145, 146,-32768,-32768, 149, 150, 151, 152, 158, + 147, 148, 149, 150, 151, 152, 159, 160, 189, 203, + 187, 205, 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, 57, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, + 28, 161, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51, 52, 53, 54, 55, 134, + 145, 146, 162, 190, 191, 207, 201, 208, 96, 147, + 148, 149, 150, 151, 152, 169, 0, 0, 58, 59, + 60, 61, 62, 63, 64, 65, 204, 0, 0, 0, + 66, 0, 0, 67, 0, 0, 0, 56, 0, 68, + 0, 0, 0, 69, 70, 71, 72, 73, 74, 75, + 76, 0, 0, 77, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 135, 136, 137, 0, + 0, 138, 139, 140, 141, 142, 143, 144, 0, 145, + 146, 0, 0, 0, 0, 0, 0, 0, 147, 148, + 149, 150, 151, 152, 202, 135, 136, 137, 0, 0, + 138, 139, 140, 141, 142, 143, 144, 0, 145, 146, + 0, 0, 0, 0, 0, 0, 0, 147, 148, 149, + 150, 151, 152, 135, 136, 137, 0, 0, 138, 139, + 140, 141, 142, 143, 144, 0, 145, 146, 0, 0, + 0, 0, 0, 0, 0, 147, 148, 149, 150, 151, + 152 +}; + +static const short yycheck[] = +{ + 15, 16, 17, 18, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 22, 3, 3, 3, 4, + 71, 71, 71, 71, 71, 71, 79, 80, 81, 82, + 83, 3, 85, 86, 98, 99, 3, 3, 53, 98, + 55, 94, 95, 96, 97, 98, 99, 3, 3, 3, + 3, 3, 103, 103, 103, 103, 103, 103, 3, 3, + 3, 3, 77, 3, 71, 94, 95, 96, 97, 98, + 99, 133, 3, 135, 3, 3, 138, 139, 140, 141, + 142, 143, 144, 145, 146, 71, 3, 149, 150, 151, + 152, 78, 77, 78, 79, 3, 3, 3, 84, 98, + 98, 116, 117, 118, 119, 120, 98, 98, 71, 171, + 3, 173, 174, 77, 78, 79, 80, 81, 82, 83, + 102, 85, 86, 94, 95, 96, 97, 98, 99, 3, + 94, 95, 96, 97, 98, 99, 3, 35, 153, 76, + 202, 203, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 3, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 3, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 62, 5, + 85, 86, 3, 3, 3, 0, 103, 0, 27, 94, + 95, 96, 97, 98, 99, 133, -1, -1, 63, 64, + 65, 66, 67, 68, 69, 70, 202, -1, -1, -1, + 75, -1, -1, 78, -1, -1, -1, 101, -1, 84, + -1, -1, -1, 88, 89, 90, 91, 92, 93, 94, + 95, -1, -1, 98, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, 72, 73, 74, -1, + -1, 77, 78, 79, 80, 81, 82, 83, -1, 85, + 86, -1, -1, -1, -1, -1, -1, -1, 94, 95, + 96, 97, 98, 99, 71, 72, 73, 74, -1, -1, + 77, 78, 79, 80, 81, 82, 83, -1, 85, 86, + -1, -1, -1, -1, -1, -1, -1, 94, 95, 96, + 97, 98, 99, 72, 73, 74, -1, -1, 77, 78, + 79, 80, 81, 82, 83, -1, 85, 86, -1, -1, + -1, -1, -1, -1, -1, 94, 95, 96, 97, 98, + 99 +}; +/* -*-C-*- Note some compilers choke on comments on `#line' lines. */ +#line 3 "/usr/share/bison/bison.simple" + +/* Skeleton output parser for bison, + + Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002 Free Software + Foundation, Inc. + + 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, 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., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +/* As a special exception, when this file is copied by Bison into a + Bison output file, you may use that output file without restriction. + This special exception was added by the Free Software Foundation + in version 1.24 of Bison. */ + +/* This is the parser code that is written into each bison parser when + the %semantic_parser declaration is not specified in the grammar. + It was written by Richard Stallman by simplifying the hairy parser + used when %semantic_parser is specified. */ + +/* All symbols defined below should begin with yy or YY, to avoid + infringing on user name space. This should be done even for local + variables, as they might otherwise be expanded by user macros. + There are some unavoidable exceptions within include files to + define necessary library symbols; they are noted "INFRINGES ON + USER NAME SPACE" below. */ + +#if ! defined (yyoverflow) || defined (YYERROR_VERBOSE) + +/* The parser invokes alloca or malloc; define the necessary symbols. */ + +# if YYSTACK_USE_ALLOCA +# define YYSTACK_ALLOC alloca +# else +# ifndef YYSTACK_USE_ALLOCA +# if defined (alloca) || defined (_ALLOCA_H) +# define YYSTACK_ALLOC alloca +# else +# ifdef __GNUC__ +# define YYSTACK_ALLOC __builtin_alloca +# endif +# endif +# endif +# endif + +# ifdef YYSTACK_ALLOC + /* Pacify GCC's `empty if-body' warning. */ +# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0) +# else +# if defined (__STDC__) || defined (__cplusplus) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# endif +# define YYSTACK_ALLOC malloc +# define YYSTACK_FREE free +# endif +#endif /* ! defined (yyoverflow) || defined (YYERROR_VERBOSE) */ + + +#if (! defined (yyoverflow) \ + && (! defined (__cplusplus) \ + || (YYLTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL))) + +/* A type that is properly aligned for any stack member. */ +union yyalloc +{ + short yyss; + YYSTYPE yyvs; +# if YYLSP_NEEDED + YYLTYPE yyls; +# endif +}; + +/* The size of the maximum gap between one aligned stack and the next. */ +# define YYSTACK_GAP_MAX (sizeof (union yyalloc) - 1) + +/* The size of an array large to enough to hold all stacks, each with + N elements. */ +# if YYLSP_NEEDED +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (short) + sizeof (YYSTYPE) + sizeof (YYLTYPE)) \ + + 2 * YYSTACK_GAP_MAX) +# else +# define YYSTACK_BYTES(N) \ + ((N) * (sizeof (short) + sizeof (YYSTYPE)) \ + + YYSTACK_GAP_MAX) +# endif + +/* Copy COUNT objects from FROM to TO. The source and destination do + not overlap. */ +# ifndef YYCOPY +# if 1 < __GNUC__ +# define YYCOPY(To, From, Count) \ + __builtin_memcpy (To, From, (Count) * sizeof (*(From))) +# else +# define YYCOPY(To, From, Count) \ + do \ + { \ + register YYSIZE_T yyi; \ + for (yyi = 0; yyi < (Count); yyi++) \ + (To)[yyi] = (From)[yyi]; \ + } \ + while (0) +# endif +# endif + +/* Relocate STACK from its old location to the new one. The + local variables YYSIZE and YYSTACKSIZE give the old and new number of + elements in the stack, and YYPTR gives the new location of the + stack. Advance YYPTR to a properly aligned location for the next + stack. */ +# define YYSTACK_RELOCATE(Stack) \ + do \ + { \ + YYSIZE_T yynewbytes; \ + YYCOPY (&yyptr->Stack, Stack, yysize); \ + Stack = &yyptr->Stack; \ + yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAX; \ + yyptr += yynewbytes / sizeof (*yyptr); \ + } \ + while (0) + +#endif + + +#if ! defined (YYSIZE_T) && defined (__SIZE_TYPE__) +# define YYSIZE_T __SIZE_TYPE__ +#endif +#if ! defined (YYSIZE_T) && defined (size_t) +# define YYSIZE_T size_t +#endif +#if ! defined (YYSIZE_T) +# if defined (__STDC__) || defined (__cplusplus) +# include /* INFRINGES ON USER NAME SPACE */ +# define YYSIZE_T size_t +# endif +#endif +#if ! defined (YYSIZE_T) +# define YYSIZE_T unsigned int +#endif + +#define yyerrok (yyerrstatus = 0) +#define yyclearin (yychar = YYEMPTY) +#define YYEMPTY -2 +#define YYEOF 0 +#define YYACCEPT goto yyacceptlab +#define YYABORT goto yyabortlab +#define YYERROR goto yyerrlab1 +/* Like YYERROR except do call yyerror. This remains here temporarily + to ease the transition to the new meaning of YYERROR, for GCC. + Once GCC version 2 has supplanted version 1, this can go. */ +#define YYFAIL goto yyerrlab +#define YYRECOVERING() (!!yyerrstatus) +#define YYBACKUP(Token, Value) \ +do \ + if (yychar == YYEMPTY && yylen == 1) \ + { \ + yychar = (Token); \ + yylval = (Value); \ + yychar1 = YYTRANSLATE (yychar); \ + YYPOPSTACK; \ + goto yybackup; \ + } \ + else \ + { \ + yyerror ("syntax error: cannot back up"); \ + YYERROR; \ + } \ +while (0) + +#define YYTERROR 1 +#define YYERRCODE 256 + + +/* YYLLOC_DEFAULT -- Compute the default location (before the actions + are run). + + When YYLLOC_DEFAULT is run, CURRENT is set the location of the + first token. By default, to implement support for ranges, extend + its range to the last symbol. */ + +#ifndef YYLLOC_DEFAULT +# define YYLLOC_DEFAULT(Current, Rhs, N) \ + Current.last_line = Rhs[N].last_line; \ + Current.last_column = Rhs[N].last_column; +#endif + + +/* YYLEX -- calling `yylex' with the right arguments. */ + +#if YYPURE +# if YYLSP_NEEDED +# ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, &yylloc, YYLEX_PARAM) +# else +# define YYLEX yylex (&yylval, &yylloc) +# endif +# else /* !YYLSP_NEEDED */ +# ifdef YYLEX_PARAM +# define YYLEX yylex (&yylval, YYLEX_PARAM) +# else +# define YYLEX yylex (&yylval) +# endif +# endif /* !YYLSP_NEEDED */ +#else /* !YYPURE */ +# define YYLEX yylex () +#endif /* !YYPURE */ + + +/* Enable debugging if requested. */ +#if YYDEBUG + +# ifndef YYFPRINTF +# include /* INFRINGES ON USER NAME SPACE */ +# define YYFPRINTF fprintf +# endif + +# define YYDPRINTF(Args) \ +do { \ + if (yydebug) \ + YYFPRINTF Args; \ +} while (0) +/* Nonzero means print parse trace. It is left uninitialized so that + multiple parsers can coexist. */ +int yydebug; +#else /* !YYDEBUG */ +# define YYDPRINTF(Args) +#endif /* !YYDEBUG */ + +/* YYINITDEPTH -- initial size of the parser's stacks. */ +#ifndef YYINITDEPTH +# define YYINITDEPTH 200 +#endif + +/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only + if the built-in stack extension method is used). + + Do not make this value too large; the results are undefined if + SIZE_MAX < YYSTACK_BYTES (YYMAXDEPTH) + evaluated with infinite-precision integer arithmetic. */ + +#if YYMAXDEPTH == 0 +# undef YYMAXDEPTH +#endif + +#ifndef YYMAXDEPTH +# define YYMAXDEPTH 10000 +#endif + +#ifdef YYERROR_VERBOSE + +# ifndef yystrlen +# if defined (__GLIBC__) && defined (_STRING_H) +# define yystrlen strlen +# else +/* Return the length of YYSTR. */ +static YYSIZE_T +# if defined (__STDC__) || defined (__cplusplus) +yystrlen (const char *yystr) +# else +yystrlen (yystr) + const char *yystr; +# endif +{ + register const char *yys = yystr; + + while (*yys++ != '\0') + continue; + + return yys - yystr - 1; +} +# endif +# endif + +# ifndef yystpcpy +# if defined (__GLIBC__) && defined (_STRING_H) && defined (_GNU_SOURCE) +# define yystpcpy stpcpy +# else +/* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in + YYDEST. */ +static char * +# if defined (__STDC__) || defined (__cplusplus) +yystpcpy (char *yydest, const char *yysrc) +# else +yystpcpy (yydest, yysrc) + char *yydest; + const char *yysrc; +# endif +{ + register char *yyd = yydest; + register const char *yys = yysrc; + + while ((*yyd++ = *yys++) != '\0') + continue; + + return yyd - 1; +} +# endif +# endif +#endif + +#line 315 "/usr/share/bison/bison.simple" + + +/* The user can define YYPARSE_PARAM as the name of an argument to be passed + into yyparse. The argument should have type void *. + It should actually point to an object. + Grammar actions can access the variable by casting it + to the proper pointer type. */ + +#ifdef YYPARSE_PARAM +# if defined (__STDC__) || defined (__cplusplus) +# define YYPARSE_PARAM_ARG void *YYPARSE_PARAM +# define YYPARSE_PARAM_DECL +# else +# define YYPARSE_PARAM_ARG YYPARSE_PARAM +# define YYPARSE_PARAM_DECL void *YYPARSE_PARAM; +# endif +#else /* !YYPARSE_PARAM */ +# define YYPARSE_PARAM_ARG +# define YYPARSE_PARAM_DECL +#endif /* !YYPARSE_PARAM */ + +/* Prevent warning if -Wstrict-prototypes. */ +#ifdef __GNUC__ +# ifdef YYPARSE_PARAM +int yyparse (void *); +# else +int yyparse (void); +# endif +#endif + +/* YY_DECL_VARIABLES -- depending whether we use a pure parser, + variables are global, or local to YYPARSE. */ + +#define YY_DECL_NON_LSP_VARIABLES \ +/* The lookahead symbol. */ \ +int yychar; \ + \ +/* The semantic value of the lookahead symbol. */ \ +YYSTYPE yylval; \ + \ +/* Number of parse errors so far. */ \ +int yynerrs; + +#if YYLSP_NEEDED +# define YY_DECL_VARIABLES \ +YY_DECL_NON_LSP_VARIABLES \ + \ +/* Location data for the lookahead symbol. */ \ +YYLTYPE yylloc; +#else +# define YY_DECL_VARIABLES \ +YY_DECL_NON_LSP_VARIABLES +#endif + + +/* If nonreentrant, generate the variables here. */ + +#if !YYPURE +YY_DECL_VARIABLES +#endif /* !YYPURE */ + +int +yyparse (YYPARSE_PARAM_ARG) + YYPARSE_PARAM_DECL +{ + /* If reentrant, generate the variables here. */ +#if YYPURE + YY_DECL_VARIABLES +#endif /* !YYPURE */ + + register int yystate; + register int yyn; + int yyresult; + /* Number of tokens to shift before error messages enabled. */ + int yyerrstatus; + /* Lookahead token as an internal (translated) token number. */ + int yychar1 = 0; + + /* Three stacks and their tools: + `yyss': related to states, + `yyvs': related to semantic values, + `yyls': related to locations. + + Refer to the stacks thru separate pointers, to allow yyoverflow + to reallocate them elsewhere. */ + + /* The state stack. */ + short yyssa[YYINITDEPTH]; + short *yyss = yyssa; + register short *yyssp; + + /* The semantic value stack. */ + YYSTYPE yyvsa[YYINITDEPTH]; + YYSTYPE *yyvs = yyvsa; + register YYSTYPE *yyvsp; + +#if YYLSP_NEEDED + /* The location stack. */ + YYLTYPE yylsa[YYINITDEPTH]; + YYLTYPE *yyls = yylsa; + YYLTYPE *yylsp; +#endif + +#if YYLSP_NEEDED +# define YYPOPSTACK (yyvsp--, yyssp--, yylsp--) +#else +# define YYPOPSTACK (yyvsp--, yyssp--) +#endif + + YYSIZE_T yystacksize = YYINITDEPTH; + + + /* The variables used to return semantic value and location from the + action routines. */ + YYSTYPE yyval; +#if YYLSP_NEEDED + YYLTYPE yyloc; +#endif + + /* When reducing, the number of symbols on the RHS of the reduced + rule. */ + int yylen; + + YYDPRINTF ((stderr, "Starting parse\n")); + + yystate = 0; + yyerrstatus = 0; + yynerrs = 0; + yychar = YYEMPTY; /* Cause a token to be read. */ + + /* Initialize stack pointers. + Waste one element of value and location stack + so that they stay on the same level as the state stack. + The wasted elements are never initialized. */ + + yyssp = yyss; + yyvsp = yyvs; +#if YYLSP_NEEDED + yylsp = yyls; +#endif + goto yysetstate; + +/*------------------------------------------------------------. +| yynewstate -- Push a new state, which is found in yystate. | +`------------------------------------------------------------*/ + yynewstate: + /* In all cases, when you get here, the value and location stacks + have just been pushed. so pushing a state here evens the stacks. + */ + yyssp++; + + yysetstate: + *yyssp = yystate; + + if (yyssp >= yyss + yystacksize - 1) + { + /* Get the current used size of the three stacks, in elements. */ + YYSIZE_T yysize = yyssp - yyss + 1; + +#ifdef yyoverflow + { + /* Give user a chance to reallocate the stack. Use copies of + these so that the &'s don't force the real ones into + memory. */ + YYSTYPE *yyvs1 = yyvs; + short *yyss1 = yyss; + + /* Each stack pointer address is followed by the size of the + data in use in that stack, in bytes. */ +# if YYLSP_NEEDED + YYLTYPE *yyls1 = yyls; + /* This used to be a conditional around just the two extra args, + but that might be undefined if yyoverflow is a macro. */ + yyoverflow ("parser stack overflow", + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yyls1, yysize * sizeof (*yylsp), + &yystacksize); + yyls = yyls1; +# else + yyoverflow ("parser stack overflow", + &yyss1, yysize * sizeof (*yyssp), + &yyvs1, yysize * sizeof (*yyvsp), + &yystacksize); +# endif + yyss = yyss1; + yyvs = yyvs1; + } +#else /* no yyoverflow */ +# ifndef YYSTACK_RELOCATE + goto yyoverflowlab; +# else + /* Extend the stack our own way. */ + if (yystacksize >= YYMAXDEPTH) + goto yyoverflowlab; + yystacksize *= 2; + if (yystacksize > YYMAXDEPTH) + yystacksize = YYMAXDEPTH; + + { + short *yyss1 = yyss; + union yyalloc *yyptr = + (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize)); + if (! yyptr) + goto yyoverflowlab; + YYSTACK_RELOCATE (yyss); + YYSTACK_RELOCATE (yyvs); +# if YYLSP_NEEDED + YYSTACK_RELOCATE (yyls); +# endif +# undef YYSTACK_RELOCATE + if (yyss1 != yyssa) + YYSTACK_FREE (yyss1); + } +# endif +#endif /* no yyoverflow */ + + yyssp = yyss + yysize - 1; + yyvsp = yyvs + yysize - 1; +#if YYLSP_NEEDED + yylsp = yyls + yysize - 1; +#endif + + YYDPRINTF ((stderr, "Stack size increased to %lu\n", + (unsigned long int) yystacksize)); + + if (yyssp >= yyss + yystacksize - 1) + YYABORT; + } + + YYDPRINTF ((stderr, "Entering state %d\n", yystate)); + + goto yybackup; + + +/*-----------. +| yybackup. | +`-----------*/ +yybackup: + +/* Do appropriate processing given the current state. */ +/* Read a lookahead token if we need one and don't already have one. */ +/* yyresume: */ + + /* First try to decide what to do without reference to lookahead token. */ + + yyn = yypact[yystate]; + if (yyn == YYFLAG) + goto yydefault; + + /* Not known => get a lookahead token if don't already have one. */ + + /* yychar is either YYEMPTY or YYEOF + or a valid token in external form. */ + + if (yychar == YYEMPTY) + { + YYDPRINTF ((stderr, "Reading a token: ")); + yychar = YYLEX; + } + + /* Convert token to internal form (in yychar1) for indexing tables with */ + + if (yychar <= 0) /* This means end of input. */ + { + yychar1 = 0; + yychar = YYEOF; /* Don't call YYLEX any more */ + + YYDPRINTF ((stderr, "Now at end of input.\n")); + } + else + { + yychar1 = YYTRANSLATE (yychar); + +#if YYDEBUG + /* We have to keep this `#if YYDEBUG', since we use variables + which are defined only if `YYDEBUG' is set. */ + if (yydebug) + { + YYFPRINTF (stderr, "Next token is %d (%s", + yychar, yytname[yychar1]); + /* Give the individual parser a way to print the precise + meaning of a token, for further debugging info. */ +# ifdef YYPRINT + YYPRINT (stderr, yychar, yylval); +# endif + YYFPRINTF (stderr, ")\n"); + } +#endif + } + + yyn += yychar1; + if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != yychar1) + goto yydefault; + + yyn = yytable[yyn]; + + /* yyn is what to do for this token type in this state. + Negative => reduce, -yyn is rule number. + Positive => shift, yyn is new state. + New state is final state => don't bother to shift, + just return success. + 0, or most negative number => error. */ + + if (yyn < 0) + { + if (yyn == YYFLAG) + goto yyerrlab; + yyn = -yyn; + goto yyreduce; + } + else if (yyn == 0) + goto yyerrlab; + + if (yyn == YYFINAL) + YYACCEPT; + + /* Shift the lookahead token. */ + YYDPRINTF ((stderr, "Shifting token %d (%s), ", + yychar, yytname[yychar1])); + + /* Discard the token being shifted unless it is eof. */ + if (yychar != YYEOF) + yychar = YYEMPTY; + + *++yyvsp = yylval; +#if YYLSP_NEEDED + *++yylsp = yylloc; +#endif + + /* Count tokens shifted since error; after three, turn off error + status. */ + if (yyerrstatus) + yyerrstatus--; + + yystate = yyn; + goto yynewstate; + + +/*-----------------------------------------------------------. +| yydefault -- do the default action for the current state. | +`-----------------------------------------------------------*/ +yydefault: + yyn = yydefact[yystate]; + if (yyn == 0) + goto yyerrlab; + goto yyreduce; + + +/*-----------------------------. +| yyreduce -- Do a reduction. | +`-----------------------------*/ +yyreduce: + /* yyn is the number of a rule to reduce with. */ + yylen = yyr2[yyn]; + + /* If YYLEN is nonzero, implement the default value of the action: + `$$ = $1'. + + Otherwise, the following line sets YYVAL to the semantic value of + the lookahead token. This behavior is undocumented and Bison + users should not rely upon it. Assigning to YYVAL + unconditionally makes the parser a bit smaller, and it avoids a + GCC warning that YYVAL may be used uninitialized. */ + yyval = yyvsp[1-yylen]; + +#if YYLSP_NEEDED + /* Similarly for the default location. Let the user run additional + commands if for instance locations are ranges. */ + yyloc = yylsp[1-yylen]; + YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen); +#endif + +#if YYDEBUG + /* We have to keep this `#if YYDEBUG', since we use variables which + are defined only if `YYDEBUG' is set. */ + if (yydebug) + { + int yyi; + + YYFPRINTF (stderr, "Reducing via rule %d (line %d), ", + yyn, yyrline[yyn]); + + /* Print the symbols being reduced, and their result. */ + for (yyi = yyprhs[yyn]; yyrhs[yyi] > 0; yyi++) + YYFPRINTF (stderr, "%s ", yytname[yyrhs[yyi]]); + YYFPRINTF (stderr, " -> %s\n", yytname[yyr1[yyn]]); + } +#endif + + switch (yyn) { + +case 2: +#line 132 "inform.y" +{ lex_offset = n_strlen(lex_expression); } + break; +case 3: +#line 134 "inform.y" +{ + strid_t f; + f = n_file_name_or_prompt(fileusage_Data|fileusage_BinaryMode, + filemode_Write, lex_tail()); + w_glk_put_buffer_stream(f, (char *) z_memory, total_size); + glk_stream_close(f, NULL); + } + break; +case 4: +#line 142 "inform.y" +{ parse_new_alias(lex_tail(), FALSE); } + break; +case 5: +#line 144 "inform.y" +{ parse_new_alias(lex_tail(), TRUE); } + break; +case 6: +#line 146 "inform.y" +{ remove_alias(lex_tail()); } + break; +case 7: +#line 148 "inform.y" +{ automap_init(object_count, lex_tail()); } + break; +case 8: +#line 150 "inform.y" +{ inform_help(); } + break; +case 9: +#line 152 "inform.y" +{ op_restart(); exit_debugger = TRUE; read_abort = TRUE; } + break; +case 10: +#line 154 "inform.y" +{ + if(restoregame()) { + exit_debugger = TRUE; read_abort = TRUE; + if(zversion <= 3) + mop_take_branch(); + else + mop_store_result(2); + } else { + infix_print_string("Restore failed.\n"); + } } + break; +case 11: +#line 165 "inform.y" +{ zword oldop0 = operand[0]; operand[0] = 4; op_output_stream(); operand[0] = oldop0; } + break; +case 12: +#line 167 "inform.y" +{ zword oldop0 = operand[0]; operand[0] = neg(4); op_output_stream(); operand[0] = oldop0; } + break; +case 13: +#line 169 "inform.y" +{ zword oldop0 = operand[0]; operand[0] = 1; op_input_stream(); operand[0] = oldop0; exit_debugger = TRUE; } + break; +case 14: +#line 171 "inform.y" +{ zword oldop0 = operand[0]; operand[0] = 0; op_input_stream(); operand[0] = oldop0; } + break; +case 15: +#line 173 "inform.y" +{ z_close(); glk_exit(); } + break; +case 16: +#line 175 "inform.y" +{ + if(restoreundo()) { + read_abort = TRUE; exit_debugger = TRUE; + } else { + infix_print_string("No undo slots.\n"); + } } + break; +case 17: +#line 182 "inform.y" +{ + if(restoreredo()) { + read_abort = TRUE; exit_debugger = TRUE; + } else { + infix_print_string("No redo slots.\n"); + } } + break; +case 18: +#line 189 "inform.y" +{ + strid_t f; + f = n_file_name_or_prompt(fileusage_Data|fileusage_BinaryMode, + filemode_Read, lex_tail()); + if(f) { + kill_infix(); + init_infix(f); + } } + break; +case 19: +#line 198 "inform.y" +{ infix_display(yyvsp[0].val); } + break; +case 20: +#line 200 "inform.y" +{ inform_result = yyvsp[0].val; } + break; +case 21: +#line 202 "inform.y" +{ infix_auto_display(lex_tail()); } + break; +case 22: +#line 204 "inform.y" +{ infix_auto_undisplay(yyvsp[0].val.v); } + break; +case 23: +#line 206 "inform.y" +{ infix_set_display_enabled(yyvsp[0].val.v, FALSE); } + break; +case 24: +#line 208 "inform.y" +{ infix_set_display_enabled(yyvsp[0].val.v, TRUE); } + break; +case 25: +#line 210 "inform.y" +{ infix_move(yyvsp[0].val.v, yyvsp[-2].val.v); } + break; +case 26: +#line 212 "inform.y" +{ infix_object_tree(0); } + break; +case 27: +#line 214 "inform.y" +{ infix_object_tree(yyvsp[0].val.v); } + break; +case 28: +#line 216 "inform.y" +{ + if(lex_expression[lex_offset]) + infix_object_find(lex_tail()); + } + break; +case 29: +#line 221 "inform.y" +{ + z_typed v; v.t = Z_GLOBAL; + for(v.o = 0; v.o <= 245; v.o++) { + const char *name = infix_get_name(v); + if(v.o) infix_print_string("; "); + if(name) { + infix_print_string(name); + } else { + infix_print_char('G'); + infix_print_number(v.o); + } + infix_print_char('='); + infix_get_val(&v); + infix_print_number(v.v); + } + infix_print_char(10); + } + break; +case 30: +#line 239 "inform.y" +{ + z_typed v; v.t = Z_GLOBAL; + for(v.o = 0; v.o <= 245; v.o++) { + infix_get_val(&v); + if(v.v == yyvsp[0].val.v) { + const char *name = infix_get_name(v); + if(name) { + infix_print_string(name); + } else { + infix_print_char('G'); + infix_print_number(v.o); + } + infix_print_char(10); + } + } } + break; +case 31: +#line 255 "inform.y" +{ infix_set_attrib(yyvsp[-1].val.v, yyvsp[0].val.v); } + break; +case 32: +#line 257 "inform.y" +{ infix_clear_attrib(yyvsp[-2].val.v, yyvsp[0].val.v); } + break; +case 33: +#line 259 "inform.y" +{ infix_remove(yyvsp[0].val.v); } + break; +case 34: +#line 261 "inform.y" +{ PC=yyvsp[0].pcoffset; exit_debugger = TRUE; } + break; +case 35: +#line 263 "inform.y" +{ set_step(CONT_GO, 1); } + break; +case 36: +#line 265 "inform.y" +{ set_step(CONT_GO, 1); infix_set_ignore(cur_break, yyvsp[0].val.v); } + break; +case 37: +#line 267 "inform.y" +{ set_step(CONT_STEP, 1); } + break; +case 38: +#line 269 "inform.y" +{ set_step(CONT_STEP, yyvsp[0].val.v); } + break; +case 39: +#line 271 "inform.y" +{ set_step(CONT_NEXT, 1); } + break; +case 40: +#line 273 "inform.y" +{ set_step(CONT_NEXT, yyvsp[0].val.v); } + break; +case 41: +#line 275 "inform.y" +{ set_step(CONT_UNTIL, 1); } + break; +case 42: +#line 277 "inform.y" +{ set_step(CONT_STEPI, 1); } + break; +case 43: +#line 279 "inform.y" +{ set_step(CONT_STEPI, yyvsp[0].val.v); } + break; +case 44: +#line 281 "inform.y" +{ set_step(CONT_NEXTI, 1); } + break; +case 45: +#line 283 "inform.y" +{ set_step(CONT_NEXTI, yyvsp[0].val.v); } + break; +case 46: +#line 285 "inform.y" +{ set_step(CONT_FINISH, 1); } + break; +case 47: +#line 287 "inform.y" +{ infix_set_break(yyvsp[0].pcoffset); } + break; +case 48: +#line 289 "inform.y" +{ int n = infix_set_break(yyvsp[-1].pcoffset); infix_set_cond(n, lex_tail()); } + break; +case 49: +#line 291 "inform.y" +{ infix_set_cond(yyvsp[0].val.v, lex_tail()); } + break; +case 50: +#line 293 "inform.y" +{ infix_set_ignore(yyvsp[-1].val.v, yyvsp[0].val.v); } + break; +case 51: +#line 295 "inform.y" +{ infix_delete_breakpoint(yyvsp[0].val.v); } + break; +case 52: +#line 297 "inform.y" +{ infix_show_all_breakpoints(); } + break; +case 53: +#line 299 "inform.y" +{ infix_show_breakpoint(yyvsp[0].val.v); } + break; +case 54: +#line 301 "inform.y" +{ infix_set_break_enabled(yyvsp[0].val.v, FALSE); } + break; +case 55: +#line 303 "inform.y" +{ infix_set_break_enabled(yyvsp[0].val.v, TRUE); } + break; +case 56: +#line 305 "inform.y" +{ infix_print_string("The current source language is \"inform\".\n"); } + break; +case 57: +#line 307 "inform.y" +{ infix_print_string("Current source file is "); infix_print_string(cur_file?cur_file->filename:"unknown"); infix_print_string("\nContains "); infix_print_number(cur_file?cur_file->num_lines:0); infix_print_string(" lines.\nSource language is inform.\n"); } + break; +case 58: +#line 309 "inform.y" +{ infix_print_string("Source files for which symbols have been read in:\n\n"); infix_list_files(); infix_print_char('\n'); } + break; +case 59: +#line 311 "inform.y" +{ show_copying(); } + break; +case 60: +#line 313 "inform.y" +{ show_warranty(); } + break; +case 61: +#line 315 "inform.y" +{ infix_show_frame(infix_selected_frame); } + break; +case 62: +#line 317 "inform.y" +{ infix_select_frame(yyvsp[0].val.v); infix_show_frame(yyvsp[0].val.v); } + break; +case 63: +#line 319 "inform.y" +{ infix_select_frame(yyvsp[0].val.v); } + break; +case 64: +#line 321 "inform.y" +{ infix_select_frame(infix_selected_frame - 1); infix_show_frame(infix_selected_frame); } + break; +case 65: +#line 323 "inform.y" +{ infix_select_frame(infix_selected_frame - yyvsp[0].val.v); infix_show_frame(infix_selected_frame); } + break; +case 66: +#line 325 "inform.y" +{ infix_select_frame(infix_selected_frame - 1); } + break; +case 67: +#line 327 "inform.y" +{ infix_select_frame(infix_selected_frame - yyvsp[0].val.v); } + break; +case 68: +#line 329 "inform.y" +{ infix_select_frame(infix_selected_frame + 1); infix_show_frame(infix_selected_frame); } + break; +case 69: +#line 331 "inform.y" +{ infix_select_frame(infix_selected_frame + yyvsp[0].val.v); infix_show_frame(infix_selected_frame); } + break; +case 70: +#line 333 "inform.y" +{ infix_select_frame(infix_selected_frame + 1); } + break; +case 71: +#line 335 "inform.y" +{ infix_select_frame(infix_selected_frame + yyvsp[0].val.v); } + break; +case 72: +#line 337 "inform.y" +{ infix_backtrace(0, stack_get_depth()); } + break; +case 73: +#line 339 "inform.y" +{ infix_backtrace(stack_get_depth() - yyvsp[0].val.v, yyvsp[0].val.v); } + break; +case 74: +#line 341 "inform.y" +{ infix_backtrace(0, yyvsp[0].val.v); } + break; +case 75: +#line 349 "inform.y" +{ if(yyvsp[0].val.t == Z_ROUTINE) yyval.pcoffset = infix_get_routine_PC(yyvsp[0].val.v); else { infix_location l; infix_decode_fileloc(&l, cur_file?cur_file->filename:"", yyvsp[0].val.v); yyval.pcoffset = l.thisPC; } } + break; +case 76: +#line 350 "inform.y" +{ infix_location l; infix_decode_fileloc(&l, cur_file?cur_file->filename:"", cur_line + yyvsp[0].val.v); yyval.pcoffset = l.thisPC; } + break; +case 77: +#line 351 "inform.y" +{ infix_location l; infix_decode_fileloc(&l, cur_file?cur_file->filename:"", cur_line - yyvsp[0].val.v); yyval.pcoffset = l.thisPC; } + break; +case 78: +#line 352 "inform.y" +{ if(yyvsp[0].val.t == Z_ROUTINE) yyval.pcoffset = UNPACKR(yyvsp[0].val.v); else { infix_location l; infix_decode_fileloc(&l, yyvsp[-2].filenum->filename, yyvsp[0].val.v); yyval.pcoffset = l.thisPC; } } + break; +case 79: +#line 353 "inform.y" +{ yyval.pcoffset = yyvsp[0].val.v; } + break; +case 80: +#line 357 "inform.y" +{ + if(condlist->condfunc(condlist->val, yyvsp[0].val.v) ^ condlist->opposite) { + yyval.flag = TRUE; + ignoreeffects++; + } else + yyval.flag = FALSE; + } + break; +case 81: +#line 364 "inform.y" +{ + if(yyvsp[-2].flag) + yyval.flag = TRUE; + else { + if(condlist->condfunc(condlist->val, yyvsp[0].val.v) ^ condlist->opposite) { + yyval.flag = TRUE; + ignoreeffects++; + } + else yyval.flag = FALSE; + } } + break; +case 82: +#line 377 "inform.y" +{ yyval.zlist = NULL; } + break; +case 83: +#line 378 "inform.y" +{ zword_list g; yyval.zlist = yyvsp[0].zlist; g.item = yyvsp[-2].val.v; LEaddm(yyval.zlist, g, n_rmmalloc); } + break; +case 86: +#line 384 "inform.y" +{ yyval.val = yyvsp[0].val; } + break; +case 87: +#line 385 "inform.y" +{ yyval.val = yyvsp[0].val; } + break; +case 88: +#line 390 "inform.y" +{ cond_list newcond; newcond.val = yyvsp[-1].val.v; newcond.condfunc = yyvsp[0].cond.condfunc; newcond.opposite = yyvsp[0].cond.opposite; LEaddm(condlist, newcond, n_rmmalloc); } + break; +case 89: +#line 390 "inform.y" +{ if(yyvsp[0].flag) ignoreeffects--; yyval.val.v = yyvsp[0].flag; yyval.val.t = Z_BOOLEAN; LEremovem(condlist, n_rmfreeone); } + break; +case 90: +#line 395 "inform.y" +{ yyval.val = yyvsp[0].val; } + break; +case 91: +#line 397 "inform.y" +{ yyval.val.v = 0; yyval.val.t = Z_BOOLEAN; } + break; +case 92: +#line 399 "inform.y" +{ yyval.val.v = 1; yyval.val.t = Z_BOOLEAN; } + break; +case 93: +#line 401 "inform.y" +{ yyval.val.v = 0; yyval.val.t = Z_OBJECT; } + break; +case 94: +#line 404 "inform.y" +{ yyval.val = yyvsp[0].val; infix_assign(&yyvsp[-2].val, yyvsp[0].val.v); } + break; +case 95: +#line 407 "inform.y" +{ yyval.val.v = infix_parent(yyvsp[-1].val.v); yyval.val.t = Z_OBJECT; } + break; +case 96: +#line 409 "inform.y" +{ yyval.val.v = infix_child(yyvsp[-1].val.v); yyval.val.t = Z_OBJECT; } + break; +case 97: +#line 411 "inform.y" +{ yyval.val.v = infix_sibling(yyvsp[-1].val.v); yyval.val.t = Z_OBJECT; } + break; +case 98: +#line 413 "inform.y" +{ int n = 0; zword o = infix_child(yyvsp[-1].val.v); while(o) { n++; o = infix_sibling(o); } yyval.val.v = n; yyval.val.t = Z_NUMBER; } + break; +case 99: +#line 416 "inform.y" +{ + if(!ignoreeffects) { + yyval.val.v = z_random(yyvsp[-1].val.v); + yyval.val.t = Z_NUMBER; + } else { + yyval.val.v = 0; + yyval.val.t = Z_UNKNOWN; + } + } + break; +case 100: +#line 426 "inform.y" +{ + zword locals[16]; + int i = 0; + zword_list *p; + if(!ignoreeffects) { + for(p = yyvsp[-1].zlist; p && i < 16; p=p->next) { + locals[i++] = p->item; + } + mop_call(yyvsp[-3].val.v, i, locals, -2); + decode(); + exit_decoder = FALSE; + yyval.val.v = time_ret; yyval.val.t = Z_UNKNOWN; + } else { + yyval.val.v = 0; yyval.val.t = Z_UNKNOWN; + } + } + break; +case 101: +#line 443 "inform.y" +{ if(yyvsp[-1].val.v == 0) ignoreeffects++; } + break; +case 102: +#line 444 "inform.y" +{ if(yyvsp[-3].val.v == 0) ignoreeffects--; yyval.val = z_t(yyvsp[-3].val, yyvsp[0].val, yyvsp[-3].val.v && yyvsp[0].val.v); } + break; +case 103: +#line 445 "inform.y" +{ if(yyvsp[-1].val.v != 0) ignoreeffects++; } + break; +case 104: +#line 446 "inform.y" +{ if(yyvsp[-3].val.v != 0) ignoreeffects--; yyval.val = z_t(yyvsp[-3].val, yyvsp[0].val, yyvsp[-3].val.v || yyvsp[0].val.v); } + break; +case 105: +#line 448 "inform.y" +{ yyval.val.v = !(yyvsp[0].val.v); yyval.val.t = Z_NUMBER; } + break; +case 106: +#line 451 "inform.y" +{ yyval.val = z_t(yyvsp[-2].val, yyvsp[0].val, yyvsp[-2].val.v + yyvsp[0].val.v); } + break; +case 107: +#line 453 "inform.y" +{ yyval.val = z_t(yyvsp[-2].val, yyvsp[0].val, yyvsp[-2].val.v + neg(yyvsp[0].val.v)); } + break; +case 108: +#line 455 "inform.y" +{ yyval.val = z_t(yyvsp[-2].val, yyvsp[0].val, z_mult(yyvsp[-2].val.v, yyvsp[0].val.v)); } + break; +case 109: +#line 457 "inform.y" +{ yyval.val = z_t(yyvsp[-2].val, yyvsp[0].val, z_div(yyvsp[-2].val.v, yyvsp[0].val.v)); } + break; +case 110: +#line 459 "inform.y" +{ yyval.val = z_t(yyvsp[-2].val, yyvsp[0].val, z_mod(yyvsp[-2].val.v, yyvsp[0].val.v)); } + break; +case 111: +#line 461 "inform.y" +{ yyval.val = z_t(yyvsp[-2].val, yyvsp[0].val, yyvsp[-2].val.v & yyvsp[0].val.v); } + break; +case 112: +#line 463 "inform.y" +{ yyval.val = z_t(yyvsp[-2].val, yyvsp[0].val, yyvsp[-2].val.v | yyvsp[0].val.v); } + break; +case 113: +#line 465 "inform.y" +{ yyval.val = z_t(yyvsp[0].val, yyvsp[0].val, ~yyvsp[0].val.v); } + break; +case 114: +#line 468 "inform.y" +{ yyval.val.t = Z_BYTEARRAY; yyval.val.o = yyvsp[-2].val.v; yyval.val.p = yyvsp[0].val.v; infix_get_val(&yyval.val); } + break; +case 115: +#line 470 "inform.y" +{ yyval.val.t = Z_WORDARRAY; yyval.val.o = yyvsp[-2].val.v; yyval.val.p = yyvsp[0].val.v; infix_get_val(&yyval.val); } + break; +case 116: +#line 473 "inform.y" +{ yyval.val = z_t(yyvsp[0].val, yyvsp[0].val, neg(yyvsp[0].val.v)); } + break; +case 117: +#line 476 "inform.y" +{ if(!ignoreeffects) infix_assign(&yyvsp[0].val, ARITHMASK(yyvsp[0].val.v + 1)); yyval.val = yyvsp[0].val; } + break; +case 118: +#line 478 "inform.y" +{ yyval.val = yyvsp[-1].val; if(!ignoreeffects) infix_assign(&yyvsp[-1].val, ARITHMASK(yyvsp[-1].val.v + 1)); } + break; +case 119: +#line 480 "inform.y" +{ if(!ignoreeffects) infix_assign(&yyvsp[0].val, ARITHMASK(yyvsp[0].val.v + neg(1))); yyval.val = yyvsp[0].val; } + break; +case 120: +#line 482 "inform.y" +{ yyval.val = yyvsp[-1].val; if(!ignoreeffects) infix_assign(&yyvsp[-1].val, ARITHMASK(yyvsp[-1].val.v + neg(1))); } + break; +case 121: +#line 485 "inform.y" +{ zword len; yyval.val.v = infix_get_proptable(yyvsp[-2].val.v, yyvsp[0].val.v, &len); yyval.val.t = Z_NUMBER; } + break; +case 122: +#line 487 "inform.y" +{ infix_get_proptable(yyvsp[-2].val.v, yyvsp[0].val.v, &yyval.val.v); yyval.val.t = Z_NUMBER; } + break; +case 123: +#line 490 "inform.y" +{ yyval.val.t = Z_OBJPROP; yyval.val.o = yyvsp[-2].val.v; yyval.val.p = yyvsp[0].val.v; infix_get_val(&yyval.val); } + break; +case 124: +#line 498 "inform.y" +{ yyval.val.v = yyvsp[0].val.v; yyval.val.t = Z_NUMBER; } + break; +case 125: +#line 500 "inform.y" +{ yyval.val.v = yyvsp[0].val.v; yyval.val.t = Z_OBJECT; } + break; +case 126: +#line 502 "inform.y" +{ yyval.val.v = yyvsp[0].val.v; yyval.val.t = Z_ROUTINE; } + break; +case 127: +#line 504 "inform.y" +{ yyval.val.v = yyvsp[0].val.v; yyval.val.t = Z_STRING; } + break; +case 128: +#line 506 "inform.y" +{ yyval.val.t = Z_WORDARRAY; yyval.val.o = z_globaltable; yyval.val.p = yyvsp[0].val.v; infix_get_val(&yyval.val); } + break; +case 129: +#line 508 "inform.y" +{ yyval.val.t = Z_LOCAL; yyval.val.o = infix_selected_frame; yyval.val.p = yyvsp[0].val.v; infix_get_val(&yyval.val); } + break; +case 130: +#line 510 "inform.y" +{ yyval.val = yyvsp[-1].val; } + break; +} + +#line 705 "/usr/share/bison/bison.simple" + + + yyvsp -= yylen; + yyssp -= yylen; +#if YYLSP_NEEDED + yylsp -= yylen; +#endif + +#if YYDEBUG + if (yydebug) + { + short *yyssp1 = yyss - 1; + YYFPRINTF (stderr, "state stack now"); + while (yyssp1 != yyssp) + YYFPRINTF (stderr, " %d", *++yyssp1); + YYFPRINTF (stderr, "\n"); + } +#endif + + *++yyvsp = yyval; +#if YYLSP_NEEDED + *++yylsp = yyloc; +#endif + + /* Now `shift' the result of the reduction. Determine what state + that goes to, based on the state we popped back to and the rule + number reduced by. */ + + yyn = yyr1[yyn]; + + yystate = yypgoto[yyn - YYNTBASE] + *yyssp; + if (yystate >= 0 && yystate <= YYLAST && yycheck[yystate] == *yyssp) + yystate = yytable[yystate]; + else + yystate = yydefgoto[yyn - YYNTBASE]; + + goto yynewstate; + + +/*------------------------------------. +| yyerrlab -- here on detecting error | +`------------------------------------*/ +yyerrlab: + /* If not already recovering from an error, report this error. */ + if (!yyerrstatus) + { + ++yynerrs; + +#ifdef YYERROR_VERBOSE + yyn = yypact[yystate]; + + if (yyn > YYFLAG && yyn < YYLAST) + { + YYSIZE_T yysize = 0; + char *yymsg; + int yyx, yycount; + + yycount = 0; + /* Start YYX at -YYN if negative to avoid negative indexes in + YYCHECK. */ + for (yyx = yyn < 0 ? -yyn : 0; + yyx < (int) (sizeof (yytname) / sizeof (char *)); yyx++) + if (yycheck[yyx + yyn] == yyx) + yysize += yystrlen (yytname[yyx]) + 15, yycount++; + yysize += yystrlen ("parse error, unexpected ") + 1; + yysize += yystrlen (yytname[YYTRANSLATE (yychar)]); + yymsg = (char *) YYSTACK_ALLOC (yysize); + if (yymsg != 0) + { + char *yyp = yystpcpy (yymsg, "parse error, unexpected "); + yyp = yystpcpy (yyp, yytname[YYTRANSLATE (yychar)]); + + if (yycount < 5) + { + yycount = 0; + for (yyx = yyn < 0 ? -yyn : 0; + yyx < (int) (sizeof (yytname) / sizeof (char *)); + yyx++) + if (yycheck[yyx + yyn] == yyx) + { + const char *yyq = ! yycount ? ", expecting " : " or "; + yyp = yystpcpy (yyp, yyq); + yyp = yystpcpy (yyp, yytname[yyx]); + yycount++; + } + } + yyerror (yymsg); + YYSTACK_FREE (yymsg); + } + else + yyerror ("parse error; also virtual memory exhausted"); + } + else +#endif /* defined (YYERROR_VERBOSE) */ + yyerror ("parse error"); + } + goto yyerrlab1; + + +/*--------------------------------------------------. +| yyerrlab1 -- error raised explicitly by an action | +`--------------------------------------------------*/ +yyerrlab1: + if (yyerrstatus == 3) + { + /* If just tried and failed to reuse lookahead token after an + error, discard it. */ + + /* return failure if at end of input */ + if (yychar == YYEOF) + YYABORT; + YYDPRINTF ((stderr, "Discarding token %d (%s).\n", + yychar, yytname[yychar1])); + yychar = YYEMPTY; + } + + /* Else will try to reuse lookahead token after shifting the error + token. */ + + yyerrstatus = 3; /* Each real token shifted decrements this */ + + goto yyerrhandle; + + +/*-------------------------------------------------------------------. +| yyerrdefault -- current state does not do anything special for the | +| error token. | +`-------------------------------------------------------------------*/ +yyerrdefault: +#if 0 + /* This is wrong; only states that explicitly want error tokens + should shift them. */ + + /* If its default is to accept any token, ok. Otherwise pop it. */ + yyn = yydefact[yystate]; + if (yyn) + goto yydefault; +#endif + + +/*---------------------------------------------------------------. +| yyerrpop -- pop the current state because it cannot handle the | +| error token | +`---------------------------------------------------------------*/ +yyerrpop: + if (yyssp == yyss) + YYABORT; + yyvsp--; + yystate = *--yyssp; +#if YYLSP_NEEDED + yylsp--; +#endif + +#if YYDEBUG + if (yydebug) + { + short *yyssp1 = yyss - 1; + YYFPRINTF (stderr, "Error: state stack now"); + while (yyssp1 != yyssp) + YYFPRINTF (stderr, " %d", *++yyssp1); + YYFPRINTF (stderr, "\n"); + } +#endif + +/*--------------. +| yyerrhandle. | +`--------------*/ +yyerrhandle: + yyn = yypact[yystate]; + if (yyn == YYFLAG) + goto yyerrdefault; + + yyn += YYTERROR; + if (yyn < 0 || yyn > YYLAST || yycheck[yyn] != YYTERROR) + goto yyerrdefault; + + yyn = yytable[yyn]; + if (yyn < 0) + { + if (yyn == YYFLAG) + goto yyerrpop; + yyn = -yyn; + goto yyreduce; + } + else if (yyn == 0) + goto yyerrpop; + + if (yyn == YYFINAL) + YYACCEPT; + + YYDPRINTF ((stderr, "Shifting error token, ")); + + *++yyvsp = yylval; +#if YYLSP_NEEDED + *++yylsp = yylloc; +#endif + + yystate = yyn; + goto yynewstate; + + +/*-------------------------------------. +| yyacceptlab -- YYACCEPT comes here. | +`-------------------------------------*/ +yyacceptlab: + yyresult = 0; + goto yyreturn; + +/*-----------------------------------. +| yyabortlab -- YYABORT comes here. | +`-----------------------------------*/ +yyabortlab: + yyresult = 1; + goto yyreturn; + +/*---------------------------------------------. +| yyoverflowab -- parser overflow comes here. | +`---------------------------------------------*/ +yyoverflowlab: + yyerror ("parser stack overflow"); + yyresult = 2; + /* Fall through. */ + +yyreturn: +#ifndef yyoverflow + if (yyss != yyssa) + YYSTACK_FREE (yyss); +#endif + return yyresult; +} +#line 514 "inform.y" + + +#if 0 +{ /* fanagling to get emacs indentation sane */ +int foo; +#endif + +static z_typed z_t(z_typed a, z_typed b, zword v) +{ + z_typed r; + r.v = ARITHMASK(v); + if(a.t == Z_NUMBER && b.t == Z_NUMBER) + r.t = Z_NUMBER; + else + r.t = Z_UNKNOWN; + return r; +} + + + +typedef struct { + int token; + const char *name; +} name_token; + +static name_token infix_operators[] = { + { ANDAND, "&&" }, + { OROR, "||" }, + { NOTNOT, "~~" }, + { BYTEARRAY, "->" }, + { WORDARRAY, "-->" }, + { NUMBER, "(number)" }, + { OBJECT, "(object)" }, + { ROUTINE, "(routine)" }, + { STRING, "(string)" }, + { GLOBAL, "(global)" }, + { LOCAL, "(local)" }, + { INCREMENT, "++" }, + { DECREMENT, "--" }, + { SUPERCLASS, "::" } +}; + + +static name_token infix_keywords[] = { + { TO, "to" }, + { IF, "if" }, + { OR, "or" }, + { BTRUE, "true" }, + { BFALSE, "false" }, + { NOTHING, "nothing" }, + { PARENT, "parent" }, + { CHILD, "child" }, + { SIBLING, "sibling" }, + { RANDOM, "random" }, + { CHILDREN, "children" } +}; + + +/* These are only valid as the first token in an expression. A single space + matches at least one typed whitespace character */ +static name_token infix_commands[] = { + { '#', "#" }, + { HELP, "help" }, + { ALIAS, "alias" }, + { RALIAS, "ralias" }, + { UNALIAS, "unalias" }, + { DUMPMEM, "dumpmem" }, + { AUTOMAP, "automap" }, + { UNDO, "undo" }, + { REDO, "redo" }, + { QUIT, "quit" }, + { RESTORE, "restore" }, + { RESTART, "restart" }, + { RESTART, "run" }, + { RECORDON, "recording on" }, + { RECORDOFF, "recording off" }, + { REPLAY, "replay" }, + { REPLAYOFF, "replay off" }, + { SYMBOL_FILE, "symbol-file" }, + { PRINT, "print" }, + { PRINT, "p" }, + { PRINT, "call" }, /* No void functions in inform */ + { SET, "set" }, + { MOVE, "move" }, + { OBJECT_TREE, "object-tree" }, + { OBJECT_TREE, "tree" }, + { FIND, "find" }, + { REMOVE, "remove" }, + { GIVE, "give" }, + { LIST_GLOBALS, "globals" }, + { JUMP, "jump" }, + { CONT, "continue" }, + { CONT, "c" }, + { CONT, "fg" }, + { STEP, "step" }, + { STEP, "s" }, + { NEXT, "next" }, + { NEXT, "n" }, + { STEPI, "stepi" }, + { STEPI, "si" }, + { NEXTI, "nexti" }, + { NEXTI, "ni" }, + { UNTIL, "until" }, + { UNTIL, "u" }, + { FINISH, "finish" }, + { BREAK, "break" }, + { DELETE, "delete" }, + { DELETE, "d" }, + { DELETE, "delete breakpoints" }, + { COND, "condition" }, + { IGNORE, "ignore" }, + { FRAME, "frame" }, + { FRAME, "f" }, + { SELECT_FRAME, "select-frame" }, + { UP_FRAME, "up" }, + { DOWN_FRAME, "down" }, + { DOWN_FRAME, "do" }, + { UP_SILENTLY, "up-silently" }, + { DOWN_SILENTLY,"down-silently" }, + { BREAKPOINTS, "info breakpoints" }, + { BREAKPOINTS, "info watchpoints" }, + { BREAKPOINTS, "info break" }, + { DISABLE_BREAK,"disable" }, + { DISABLE_BREAK,"disable breakpoints" }, + { DISABLE_BREAK,"dis" }, + { DISABLE_BREAK,"dis breakpoints" }, + { ENABLE_BREAK, "enable" }, + { ENABLE_BREAK, "enable breakpoints" }, + { LANGUAGE, "show language" }, + { INFOSOURCE, "info source" }, + { INFOSOURCES, "info sources" }, + { COPYING, "show copying" }, + { WARRANTY, "show warranty" }, + { BACKTRACE, "backtrace" }, + { BACKTRACE, "bt" }, + { BACKTRACE, "where" }, + { BACKTRACE, "info stack" }, + { BACKTRACE, "info s" }, + { DISPLAY, "display" }, + { UNDISPLAY, "undisplay" }, + { UNDISPLAY, "delete display" }, + { DISABLE_DISPLAY,"disable display" }, + { DISABLE_DISPLAY,"dis display" }, + { ENABLE_DISPLAY,"enable display" } +}; + +#include "dbg_help.h" + +static BOOL z_isequal(zword a, zword b) +{ + return (a == b); +} + +static BOOL z_isgreat(zword a, zword b) +{ + return is_greaterthan(a, b); +} + +static BOOL z_isless(zword a, zword b) +{ + return is_lessthan(a, b); +} + +static BOOL infix_provides(zword o, zword p) +{ + zword len; + return (infix_get_proptable(o, p, &len) != 0); +} + +static BOOL infix_in(zword a, zword b) +{ + return infix_parent(a) == b; +} + +typedef struct { + const char *name; + BOOL (*condfunc)(zword a, zword b); + BOOL opposite; +} condition; + +condition conditionlist[] = { + { "==", z_isequal, FALSE }, + { "~=", z_isequal, TRUE }, + { ">", z_isgreat, FALSE }, + { "<", z_isless, FALSE }, + { "<=", z_isgreat, TRUE }, + { ">=", z_isless, TRUE }, + { "has", infix_test_attrib, FALSE }, + { "hasnt", infix_test_attrib, TRUE }, + { "in", infix_in, FALSE }, + { "notin", infix_in, TRUE }, +/*{ "ofclass", infix_ofclass, FALSE },*/ + { "provides",infix_provides, FALSE } +}; + + +static BOOL is_command_identifier(char c) +{ + return isalpha(c) || (c == '-'); +} + +static BOOL is_identifier(char c) +{ + return isalpha(c) || isdigit(c) || (c == '_'); +} + +static BOOL is_longer_identifier(char c) +{ + return isalpha(c) || isdigit(c) || (c == '_') || (c == '.') || (c == ':'); +} + +static int grab_number(z_typed *val) +{ + int len = 0; + char *endptr; + char c = lex_expression[lex_offset + len]; + int base = 10; + long int num; + + /* Don't handle negativity here */ + if(c == '-' || c == '+') + return 0; + + if(c == '$') { + len++; + base = 16; + c = lex_expression[lex_offset + len]; + if(c == '$') { + len++; + base = 2; + c = lex_expression[lex_offset + len]; + } + } + + num = n_strtol(lex_expression + lex_offset + len, &endptr, base); + + if(endptr != lex_expression + lex_offset) { + len += endptr - lex_expression - lex_offset; + val->v = num; + val->t = Z_NUMBER; + return len; + } + return 0; +} + + +typedef enum { match_None, match_Partial, match_Complete } match_type; + +static match_type command_matches(const char *command, const char *expression, + unsigned *matchedlen) +{ + unsigned c, e; + e = 0; + + for(c = 0; command[c]; c++) { + if(command[c] != expression[e]) { + if(!is_command_identifier(expression[e])) { + *matchedlen = e; + return match_Partial; + } + return match_None; + } + + e++; + + if(command[c] == ' ') { + while(expression[e] == ' ') + e++; + } + } + + if(!is_command_identifier(expression[e])) { + *matchedlen = e; + return match_Complete; + } + + return match_None; +} + + +static int grab_command(void) +{ + unsigned i; + unsigned len; + + unsigned best; + match_type best_match = match_None; + unsigned best_len = 0; + BOOL found = FALSE; + BOOL ambig = FALSE; + + while(isspace(lex_expression[lex_offset])) + lex_offset++; + + for(i = 0; i < sizeof(infix_commands) / sizeof(*infix_commands); i++) { + switch(command_matches(infix_commands[i].name, lex_expression + lex_offset, &len)) { + case match_Complete: + if(len > best_len || best_match != match_Complete) { + best = i; + best_match = match_Complete; + best_len = len; + found = TRUE; + } + break; + + case match_Partial: + if(best_match != match_Complete) { + if(found) + ambig = TRUE; + best = i; + best_match = match_Partial; + best_len = len; + found = TRUE; + } + + case match_None: + ; + } + } + + if(ambig && best_match != match_Complete) { + infix_print_string("Ambiguous command.\n"); + return 0; + } + + if(found) { + lex_offset += best_len; + return infix_commands[best].token; + } + + infix_print_string("Undefined command.\n"); + return 0; +} + + +static void inform_help(void) +{ + int command; + unsigned i; + BOOL is_command = FALSE; + + for(i = lex_offset; lex_expression[i]; i++) + if(!isspace(lex_expression[i])) + is_command = TRUE; + + if(!is_command) { + infix_print_string("Help is available on the following commands:\n"); + for(i = 0; i < sizeof(command_help) / sizeof(*command_help); i++) { + unsigned j; + for(j = 0; j < sizeof(infix_commands) / sizeof(*infix_commands); j++) + if(command_help[i].token == infix_commands[j].token) { + infix_print_char('\''); + infix_print_string(infix_commands[j].name); + infix_print_char('\''); + break; + } + infix_print_char(' '); + } + infix_print_string("\n"); + return; + } + + command = grab_command(); + if(command) { + for(i = 0; i < sizeof(command_help) / sizeof(*command_help); i++) { + if(command_help[i].token == command) { + infix_print_string(command_help[i].name); + infix_print_char(10); + return; + } + } + infix_print_string("No help available for that command.\n"); + } +} + + +void process_debug_command(const char *buffer) +{ +#if YYDEBUG + yydebug = 1; +#endif + lex_expression = buffer; + lex_offset = 0; + ignoreeffects = 0; + yyparse(); + n_rmfree(); +} + +BOOL exp_has_locals(const char *exp) +{ + return FALSE; +} + +z_typed evaluate_expression(const char *exp, unsigned frame) +{ + unsigned old_frame = infix_selected_frame; + char *new_exp = (char *) n_malloc(n_strlen(exp) + 5); + n_strcpy(new_exp, "set "); + n_strcat(new_exp, exp); + + infix_selected_frame = frame; + process_debug_command(new_exp); + infix_selected_frame = old_frame; + + n_free(new_exp); + + return inform_result; +} + +static void yyerror(const char *s) +{ + infix_print_string(s); + infix_print_char(10); +} + +static int yylex(void) +{ + unsigned i, len, longer; + BOOL check_command = FALSE; + + if(lex_offset == 0) + check_command = TRUE; + + while(isspace(lex_expression[lex_offset])) + lex_offset++; + + if(check_command) { + return grab_command(); + } + + if((len = grab_number(&yylval.val)) != 0) { + lex_offset += len; + return NUM; + } + + for(i = 0; i < sizeof(infix_operators) / sizeof(*infix_operators); i++) { + if(n_strncmp(infix_operators[i].name, lex_expression + lex_offset, + n_strlen(infix_operators[i].name)) == 0) { + lex_offset += n_strlen(infix_operators[i].name); + return infix_operators[i].token; + } + } + + for(i = 0; i < sizeof(conditionlist) / sizeof(*conditionlist); i++) { + len = n_strlen(conditionlist[i].name); + if(len + && n_strncmp(conditionlist[i].name, + lex_expression + lex_offset, len) == 0 + && !(is_identifier(conditionlist[i].name[len-1]) + && is_identifier(lex_expression[lex_offset + len]))) { + + lex_offset += len; + yylval.cond.condfunc = conditionlist[i].condfunc; + yylval.cond.opposite = conditionlist[i].opposite; + return CONDITION; + } + } + + if((len = infix_find_file(&yylval.filenum, lex_expression + lex_offset)) != 0) { + lex_offset += len; + return DFILE; + } + + + for(len = 0; is_identifier(lex_expression[lex_offset + len]); len++) + ; + + if(!len) + return lex_expression[lex_offset++]; + + for(i = 0; i < sizeof(infix_keywords) / sizeof(*infix_keywords); i++) { + if(n_strmatch(infix_keywords[i].name, lex_expression + lex_offset, len)) { + lex_offset += len; + return infix_keywords[i].token; + } + } + + for(longer = len; is_longer_identifier(lex_expression[lex_offset + longer]); longer++) + ; + + if(infix_find_symbol(&yylval.val, lex_expression + lex_offset, longer)) { + lex_offset += longer; + return NUM; + } + + if(infix_find_symbol(&yylval.val, lex_expression + lex_offset, len)) { + lex_offset += len; + return NUM; + } + + infix_print_string("Unknown identifier \""); + for(i = 0; i < len; i++) + infix_print_char(lex_expression[lex_offset + i]); + infix_print_string("\"\n"); + + return 0; +} + +#endif /* DEBUGGING */ diff --git a/interpreters/nitfol/inform.h b/interpreters/nitfol/inform.h new file mode 100644 index 0000000..6ef60ca --- /dev/null +++ b/interpreters/nitfol/inform.h @@ -0,0 +1,5 @@ +#ifdef DEBUGGING +void process_debug_command(const char *buffer); +BOOL exp_has_locals(const char *exp); +z_typed evaluate_expression(const char *exp, unsigned frame); +#endif diff --git a/interpreters/nitfol/inform.hhh b/interpreters/nitfol/inform.hhh new file mode 100644 index 0000000..6ef60ca --- /dev/null +++ b/interpreters/nitfol/inform.hhh @@ -0,0 +1,5 @@ +#ifdef DEBUGGING +void process_debug_command(const char *buffer); +BOOL exp_has_locals(const char *exp); +z_typed evaluate_expression(const char *exp, unsigned frame); +#endif diff --git a/interpreters/nitfol/inform.y b/interpreters/nitfol/inform.y new file mode 100644 index 0000000..2c4c1ba --- /dev/null +++ b/interpreters/nitfol/inform.y @@ -0,0 +1,1012 @@ +%{ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at ecr+@andrew.cmu.edu +*/ + +#include "nitfol.h" +#include + +/* bison uses str* functions; make it use n_str* instead... */ +#ifndef n_strcat +#define strcat(d, s) n_strcat(d, s) +#endif +#ifndef n_strlen +#define strlen(s) n_strlen(s) +#endif +#ifndef n_strcpy +#define strcpy(d, s) n_strcpy(d, s) +#endif + + +#ifdef DEBUGGING + + typedef struct zword_list zword_list; + struct zword_list { + zword_list *next; + zword item; + }; + + typedef struct cond_list cond_list; + struct cond_list { + cond_list *next; + zword val; + BOOL (*condfunc)(zword a, zword b); + BOOL opposite; + }; + + cond_list *condlist; + + static z_typed z_t(z_typed a, z_typed b, zword v); + + static const char *lex_expression; + static int lex_offset; + + static const char *lex_tail(void) { + const char *t = lex_expression + lex_offset; + while(*t == ' ') + t++; + lex_offset = n_strlen(lex_expression); + return t; + } + + static z_typed inform_result; + + static int yylex(void); + static void yyerror(const char *s); + static void inform_help(void); + + int ignoreeffects; + +#define YYERROR_VERBOSE + +/* +#define YYDEBUG 1 +*/ + +%} + +%union { + glui32 pcoffset; + infix_file *filenum; + z_typed val; + + zword_list *zlist; + + struct { + BOOL (*condfunc)(zword a, zword b); + BOOL opposite; + } cond; + + BOOL flag; +} + +%token NUM +%token DFILE +%token CONDITION +%type commaexp +%type condexp +%type exp +%type linespec +%type arglist +%type orlist + + + +%token ALIAS RALIAS UNALIAS DUMPMEM AUTOMAP HELP UNDO REDO LANGUAGE INFOSOURCE INFOSOURCES COPYING WARRANTY PRINT SET MOVE TO GIVE REMOVE JUMP CONT STEP NEXT UNTIL STEPI NEXTI FINISH BREAK DELETE IF COND IGNORE BREAKPOINTS RESTORE RESTART QUIT RECORDON RECORDOFF REPLAY REPLAYOFF SYMBOL_FILE FRAME SELECT_FRAME BACKTRACE UP_FRAME DOWN_FRAME UP_SILENTLY DOWN_SILENTLY DISPLAY UNDISPLAY DISABLE_DISPLAY ENABLE_DISPLAY DISABLE_BREAK ENABLE_BREAK OBJECT_TREE FIND LIST_GLOBALS BTRUE BFALSE NOTHING PARENT CHILD SIBLING CHILDREN RANDOM + +%left ',' +%right '=' +%left ANDAND OROR NOTNOT +%nonassoc CONDITION +%left OR +%left '+' '-' +%left '*' '/' '%' '&' '|' '~' +%left BYTEARRAY WORDARRAY +%nonassoc precNEG +%nonassoc NUMBER OBJECT ROUTINE STRING GLOBAL LOCAL +%nonassoc INCREMENT DECREMENT +%left PROPADDR PROPLENGTH +%left '(' /* function calls */ +%left '.' +%left SUPERCLASS + +%% +input: /* empty string */ +/* :: Enter a comment */ + | '#' /* comment */ { lex_offset = n_strlen(lex_expression); } +/* :: Dump memory to a file */ + | DUMPMEM /*FILE*/ { + strid_t f; + f = n_file_name_or_prompt(fileusage_Data|fileusage_BinaryMode, + filemode_Write, lex_tail()); + w_glk_put_buffer_stream(f, (char *) z_memory, total_size); + glk_stream_close(f, NULL); + } +/* :: Add an alias */ + | ALIAS /*@var{name} @var{value}*/ { parse_new_alias(lex_tail(), FALSE); } +/* :: Add a recursive alias */ + | RALIAS /*@var{name} @var{value}*/ { parse_new_alias(lex_tail(), TRUE); } +/* :: Remove an alias */ + | UNALIAS /*@var{name}*/{ remove_alias(lex_tail()); } +/* :: Start automapping */ + | AUTOMAP /*commaexp*/ { automap_init(object_count, lex_tail()); } +/* :: Print list of commands. */ + | HELP { inform_help(); } +/* :: Restart the game. */ + | RESTART { op_restart(); exit_debugger = TRUE; read_abort = TRUE; } +/* :: Restore a saved game. */ + | RESTORE { + if(restoregame()) { + exit_debugger = TRUE; read_abort = TRUE; + if(zversion <= 3) + mop_take_branch(); + else + mop_store_result(2); + } else { + infix_print_string("Restore failed.\n"); + } } +/* :: Start recording a script. */ + | RECORDON { zword oldop0 = operand[0]; operand[0] = 4; op_output_stream(); operand[0] = oldop0; } +/* :: Stop recording a script. */ + | RECORDOFF { zword oldop0 = operand[0]; operand[0] = neg(4); op_output_stream(); operand[0] = oldop0; } +/* :: Replay a recorded script. */ + | REPLAY { zword oldop0 = operand[0]; operand[0] = 1; op_input_stream(); operand[0] = oldop0; exit_debugger = TRUE; } +/* :: Halt replay. */ + | REPLAYOFF { zword oldop0 = operand[0]; operand[0] = 0; op_input_stream(); operand[0] = oldop0; } +/* :: Exit nitfol. */ + | QUIT { z_close(); glk_exit(); } +/* :: Undo last move (not last debugger command). */ + | UNDO { + if(restoreundo()) { + read_abort = TRUE; exit_debugger = TRUE; + } else { + infix_print_string("No undo slots.\n"); + } } +/* :: Redo undid move. Only works immediately after an @code{undo}. */ + | REDO { + if(restoreredo()) { + read_abort = TRUE; exit_debugger = TRUE; + } else { + infix_print_string("No redo slots.\n"); + } } +/* :: Load debugging info from a file (usually @file{gameinfo.dbg}). */ + | SYMBOL_FILE /*FILE*/ { + strid_t f; + f = n_file_name_or_prompt(fileusage_Data|fileusage_BinaryMode, + filemode_Read, lex_tail()); + if(f) { + kill_infix(); + init_infix(f); + } } +/* :: Evaluates an expression and prints the result.\nThis can include function calls. */ + | PRINT commaexp { infix_display($2); } +/* :: Evaluate an expression without printing its value. */ + | SET commaexp { inform_result = $2; } +/* :: Print value of an expression each time the program stops. */ + | DISPLAY /*commaexp*/ { infix_auto_display(lex_tail()); } +/* :: Stop automatically displaying an expression. */ + | UNDISPLAY NUM { infix_auto_undisplay($2.v); } +/* :: Temporarily disable an automatic display. */ + | DISABLE_DISPLAY NUM { infix_set_display_enabled($2.v, FALSE); } +/* :: Re-enable an automatic display. */ + | ENABLE_DISPLAY NUM { infix_set_display_enabled($2.v, TRUE); } +/* :: Move an object around the object tree. */ + | MOVE commaexp TO commaexp { infix_move($4.v, $2.v); } +/* :: Display the object tree. */ + | OBJECT_TREE { infix_object_tree(0); } +/* :: An argument says which object to use as the root of the tree. */ + | OBJECT_TREE commaexp { infix_object_tree($2.v); } +/* :: Find objects whose shortnames contain a string. */ + | FIND { + if(lex_expression[lex_offset]) + infix_object_find(lex_tail()); + } +/* :: List all global variables and their values. */ + | LIST_GLOBALS { + z_typed v; v.t = Z_GLOBAL; + for(v.o = 0; v.o <= 245; v.o++) { + const char *name = infix_get_name(v); + if(v.o) infix_print_string("; "); + if(name) { + infix_print_string(name); + } else { + infix_print_char('G'); + infix_print_number(v.o); + } + infix_print_char('='); + infix_get_val(&v); + infix_print_number(v.v); + } + infix_print_char(10); + } +/* :: With an argument, list all only those with a specific value. */ + | LIST_GLOBALS commaexp { + z_typed v; v.t = Z_GLOBAL; + for(v.o = 0; v.o <= 245; v.o++) { + infix_get_val(&v); + if(v.v == $2.v) { + const char *name = infix_get_name(v); + if(name) { + infix_print_string(name); + } else { + infix_print_char('G'); + infix_print_number(v.o); + } + infix_print_char(10); + } + } } +/* :: Give an object an attribute. */ + | GIVE commaexp NUM { infix_set_attrib($2.v, $3.v); } +/* :: With a tilde clears the attribute instead of setting it. */ + | GIVE commaexp '~' NUM { infix_clear_attrib($2.v, $4.v); } +/* :: Remove an object from the object tree. */ + | REMOVE commaexp { infix_remove($2.v); } +/* :: Continue execution at a new location. */ + | JUMP linespec { PC=$2; exit_debugger = TRUE; } +/* :: Continue execution. */ + | CONT { set_step(CONT_GO, 1); } +/* :: An argument sets the ignore count of the current breakpoint. */ + | CONT NUM { set_step(CONT_GO, 1); infix_set_ignore(cur_break, $2.v); } +/* :: Step through program to a different source line. */ + | STEP { set_step(CONT_STEP, 1); } +/* :: An argument specifies a repeat count. */ + | STEP NUM { set_step(CONT_STEP, $2.v); } +/* :: Step through program, stepping over subroutine calls. */ + | NEXT { set_step(CONT_NEXT, 1); } +/* :: An argument specifies a repeat count. */ + | NEXT NUM { set_step(CONT_NEXT, $2.v); } +/* :: Resume execution until the program reaches a line number greater than the current line. */ + | UNTIL { set_step(CONT_UNTIL, 1); } +/* :: Step exactly one instruction. */ + | STEPI { set_step(CONT_STEPI, 1); } +/* :: An argument specifies a repeat count. */ + | STEPI NUM { set_step(CONT_STEPI, $2.v); } +/* :: Step one instruction, stepping over subroutine calls. */ + | NEXTI { set_step(CONT_NEXTI, 1); } +/* :: Step a specified number of instructions, stepping over subroutine calls. */ + | NEXTI NUM { set_step(CONT_NEXTI, $2.v); } +/* :: An argument specifies a repeat count. */ + | FINISH { set_step(CONT_FINISH, 1); } +/* :: Set a breakpoint. */ + | BREAK linespec { infix_set_break($2); } +/* :: An @code{if} clause specifies a condition. */ + | BREAK linespec IF /*commaexp*/ { int n = infix_set_break($2); infix_set_cond(n, lex_tail()); } +/* :: Set a condition for an existing breakpoint. */ + | COND NUM /*commaexp*/ { infix_set_cond($2.v, lex_tail()); } +/* :: Set the ignore count for a breakpoint. */ + | IGNORE NUM NUM { infix_set_ignore($2.v, $3.v); } +/* :: Delete a breakpoint. */ + | DELETE NUM { infix_delete_breakpoint($2.v); } +/* :: List breakpoints. */ + | BREAKPOINTS { infix_show_all_breakpoints(); } +/* :: An argument specifies a specific breakpoint to list. */ + | BREAKPOINTS NUM { infix_show_breakpoint($2.v); } +/* :: Temporarily disable a breakpoint. */ + | DISABLE_BREAK NUM { infix_set_break_enabled($2.v, FALSE); } +/* :: Re-enabled a breakpoint. */ + | ENABLE_BREAK NUM { infix_set_break_enabled($2.v, TRUE); } +/* :: Show the current source language. */ + | LANGUAGE { infix_print_string("The current source language is \"inform\".\n"); } +/* :: Get information on the current source file. */ + | INFOSOURCE { infix_print_string("Current source file is "); infix_print_string(cur_file?cur_file->filename:"unknown"); infix_print_string("\nContains "); infix_print_number(cur_file?cur_file->num_lines:0); infix_print_string(" lines.\nSource language is inform.\n"); } +/* :: List source files. */ + | INFOSOURCES { infix_print_string("Source files for which symbols have been read in:\n\n"); infix_list_files(); infix_print_char('\n'); } +/* :: Show licensing information. */ + | COPYING { show_copying(); } +/* :: Show warranty information. */ + | WARRANTY { show_warranty(); } +/* :: Show the selected stack frame. */ + | FRAME { infix_show_frame(infix_selected_frame); } +/* :: An argument specifies a stack frame to show. */ + | FRAME NUM { infix_select_frame($2.v); infix_show_frame($2.v); } +/* :: Select a specific stack frame. */ + | SELECT_FRAME NUM { infix_select_frame($2.v); } +/* :: Select the parent of the selected frame. */ + | UP_FRAME { infix_select_frame(infix_selected_frame - 1); infix_show_frame(infix_selected_frame); } +/* :: An argument specifies how many frames up to go. */ + | UP_FRAME NUM { infix_select_frame(infix_selected_frame - $2.v); infix_show_frame(infix_selected_frame); } +/* :: Select the parent of the selected frame silently. */ + | UP_SILENTLY { infix_select_frame(infix_selected_frame - 1); } +/* :: An argument specifies how many frames up to go. */ + | UP_SILENTLY NUM { infix_select_frame(infix_selected_frame - $2.v); } +/* :: Select the child of the selected frame. */ + | DOWN_FRAME { infix_select_frame(infix_selected_frame + 1); infix_show_frame(infix_selected_frame); } +/* :: An argument specifies how many frames down to go. */ + | DOWN_FRAME NUM { infix_select_frame(infix_selected_frame + $2.v); infix_show_frame(infix_selected_frame); } +/* :: Silently select the child of the selected frame. */ + | DOWN_SILENTLY { infix_select_frame(infix_selected_frame + 1); } +/* :: An argument specifies how many frames down to go. */ + | DOWN_SILENTLY NUM { infix_select_frame(infix_selected_frame + $2.v); } +/* :: Display the parent functions of the current frame. */ + | BACKTRACE { infix_backtrace(0, stack_get_depth()); } +/* :: An argument specifies how many frames back to show. */ + | BACKTRACE NUM { infix_backtrace(stack_get_depth() - $2.v, $2.v); } +/* :: If the argument is negative, start from the first frame instead of the current. */ + | BACKTRACE '-' NUM { infix_backtrace(0, $3.v); } +/* + | LIST { infix_print_more(); } + | LIST '-' { infix_print_before(); } + | LIST NUM { if($1.t == Z_ROUTINE) { infix_location loc; infix_decode_; infix_file_print_around(...); }; else infix_file_print_around(cur_location.file, $2.v); } +*/ +; + +linespec: NUM { if($1.t == Z_ROUTINE) $$ = infix_get_routine_PC($1.v); else { infix_location l; infix_decode_fileloc(&l, cur_file?cur_file->filename:"", $1.v); $$ = l.thisPC; } } + | '+' NUM { infix_location l; infix_decode_fileloc(&l, cur_file?cur_file->filename:"", cur_line + $2.v); $$ = l.thisPC; } + | '-' NUM { infix_location l; infix_decode_fileloc(&l, cur_file?cur_file->filename:"", cur_line - $2.v); $$ = l.thisPC; } + | DFILE ':' NUM { if($3.t == Z_ROUTINE) $$ = UNPACKR($3.v); else { infix_location l; infix_decode_fileloc(&l, $1->filename, $3.v); $$ = l.thisPC; } } + | '*' NUM { $$ = $2.v; } +; + + +orlist: exp { + if(condlist->condfunc(condlist->val, $1.v) ^ condlist->opposite) { + $$ = TRUE; + ignoreeffects++; + } else + $$ = FALSE; + } + | orlist OR exp { + if($1) + $$ = TRUE; + else { + if(condlist->condfunc(condlist->val, $3.v) ^ condlist->opposite) { + $$ = TRUE; + ignoreeffects++; + } + else $$ = FALSE; + } } +; + + +arglist: /* empty string */ { $$ = NULL; } + | exp ',' arglist { zword_list g; $$ = $3; g.item = $1.v; LEaddm($$, g, n_rmmalloc); } +; + +/* Expressions with commas */ +commaexp: exp + | condexp + | commaexp ',' exp { $$ = $3; } + | commaexp ',' condexp { $$ = $3; } +; + +/* Expressions with conditions */ +condexp: + exp CONDITION { cond_list newcond; newcond.val = $1.v; newcond.condfunc = $2.condfunc; newcond.opposite = $2.opposite; LEaddm(condlist, newcond, n_rmmalloc); } orlist { if($4) ignoreeffects--; $$.v = $4; $$.t = Z_BOOLEAN; LEremovem(condlist, n_rmfreeone); } +; + +/* Expressions without commas */ +exp: NUM + { $$ = $1; } + | BFALSE + { $$.v = 0; $$.t = Z_BOOLEAN; } + | BTRUE + { $$.v = 1; $$.t = Z_BOOLEAN; } + | NOTHING + { $$.v = 0; $$.t = Z_OBJECT; } + + | exp '=' exp + { $$ = $3; infix_assign(&$1, $3.v); } + + | PARENT '(' commaexp ')' + { $$.v = infix_parent($3.v); $$.t = Z_OBJECT; } + | CHILD '(' commaexp ')' + { $$.v = infix_child($3.v); $$.t = Z_OBJECT; } + | SIBLING '(' commaexp ')' + { $$.v = infix_sibling($3.v); $$.t = Z_OBJECT; } + | CHILDREN '(' commaexp ')' + { int n = 0; zword o = infix_child($3.v); while(o) { n++; o = infix_sibling(o); } $$.v = n; $$.t = Z_NUMBER; } + + | RANDOM '(' commaexp ')' + { + if(!ignoreeffects) { + $$.v = z_random($3.v); + $$.t = Z_NUMBER; + } else { + $$.v = 0; + $$.t = Z_UNKNOWN; + } + } + | exp '(' arglist ')' + { + zword locals[16]; + int i = 0; + zword_list *p; + if(!ignoreeffects) { + for(p = $3; p && i < 16; p=p->next) { + locals[i++] = p->item; + } + mop_call($1.v, i, locals, -2); + decode(); + exit_decoder = FALSE; + $$.v = time_ret; $$.t = Z_UNKNOWN; + } else { + $$.v = 0; $$.t = Z_UNKNOWN; + } + } + + | exp ANDAND { if($1.v == 0) ignoreeffects++; } exp + { if($1.v == 0) ignoreeffects--; $$ = z_t($1, $4, $1.v && $4.v); } + | exp OROR { if($1.v != 0) ignoreeffects++; } exp + { if($1.v != 0) ignoreeffects--; $$ = z_t($1, $4, $1.v || $4.v); } + | NOTNOT exp + { $$.v = !($2.v); $$.t = Z_NUMBER; } + + | exp '+' exp + { $$ = z_t($1, $3, $1.v + $3.v); } + | exp '-' exp + { $$ = z_t($1, $3, $1.v + neg($3.v)); } + | exp '*' exp + { $$ = z_t($1, $3, z_mult($1.v, $3.v)); } + | exp '/' exp + { $$ = z_t($1, $3, z_div($1.v, $3.v)); } + | exp '%' exp + { $$ = z_t($1, $3, z_mod($1.v, $3.v)); } + | exp '&' exp + { $$ = z_t($1, $3, $1.v & $3.v); } + | exp '|' exp + { $$ = z_t($1, $3, $1.v | $3.v); } + | '~' exp + { $$ = z_t($2, $2, ~$2.v); } + + | exp BYTEARRAY exp + { $$.t = Z_BYTEARRAY; $$.o = $1.v; $$.p = $3.v; infix_get_val(&$$); } + | exp WORDARRAY exp + { $$.t = Z_WORDARRAY; $$.o = $1.v; $$.p = $3.v; infix_get_val(&$$); } + + | '-' exp %prec precNEG + { $$ = z_t($2, $2, neg($2.v)); } + + | INCREMENT exp + { if(!ignoreeffects) infix_assign(&$2, ARITHMASK($2.v + 1)); $$ = $2; } + | exp INCREMENT + { $$ = $1; if(!ignoreeffects) infix_assign(&$1, ARITHMASK($1.v + 1)); } + | DECREMENT exp + { if(!ignoreeffects) infix_assign(&$2, ARITHMASK($2.v + neg(1))); $$ = $2; } + | exp DECREMENT + { $$ = $1; if(!ignoreeffects) infix_assign(&$1, ARITHMASK($1.v + neg(1))); } + + | exp PROPADDR exp + { zword len; $$.v = infix_get_proptable($1.v, $3.v, &len); $$.t = Z_NUMBER; } + | exp PROPLENGTH exp + { infix_get_proptable($1.v, $3.v, &$$.v); $$.t = Z_NUMBER; } + + | exp '.' exp + { $$.t = Z_OBJPROP; $$.o = $1.v; $$.p = $3.v; infix_get_val(&$$); } + +/* + | exp SUPERCLASS exp + { $$ = infix_superclass($1, $3); } +*/ + + | NUMBER exp + { $$.v = $2.v; $$.t = Z_NUMBER; } + | OBJECT exp + { $$.v = $2.v; $$.t = Z_OBJECT; } + | ROUTINE exp + { $$.v = $2.v; $$.t = Z_ROUTINE; } + | STRING exp + { $$.v = $2.v; $$.t = Z_STRING; } + | GLOBAL exp + { $$.t = Z_WORDARRAY; $$.o = z_globaltable; $$.p = $2.v; infix_get_val(&$$); } + | LOCAL exp + { $$.t = Z_LOCAL; $$.o = infix_selected_frame; $$.p = $2.v; infix_get_val(&$$); } + | '(' commaexp ')' + { $$ = $2; } +; + + +%% + +#if 0 +{ /* fanagling to get emacs indentation sane */ +int foo; +#endif + +static z_typed z_t(z_typed a, z_typed b, zword v) +{ + z_typed r; + r.v = ARITHMASK(v); + if(a.t == Z_NUMBER && b.t == Z_NUMBER) + r.t = Z_NUMBER; + else + r.t = Z_UNKNOWN; + return r; +} + + + +typedef struct { + int token; + const char *name; +} name_token; + +static name_token infix_operators[] = { + { ANDAND, "&&" }, + { OROR, "||" }, + { NOTNOT, "~~" }, + { BYTEARRAY, "->" }, + { WORDARRAY, "-->" }, + { NUMBER, "(number)" }, + { OBJECT, "(object)" }, + { ROUTINE, "(routine)" }, + { STRING, "(string)" }, + { GLOBAL, "(global)" }, + { LOCAL, "(local)" }, + { INCREMENT, "++" }, + { DECREMENT, "--" }, + { SUPERCLASS, "::" } +}; + + +static name_token infix_keywords[] = { + { TO, "to" }, + { IF, "if" }, + { OR, "or" }, + { BTRUE, "true" }, + { BFALSE, "false" }, + { NOTHING, "nothing" }, + { PARENT, "parent" }, + { CHILD, "child" }, + { SIBLING, "sibling" }, + { RANDOM, "random" }, + { CHILDREN, "children" } +}; + + +/* These are only valid as the first token in an expression. A single space + matches at least one typed whitespace character */ +static name_token infix_commands[] = { + { '#', "#" }, + { HELP, "help" }, + { ALIAS, "alias" }, + { RALIAS, "ralias" }, + { UNALIAS, "unalias" }, + { DUMPMEM, "dumpmem" }, + { AUTOMAP, "automap" }, + { UNDO, "undo" }, + { REDO, "redo" }, + { QUIT, "quit" }, + { RESTORE, "restore" }, + { RESTART, "restart" }, + { RESTART, "run" }, + { RECORDON, "recording on" }, + { RECORDOFF, "recording off" }, + { REPLAY, "replay" }, + { REPLAYOFF, "replay off" }, + { SYMBOL_FILE, "symbol-file" }, + { PRINT, "print" }, + { PRINT, "p" }, + { PRINT, "call" }, /* No void functions in inform */ + { SET, "set" }, + { MOVE, "move" }, + { OBJECT_TREE, "object-tree" }, + { OBJECT_TREE, "tree" }, + { FIND, "find" }, + { REMOVE, "remove" }, + { GIVE, "give" }, + { LIST_GLOBALS, "globals" }, + { JUMP, "jump" }, + { CONT, "continue" }, + { CONT, "c" }, + { CONT, "fg" }, + { STEP, "step" }, + { STEP, "s" }, + { NEXT, "next" }, + { NEXT, "n" }, + { STEPI, "stepi" }, + { STEPI, "si" }, + { NEXTI, "nexti" }, + { NEXTI, "ni" }, + { UNTIL, "until" }, + { UNTIL, "u" }, + { FINISH, "finish" }, + { BREAK, "break" }, + { DELETE, "delete" }, + { DELETE, "d" }, + { DELETE, "delete breakpoints" }, + { COND, "condition" }, + { IGNORE, "ignore" }, + { FRAME, "frame" }, + { FRAME, "f" }, + { SELECT_FRAME, "select-frame" }, + { UP_FRAME, "up" }, + { DOWN_FRAME, "down" }, + { DOWN_FRAME, "do" }, + { UP_SILENTLY, "up-silently" }, + { DOWN_SILENTLY,"down-silently" }, + { BREAKPOINTS, "info breakpoints" }, + { BREAKPOINTS, "info watchpoints" }, + { BREAKPOINTS, "info break" }, + { DISABLE_BREAK,"disable" }, + { DISABLE_BREAK,"disable breakpoints" }, + { DISABLE_BREAK,"dis" }, + { DISABLE_BREAK,"dis breakpoints" }, + { ENABLE_BREAK, "enable" }, + { ENABLE_BREAK, "enable breakpoints" }, + { LANGUAGE, "show language" }, + { INFOSOURCE, "info source" }, + { INFOSOURCES, "info sources" }, + { COPYING, "show copying" }, + { WARRANTY, "show warranty" }, + { BACKTRACE, "backtrace" }, + { BACKTRACE, "bt" }, + { BACKTRACE, "where" }, + { BACKTRACE, "info stack" }, + { BACKTRACE, "info s" }, + { DISPLAY, "display" }, + { UNDISPLAY, "undisplay" }, + { UNDISPLAY, "delete display" }, + { DISABLE_DISPLAY,"disable display" }, + { DISABLE_DISPLAY,"dis display" }, + { ENABLE_DISPLAY,"enable display" } +}; + +#include "dbg_help.h" + +static BOOL z_isequal(zword a, zword b) +{ + return (a == b); +} + +static BOOL z_isgreat(zword a, zword b) +{ + return is_greaterthan(a, b); +} + +static BOOL z_isless(zword a, zword b) +{ + return is_lessthan(a, b); +} + +static BOOL infix_provides(zword o, zword p) +{ + zword len; + return (infix_get_proptable(o, p, &len) != 0); +} + +static BOOL infix_in(zword a, zword b) +{ + return infix_parent(a) == b; +} + +typedef struct { + const char *name; + BOOL (*condfunc)(zword a, zword b); + BOOL opposite; +} condition; + +condition conditionlist[] = { + { "==", z_isequal, FALSE }, + { "~=", z_isequal, TRUE }, + { ">", z_isgreat, FALSE }, + { "<", z_isless, FALSE }, + { "<=", z_isgreat, TRUE }, + { ">=", z_isless, TRUE }, + { "has", infix_test_attrib, FALSE }, + { "hasnt", infix_test_attrib, TRUE }, + { "in", infix_in, FALSE }, + { "notin", infix_in, TRUE }, +/*{ "ofclass", infix_ofclass, FALSE },*/ + { "provides",infix_provides, FALSE } +}; + + +static BOOL is_command_identifier(char c) +{ + return isalpha(c) || (c == '-'); +} + +static BOOL is_identifier(char c) +{ + return isalpha(c) || isdigit(c) || (c == '_'); +} + +static BOOL is_longer_identifier(char c) +{ + return isalpha(c) || isdigit(c) || (c == '_') || (c == '.') || (c == ':'); +} + +static int grab_number(z_typed *val) +{ + int len = 0; + char *endptr; + char c = lex_expression[lex_offset + len]; + int base = 10; + long int num; + + /* Don't handle negativity here */ + if(c == '-' || c == '+') + return 0; + + if(c == '$') { + len++; + base = 16; + c = lex_expression[lex_offset + len]; + if(c == '$') { + len++; + base = 2; + c = lex_expression[lex_offset + len]; + } + } + + num = n_strtol(lex_expression + lex_offset + len, &endptr, base); + + if(endptr != lex_expression + lex_offset) { + len += endptr - lex_expression - lex_offset; + val->v = num; + val->t = Z_NUMBER; + return len; + } + return 0; +} + + +typedef enum { match_None, match_Partial, match_Complete } match_type; + +static match_type command_matches(const char *command, const char *expression, + unsigned *matchedlen) +{ + unsigned c, e; + e = 0; + + for(c = 0; command[c]; c++) { + if(command[c] != expression[e]) { + if(!is_command_identifier(expression[e])) { + *matchedlen = e; + return match_Partial; + } + return match_None; + } + + e++; + + if(command[c] == ' ') { + while(expression[e] == ' ') + e++; + } + } + + if(!is_command_identifier(expression[e])) { + *matchedlen = e; + return match_Complete; + } + + return match_None; +} + + +static int grab_command(void) +{ + unsigned i; + unsigned len; + + unsigned best; + match_type best_match = match_None; + unsigned best_len = 0; + BOOL found = FALSE; + BOOL ambig = FALSE; + + while(isspace(lex_expression[lex_offset])) + lex_offset++; + + for(i = 0; i < sizeof(infix_commands) / sizeof(*infix_commands); i++) { + switch(command_matches(infix_commands[i].name, lex_expression + lex_offset, &len)) { + case match_Complete: + if(len > best_len || best_match != match_Complete) { + best = i; + best_match = match_Complete; + best_len = len; + found = TRUE; + } + break; + + case match_Partial: + if(best_match != match_Complete) { + if(found) + ambig = TRUE; + best = i; + best_match = match_Partial; + best_len = len; + found = TRUE; + } + + case match_None: + ; + } + } + + if(ambig && best_match != match_Complete) { + infix_print_string("Ambiguous command.\n"); + return 0; + } + + if(found) { + lex_offset += best_len; + return infix_commands[best].token; + } + + infix_print_string("Undefined command.\n"); + return 0; +} + + +static void inform_help(void) +{ + int command; + unsigned i; + BOOL is_command = FALSE; + + for(i = lex_offset; lex_expression[i]; i++) + if(!isspace(lex_expression[i])) + is_command = TRUE; + + if(!is_command) { + infix_print_string("Help is available on the following commands:\n"); + for(i = 0; i < sizeof(command_help) / sizeof(*command_help); i++) { + unsigned j; + for(j = 0; j < sizeof(infix_commands) / sizeof(*infix_commands); j++) + if(command_help[i].token == infix_commands[j].token) { + infix_print_char('\''); + infix_print_string(infix_commands[j].name); + infix_print_char('\''); + break; + } + infix_print_char(' '); + } + infix_print_string("\n"); + return; + } + + command = grab_command(); + if(command) { + for(i = 0; i < sizeof(command_help) / sizeof(*command_help); i++) { + if(command_help[i].token == command) { + infix_print_string(command_help[i].name); + infix_print_char(10); + return; + } + } + infix_print_string("No help available for that command.\n"); + } +} + + +void process_debug_command(const char *buffer) +{ +#ifdef YYDEBUG + yydebug = 1; +#endif + lex_expression = buffer; + lex_offset = 0; + ignoreeffects = 0; + yyparse(); + n_rmfree(); +} + +BOOL exp_has_locals(const char *exp) +{ + return FALSE; +} + +z_typed evaluate_expression(const char *exp, unsigned frame) +{ + unsigned old_frame = infix_selected_frame; + char *new_exp = (char *) n_malloc(n_strlen(exp) + 5); + n_strcpy(new_exp, "set "); + n_strcat(new_exp, exp); + + infix_selected_frame = frame; + process_debug_command(new_exp); + infix_selected_frame = old_frame; + + n_free(new_exp); + + return inform_result; +} + +static void yyerror(const char *s) +{ + infix_print_string(s); + infix_print_char(10); +} + +static int yylex(void) +{ + unsigned i, len, longer; + BOOL check_command = FALSE; + + if(lex_offset == 0) + check_command = TRUE; + + while(isspace(lex_expression[lex_offset])) + lex_offset++; + + if(check_command) { + return grab_command(); + } + + if((len = grab_number(&yylval.val)) != 0) { + lex_offset += len; + return NUM; + } + + for(i = 0; i < sizeof(infix_operators) / sizeof(*infix_operators); i++) { + if(n_strncmp(infix_operators[i].name, lex_expression + lex_offset, + n_strlen(infix_operators[i].name)) == 0) { + lex_offset += n_strlen(infix_operators[i].name); + return infix_operators[i].token; + } + } + + for(i = 0; i < sizeof(conditionlist) / sizeof(*conditionlist); i++) { + len = n_strlen(conditionlist[i].name); + if(len + && n_strncmp(conditionlist[i].name, + lex_expression + lex_offset, len) == 0 + && !(is_identifier(conditionlist[i].name[len-1]) + && is_identifier(lex_expression[lex_offset + len]))) { + + lex_offset += len; + yylval.cond.condfunc = conditionlist[i].condfunc; + yylval.cond.opposite = conditionlist[i].opposite; + return CONDITION; + } + } + + if((len = infix_find_file(&yylval.filenum, lex_expression + lex_offset)) != 0) { + lex_offset += len; + return DFILE; + } + + + for(len = 0; is_identifier(lex_expression[lex_offset + len]); len++) + ; + + if(!len) + return lex_expression[lex_offset++]; + + for(i = 0; i < sizeof(infix_keywords) / sizeof(*infix_keywords); i++) { + if(n_strmatch(infix_keywords[i].name, lex_expression + lex_offset, len)) { + lex_offset += len; + return infix_keywords[i].token; + } + } + + for(longer = len; is_longer_identifier(lex_expression[lex_offset + longer]); longer++) + ; + + if(infix_find_symbol(&yylval.val, lex_expression + lex_offset, longer)) { + lex_offset += longer; + return NUM; + } + + if(infix_find_symbol(&yylval.val, lex_expression + lex_offset, len)) { + lex_offset += len; + return NUM; + } + + infix_print_string("Unknown identifier \""); + for(i = 0; i < len; i++) + infix_print_char(lex_expression[lex_offset + i]); + infix_print_string("\"\n"); + + return 0; +} + +#endif /* DEBUGGING */ diff --git a/interpreters/nitfol/init.c b/interpreters/nitfol/init.c new file mode 100644 index 0000000..6196d40 --- /dev/null +++ b/interpreters/nitfol/init.c @@ -0,0 +1,418 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +enum z_caps { HAS_STATUSLINE, HAS_COLOR, HAS_CHARGRAPH, HAS_BOLD, HAS_ITALIC, + HAS_FIXED, HAS_SOUND, HAS_GRAPHICS, HAS_TIMER, HAS_MOUSE, + HAS_DEFVAR, IS_TRANS, FORCE_FIXED, HAS_UNDO, HAS_MENU, + DO_TANDY, + HAS_ENDNUM }; + +struct z_cap_entry { + enum z_caps c; + int flagnum; /* 1 or 2 */ + int bit; + int min_zversion; + int max_zversion; + BOOL reverse; +}; + +static const struct z_cap_entry z_cap_table[] = { + { DO_TANDY, 1, 3, 1, 3, FALSE }, + { HAS_STATUSLINE, 1, 4, 1, 3, TRUE }, + { HAS_STATUSLINE, 1, 5, 1, 3, FALSE }, + { HAS_DEFVAR, 1, 6, 1, 3, FALSE }, + { HAS_COLOR, 1, 0, 5, 99, FALSE }, + { HAS_GRAPHICS, 1, 1, 6, 6, FALSE }, + { HAS_BOLD, 1, 2, 4, 99, FALSE }, + { HAS_ITALIC, 1, 3, 4, 99, FALSE }, + { HAS_FIXED, 1, 4, 4, 99, FALSE }, + { HAS_SOUND, 1, 5, 6, 99, FALSE }, + { HAS_TIMER, 1, 7, 4, 99, FALSE }, + { IS_TRANS, 2, 0, 1, 99, FALSE }, + { FORCE_FIXED, 2, 1, 3, 99, FALSE }, + { HAS_CHARGRAPH, 2, 3, 5, 99, FALSE }, + { HAS_UNDO, 2, 4, 5, 99, FALSE }, + { HAS_MOUSE, 2, 5, 5, 99, FALSE }, + { HAS_SOUND, 2, 7, 5, 99, FALSE }, + { HAS_MENU, 2, 8, 6, 6, FALSE } +}; + +static BOOL cur_cap[HAS_ENDNUM]; + +static void investigate_suckage(glui32 *wid, glui32 *hei) +{ + winid_t w1, w2; + + *wid = 70; *hei = 24; /* sensible defaults if we can't tell */ + + glk_stylehint_set(wintype_AllTypes, style_User2, + stylehint_TextColor, 0x00ff0000); + glk_stylehint_set(wintype_AllTypes, style_Subheader, + stylehint_Weight, 1); + + w1 = glk_window_open(0, 0, 0, wintype_TextBuffer, 0); + if(!w1) + glk_exit(); /* Run screaming! */ + w2 = glk_window_open(w1, winmethod_Above | winmethod_Fixed, 1, + wintype_TextGrid, 0); + + if(w2) { + winid_t o; + o = glk_window_get_parent(w2); + glk_window_set_arrangement(o, winmethod_Above | winmethod_Proportional, + 100, w2); + glk_window_get_size(w2, wid, hei); + glk_window_set_arrangement(o, winmethod_Above | winmethod_Fixed, + 1, w2); + } + + cur_cap[HAS_STATUSLINE] = (w2 != 0); + cur_cap[HAS_COLOR] = glk_style_distinguish(w1, style_Normal, style_User2); + cur_cap[HAS_CHARGRAPH] = 0; + cur_cap[HAS_BOLD] = glk_style_distinguish(w1, style_Normal, style_Subheader); + cur_cap[HAS_ITALIC] = glk_style_distinguish(w1, style_Normal, style_Emphasized); + cur_cap[HAS_FIXED] = glk_style_distinguish(w1, style_Normal, style_Preformatted); +#ifdef GLK_MODULE_SOUND + cur_cap[HAS_SOUND] = glk_gestalt(gestalt_Sound, 0); +#else + cur_cap[HAS_SOUND] = FALSE; +#endif +#ifdef GLK_MODULE_IMAGE + cur_cap[HAS_GRAPHICS] = glk_gestalt(gestalt_Graphics, 0); +#else + cur_cap[HAS_GRAPHICS] = FALSE; +#endif + cur_cap[HAS_TIMER] = glk_gestalt(gestalt_Timer, 0); + cur_cap[HAS_MOUSE] = glk_gestalt(gestalt_MouseInput, wintype_TextGrid); + + cur_cap[HAS_DEFVAR] = TRUE; + + cur_cap[HAS_UNDO] = TRUE; + cur_cap[HAS_MENU] = FALSE; + + if(w1) + glk_window_close(w1, NULL); + if(w2) + glk_window_close(w2, NULL); +} + + +void set_header(glui32 width, glui32 height) +{ + unsigned i; + zword flags[3]; + flags[1] = LOBYTE(HD_FLAGS1); + flags[2] = LOWORD(HD_FLAGS2); + + cur_cap[DO_TANDY] = do_tandy; + + cur_cap[IS_TRANS] = is_transcripting(); + cur_cap[FORCE_FIXED] = is_fixed; + + for(i = 0; i < sizeof(z_cap_table) / sizeof(*z_cap_table); i++) { + if(zversion >= z_cap_table[i].min_zversion + && zversion <= z_cap_table[i].max_zversion) { + if(cur_cap[z_cap_table[i].c]) + flags[z_cap_table[i].flagnum] |= 1 << z_cap_table[i].bit; + else + flags[z_cap_table[i].flagnum] &= ~(1 << z_cap_table[i].bit); + } + } + + LOBYTEwrite(HD_FLAGS1, flags[1]); + LOWORDwrite(HD_FLAGS2, flags[2]); + + LOBYTEwrite(HD_TERPNUM, interp_num); + LOBYTEwrite(HD_TERPVER, interp_ver); + + /* We should be allowed 8 bytes here, but inform 6 (incorrectly) expects + this space not to be used, so only use zeroed bytes */ + if(username) + for(i = 0; i < 8 && username[i] && !LOBYTE(HD_USERID + i); i++) + LOBYTEwrite(HD_USERID + i, username[i]); + + if(zversion == 6) { + width = 320; + height = 200; + } + + + if(height > 255) + height = 255; + LOBYTEwrite(HD_SCR_HEIGHT, height); /* screen height (255 = infinite) */ + LOBYTEwrite(HD_SCR_WIDTH, width); /* screen width */ + if(zversion >= 5) { + LOWORDwrite(HD_SCR_WUNIT, width); /* screen width in units */ + LOWORDwrite(HD_SCR_HUNIT, height); /* screen height in units */ + } + + if(zversion != 6) { + LOBYTEwrite(HD_FNT_WIDTH, 1); /* font width/height in units */ + LOBYTEwrite(HD_FNT_HEIGHT, 1); /* font height/width in units */ + } else { + LOBYTEwrite(HD_FNT_WIDTH, 8); + LOBYTEwrite(HD_FNT_HEIGHT, 8); + } + + LOBYTEwrite(HD_DEF_BACK, 1); /* default background color */ + LOBYTEwrite(HD_DEF_FORE, 1); /* default foreground color */ + + /* Well, we're close enough given the limitations of Glk, so go ahead + and lie about the impossible stuff and claim spec 1.0 compliance */ + LOBYTEwrite(HD_STD_REV, 1); + LOBYTEwrite(HD_STD_REV + 1, 0); +} + +static void check_ascii_mode(void) +{ + BOOL hascr = FALSE; + BOOL haslf = FALSE; + BOOL hascrnolf = FALSE; + BOOL haslfnocr = FALSE; + BOOL has8bit = FALSE; + offset i; + for(i = 0; i < total_size; i++) { + if(z_memory[i] & 0x80) + has8bit = TRUE; + else if(z_memory[i] == 0x0a) { + haslf = TRUE; + if(i && z_memory[i-1] != 0x0d) + haslfnocr = TRUE; + } + else if(z_memory[i] == 0x0d) { + hascr = TRUE; + if(i+1 < total_size && z_memory[i+1] != 0x0a) + hascrnolf = TRUE; + } + } + if(!has8bit) + n_show_error(E_CORRUPT, "All bytes are 7 bit; top bits were likely stripped in transfer", 0); + else if(!hascr) + n_show_error(E_CORRUPT, "No CR bytes; likely transfered in ASCII mode", 0); + else if(!haslf) + n_show_error(E_CORRUPT, "No LF bytes; likely transfered in ASCII mode", 0); + else if(!hascrnolf) + n_show_error(E_CORRUPT, "All CR are followed by LFs; likely transfered in ASCII mode", 0); + else if(!haslfnocr) + n_show_error(E_CORRUPT, "All LFs are preceded by CRs; likely transfered in ASCII mode", 0); +} + + +/* Loads header into global values. Returns true if could be a valid header */ +BOOL load_header(strid_t zfile, offset filesize, BOOL report_errors) +{ + zbyte header[64]; + + if(glk_get_buffer_stream(zfile, (char *) header, 64) != 64) { + if(report_errors) + n_show_error(E_SYSTEM, "file too small", 0); + return FALSE; + } + + zversion = header[HD_ZVERSION]; + switch(zversion) { + case 1: case 2: case 3: + granularity = 2; break; + case 4: case 5: case 6: case 7: + granularity = 4; break; + case 8: + granularity = 8; break; + default: + if(report_errors) + n_show_error(E_VERSION, "unknown version number or not zcode", zversion); + return FALSE; + } + + high_mem_mark = MSBdecodeZ(header + HD_HIMEM); + PC = MSBdecodeZ(header + HD_INITPC); + z_dictionary = MSBdecodeZ(header + HD_DICT); + z_propdefaults = MSBdecodeZ(header + HD_OBJTABLE); + z_globaltable = MSBdecodeZ(header + HD_GLOBVAR); + dynamic_size = MSBdecodeZ(header + HD_STATMEM); + z_synonymtable = MSBdecodeZ(header + HD_ABBREV); + + switch(zversion) { + case 1: case 2: + game_size = filesize; + break; + case 3: + game_size = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 2; + break; + case 4: case 5: + game_size = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 4; + break; + case 6: case 7: case 8: + game_size = ((offset) MSBdecodeZ(header + HD_LENGTH)) * 8; + break; + } + if(zversion == 6 || zversion == 7) { + rstart = ((offset) MSBdecodeZ(header + HD_RTN_OFFSET)) * 8; + sstart = ((offset) MSBdecodeZ(header + HD_STR_OFFSET)) * 8; + } else { + rstart = 0; + sstart = 0; + } + + /* Check consistency of stuff */ + + if(filesize < game_size) { + if(report_errors) + n_show_error(E_CORRUPT, "file on a diet", filesize); + return FALSE; + } + + if(dynamic_size > game_size) { + if(report_errors) + n_show_error(E_CORRUPT, "dynamic memory length > game size", dynamic_size); + return FALSE; + } + + if(high_mem_mark < dynamic_size) { + if(report_errors) + n_show_error(E_CORRUPT, "dynamic memory overlaps high memory", dynamic_size); + } + + if(PC > game_size) { + if(report_errors) + n_show_error(E_CORRUPT, "initial PC greater than game size", PC); + return FALSE; + } + + if(PC < 64) { + if(report_errors) + n_show_error(E_CORRUPT, "initial PC in header", PC); + return FALSE; + } + + return TRUE; +} + + +void z_init(strid_t zfile) +{ + offset bytes_read; + offset i; + glui32 width, height; + + z_random(0); /* Initialize random number generator */ + + init_windows(is_fixed, 80, 24); + + glk_stream_set_position(zfile, zfile_offset, seekmode_Start); + if(!load_header(zfile, total_size, TRUE)) + n_show_fatal(E_CORRUPT, "couldn't load file", 0); + + n_free(z_memory); + if(game_size <= 65535) + z_memory = (zbyte *) n_malloc(65535); + else + z_memory = (zbyte *) n_malloc(game_size); + + glk_stream_set_position(zfile, zfile_offset, seekmode_Start); + bytes_read = glk_get_buffer_stream(zfile, (char *) z_memory, game_size); + if(bytes_read != game_size) + n_show_fatal(E_SYSTEM, "unexpected number of bytes read", bytes_read); + + z_checksum = 0; + if (zversion >= 3) { + for(i = 0x40; i < game_size; i++) + z_checksum += HIBYTE(i); + z_checksum = ARITHMASK(z_checksum); + + if(LOWORD(HD_CHECKSUM) != 0 && z_checksum != LOWORD(HD_CHECKSUM)) { + n_show_error(E_CORRUPT, "Checksum does not match", z_checksum); + check_ascii_mode(); + } + } + + + + init_stack(1024, 128); + + if(zversion == 6) { + zword dummylocals[15]; + mop_call(PC, 0, dummylocals, 0); + } + + kill_windows(); + investigate_suckage(&width, &height); + if(height == 0) + height = 1; + if(width == 0) + width = 80; + set_header(width, height); + init_windows(is_fixed, width, height); + + if(zversion <= 3) { + opcodetable[OFFSET_0OP + 5] = op_save1; + opcodetable[OFFSET_0OP + 6] = op_restore1; + } else { + opcodetable[OFFSET_0OP + 5] = op_save4; + opcodetable[OFFSET_0OP + 6] = op_restore4; + } + if(zversion <= 4) { + opcodetable[OFFSET_0OP + 9] = op_pop; + opcodetable[OFFSET_1OP + 15] = op_not; + opcodetable[OFFSET_VAR + 4] = op_sread; + } else { + opcodetable[OFFSET_0OP + 9] = op_catch; + opcodetable[OFFSET_1OP + 15] = op_call_n; + opcodetable[OFFSET_VAR + 4] = op_aread; + } + if(zversion == 6) { + opcodetable[OFFSET_VAR + 11] = op_set_window6; + } else { + opcodetable[OFFSET_VAR + 11] = op_set_window; + } + + objects_init(); + init_sound(); + + in_timer = FALSE; + exit_decoder = FALSE; + time_ret = 0; + +#ifdef DEBUGGING + init_infix(0); +#endif + + if(!quiet) { + output_string("Nitfol 0.5 Copyright 1999 Evin Robertson.\r"); +#ifdef DEBUGGING + output_string("Nitfol comes with ABSOLUTELY NO WARRANTY; for details type \"/show w\". This is free software, and you are welcome to change and distribute it under certain conditions; type \"/show copying\" for details.\r"); +#else + output_string("Nitfol is free software which comes with ABSOLUTELY NO WARRANTY. It is covered by the GNU General Public License, and you are welcome to change and distribute it under certain conditions. Read the file COPYING which should have been included in this distribution for details.\r"); +#endif + } +} + + +void z_close(void) +{ +#ifdef DEBUGGING + kill_infix(); +#endif + kill_sound(); + kill_undo(); + free_windows(); + kill_stack(); + n_free(z_memory); +} diff --git a/interpreters/nitfol/init.h b/interpreters/nitfol/init.h new file mode 100644 index 0000000..33ffc6f --- /dev/null +++ b/interpreters/nitfol/init.h @@ -0,0 +1,16 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i init.c' */ +#ifndef CFH_INIT_H +#define CFH_INIT_H + +/* From `init.c': */ +void set_header (glui32 width , glui32 height ); +BOOL load_header (strid_t zfile , offset filesize , BOOL report_errors ); +void z_init (strid_t zfile ); +void z_close (void); + +#endif /* CFH_INIT_H */ diff --git a/interpreters/nitfol/io.c b/interpreters/nitfol/io.c new file mode 100644 index 0000000..ee29799 --- /dev/null +++ b/interpreters/nitfol/io.c @@ -0,0 +1,1546 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" +#include "nio.h" + +#ifdef HEADER + +typedef struct z_window *zwinid; + +#endif + +BOOL is_fixed; /* If we are forcing output to be fixed-width */ + + +/* descriptions of z-machine colors, in glk 0x00rrggbb style. + -1 means to call glk_stylehint_clear instead of glk_stylehint_set. + The 'current color' will be overwritten on calls to set_colour. + + Go ahead and customize these (making background colors lighter than + foreground colors might be interesting) +*/ + +glsi32 bgcolortable[] = { + -1L, /* current color */ + -1L, /* defualt setting */ + 0x00000000L, /* black */ + 0x00ff0000L, /* red */ + 0x00008000L, /* green */ + 0x00ffff00L, /* yellow */ + 0x000000ffL, /* blue */ + 0x00ff00ffL, /* magenta */ + 0x0000ffffL, /* cyan */ + 0x00ffffffL, /* white */ + 0x00c0c0c0L, /* light grey */ + 0x00808080L, /* medium grey */ + 0x00404040L /* dark grey */ +}; + +glsi32 fgcolortable[] = { + -1L, /* current color */ + -1L, /* defualt setting */ + 0x00000000L, /* black */ + 0x00ff0000L, /* red */ + 0x00008000L, /* green */ + 0x00ffff00L, /* yellow */ + 0x000000ffL, /* blue */ + 0x00ff00ffL, /* magenta */ + 0x0000ffffL, /* cyan */ + 0x00ffffffL, /* white */ + 0x00c0c0c0L, /* light grey */ + 0x00808080L, /* medium grey */ + 0x00404040L /* dark grey */ +}; + + +static void killglkwithcolor(glui32 styl, int fore, int back) +{ + if(fgcolortable[fore] == -1) + glk_stylehint_clear(wintype_AllTypes, styl, + stylehint_TextColor); + else + glk_stylehint_set(wintype_AllTypes, styl, + stylehint_TextColor, fgcolortable[fore]); + + if(bgcolortable[back] == -1) + glk_stylehint_clear(wintype_AllTypes, styl, + stylehint_BackColor); + else + glk_stylehint_set(wintype_AllTypes, styl, + stylehint_BackColor, bgcolortable[back]); +} + + +static void set_stylehints(char fore, char back) +{ + glui32 n; + for(n = 0; n < style_NUMSTYLES; n++) + killglkwithcolor(n, fore, back); + + /* Subheader will be used for bold */ + glk_stylehint_set(wintype_TextBuffer, style_Subheader, + stylehint_Weight, 1); + + /* BlockQuote will be used for reverse proportional text */ + glk_stylehint_set(wintype_TextBuffer, style_BlockQuote, + stylehint_Proportional, 0); + glk_stylehint_set(wintype_TextBuffer, style_BlockQuote, + stylehint_Justification, stylehint_just_Centered); +#ifdef stylehint_ReverseColor + glk_stylehint_set(wintype_TextBuffer, style_BlockQuote, + stylehint_ReverseColor, 1); +#endif + + /* User1 will be used for bold italics */ + glk_stylehint_set(wintype_TextBuffer, style_User1, + stylehint_Weight, 1); + glk_stylehint_set(wintype_TextBuffer, style_User1, + stylehint_Oblique, 1); + + /* User2 will be used for proportional bold/italic */ + glk_stylehint_set(wintype_TextBuffer, style_User2, + stylehint_Proportional, 0); + glk_stylehint_set(wintype_TextBuffer, style_User2, + stylehint_Weight, 1); +} + +#define sBOLD 1 +#define sITAL 2 +#define sFIXE 4 +#define sREVE 8 + +#if 0 + +static glui32 bitmap_to_style[16] = { + style_Normal, + style_Subheader, /* sBOLD */ + style_Emphasized, /* sITAL */ + style_User1, /* sBOLD | sITAL */ + style_Preformatted,/* sFIXE */ + style_User2, /* sFIXE | sBOLD */ + style_User2, /* sFIXE | sITAL */ + style_User2, /* sFIXE | sBOLD | sITAL*/ + style_BlockQuote, /* sREVE */ + style_BlockQuote, /* sREVE | sBOLD */ + style_BlockQuote, /* sREVE | sITAL */ + style_BlockQuote, /* sREVE | sBOLD | sITAL */ + style_BlockQuote, /* sFIXE | sREVE */ + style_BlockQuote, /* sFIXE | sREVE | sBOLD */ + style_BlockQuote, /* sFIXE | sREVE | sITAL */ + style_BlockQuote /* sFIXE | sREVE | sBOLD | sITAL */ +}; + +#else + +static glui32 bitmap_to_style[16] = { + style_Normal, + style_Subheader, /* sBOLD */ + style_Emphasized, /* sITAL */ + style_Subheader, /* sBOLD | sITAL */ + style_Preformatted,/* sFIXE */ + style_Subheader, /* sFIXE | sBOLD */ + style_Emphasized, /* sFIXE | sITAL */ + style_Subheader, /* sFIXE | sBOLD | sITAL*/ + style_Normal, + style_Subheader, /* sBOLD */ + style_Emphasized, /* sITAL */ + style_Subheader, /* sBOLD | sITAL */ + style_Preformatted,/* sFIXE */ + style_Subheader, /* sFIXE | sBOLD */ + style_Emphasized, /* sFIXE | sITAL */ + style_Subheader /* sFIXE | sBOLD | sITAL*/ +}; + +#endif + +typedef struct { + char fore; + char back; + unsigned char style; +} colorstyle; + +typedef struct { + glui32 image_num; + glui32 x, y; + glui32 width, height; +} z_image; + + +struct z_window { + winid_t win; + strid_t str; + glui32 wintype; + glui32 method; + + strid_t transcript; + + BOOL glk_input_pending; + glui32 pending_input_type; + glui32 pending_input_length; + + /* for upper window of v3 - returns # of lines drawn */ + glui32 (*draw_callback)(winid_t win, glui32 width, glui32 height); + BOOL (*mouse_callback)(BOOL is_char_event, winid_t win, glui32 x, glui32 y); + + glui32 width, height; + glui32 x1, y1, x2, y2; + + glui32 last_height; /* What the height was last time we got input */ + glui32 biggest_height;/* The biggest it's been since */ + + glui32 curr_offset; /* offset into text_buffer/color_buffer */ + glui32 max_offset; /* curr_offset must stay < max_offset */ + glui32 buffer_size; /* max_offset must stay < buffer_size */ + + BOOL dirty; /* Has window been changed since last redraw? */ + BOOL defined; /* Is our location well defined? */ + + unsigned char *text_buffer; /* whole window for grid, current line for buffer */ + colorstyle *color_buffer; + + z_image images[12]; + + colorstyle current; + colorstyle actual; +}; + + +#define num_z_windows 16 + +static struct z_window game_windows[num_z_windows]; + +static glui32 upper_width, upper_height; + + +static int waitforinput(zwinid window, glui32 *val, + BOOL (*timer_callback)(zword), zword timer_arg); + + +void set_glk_stream_current(void) +{ + z_flush_text(&game_windows[0]); + glk_stream_set_current(game_windows[0].str); +} + +void draw_intext_picture(zwinid window, glui32 picture, glui32 alignment) +{ + z_flush_text(window); + wrap_glk_image_draw(window->win, picture, alignment, 0); +} + +void draw_picture(zwinid window, glui32 picture, glui32 x, glui32 y) +{ + int i; + int useimage = -1; + glui32 width, height; + + wrap_glk_image_get_info(operand[0], &width, &height); + + for(i = 0; i < 12; i++) { + if(is_in_bounds(window->images[i].x, window->images[i].y, + window->images[i].width, window->images[i].height, + x, y, width, height)) + useimage = i; + } + if(useimage == -1) + for(i = 0; i < 12; i++) + if(window->images[i].image_num == 0) + useimage = i; + if(useimage == -1) + return; + + window->images[useimage].image_num = picture; + window->images[useimage].x = x; + window->images[useimage].y = y; + window->images[useimage].width = width; + window->images[useimage].height = height; +} + + +static int showstuffcount = 0; + +/* Show an interpreter message */ +void showstuff(const char *title, const char *type, const char *message, offset number) +{ + static BOOL loopy = FALSE; + if(loopy) { + loopy = FALSE; + n_show_fatal(E_SYSTEM, "loopy message reporting", 0); + } + loopy = TRUE; + + z_pause_timed_input(&game_windows[0]); + z_flush_text(&game_windows[0]); + glk_stream_set_current(game_windows[0].str); + + glk_set_style(style_Alert); + w_glk_put_string("\n["); + w_glk_put_string(title); + w_glk_put_string(": "); + w_glk_put_string(type); + w_glk_put_string("]: "); + w_glk_put_string(message); + w_glk_put_string(" ("); + g_print_snumber(number); + w_glk_put_string(") "); + +#ifdef DEBUGGING + infix_gprint_loc(stack_get_depth(), 0); +#else + w_glk_put_string("PC="); + g_print_number(oldPC); + glk_put_char('\n'); +#endif + + if(++showstuffcount == 100) { + w_glk_put_string("[pausing every 100 errors]\n"); + z_wait_for_key(&game_windows[0]); + } + + glk_set_style(style_Normal); + + loopy = FALSE; +} + + +void init_lower(zwinid *lower) +{ + zwinid lower_win = &game_windows[0]; + glui32 i; + + if(lower) + *lower = lower_win; + + if(lower_win->win) { + z_pause_timed_input(lower_win); + glk_window_close(lower_win->win, NULL); + } + + set_stylehints(lower_win->current.fore, + lower_win->current.back); + + + lower_win->dirty = TRUE; + lower_win->wintype = wintype_TextBuffer; + lower_win->method = winmethod_Below; + lower_win->curr_offset = 0; + lower_win->max_offset = 80 * 24; + lower_win->buffer_size = lower_win->max_offset; + + if(!lower_win->text_buffer) + lower_win->text_buffer = (unsigned char *) n_malloc(lower_win->buffer_size); + if(!lower_win->color_buffer) + lower_win->color_buffer = (colorstyle *) n_malloc(lower_win->buffer_size * sizeof(colorstyle)); + + for(i = 0; i < lower_win->buffer_size; i++) { + lower_win->text_buffer[i] = ' '; + lower_win->color_buffer[i] = lower_win->current; + } + + lower_win->actual = lower_win->current; + + lower_win->win = glk_window_open(game_windows[1].win, + winmethod_Below | winmethod_Proportional, + 50, /* Percent doesn't matter */ + wintype_TextBuffer, 0); + + + if(lower_win->win == 0) { + n_show_fatal(E_OUTPUT, "cannot open lower window", 0); + return; + } + + lower_win->str = glk_window_get_stream(lower_win->win); + + if(lower_win->transcript) + glk_window_set_echo_stream(lower_win->win, lower_win->transcript); +} + + +void init_upper(zwinid *upper) +{ + zwinid upper_win = &game_windows[1]; + glui32 i; + + if(upper) + *upper = upper_win; + + if(upper_win->win) { + z_pause_timed_input(upper_win); + glk_window_close(upper_win->win, NULL); + } + + upper_win->dirty = TRUE; + upper_win->wintype = wintype_TextGrid; + upper_win->method = winmethod_Above | winmethod_Fixed; + upper_win->height = 0; + upper_win->width = upper_width; + upper_win->curr_offset = 0; + upper_win->max_offset = upper_height * upper_width; + upper_win->buffer_size = upper_win->max_offset; + + if(!upper_win->text_buffer) + upper_win->text_buffer = (unsigned char *) n_malloc(upper_win->buffer_size); + if(!upper_win->color_buffer) + upper_win->color_buffer = (colorstyle *) n_malloc(upper_win->buffer_size * sizeof(colorstyle)); + + for(i = 0; i < upper_win->buffer_size; i++) { + upper_win->text_buffer[i] = ' '; + upper_win->color_buffer[i] = upper_win->current; + } + + upper_win->actual = upper_win->current; + + upper_win->win = glk_window_open(game_windows[0].win, + winmethod_Above | winmethod_Fixed, + 1, /* XXX huh? upper_height, */ + wintype_TextGrid, 1); + + if(upper_win->win == 0) { + upper_win->str = 0; + return; + } + + upper_win->str = glk_window_get_stream(upper_win->win); + + if(upper_win->str == 0) { + glk_window_close(upper_win->win, NULL); + upper_win->win = 0; + return; + } +} + + +void z_init_windows(BOOL dofixed, + glui32 (*draw_callback)(winid_t, glui32, glui32), + BOOL (*mouse_callback)(BOOL, winid_t, glui32, glui32), + glui32 maxwidth, glui32 maxheight, + zwinid *upper, zwinid *lower) +{ + colorstyle defaultstyle; + defaultstyle.fore = 1; defaultstyle.back = 1; defaultstyle.style = 0; + + is_fixed = dofixed; + + kill_windows(); + + upper_width = maxwidth; upper_height = maxheight; + + game_windows[0].current = game_windows[1].current = defaultstyle; + game_windows[1].draw_callback = draw_callback; + game_windows[1].mouse_callback = mouse_callback; + + init_lower(lower); + init_upper(upper); +} + + +zwinid z_split_screen(glui32 wintype, glui32 method, + glui32 (*draw_callback)(winid_t, glui32, glui32), + BOOL (*mouse_callback)(BOOL, winid_t, glui32, glui32)) +{ + int i; + for(i = 0; i < num_z_windows; i++) { + if(!game_windows[i].win) { + winid_t root = glk_window_get_root(); + game_windows[i].win = glk_window_open(root, method, 0, wintype, 0); + game_windows[i].str = glk_window_get_stream(game_windows[i].win); + game_windows[i].wintype = wintype; + game_windows[i].method = method; + game_windows[i].transcript = NULL; + game_windows[i].glk_input_pending = FALSE; + game_windows[i].draw_callback = draw_callback; + game_windows[i].mouse_callback = mouse_callback; + game_windows[i].width = 0; + game_windows[i].height = 0; + game_windows[i].curr_offset = 0; + game_windows[i].max_offset = 1; + game_windows[i].buffer_size = 2; + game_windows[i].text_buffer = n_malloc(2); + game_windows[i].color_buffer = n_malloc(2); + game_windows[i].dirty = TRUE; + return &game_windows[i]; + } + } + return NULL; +} + + +void z_kill_window(zwinid win) +{ + if(!win) + return; + n_free(win->text_buffer); + win->text_buffer = NULL; + n_free(win->color_buffer); + win->color_buffer = NULL; + win->transcript = NULL; + glk_window_close(win->win, NULL); + win->win = NULL; + win->str = NULL; +} + + +/* close any open windows */ +void kill_windows(void) +{ + int i; + + for(i = 0; i < num_z_windows; i++) + z_clear_window(&game_windows[i]); + + free_windows(); + for(i = 0; i < num_z_windows; i++) { + if(game_windows[i].win) { + game_windows[i].transcript = NULL; + + glk_window_close(game_windows[i].win, NULL); + game_windows[i].win = NULL; + game_windows[i].str = NULL; + } + } + showstuffcount = 0; +} + + +/* free memory space used by windows, but don't close them */ +void free_windows(void) +{ + int i; + + z_flush_all_windows(); + + for(i = 0; i < num_z_windows; i++) { + if(game_windows[i].win) { + n_free(game_windows[i].text_buffer); + game_windows[i].text_buffer = NULL; + + n_free(game_windows[i].color_buffer); + game_windows[i].color_buffer = NULL; + } + } +} + +zwinid z_find_win(winid_t win) +{ + int i; + for(i = 0; i < num_z_windows; i++) { + if(game_windows[i].win == win) + return &game_windows[i]; + } + return NULL; +} + + +static BOOL coloreq(colorstyle a, colorstyle b) /* return true if colors are equivalent */ +{ + return (fgcolortable[(int) a.fore] == fgcolortable[(int) b.fore]) && + (bgcolortable[(int) a.back] == bgcolortable[(int) b.back]); +} + + +static void checkforblockquote(zwinid window, zwinid dest_win) +{ + if(window->biggest_height > window->last_height && + window->biggest_height > window->height) { + /* find borders of the blockquote */ + unsigned leftx = window->width, rightx = 0; + unsigned topy = window->biggest_height; + unsigned bottomy = window->height; + unsigned x, y, i; + + i = window->height * window->width; + for(y = window->height; y < window->biggest_height; y++) + for(x = 0; x < window->width; x++) + if(window->text_buffer[i++] != ' ') { + if(x < leftx) + leftx = x; + if(x > rightx) + rightx = x; + if(y < topy) + topy = y; + if(y > bottomy) + bottomy = y; + } + + z_pause_timed_input(dest_win); + glk_stream_set_current(game_windows[1].str); + glk_put_char(10); + glk_set_style(style_BlockQuote); + + /* draw the blockquote */ + for(y = topy; y <= bottomy; y++) { + i = y * window->width + leftx; + for(x = leftx; x <= rightx; x++) + glk_put_char(window->text_buffer[i++]); + glk_put_char(10); + } + } +} + + +void z_pause_timed_input(zwinid window) +{ + event_t eep; + if(window->glk_input_pending) { + window->glk_input_pending = FALSE; + + switch(window->pending_input_type) { + case evtype_CharInput: + glk_cancel_char_event(window->win); + break; + case evtype_LineInput: + glk_cancel_line_event(window->win, &eep); + window->pending_input_length = eep.val1; + } + } +} + + +void z_flush_all_windows(void) +{ + int window; + for(window = 0; window < num_z_windows; window++) { + if(game_windows[window].dirty) { + z_pause_timed_input(&game_windows[window]); + + switch(game_windows[window].wintype) { + case wintype_TextBuffer: + z_flush_text(&game_windows[window]); + break; + case wintype_TextGrid: + z_flush_fixed(&game_windows[window]); + break; + case wintype_Graphics: + z_flush_graphics(&game_windows[window]); + break; + } + } + } +} + +void z_draw_all_windows(void) +{ + int window; + for(window = 0; window < num_z_windows; window++) { + if(game_windows[window].wintype == wintype_TextGrid) { + game_windows[window].dirty = TRUE; + z_flush_fixed(&game_windows[window]); + } + } +} + + +static void z_put_styled_string(zwinid window, unsigned char *text, + colorstyle *color, glui32 length) +{ + glui32 n; + colorstyle laststyle = color[0]; + + if(!length) + return; + + glk_set_style_stream(window->str, bitmap_to_style[laststyle.style]); + + for(n = 0; n < length; n++) { + if(color[n].style != laststyle.style) + glk_set_style_stream(window->str, bitmap_to_style[color[n].style]); + glk_put_char_stream(window->str, text[n]); + laststyle = color[n]; + } +} + +void z_flush_fixed(zwinid window) +{ + glui32 winx, winy; + winid_t o; + glui32 start_line, end_line; + + /* If there's no such window, give up */ + if(!window->win || !window->str || + !window->text_buffer || !window->color_buffer) + return; + + /* glk doesn't allow writing to a window while input is pending */ + z_pause_timed_input(window); + + end_line = window->height; + + /* Has the window grown and shrunk? If so, probably because someone wants + to draw a box quote - don't let them shrink the window quite so fast */ + if(window->biggest_height > window->last_height && + window->biggest_height > window->height) + end_line = window->biggest_height; + + /* For v3 games, there's a callback function to draw the room name and + score; if this is present, we start drawing at a lower position */ + start_line = 0; + if(window->draw_callback) + start_line = window->draw_callback(NULL, 0, 0); + end_line += start_line; + + o = glk_window_get_parent(window->win); +#if 0 + glk_window_get_size(window->win, &winx, &winy); + if (!(window->method & winmethod_Above || window->method & winmethod_Below) + || winy != end_line) + glk_window_set_arrangement(o, window->method, + end_line, window->win); +#else + glk_window_set_arrangement(o, window->method, end_line, window->win); +#endif + glk_window_get_size(window->win, &winx, &winy); + + if(window->draw_callback) { + glk_stream_set_current(window->str); + glk_window_clear(window->win); + glk_window_move_cursor(window->win, 0, 0); + window->draw_callback(window->win, winx, winy); + } + + if(end_line > start_line && window->dirty) { + + unsigned padleft = 0, padmiddle = 0, padright = 0; + unsigned skipleft = 0, skipmiddle = 0, skipright = 0; + unsigned width; + unsigned firstwidth, lastwidth; + + unsigned x, y, i; + + /* Calculate how much space is used for margins */ + + unsigned left_margin = window->width, right_margin = window->width; + + i = 0; + for(y = start_line; y < end_line; y++) { + + for(x = 0; x < window->width; x++) + if(window->text_buffer[i + x] != ' ') { + if(x < left_margin) + left_margin = x; + break; + } + + for(x = 0; x < window->width; x++) + if(window->text_buffer[i + window->width - x - 1] != ' ') { + if(x < right_margin) + right_margin = x; + break; + } + + i += window->width; + } + + firstwidth = window->width; lastwidth = 0; + + if(start_line + 1 == end_line) { + unsigned longestx = 0; + unsigned longestlen = 0; + unsigned thisx = 0; + unsigned thislen = 0; + colorstyle lastcolor; + width = window->width; + + for(x = skipleft; x < width + skipleft; x++) { + if(window->text_buffer[x] == ' ' + && (!thislen || coloreq(window->color_buffer[x], lastcolor))) { + if(!thislen) + thisx = x; + thislen++; + lastcolor = window->color_buffer[x]; + } else { + if(thislen > longestlen) { + longestx = thisx; + longestlen = thislen; + } + thislen = 0; + } + } + if(longestlen > 4) { + firstwidth = longestx - skipleft; + skipmiddle = longestlen - 1; + lastwidth = width - firstwidth - skipmiddle; + } + } + + if(skipmiddle && winx < firstwidth + 2 + lastwidth) + padmiddle = 2; + + if(lastwidth && winx >= firstwidth + padmiddle + lastwidth) { + padmiddle = winx - firstwidth - lastwidth; + } else { + if(winx >= window->width) + width = window->width; + else + width = winx; + + if(right_margin + left_margin) { + if(winx > window->width) + padleft = (unsigned) ((winx - window->width) * (((float) left_margin) / (right_margin + left_margin))); + else + skipleft = (unsigned) ((window->width - winx) * (((float) left_margin) / (right_margin + left_margin))); + } + else { + padleft = winx - window->width; + } + + if(skipleft > left_margin) + skipleft = left_margin; + + if(winx > window->width) + padright = winx - window->width - padleft; + else + skipright = window->width - winx - skipleft; + + if(width < firstwidth + padmiddle) { + firstwidth = width; + padmiddle = 0; + lastwidth = 0; + } else if(width < firstwidth + padmiddle + lastwidth) { + lastwidth = width - firstwidth - padmiddle; + } + } + + + glk_stream_set_current(window->str); + glk_window_move_cursor(window->win, 0, start_line); + + /* draw to the upper window */ + i = 0; + for(y = start_line; y < end_line; y++) { + + for(x = 0; x < padleft; x++) + glk_put_char(' '); + + i += skipleft; + + z_put_styled_string(window, window->text_buffer + i, + window->color_buffer + i, firstwidth); + i += firstwidth; + + for(x = 0; x < padmiddle; x++) + glk_put_char(' '); + i += skipmiddle; + + z_put_styled_string(window, window->text_buffer + i, + window->color_buffer + i, lastwidth); + i += lastwidth; + + for(x = 0; x < padright; x++) + glk_put_char(' '); + + i += skipright; + } + + /* Bureaucracy needs the cursor positioned and visible in upper window. */ + glk_window_move_cursor(window->win, + window->curr_offset % window->width, + window->curr_offset / window->width); + window->dirty = FALSE; + } +} + + +void z_flush_text(zwinid window) +{ + z_pause_timed_input(window); + + if(!window->win || !window->str + || !window->text_buffer || !window->color_buffer + || window->curr_offset == 0) { + window->curr_offset = 0; + return; + } + + z_put_styled_string(window, window->text_buffer, window->color_buffer, + window->curr_offset); + + window->curr_offset = 0; + window->dirty = FALSE; +} + + +void z_flush_graphics(zwinid window) +{ + int i; + glui32 winx, winy; + float xratio, yratio; + winid_t parent; + + if(!window->win) + return; + + glk_window_get_size(window->win, &winx, &winy); + xratio = ((float) winx) / window->width; + yratio = ((float) winy) / window->height; + + parent = glk_window_get_parent(window->win); + + /* We want the window to maintain its original height/width ratio */ + switch(window->method & winmethod_DirMask) { + case winmethod_Left: /* Left and right splits mean height is fixed - */ + case winmethod_Right: /* adjust width to the yratio */ + glk_window_set_arrangement(parent, window->method, + (glui32) (window->width * yratio), 0); + break; + case winmethod_Above: /* Above and below splits mean width is fixed - */ + case winmethod_Below: /* adjust height to the xratio */ + glk_window_set_arrangement(parent, window->method, + (glui32) (window->height * xratio), 0); + break; + } + + /* Check to see what it became, and if it's still off, don't worry */ + glk_window_get_size(window->win, &winx, &winy); + xratio = ((float) winx) / window->width; + yratio = ((float) winy) / window->height; + + for(i = 0; i < 12; i++) { + if(window->images[i].image_num) { + wrap_glk_image_draw_scaled(window->win, window->images[i].image_num, + (glui32) (window->images[i].x * xratio), + (glui32) (window->images[i].y * yratio), + (glui32) (window->images[i].width * xratio), + (glui32) (window->images[i].height * yratio)); + } + } +} + +void z_print_number(zwinid window, int number) +{ + int i; + char buffer[12]; + int length = n_to_decimal(buffer, number); + + for(i = length - 1; i >= 0; i--) + z_put_char(window, buffer[i]); +} + +void z_put_char(zwinid window, unsigned c) +{ + colorstyle color = window->current; + if(is_fixed) + color.style |= sFIXE; + + if(c == 0) /* Section 3.8.2.1 */ + return; + + window->dirty = TRUE; + + if((c < 32 && c != 13) || (c >= 127 && c <= 159)) { /*Undefined in latin-1*/ + switch(window->wintype) { + case wintype_TextBuffer: + z_put_char(window, '['); + z_print_number(window, c); + z_put_char(window, ']'); + return; + case wintype_TextGrid: + c = '?'; + } + } + + switch(c) { + case 0x152: + z_put_char(window, 'O'); + c = 'E'; + break; + case 0x153: + z_put_char(window, 'o'); + c = 'e'; + break; + } + + + if(c > 255) /* Section 3.8.5.4.3 */ + c = '?'; + + if(c == 13) { /* Section 7.1.2.2.1 */ + switch(window->wintype) { + case wintype_TextBuffer: + window->text_buffer[window->curr_offset] = 10; + window->curr_offset++; + z_flush_text(window); + break; + case wintype_TextGrid: + window->curr_offset += window->width; + window->curr_offset -= window->curr_offset % window->width; + } + } else { + window->text_buffer[window->curr_offset] = c; + window->color_buffer[window->curr_offset] = color; + window->curr_offset++; + } + + if(window->curr_offset >= window->max_offset) { + switch(window->wintype) { + case wintype_TextBuffer: + z_flush_text(window); + break; + case wintype_TextGrid: + if(!window->defined) /* Section 8.6.2 */ + n_show_port(E_OUTPUT, "writing past end of window", c); + + if(window->max_offset) + window->curr_offset = window->max_offset - 1; + else + window->curr_offset = 0; + + window->defined = FALSE; + } + } +} + +void z_setxy(zwinid window, zword x, zword y) +{ + window->curr_offset = (y - 1) * window->width + (x - 1); + window->defined = TRUE; +} + +void z_getxy(zwinid window, zword *x, zword *y) +{ + if(window->width) { + *x = (window->curr_offset % window->width) + 1; + *y = (window->curr_offset / window->width) + 1; + } else { + *x = window->curr_offset + 1; + *y = 1; + } +} + +void z_getsize(zwinid window, unsigned *width, unsigned *height) +{ + *width = window->width; + *height = window->height; +} + +void z_find_size(glui32 *wid, glui32 *hei) +{ + glui32 oldwid, oldhei; + zwinid upper = &game_windows[1]; + winid_t o = glk_window_get_parent(upper->win); + glk_window_get_size(upper->win, &oldwid, &oldhei); + glk_window_set_arrangement(o, (upper->method & ~winmethod_Fixed) | + winmethod_Proportional, 100, upper->win); + glk_window_get_size(upper->win, wid, hei); + glk_window_set_arrangement(o, upper->method, oldhei, upper->win); + + upper_width = *wid; upper_height = *hei; + init_upper(&upper); +} + +void z_set_height(zwinid window, unsigned height) +{ + unsigned x; + if(height * window->width > window->buffer_size) { + n_show_error(E_OUTPUT, "height too large", height); + return; + } + + window->height = height; + if(height > window->biggest_height) + window->biggest_height = height; + + x = window->max_offset; + window->max_offset = height * window->width; + + for(; x < window->max_offset; x++) { + window->text_buffer[x] = ' '; + window->color_buffer[x] = window->current; + } + + window->dirty = TRUE; +} + +void z_set_color(zwinid window, unsigned fore, unsigned back) +{ + if(fore >= sizeof(fgcolortable) / sizeof(*fgcolortable)) { + n_show_error(E_OUTPUT, "illegal foreground color", fore); + return; + } + if(back >= sizeof(bgcolortable) / sizeof(*bgcolortable)) { + n_show_error(E_OUTPUT, "illegal background color", back); + return; + } + + fgcolortable[0] = fgcolortable[fore]; + bgcolortable[0] = bgcolortable[back]; + + window->current.fore = fore; + window->current.back = back; +} + +void z_set_style(zwinid window, int style) +{ + switch(style) { + case 0: window->current.style = 0; break; + case 1: window->current.style |= sREVE; break; + case 2: window->current.style |= sBOLD; break; + case 4: window->current.style |= sITAL; break; + case 8: window->current.style |= sFIXE; break; + default: n_show_error(E_OUTPUT, "undefined style", style); + } +} + +void set_fixed(BOOL p) +{ + if(!p) { + is_fixed = FALSE; + } else { + is_fixed = TRUE; + } +} + +void z_set_transcript(zwinid window, strid_t stream) +{ + window->transcript = stream; + glk_window_set_echo_stream(window->win, stream); +} + +void z_clear_window(zwinid window) +{ + glui32 i; + + if(window == &game_windows[0] && showstuffcount) { + z_pause_timed_input(&game_windows[0]); + z_flush_text(&game_windows[0]); + glk_stream_set_current(game_windows[0].str); + w_glk_put_string("[pausing to show unread error message]\n"); + z_wait_for_key(&game_windows[0]); + } + + window->dirty = TRUE; + window->curr_offset = 0; + + if(window->win && window->text_buffer && window->color_buffer) { + switch(window->wintype) { + case wintype_TextGrid: + for(i = 0; i < window->max_offset; i++) { + window->text_buffer[i] = ' '; + window->color_buffer[i] = window->current; + } + window->curr_offset = 0; + window->dirty = TRUE; + break; + case wintype_TextBuffer: + z_pause_timed_input(window); + z_flush_text(window); + if(coloreq(window->actual, window->current)) { + glk_window_clear(window->win); + } else { + init_lower(NULL); /* **FIXME** This is wrong, but deal with it later */ + } + } + } +} + +void z_erase_line(zwinid window) +{ + if(window->wintype == wintype_TextGrid) { + int i; + int x = window->curr_offset % window->width; + int endoffset = window->curr_offset + (window->width - x); + + window->dirty = TRUE; + for(i = window->curr_offset; i < endoffset; i++) { + window->text_buffer[i] = ' '; + window->color_buffer[i] = window->current; + } + } +} + + +/* Waits for input or timeout + * Returns: + * 0 - output during wait; may need to redraw or somesuch + * -1 - callback routine said to stop + * 10 - read input + * 254 - mouse input + * char and line events will be canceled by the time it exits + */ +static int waitforinput(zwinid window, glui32 *val, + BOOL (*timer_callback)(zword), zword timer_arg) +{ + int i; + event_t moo; + zwinid t; + + showstuffcount = 0; + + for(i = 0; i < num_z_windows; i++) + if(game_windows[i].mouse_callback && game_windows[i].win) + glk_request_mouse_event(game_windows[i].win); + + window->glk_input_pending = TRUE; + + while(window->glk_input_pending) { + glk_select(&moo); + + check_sound(moo); + + switch(moo.type) { + case evtype_Timer: + if(timer_callback && timer_callback(timer_arg)) { + if(window->pending_input_type == evtype_CharInput) { + glk_cancel_char_event(window->win); + *val = 0; + } else { + glk_cancel_line_event(window->win, &moo); + *val = moo.val1; + } + window->glk_input_pending = FALSE; + return -1; + } + break; + + case evtype_CharInput: + *val = moo.val1; + window->glk_input_pending = FALSE; + return 10; + + case evtype_LineInput: + *val = moo.val1; + window->glk_input_pending = FALSE; + return 10; + + case evtype_MouseInput: + t = z_find_win(moo.win); + if(t && t->mouse_callback && + t->mouse_callback(window->pending_input_type == evtype_CharInput, + moo.win, moo.val1, moo.val2)) { + if(window->pending_input_type == evtype_CharInput) { + glk_cancel_char_event(window->win); + *val = 254; + } else { + glk_cancel_line_event(window->win, &moo); + *val = moo.val1; + } + window->glk_input_pending = FALSE; + return 254; + } + glk_request_mouse_event(moo.win); + break; + + case evtype_Arrange: + z_draw_all_windows(); + } + + z_flush_all_windows(); + } + + if(window->pending_input_type == evtype_LineInput) + *val = window->pending_input_length; + else + *val = 0; + + return 0; +} + + +void z_wait_for_key(zwinid window) +{ + glui32 ch; + do { + z_draw_all_windows(); + glk_request_char_event(window->win); + window->pending_input_type = evtype_CharInput; + } while(waitforinput(window, &ch, NULL, 0) == 0); + window->pending_input_type = 0; +} + + +zwinid check_valid_for_input(zwinid window) +{ + glui32 y, i; + if(!window->win) { + zwinid newwin = NULL; + for(i = 0; i < num_z_windows; i++) { + if(game_windows[i].win) { + newwin = &game_windows[i]; + break; + } + } + if(!newwin) + return NULL; + + if(window->wintype == wintype_TextGrid) { + i = 0; + for(y = 0; y < window->height; y++) { + z_put_char(newwin, 13); + z_put_styled_string(newwin, window->text_buffer + i, + window->color_buffer + i, window->width); + i += window->width; + } + z_put_char(newwin, 13); + } + + window = newwin; + } + return window; +} + + +/* returns number of characters read */ +int z_read(zwinid window, char *dest, unsigned maxlen, unsigned initlen, + zword timer, BOOL (*timer_callback)(zword), zword timer_arg, + unsigned char *terminator) +{ + /* FIXME: support terminating characters when (if) glk gets support for + them */ + unsigned i; + unsigned ux, uy; + glui32 length; + BOOL done; + + if(automap_unexplore()) { + read_abort = TRUE; + return 0; + } + + read_abort = FALSE; + + if(initlen > maxlen) { + n_show_error(E_OUTPUT, "initlen > maxlen", initlen); + return 0; + } + + if(window == 0) + window = &game_windows[0]; + + if(window->pending_input_type != 0) { + n_show_error(E_OUTPUT, "nested input attempted", 0); + return 0; + } + +#ifdef DEBUGGING + + if(do_automap) { + const char *dir = automap_explore(); + if(dir) { + length = n_strlen(dir); + if(length > maxlen) + length = maxlen; + n_strncpy(dest, dir, length); + return length; + } + } +#endif + + glk_request_timer_events(timer * 100); /* if time is zero, does nothing */ + + if(initlen != 0 && window->wintype == wintype_TextBuffer) { + BOOL good = FALSE; + if(initlen <= window->curr_offset) { + good = TRUE; + for(i = 0; i < initlen; i++) /* check the end of the linebuffer */ + if(window->text_buffer[window->curr_offset - initlen + i] != dest[i]) { + good = FALSE; + break; + } + } + if(!good) { + /* bleah */ + /* argh */ + /* oof */ + } else { + window->curr_offset -= initlen; /* Remove initial text from linebuffer */ + } + } + + if(window->wintype == wintype_TextGrid) { + ux = window->curr_offset % window->width; + uy = window->curr_offset / window->width; + } + + z_flush_all_windows(); + window = check_valid_for_input(window); + + done = FALSE; + length = initlen; + while(!done) { + int t; + + if(window->wintype == wintype_TextGrid) + glk_window_move_cursor(window->win, ux, uy); + + if(input_stream1) { + glui32 len = maxlen; + *terminator = transcript_getline(dest, &len); + length = len; + } + if(input_stream1) { /* If didn't EOF, input_stream1 will be non-zero */ + glk_stream_set_current(window->str); + set_glk_stream_current(); + glk_set_style(style_Input); + glk_put_buffer(dest, length); + glk_put_char(10); + done = TRUE; + } else { + glk_request_line_event(window->win, dest, maxlen, length); + window->pending_input_type = evtype_LineInput; + + t = waitforinput(window, &length, timer_callback, timer_arg); + if(t != 0) { + if(t == -1) + *terminator = 0; + else + *terminator = t; + done = TRUE; + } + } + + if(done) + stream4line(dest, length, *terminator); + +#ifdef DEBUGGING + if(done && length >= 2 && dest[0] == '/') { + if(dest[1] == '/') { /* "//" means no command, but start with "/" */ + for(i = 1; i < length; i++) + dest[i-1] = dest[i]; + length--; + } else { + done = FALSE; + dest[length] = 0; + + process_debug_command(dest+1); + + if(read_abort) + done = TRUE; + + length = 0; + } + } +#endif + } + glk_request_timer_events(0); /* stop timer */ + + window->pending_input_type = 0; + + for(i = 0; i < num_z_windows; i++) + game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height; + + return length; +} + +zword z_read_char(zwinid window, + zword timer, BOOL (*timer_callback)(zword), zword timer_arg) +{ + unsigned i; + glui32 ch; + zword validch = 0; + + if(automap_unexplore()) { + read_abort = TRUE; + return 0; + } + + if(input_stream1) { + unsigned num; + validch = transcript_getchar(&num); + if(!validch) + validch = num; + } + if(input_stream1) { + return validch; + } + + read_abort = FALSE; + + glk_request_timer_events(timer * 100); + + z_flush_all_windows(); + window = check_valid_for_input(window); + + do { + do { + z_draw_all_windows(); + glk_request_char_event(window->win); + window->pending_input_type = evtype_CharInput; + } while(waitforinput(window, &ch, timer_callback, timer_arg) == 0); + + if(' ' <= ch && ch <= '~') + validch = ch; + + switch(ch) { + case 8: + case keycode_Delete: validch = 8; break; + case 9: + case keycode_Tab: validch = 9; break; + case 13: + case keycode_Return: validch = 13; break; +/* case 21: + if(restoreundo()) { + read_abort = TRUE; + return 0; + } + break; */ + case 27: + case keycode_Escape: validch = 27; break; + case 16: + case keycode_Up: validch = 129; break; + case 14: + case keycode_Down: validch = 130; break; + case 2: + case keycode_Left: validch = 131; break; + case 6: + case keycode_Right: validch = 132; break; + case keycode_Func1: validch = 133; break; + case keycode_Func2: validch = 134; break; + case keycode_Func3: validch = 135; break; + case keycode_Func4: validch = 136; break; + case keycode_Func5: validch = 137; break; + case keycode_Func6: validch = 138; break; + case keycode_Func7: validch = 139; break; + case keycode_Func8: validch = 140; break; + case keycode_Func9: validch = 141; break; + case keycode_Func10: validch = 142; break; + case keycode_Func11: validch = 143; break; + case keycode_Func12: validch = 144; break; + } + } while(!(validch || ch == 0)); + + glk_request_timer_events(0); /* stop timer */ + + window->pending_input_type = 0; + + for(i = 0; i < num_z_windows; i++) + game_windows[i].biggest_height = game_windows[i].last_height = game_windows[i].height; + + return validch; +} + + +/* +void zwin_init(int number, glui32 wintype, + glui32 x_coord, glui32 y_coord, glui32 x_size, glui32 y_size) +{ + zwinid self = game_windows + number; + + if(x_coord == self->x1) { + if(y_coord == self->y1) { + + + if(game_windows[number].win) { + z_pause_timed_input(game_windows[number].win); + glk_window_close(game_windows[number].win, NULL); + } + set_style_hints(); + game_windows[number].win = glk_window_open( +} +*/ diff --git a/interpreters/nitfol/linkevil.h b/interpreters/nitfol/linkevil.h new file mode 100644 index 0000000..df65116 --- /dev/null +++ b/interpreters/nitfol/linkevil.h @@ -0,0 +1,144 @@ +/* Evil linked-list implementation in C with macros + + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at ecr+@andrew.cmu.edu +*/ + + +#define LEaddm(list, member, mallocfunc) do { \ + void *temppointer = list; \ + list = mallocfunc(sizeof(*list)); \ + *list = member; \ + list->next = temppointer; \ + } while(0) + +#define LEadd(list, member) LEaddm(list, member, n_malloc) + +#define LEremovem(list, freefunc) do { \ + if(list) { \ + void *temppointer = list; \ + list = list->next; \ + freefunc(temppointer); \ + } \ + } while(0); + +#define LEremove(list) LEremovem(list, n_free) + + +#define LEdestructm(list, destruct, freefunc) do { \ + while(list) { \ + void *temppointer = list; \ + destruct; \ + list = list->next; \ + freefunc(temppointer); \ + } \ + } while(0) + +#define LEdestruct(list, destruct) LEdestructm(list, destruct, n_free) + +#define LEdestroym(list, freefunc) LEdestructm(list, , freefunc) + +#define LEdestroy(list) LEdestroym(list, n_free) + + +#define LEsearch(list, dest, cond) \ + do { \ + for(dest = list; dest; dest=dest->next) \ + if(cond) break; \ + } while(0) + +/* temp != NULL if it successfully performed the removal */ +#define LEsearchremovem(list, dest, temp, cond, destruct, freefunc) \ + do { \ + dest = list; \ + if(dest && (cond)) { \ + temp = dest; \ + destruct; \ + LEremovem(list, freefunc); \ + } else { \ + for(temp = list; temp; temp=temp->next) { \ + dest=temp->next; \ + if(dest && (cond)) { \ + temp->next = dest->next; \ + destruct; \ + freefunc(dest); \ + } \ + } \ + } \ + } while(0) + +#define LEsearchremove(list, dest, temp, cond, destruct) LEsearchremovem(list, dest, temp, cond, destruct, n_free) + +#define LEappend(dest, src) do { \ + void *temppointer = dest; \ + if(dest) { \ + while(dest->next) \ + dest = dest->next; \ + dest->next = src; \ + dest = temppointer; \ + } else { \ + dest = scr; \ + } \ + } while(0) + + + + +/* This is just an example: */ +#if 0 + +/* +Use: Declare a struct like this: +*/ +typedef struct { + struct mylist *next; /* The evil linked list library depends on this */ + int myint; + char *mystring; +} mylist; + + +void foo() { + mylist *foo = NULL; + mylist *found; + mylist *another = NULL; + + mylist mymember1 = { NULL, 42, "frogs" }; + mylist mymember2 = { NULL, 38, "potato" }; + mylist mymember3 = { NULL, 21, "monkey" }; + + LEremove(foo); /* Remove nonexistent head element - this is safe */ + + LEadd(foo, mymember1); /* Add an element to the head */ + LEadd(foo, mymember2); /* Add another element to the head */ + LEadd(foo, mymember3); + LEadd(foo, mymember1); + + LEremove(foo); /* Remove the head element */ + + LEsearch(foo, found, (strcmp(found->mystring, "potato") == 0)); + + LEdup(another, found); + + LEdestroy(foo); + + LEadd(foo, mymember3); + + LEprepend(another, foo); +} + +#endif diff --git a/interpreters/nitfol/main.c b/interpreters/nitfol/main.c new file mode 100644 index 0000000..ce0ceac --- /dev/null +++ b/interpreters/nitfol/main.c @@ -0,0 +1,133 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +#include "gi_blorb.h" + + +static void set_zfile(strid_t file) +{ + glk_stream_set_position(file, 0, seekmode_End); + total_size = glk_stream_get_position(file); + glk_stream_set_position(file, 0, seekmode_Start); + + current_zfile = file; + zfile_offset = 0; + + if(!load_header(file, total_size, FALSE)) { + /* FIMXE: add code to check to see if it's a jzexe product, and perhaps + even code to grab it out of disk images */ + current_zfile = NULL; + return; + } + + glk_stream_set_position(file, zfile_offset, seekmode_Start); +} + + +static strid_t savefile; + +int game_use_file(strid_t file) +{ + giblorb_map_t *map; + giblorb_result_t res; + strid_t z; + if(wrap_gib_create_map(file, &map) == giblorb_err_None) { + if(!current_zfile) { + if(wrap_gib_load_resource(map, giblorb_method_FilePos, &res, + giblorb_ID_Exec, 0) == giblorb_err_None) { + current_zfile = file; + zfile_offset = res.data.startpos; + total_size = res.length; + } + } + if(!blorb_file) + wrap_gib_count_resources(map, giblorb_ID_Pict, &imagecount, NULL, NULL); + wrap_gib_destroy_map(map); + + if(!blorb_file) { + if(wrap_gib_set_resource_map(file) == giblorb_err_None) { + blorb_file = file; + return TRUE; + } + } + } + + if((z = quetzal_findgamefile(file)) != 0) { + savefile = file; + file = z; + } + + if(!current_zfile) { + set_zfile(file); + + return TRUE; + } + + return FALSE; +} + + +void glk_main(void) +{ + if(!current_zfile) + { + winid_t tempwin; + tempwin = glk_window_open(0, 0, 100, wintype_TextBuffer, 0); + glk_set_window(tempwin); + glk_set_style(style_Preformatted); + glk_put_string( +"Usage: nitfol [OPTIONS] gamefile\n" +" -i, -ignore Ignore Z-machine strictness errors\n" +" -f, -fullname For running under Emacs or DDD\n" +" -x, -command Read commands from this file\n" +" -P, -pirate Aye, matey\n" +" -spell Perform spelling correction\n" +" -expand Expand one letter abbreviations\n" +" -s, -symbols Specify symbol file for game\n" +" -t, -tandy Censors some Infocom games\n" +" -T, -transcript Write transcript to this file\n" +" -d, -debug Enter debugger immediatly\n" +" -prompt Specify debugging prompt\n" +" -autoundo Ensure '@save_undo' is called every turn\n" +" -S, -stacklimit Exit when the stack is this deep\n" +" -a, -alias Specify an alias\n" +" -ralias Specify an recursive alias\n" +" -unalias Remove an alias\n" +" -r, -random Set random seed\n" +" -mapsym Specify mapping glyphs\n" +" -mapsize Specify map size\n" +" -maploc Specify map location\n" +" -terpnum Specify interpreter number\n" +" -terpver Specify interpreter version\n"); + glk_exit(); + } + z_init(current_zfile); + if(savefile) { + if(restorequetzal(savefile)) { + if(zversion <= 3) + mop_take_branch(); + else + mop_store_result(2); + } + } + init_undo(); + decode(); +} diff --git a/interpreters/nitfol/main.h b/interpreters/nitfol/main.h new file mode 100644 index 0000000..b8d224d --- /dev/null +++ b/interpreters/nitfol/main.h @@ -0,0 +1,14 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i main.c' */ +#ifndef CFH_MAIN_H +#define CFH_MAIN_H + +/* From `main.c': */ +int game_use_file (strid_t file ); +void glk_main (void); + +#endif /* CFH_MAIN_H */ diff --git a/interpreters/nitfol/nio.h b/interpreters/nitfol/nio.h new file mode 100644 index 0000000..980b4c5 --- /dev/null +++ b/interpreters/nitfol/nio.h @@ -0,0 +1,51 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i io.c' */ +#ifndef CFH_IO_H +#define CFH_IO_H + +/* From `io.c': */ +typedef struct z_window * zwinid; +extern BOOL is_fixed; +extern glsi32 bgcolortable[]; +extern glsi32 fgcolortable[]; +void set_glk_stream_current (void); +void draw_intext_picture (zwinid window , glui32 picture , glui32 alignment ); +void draw_picture (zwinid window , glui32 picture , glui32 x , glui32 y ); +void showstuff (const char *title , const char *type , const char *message , offset number ); +void init_lower (zwinid *lower ); +void init_upper (zwinid *upper ); +void z_init_windows (BOOL dofixed , glui32 ( *draw_callback ) ( winid_t , glui32 , glui32 ) , BOOL ( *mouse_callback ) ( BOOL , winid_t , glui32 , glui32 ) , glui32 maxwidth , glui32 maxheight , zwinid *upper , zwinid *lower ); +zwinid z_split_screen (glui32 wintype , glui32 method , glui32 ( *draw_callback ) ( winid_t , glui32 , glui32 ) , BOOL ( *mouse_callback ) ( BOOL , winid_t , glui32 , glui32 ) ); +void z_kill_window (zwinid win ); +void kill_windows (void); +void free_windows (void); +zwinid z_find_win (winid_t win ); +void z_pause_timed_input (zwinid window ); +void z_flush_all_windows (void); +void z_draw_all_windows (void); +void z_flush_fixed (zwinid window ); +void z_flush_text (zwinid window ); +void z_flush_graphics (zwinid window ); +void z_print_number (zwinid window , int number ); +void z_put_char (zwinid window , unsigned c ); +void z_setxy (zwinid window , zword x , zword y ); +void z_getxy (zwinid window , zword *x , zword *y ); +void z_getsize (zwinid window , unsigned *width , unsigned *height ); +void z_find_size (glui32 *wid , glui32 *hei ); +void z_set_height (zwinid window , unsigned height ); +void z_set_color (zwinid window , unsigned fore , unsigned back ); +void z_set_style (zwinid window , int style ); +void set_fixed (BOOL p ); +void z_set_transcript (zwinid window , strid_t stream ); +void z_clear_window (zwinid window ); +void z_erase_line (zwinid window ); +void z_wait_for_key (zwinid window ); +zwinid check_valid_for_input (zwinid window ); +int z_read (zwinid window , char *dest , unsigned maxlen , unsigned initlen , zword timer , BOOL ( *timer_callback ) ( zword ) , zword timer_arg , unsigned char *terminator ); +zword z_read_char (zwinid window , zword timer , BOOL ( *timer_callback ) ( zword ) , zword timer_arg ); + +#endif /* CFH_IO_H */ diff --git a/interpreters/nitfol/nitfol.6 b/interpreters/nitfol/nitfol.6 new file mode 100644 index 0000000..8241d06 --- /dev/null +++ b/interpreters/nitfol/nitfol.6 @@ -0,0 +1,178 @@ +.TH NITFOL 6 +.SH NAME +nitfol \- Z-code interpreter and debugger. +.SH SYNOPSIS +.B nitfol +.I "[options] file" +.SH DESCRIPTION +This manpage was generated from bits of the info page. See the info page for complete documentation. + +Nitfol is a portable interpreter for Z-machine code, the game format used by Infocom and more recently, Inform . Nitfol handles versions one through eight of the format, and attempts to comply with version 1.0 of the Z-machine specification. + +You will need game files to use nitfol. The ``if-archive'' contains a large collection of these, available at or at the USA mirror . + +This manual describes how to use nitfol and how it differs from other Z-machine interpreters. This manual was written with unix-like systems in mind; ignore that which does not apply to your platform. Comments on and corrections for this manual and nitfol are appreciated and should be sent to . + +.SH OPTIONS +.TP +.B \-ignore, \-no\-ignore, \-i +Ignore Z-machine strictness errors. Normally nitfol checks for illegal and undefined Z-machine behaviour and alerts the user. If you're playing someone else's buggy game, this can be annoying and you should use this option. +.TP +.B \-fullname, \-no\-fullname, \-f +For running under Emacs or DDD. Tells nitfol to give machine-recognizeable markers when stack frames are displayed instead of displaying the line. Only useful if you are using nitfol as an inferior debugger. +.TP +.B \-command \fIfile\fB, \-x \fIfile +Read commands from this file. Load a script from a file for playback in the game. +.TP +.B \-pirate, \-no\-pirate, \-P +Aye, matey. Make the piracy opcode not branch, letting the game know the game disc is not ``genuine.'' Infocom never used this opcode and neither should you, but if obscurity amuses you... +.TP +.B \-quiet, \-no\-quiet, \-q +Do not print introductory messages. For GDB compatibility. +.TP +.B \-spell, \-no\-spell +Perform spelling correction. Normally Z-machine games are unforgiving of typos (though they do have an \fBoops\fP command). If you type in a word which isn't in the game's dictionary and have typo correction enabled, nitfol will search for a word which is off by one letter by a substitution, deletion, insertion or transposition. +.TP +.B \-expand, \-no\-expand +Expand one letter abbreviations. Early Z-machine games don't include `x' as a synonym for `examine' and such. If the first word in a sentence is one letter long and not recognized by the game, this will attempt to expand it to a full word. +.TP +.B \-symbols \fIfile\fB, \-s \fIfile +Specify symbol file for game. If you want to perform source-level debugging, you will need to specify a symbol file, which contains the names of variables and tells nitfol how source code corresponds to object code. +.TP +.B \-tandy, \-no\-tandy, \-t +Censors some Infocom games. Some version 3 games perform minor wording changes when this bit is set to appease the sensitivity of Tandy Corporation. +.TP +.B \-transcript \fIwfile\fB, \-T \fIwfile +Write transcript to this file. This transcript begins as soon as the game starts. +.TP +.B \-debug, \-no\-debug, \-d +Enter debugger immediatly. Imitate GDB by not automatically starting the story. +.TP +.B \-prompt \fIstring\fB +Specify debugging prompt. This is the prompt nitfol prints when it is waiting for a debugging command (as opposed to the > the game story prints when waiting for a game command). DDD requires this to be `(gdb) '. +.TP +.B \-path \fIstring\fB +Look for games in this directory. If nitfol cannot find the requested game in the current directory, it looks through the directories listed in the given colon separated string. The directories specified here are also used to search for gamefiles from saved games. If this option is not used, nitfol looks at the \fBINFOCOM_PATH\fP environment variable. +.TP +.B \-autoundo, \-no\-autoundo +Ensure \fB@save_undo\fP is called every turn. If a turn passes with no \fB@save_undo\fP between, this option performs the \fB@save_undo\fP automagically. Could cause problems with some games which have a different concept of a turn. +.TP +.B \-stacklimit \fInumber\fB, \-S \fInumber +Exit when the stack is this deep. If a game is infinitely recursing, nitfol will allocate large amounts of memory and take a long time before the problem is reported. This option makes it fatal to recurse more than the given number of stack frames. Setting this to 0 makes nitfol allow as many as fit contiguously in memory. The Z-machine Standards Document recommends games use no more than 1024 words of total stack (frames and pushed data) in ZIP, which roughly works out to 90 routine calls deep. +.TP +.B \-alias \fIstring\fB, \-a \fIstring +Specify an alias. Adds an alias which will be expanded in read lines before tokenisation. The alias is of the form \fIname\fP \fIvalue\fP; you will need to use quotes around it on the commandline. +.TP +.B \-ralias \fIstring\fB +Specify an recursive alias. Adds an alias whose result is checked for further alias expansion. Identical syntax to adding a normal alias. +.TP +.B \-unalias \fIstring\fB +Remove an alias. Removes an alias previously added by -alias. Useful for removing aliases in preference files. +.TP +.B \-random \fInumber\fB, \-r \fInumber +Set random seed. Normally the random number generator is initialized with the time of day. If this option is used with a non-zero argument, the given number will be used to initialize the generator and for \fB@random 0\fP. +.TP +.B \-mapsym \fIstring\fB +Specify mapping glyphs. Nitfol draws maps using ASCII characters; you can choose which characters it uses to draw rooms. Defaults to `*udb@UDB+', which is, in order: empty room, room with down exit, room with up exit, room with up and down exits, room with player, room with player and up exit, room with player and down exit, room with player and up and down exits, bend symbol. +.TP +.B \-mapsize \fInumber\fB +Specify map size. Determines the number of lines to be used for the map. +.TP +.B \-maploc \fIstring\fB +Specify map location. Nitfol creates a Glk window for the map it generates. The map can be placed `above', `below', to the `right', or the `left', of the main game window. Follow this option with one those locations. +.TP +.B \-terpnum \fInumber\fB +Specify interpreter number. Each port of Infocom's Z-machine interpreter was given a number. `1' for their own DECSystem-20, `2' for Apple IIe, `3' for Macintosh, `4' for Amiga, `5' for Atari ST, `6' for IBM PC, `7' for Commodore 128, `8' for Commodore 64, `9' for Apple IIc, `10' for Apple IIgs, `11' for Tandy Color. Giving this option makes nitfol claim to be running on the specified system. A few games change their behaviour slightly depending on which machine type they run. By default nitfol claims to be on an Apple IIe, as this makes Beyond Zork not do character graphics. +.TP +.B \-terpver \fIstring\fB +Specify interpreter version. Infocom's interpreters were given versions, typically a capital letter. Nitfol defaults to `N', Frotz uses `F', and ZIP uses `B'. Any single character version is allowed. Multicharacter options are read as a number instead of an ASCII character. Only known effect upon games is the letter printed by banners and the `version' command. Version 6 games interpret this as a number instead of a letter. +.SH BUGS + +A nitfol bug is any behaviour which makes nitfol reliably misbehave, with the exceptions of bugs in Glk libraries. These include: anything which makes nitfol crash (other than when nitfol reports `FATAL' errors), anything which causes nitfol to contradict the Z-machine standards documents (except for optional enhancements like spelling correction and debug mode), any buffer overflows, and anything which makes nitfol infinite loop other than infinite loops in the game itself. + +Before reporting a bug, make sure the bug is not listed below and your copy of nitfol is not compiled with `-DFAST'. Please report the version of nitfol, your system type and a series of commands which reliably cause the bug. + +Nitfol is lacking: +.IP \(bu +Graphical font (\fIBeyond Zork\fP) (should use images for this) +.IP \(bu +Terminating character support (mostly \fIBeyond Zork\fP) +.IP \(bu +Reverse video, full color (should querry Glk more aggressively) +.IP \(bu +Unicode support +.IP \(bu +keypad character codes +.IP \(bu +its own random number generator (relies on system one) +.PP + +Nitfol does incorrectly: +.IP \(bu +Play is not paused to wait for sounds to complete in \fIThe Lurking Horror\fP. +.IP \(bu +Pictures and text are not placed correctly for v6 games. +.IP \(bu +block quotes are placed in the upper window, so \fBcheapnitfol\fP can't see them. +.IP \(bu +Corrupted save files may make nitfol do bad things. +.IP \(bu +Should figure out a way to handle buggy games like \fIAMFV\fP and \fIVaricella\fP which assume the upper window is 80 columns wide. +.IP \(bu +Doesn't catch header writing other than \fB@storeb\fP and \fB@storew\fP. +.PP + +Debugger problems: +.IP \(bu +Sometimes says there's no code at a location where it could be clever and + find some. +.IP \(bu +\fBofclass\fP, superclass not implemented. +.IP \(bu +Should perform more sanity checks everywhere. +.IP \(bu +Lots of useful commands not yet implemented. +.IP \(bu +\fIobject\fP.\fIfunction\fP is handled incorrectly, both for assignments and calls. +.IP \(bu +Assumes you know what you're doing, so \fBquit\fP, \fBrun\fP, etc., don't prompt you for confirmation. +.PP + +Automapping problems: +.IP \(bu +Doesn't work well for random destinations (the forest in \fIAdvent\fP) +.IP \(bu +\fB@get_cursor\fP doesn't return the correct value during automapping since output is disabled. +.IP \(bu +Requires too much work for the end-user; should put in stuff to make it figure out the location global in 95% of games. +.IP \(bu +Doesn't really work if multiple locations are coded as being in the same room (long road in \fIEnchanter\fP). +.IP \(bu +Doesn't show exits which go nowhere, but change the game. +.IP \(bu +Perhaps should use graphics windows when available. +.IP \(bu +Movement causing teleportation confuses it. +.IP \(bu +Reincarnation handling isn't optimal. +.IP \(bu +Still very buggy. +.IP \(bu +It's too slow. +.IP \(bu +Should realize it can add extra bends (especially in one-way passages). +.IP \(bu +Should be able to output nice-looking Postscript. +.IP \(bu +Should store map in saved games (wait until automapping code stabilizes). +.PP + +.SH "SEE ALSO" +.RB "`\|" nitfol "\|'" +entry in +.B +info; +.BR frotz (6), +.BR txd (1). +.SH AUTHOR +nitfol was written by Evin Robertson, who can be reached at nitfol@my-deja.com. diff --git a/interpreters/nitfol/nitfol.h b/interpreters/nitfol/nitfol.h new file mode 100644 index 0000000..319a536 --- /dev/null +++ b/interpreters/nitfol/nitfol.h @@ -0,0 +1,371 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#ifndef NITFOL_H +#define NITFOL_H + +#include /* For NULL, rand, srand */ +#include /* For time() */ +#include /* for isspace, isgraph, etc. */ +#include +#include "glk.h" +#define GLK_EOF ((glsi32) -1) + +#define NITFOL_MAJOR 0 +#define NITFOL_MINOR 5 + +/* Change these next few typedefs depending on your compiler */ +#if UCHAR_MAX==0xff +typedef unsigned char zbyte; +#else +#error "Can't find an 8-bit integer type" +#endif + +#ifdef FAST_SHORT + +#if SHRT_MAX==0x7fff +typedef unsigned short zword; +#elif INT_MAX==0x7fff +typedef unsigned int zword; +#else +#error "Can't find a 16-bit integer type" +#endif + +#if INT_MAX==0x7fffffff +typedef unsigned int offset; +#elif LONG_MAX==0x7fffffff +typedef unsigned long offset; +#else +#error "Can't find a 32-bit integer type" +#endif + +#ifdef TWOS16SHORT +#define FAST_TWOS16SHORT +#endif + +#else + +#ifdef FAST_SIGNED +#if INT_MAX==0x7fffffff +typedef int zword; +typedef int offset; +#elif LONG_MAX==0x7fffffff +typedef long zword; +typedef long offset; +#else +#error "Can't find a 32-bit integer type" +#endif + +#else + +#if INT_MAX==0x7fffffff +typedef unsigned int zword; +typedef unsigned int offset; +#elif LONG_MAX==0x7fffffff +typedef unsigned long zword; +typedef unsigned long offset; +#else +#error "Can't find a 32-bit integer type" +#endif + +#endif + +#endif + +#ifndef BOOL +#define BOOL int +#endif +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef MAX +#define MAX(a, b) (a > b ? a : b) +#endif +#ifndef MIN +#define MIN(a, b) (a < b ? a : b) +#endif + +#ifdef PASTE +#undef PASTE +#endif +#ifdef XPASTE +#undef XPASTE +#endif +#define PASTE(a, b) a##b +#define XPASTE(a, b) PASTE(a, b) + + +#if defined(__cplusplus) || defined(USE_INLINE) +#define N_INLINE inline +#elif defined(INLINE) +#define N_INLINE INLINE +#elif defined(__GNUC__) +#define N_INLINE __inline__ +#else +#define N_INLINE +#endif + + + +#ifdef ZVERSION_GRAHAM_9 + +#define ZWORD_SIZE 4 +#define ZWORD_MAX 0x7fffffffL +#define ZWORD_MASK 0xffffffffL +#define ZWORD_WRAP 0x100000000L +#define ZWORD_TOPBITMASK 0x80000000L + +#else + +#define ZWORD_SIZE 2 +#define ZWORD_MAX 0x7fff +#define ZWORD_MASK 0xffff +#define ZWORD_WRAP 0x10000L +#define ZWORD_TOPBITMASK 0x8000 + +#endif + + +#ifdef FAST_TWOS16SHORT + +#define ARITHMASK(n) (n) +#define is_neg(a) ((short) (a) < 0) +#define neg(a) (-((short) (a))) + +#else + +#define ARITHMASK(n) ((n) & ZWORD_MASK) +#define is_neg(a) ((a) > ZWORD_MAX) +#define neg(a) ((ZWORD_WRAP - (a)) & ZWORD_MASK) + +#endif + + +#ifdef TWOS16SHORT + +#define is_greaterthan(a, b) (((short) (a)) > ((short) (b))) +#define is_lessthan(a, b) (((short) (a)) < ((short) (b))) + +#else + +#define is_greaterthan(a, b) (((b) - (a)) & ZWORD_TOPBITMASK) +#define is_lessthan(a, b) (((a) - (b)) & ZWORD_TOPBITMASK) + +#endif + + +#ifdef FAST + +#define LOBYTE(p) z_memory[p] +#define LOBYTEcopy(d, s) z_memory[d] = LOBYTE(s) +#define LOBYTEwrite(p, n) z_memory[p] = (n) +#define LOWORD(p) MSBdecodeZ(z_memory + (p)) +#define LOWORDcopy(d, s) BYTEcopyZ(z_memory + (d), z_memory + (s)) +#define LOWORDwrite(p, n) MSBencodeZ(z_memory + (p), n) + +/* If you have a segmented memory model or need to implement virtual memory, + you can change the next three lines, and the corresponding three in the + not FAST section. */ +#define HIBYTE(p) z_memory[p] +#define HIWORD(p) MSBdecodeZ(z_memory + (p)) +#define HISTRWORD(p) HIWORD(p) + +#else /* not FAST */ + +/* FIXME: these tests may not work on 16 bit machines */ + +#define LOBYTE(p) ((p) >= ZWORD_WRAP ? z_range_error(p) : \ + z_memory[p]) +#define LOBYTEcopy(a, b) ((void) \ + ((a) >= dynamic_size ? z_range_error(a) : \ + (z_memory[a] = LOBYTE(b)))) +#define LOBYTEwrite(p, n) ((void) \ + ((p) >= dynamic_size ? z_range_error(p) : \ + (z_memory[p] = (n)))) +#define LOWORD(p) ((p) + ZWORD_SIZE > ZWORD_WRAP ? z_range_error(p) : \ + MSBdecodeZ(z_memory + (p))) +#define LOWORDcopy(d, s) ((void) \ + ((d) + ZWORD_SIZE > dynamic_size ? z_range_error(d) : \ + BYTEcopyZ(z_memory + (d), z_memory + (s)))) +#define LOWORDwrite(p, n) ((void) \ + ((p) + ZWORD_SIZE > dynamic_size ? z_range_error(p) : \ + MSBencodeZ(z_memory + (p), n))) + +#define HIBYTE(p) ((p) >= game_size ? z_range_error(p) : z_memory[p]) +#define HIWORD(p) ((p) + ZWORD_SIZE > total_size ? z_range_error(p) : \ + MSBdecodeZ(z_memory + (p))) +#define HISTRWORD(p) HIWORD(p) + + +#endif /* not FAST */ + + + +/* Probably your system has more efficient ways of reading/writing MSB values, + so go ahead and plop it in here if you crave that extra bit of speed */ + +#define MSBdecode1(v) ((v)[0]) +#define MSBdecode2(v) ((((zword) (v)[0]) << 8) | (v)[1]) +#define MSBdecode3(v) ((((offset) (v)[0]) << 16) | (((offset) (v)[1]) << 8) \ + | (v)[2]) +#define MSBdecode4(v) ((((offset) (v)[0]) << 24) | (((offset) (v)[1]) << 16) \ + | (((offset) (v)[2]) << 8) | (v)[3]) + +#define MSBencode1(v, n) ((v)[0] = (char) (n)) +#define MSBencode2(v, n) (((v)[0] = (char) ((n) >> 8)), ((v)[1] = (char) (n))) +#define MSBencode3(v, n) (((v)[0] = (char) ((n) >> 16)), \ + ((v)[1] = (char) ((n) >> 8)), \ + ((v)[2] = (char) (n))) +#define MSBencode4(v, n) (((v)[0] = (char) ((n) >> 24)), \ + ((v)[1] = (char) ((n) >> 16)), \ + ((v)[2] = (char) ((n) >> 8)), \ + ((v)[3] = (char) (n))) + +#define BYTEcopy1(d, s) ((d)[0] = (s)[0]) +#define BYTEcopy2(d, s) ((d)[0] = (s)[0], (d)[1] = (s)[1]) +#define BYTEcopy3(d, s) ((d)[0] = (s)[0], (d)[1] = (s)[1], (d)[2] = (s)[2]) +#define BYTEcopy4(d, s) ((d)[0] = (s)[0], (d)[1] = (s)[1], \ + (d)[2] = (s)[2], (d)[3] = (s)[3]) + + +#define MSBdecodeZ(v) XPASTE(MSBdecode, ZWORD_SIZE)(v) +#define MSBencodeZ(v, n) XPASTE(MSBencode, ZWORD_SIZE)(v, n) +#define BYTEcopyZ(d, s) XPASTE(BYTEcopy, ZWORD_SIZE)(d, s) + + + +#define UNPACKR(paddr) (paddr * granularity + rstart) +#define UNPACKS(paddr) (paddr * granularity + sstart) + +#define PACKR(addr) ((addr - rstart) / granularity) +#define PACKS(addr) ((addr - sstart) / granularity) + +/* Byte offsets into the header */ +#define HD_ZVERSION 0x00 +#define HD_FLAGS1 0x01 +#define HD_RELNUM 0x02 +#define HD_HIMEM 0x04 +#define HD_INITPC 0x06 +#define HD_DICT 0x08 +#define HD_OBJTABLE 0x0a +#define HD_GLOBVAR 0x0c +#define HD_STATMEM 0x0e +#define HD_FLAGS2 0x10 +#define HD_SERNUM 0x12 +#define HD_ABBREV 0x18 +#define HD_LENGTH 0x1a +#define HD_CHECKSUM 0x1c +#define HD_TERPNUM 0x1e +#define HD_TERPVER 0x1f +#define HD_SCR_HEIGHT 0x20 +#define HD_SCR_WIDTH 0x21 +#define HD_SCR_WUNIT 0x22 +#define HD_SCR_HUNIT 0x24 +#define HD_FNT_WIDTH 0x26 +#define HD_FNT_HEIGHT 0x27 +#define HD_RTN_OFFSET 0x28 +#define HD_STR_OFFSET 0x2a +#define HD_DEF_BACK 0x2c +#define HD_DEF_FORE 0x2d +#define HD_TERM_CHAR 0x2e +#define HD_STR3_WIDTH 0x30 +#define HD_STD_REV 0x32 +#define HD_ALPHABET 0x34 +#define HD_HEADER_EXT 0x36 +#define HD_USERID 0x38 +#define HD_INFORMVER 0x3c + + +enum zversions { v1 = 1, v2, v3, v4, v5, v6, v7, v8, vM }; +enum opcodeinfoflags { opNONE = 0, opSTORES = 1, opBRANCHES = 2, opJUMPS = 4, + opTEXTINLINE = 8 }; + +typedef struct { + const char *name; + int minversion, maxversion; + int minargs, maxargs; + int flags; + int watchlevel; +} opcodeinfo; + + +typedef enum { OBJ_GET_INFO, OBJ_RECEIVE, OBJ_MOVE } watchinfo; + + +#include "portfunc.h" /* For miscellaneous string functions, etc. */ +#include "hash.h" +#include "linkevil.h" +#include "struct.h" +#include "globals.h" +#include "binary.h" +#include "errmesg.h" +#include "iff.h" +#include "init.h" +#include "decode.h" +#include "main.h" + +#include "nio.h" +#include "z_io.h" + +#include "no_snd.h" +#include "gi_blorb.h" +#include "no_graph.h" +#include "no_blorb.h" + +#include "infix.h" +#include "debug.h" +#include "inform.h" +#include "copying.h" +#include "solve.h" +#include "automap.h" + +#include "zscii.h" +#include "tokenise.h" +#include "op_call.h" +#include "op_jmp.h" +#include "op_math.h" +#include "quetzal.h" +#include "undo.h" +#include "op_save.h" +#include "op_table.h" +#include "op_v6.h" +#include "objects.h" +#include "stack.h" +#include "oplist.h" + + +strid_t startup_findfile(void); + +strid_t intd_filehandle_open(strid_t savefile, glui32 operating_id, + glui32 contents_id, glui32 interp_id, + glui32 length); + +void intd_filehandle_make(strid_t savefile); + +glui32 intd_get_size(void); + +strid_t startup_open(const char *name); + + +#endif + diff --git a/interpreters/nitfol/nitfol.html b/interpreters/nitfol/nitfol.html new file mode 100644 index 0000000..756cb25 --- /dev/null +++ b/interpreters/nitfol/nitfol.html @@ -0,0 +1,1189 @@ + + + + +Nitfol + + + +

Nitfol

+

+


+

Table of Contents

+ +


+ + +

1 Introduction

+ +

+Nitfol is a portable interpreter for Z-machine code, the game format used by Infocom and more recently, Inform. Nitfol handles versions one through eight of the format, and attempts to comply with version 1.0 of the Z-machine specification. + +

+

+You will need game files to use nitfol. The "if-archive" contains a large collection of these, available at ftp://ftp.gmd.de/if-archive/games/zcode/ or at the USA mirror http://ifarchive.org/indexes/if-archiveXgamesXzcode.html. + +

+

+This manual describes how to use nitfol and how it differs from other Z-machine interpreters. This manual was written with UNIX-like systems in mind; ignore that which does not apply to your platform. Comments on and corrections for this manual and nitfol are appreciated and should be sent to nitfol@my-deja.com. + +

+ + + +

2 Invoking nitfol

+ +

+Invoke nitfol with the game filename, and options. If you omit the game filename, nitfol will prompt you for one. The following options are recognized: + +

+
+ +
-ignore +
+
-no-ignore +
+
-i +
+Ignore Z-machine strictness errors. Normally nitfol checks for illegal and undefined Z-machine behaviour and alerts the user. If you're playing someone else's buggy game, this can be annoying and you should use this option. + +
-fullname +
+
-no-fullname +
+
-f +
+For running under Emacs or DDD. Tells nitfol to give machine-recognizeable markers when stack frames are displayed instead of displaying the line. Only useful if you are using nitfol as an inferior debugger. + +
-command file +
+
-x file +
+Read commands from this file. Load a script from a file for playback in the game. + +
-pirate +
+
-no-pirate +
+
-P +
+Aye, matey. Make the piracy opcode not branch, letting the game know the game disc is not "genuine." Infocom never used this opcode and neither should you, but if obscurity amuses you... + +
-quiet +
+
-no-quiet +
+
-q +
+Do not print introductory messages. For GDB compatibility. + +
-spell +
+
-no-spell +
+Perform spelling correction. Normally Z-machine games are unforgiving of typos (though they do have an oops command). If you type in a word which isn't in the game's dictionary and have typo correction enabled, nitfol will search for a word which is off by one letter by a substitution, deletion, insertion or transposition. + +
-expand +
+
-no-expand +
+Expand one letter abbreviations. Early Z-machine games don't include x as a synonym for examine and such. If the first word in a sentence is one letter long and not recognized by the game, this will attempt to expand it to a full word. + +
-symbols file +
+
-s file +
+Specify symbol file for game. If you want to perform source-level debugging, you will need to specify a symbol file, which contains the names of variables and tells nitfol how source code corresponds to object code. + +
-tandy +
+
-no-tandy +
+
-t +
+Censors some Infocom games. Some version 3 games perform minor wording changes when this bit is set to appease the sensitivity of Tandy Corporation. + +
-transcript wfile +
+
-T wfile +
+Write transcript to this file. This transcript begins as soon as the game starts. + +
-debug +
+
-no-debug +
+
-d +
+Enter debugger immediatly. Imitate GDB by not automatically starting the story. + +
-prompt string +
+Specify debugging prompt. This is the prompt nitfol prints when it is waiting for a debugging command (as opposed to the > the game story prints when waiting for a game command). DDD requires this to be `(gdb) '. + +
-path string +
+Look for games in this directory. If nitfol cannot find the requested game in the current directory, it looks through the directories listed in the given colon separated string. The directories specified here are also used to search for gamefiles from saved games. If this option is not used, nitfol looks at the INFOCOM_PATH environment variable. + +
-autoundo +
+
-no-autoundo +
+Ensure @save_undo is called every turn. If a turn passes with no @save_undo between, this option performs the @save_undo automagically. Could cause problems with some games which have a different concept of a turn. + +
-stacklimit number +
+
-S number +
+Exit when the stack is this deep. If a game is infinitely recursing, nitfol will allocate large amounts of memory and take a long time before the problem is reported. This option makes it fatal to recurse more than the given number of stack frames. Setting this to 0 makes nitfol allow as many as fit contiguously in memory. The Z-machine Standards Document recommends games use no more than 1024 words of total stack (frames and pushed data) in ZIP, which roughly works out to 90 routine calls deep. + +
-alias string +
+
-a string +
+Specify an alias. Adds an alias which will be expanded in read lines before tokenisation. The alias is of the form name value; you will need to use quotes around it on the commandline. + +
-ralias string +
+Specify an recursive alias. Adds an alias whose result is checked for further alias expansion. Identical syntax to adding a normal alias. + +
-unalias string +
+Remove an alias. Removes an alias previously added by -alias. Useful for removing aliases in preference files. + +
-random number +
+
-r number +
+Set random seed. Normally the random number generator is initialized with the time of day. If this option is used with a non-zero argument, the given number will be used to initialize the generator and for @random 0. + +
-mapsym string +
+Specify mapping glyphs. Nitfol draws maps using ASCII characters; you can choose which characters it uses to draw rooms. Defaults to `*udb@UDB+', which is, in order: empty room, room with down exit, room with up exit, room with up and down exits, room with player, room with player and up exit, room with player and down exit, room with player and up and down exits, bend symbol. + +
-mapsize number +
+Specify map size. Determines the number of lines to be used for the map. + +
-maploc string +
+Specify map location. Nitfol creates a Glk window for the map it generates. The map can be placed `above', `below', to the `right', or the `left', of the main game window. Follow this option with one those locations. + +
-terpnum number +
+Specify interpreter number. Each port of Infocom's Z-machine interpreter was given a number. `1' for their own DECSystem-20, `2' for Apple IIe, `3' for Macintosh, `4' for Amiga, `5' for Atari ST, `6' for IBM PC, `7' for Commodore 128, `8' for Commodore 64, `9' for Apple IIc, `10' for Apple IIgs, `11' for Tandy Color. Giving this option makes nitfol claim to be running on the specified system. A few games change their behaviour slightly depending on which machine type they run. By default nitfol claims to be on an Apple IIe, as this makes Beyond Zork not do character graphics. + +
-terpver string +
+Specify interpreter version. Infocom's interpreters were given versions, typically a capital letter. Nitfol defaults to `N', Frotz uses `F', and ZIP uses `B'. Any single character version is allowed. Multicharacter options are read as a number instead of an ASCII character. Only known effect upon games is the letter printed by banners and the `version' command. Version 6 games interpret this as a number instead of a letter. + +
+ + + +

3 Features

+ + + +

3.1 Preferences

+ +

+If you don't like the default options and don't want to recompile, you can set your preferences by writing a `.nitfolrc' in your home directory. + +

+

+Each line should be of the form option=value. Blank lines and lines starting with a # are ignored. If you want to specify different options for different copies of nitfol, you can put those options in a block which will only be read by copies of nitfol with a specific name. + +

+

+Here's an example `.nitfolrc': + +

+path=/usr/local/games/infocom
+alias=v verbose
+alias=asierra tone cordial. ask sierra about
+ignore=true
+
+[strictnitfol]
+ignore=false
+spell=false
+expand=false
+autoundo=false
+unalias=v
+unalias=asierra
+
+[xnitfol]
+tandy=true
+pirate=true
+
+ +

+Nitfol will look in `/usr/local/games/infocom' for game files. Copies of nitfol named `strictnitfol' will report Z-machine strictness errors, perform strict tokenisation, and not automatically @save_undo. All others will ignore strictness errors and have two aliases. `xnitfol' will set the Tandy bit and branch on the piracy opcode. + +

+

+Options specified in the preference file may be overruled by environment variables and command line options. + +

+ + + +

3.2 Infinite undo/redo

+ +

+Multiple @restore_undo opcodes with no intervening @save_undo will restore earlier and earlier saved states. However, Inform games will not do this, so if you want infinite undo, you must enter the commands /undo and /redo. The /... commands are part of the debugger, so you will need to compile in debugger support to use this feature. + +

+

+Z-machine games prior to version 5 do not provide undo (none of them provide redo), and some version 5 games don't use it (like Wishbringer). If the game performs two @read opcodes with no intervening @save_undo or @restore_undo, nitfol will perform a @save_undo. + +

+ + + +

3.3 Aliases

+ +

+If the game has long words which you wish to abbreviate, you can use aliases. Use the command /alias name value. All instances of name in line input will be replaced with value. name may not contain whitespace. + +

+

+Unlike abbreviation expansion and typo correction, alias expansion modifies the text buffer, inserting the requested text. This is necessary to allow multiple commands to be given in an alias through the use of periods. + +

+

+Aliases are not expanded recursively, so you could do something clever like this: + +

+>/alias e w
+/alias w e
+/alias nw ne
+/alias ne nw
+/alias sw se
+/alias se sw
+
+ +

+And your east-west movement will be swapped (e will do a w, though east will still do east). Aliases expand on all input the game receives using @read, including transcripts and directions from the automapper. + +

+

+If you want the expansion of the alias to be checked for further aliases, you must use the /ralias command. This expansion is stopped when an alias would expand itself. + +

+>/ralias e w
+/ralias w e
+
+ +

+Would do nothing, as e is expanded to w, which is expanded to e, and then it stops because the rule for expanding e has already taken place. + +

+ +
+>/ralias hanoi2 move src to extra. move src to dest. move extra to dest
+/alias src left
+/alias dest center
+/alias extra right
+hanoi2
+You move the small disc from the left peg to the right peg.
+You move the medium disc from the left peg to the middle peg.
+You move the small disc from the right peg to the middle peg.
+>move left to right
+You move the large disc from the left peg to the right peg.
+>/alias src center
+/alias dest right
+/alias extra left
+hanoi2
+You move the small disc from the middle peg to the left peg.
+You move the medium disc from the middle peg to the right peg.
+You move the small disc from the left peg to the right peg.
+
+ +

+Ideally you should be able to define an alias which recursively solves any depth by relying on lower levels being solvable, but this isn't yet possible. You must keep the expansion of aliases to a reasonable size, since Inform has a fairly small buffer size. + +

+

+You can remove aliases using the /unalias command. + +

+>/unalias hanoi2
+
+ +

+Aliases do not effect /... commands; if they did, it wouldn't be possible to /unalias. + +

+ + + +

3.4 Abbreviation Expansion

+ +

+Early Infocom games don't provide abbreviations like x for examine. If you enable abbreviation expansion, nitfol will attempt to expand one letter words at the beginning of inputs which are not in the game's dictionary. + +

+

+Nitfol supports the following expansions (note that some are non-standard): + +

+ + +c i o s x
close + d down + e east + g again +
+
inventory + k attack + l look + n north +
+
oops + p open + q quit + r drop +
+
south + t take + u up + w west +
+
examine + y yes + z wait +
+ +From Zork I: + +
+West of House
+You are standing in an open field west of a white house, with a
+boarded front door.
+There is a small mailbox here.
+
+>x mailbox
+[x -> examine]
+The small mailbox is closed.
+
+>p it
+[p -> open]
+Opening the small mailbox reveals a leaflet.
+
+>t leaflet
+[t -> take]
+Taken.
+
+ + + +

3.5 Typo correction

+ +

+In the Z-machine, the @read opcode provides the interpreter with a dictionary to search in order to do tokenisation and word matching. If you enable typo correction and enter a word not in the provided dictionary, nitfol will search for near misses. + +

+

+From Curses: + +

+>ask jemmia about gloves
+[jemmia -> jemima]
+"Those are my gloves."
+
+ +

+Nitfol takes the following steps to correct typos: + +

+ +
    +
  1. + +If the entered word is in the dictionary, behave as normal. + +
  2. + +If the length of the word is less than 3 letters long, give up. We don't want to make assumptions about what so short words might be. + +
  3. + +If the word is the same as a dictionary word with one transposition, assume it is that word. exmaine becomes examine. + +
  4. + +If it is a dictionary word with one deleted letter, assume it is that word. botle becomes bottle. + +
  5. + +If it is a dictionary word with one inserted letter, assume it is that word. tastey becomes tasty. + +
  6. + +If it is a dictionary word with one substitution, assume it is that word. opin becomes open. +
+ +

+This behavior can be annoying when nitfol "corrects" intentionally entered words which are similar to dictionary words. Usually this has no effect upon the game, perhaps slightly changing the game's error message, but may have negative effects when it causes an undesired action. Games like Beyond Zork expect you to type words not in their dictionary to name things. Nitfol might "correct" your entered word to a dictionary word, which the game might complain about. + +

+

+If typo correction is getting in your way, run nitfol with `-no-smart', compile it without applying `-DSMART_TOKENISER', or edit `nitfol.opt' to change the compile-time default. + +

+ + + +

3.6 Automapping

+ +

+Nitfol has the ability to display an on-screen map showing visited rooms and their connections on the current floor. Below is a map generated from Enchanter. + +

+                                   *-* *
+                                   |   |
+                   u-*-*-*-*-------*---*
+                   |               |
+       * *   *     |     *---*     |
+       |/ \ /      |    /|\ / \    |
+       *   *       *   / | X * \   *
+      /     \      |  /  |/ v|  \  |
+     /   *   *-*-*-*-*---*---u---*-*-*-@
+    /    |  /      | |\  |\ ^|  /  |    
+ *-*     * *       | | \ | X * /   *-*
+    \    |/        | |  \|/ \ /    |  
+     *   *         * *   *---*     *
+      \ /          | |             |
+       *           u-d-*-----------*-u
+                                   |  
+                                   *
+                                    \
+                                     *
+
+ +

+The `*'s designate rooms; the `@' the current room. Rooms containing staircases are shown with a `u' or `d', or `b' if the staircase is bi-directional. If the current room contains a staircase, nitfol draws it with a `U', `D', or `B'. Passageways are shown with lines; the `X's are crossed lines. One-way passages are shown as lines with arrows. Nitfol uses `v', `^', `<', and `>' for arrow heads. + +

+

+In Glks which provide mouse events, you can click on rooms and it will display the room name (and number) in the upper left hand corner of the map. Note that XGlk is slightly broken, so you need to click on the left-hand side of the room. Clicking on an empty map space clears the name. + +

+

+In order to use automapping, you must tell nitfol how to calculate the current location. You do this by specifying an Inform expression, so you must have debugging enabled. + +

+

+Typically the current location is available in a global. In Z-code versions 3 and prior, the current location is always stored in global zero, so typing /automap (global) 0 should work. In later versions, you must figure out an expression which evaluates to the current location. + +

+

+First, find out where the player object is. Typically, the player object is named `self', `cretin', `self-object', or the name of the PC. You can use the find command to search object names. If this all fails, try object-tree to find the location number. + +

+

+Once you have found the number of the location, you need to figure out which global keeps track of the location. You can use the globals command to search the globals. + +

+

+From Spider And Web: + +

+>/find self
+20 "(self object)"
+25 "yourself" in 91 "End of Alley"
+26 "yourself" in 48 "chair"
+/globals 91
+G15 G36 G39
+s
+
+Mouth of Alley
+You're in the entrance of a narrow brick alley, which runs further
+in to the north. To the south a broad street courses by, congested
+with traffic and bicycles, although none of them seem to notice you.
+
+>/find self
+20 "(self object)"
+25 "yourself" in 94 "Mouth of Alley"
+26 "yourself" in 48 "chair"
+/globals 94
+G15 G36 G39
+/automap (global) 15
+
+ +

+Obviously we have 3 globals tracking the player location. Typically there are only two, but some games have more. In this, we just picked the first one, which is probably the Inform location variable; another is probably the real_location variable. Depending on how you want automapping to behave in the dark, or when dealing with game-specific stuff, you may want to pick a different one. + +

+

+To figure out what is in which direction, nitfol checks the current location, tells the game to go north, checks the new location, undoes the north movement, tries to go northeast, and so on. During all of this, output is disabled. + +

+

+Drawing the map is more complicated. First nitfol looks for cycles in the graph and makes the cycles connect properly. Then it draws the map. If parts of the map overlapp, it finds a path connecting the overlapping bits and tries increasing the length of each passage in this path by one, and recalculates cycle connections. If this solves the problem, it's done; otherwise, it tries increasing the length of two passages, or of one of the passages by two. If this fails, it gives up. + +

+

+This technique isn't perfect. The implementation of this technique isn't perfect either. So expect nitfol to misbehave a lot on complex maps, and a little on simple maps. If you clever ideas on how to improve it, let me know. + +

+

+Nitfol makes an effort to simplify the map. If multiple exits go from the barn to cornfield and you've been to both places, nitfol will draw a single two-way passage if possible. If both up and west go up the stairs and nitfol knows east returns from the top of the stairs, nitfol will draw it as a simple west-east passage ignoring the up/down. If east doesn't return from the top of the staircase, nitfol will draw it as up/down, leaving out the west passage. + +

+

+If you've been north of a gate, and come up to the gate from the south, +and unlock the gate, nitfol will draw it as a one-way passage since last +time it was north of the gate, it couldn't go south. + +

+

+Some games feature reincarnation, perhaps moving you to a new location. If movement leads to your death, this makes nitfol think the reincarnation location is in that direction. Nitfol watches for three asterisks in a row and will assume they mean death instead of a normal passage. + +

+

+Some of these problems could be avoided by having nitfol explore each neighboring room, but this would make automapping even slower. + +

+ + + +

3.7 Quetzal

+ +

+Nitfol uses Quetzal version 1.4 for its save format, so you can use your saves between different computers and interpreters. More information about Quetzal is available at http://www.geocities.com/SiliconValley/Vista/6631/. + +

+

+If you specify a save-file on the command-line on UNIX, nitfol uses a UNIX IntD chunk to locate the game file associated with the save name. This chunk is included in save games when nitfol can figure out the current filename. If you compile nitfol with -D__USE_GNU, -D__USE_BSD, or -D__USE_XOPEN_EXTENDED, nitfol will canonicalize the file name, so you don't have to worry about relative file name specifications no longer working if you invoke nitfol from a different directory. + +

+

+On MacOS, nitfol uses alias records from a MACS IntD chunk to locate the game file. This won't work for games built-in to the interpreter. + +

+

+If no IntD chunk is included, nitfol searches the environment variable INFOCOM_PATH for a game with matching release number, serial number, and checksum. + +

+

+Looking for games without an IntD chunk isn't foolproof, but it should work most of the time. Serial numbers are basically the date and it's extremely unlikely more than ten games will be compiled on the same day (the only time lots of games are compiled on same day is right before competition time). Assuming they all have the same release number, there's still only a .0686% chance that at least two of these ten will share the same checksum. If someone reports this as a problem, I'll make nitfol ensure the game contains a save opcode right before the restored PC. + +

+ + + +

3.8 Blorb

+ +

+If you wish to hear sounds or see graphics in your games, they must be packaged in Blorb files. The Z-machine game may included in the Blorb file or may be specified separately. Nitfol does not support the traditional Infocom `.mg1' and `.snd' files. + +

+

+Note that graphics are displayed incorrectly, and sound has not yet been tested. + +

+ + +

4 Debugger

+ +

+Nitfol debugging mode tries to imitate the GDB interface. If you're familiar with that, you should have no problem using nitfol (other than dealing with the current incompleteness). + +

+

+You need inform 6.21 or later, as earlier versions don't produce correct infix files without a patch. You then need to compile infix information for your game. I recommend doing: + +

+

+inform -k -~S -~X -~D MyGame.inf + +

+

+Then your debug information will be in `gameinfo.dbg'. If you have a command-line on your platform, run nitfol like nitfol MyGame.z5 -symbols gameinfo.dbg. Otherwise, start up your game and type /symbol-file gameinfo.dbg the first time you get a prompt. + +

+

+When the game stops to read a line of text, you can begin that line with / to give a debug command. If you want to pass a line beginning with a / to the game, double the / and the game will be passed the second one. When at a (nitfol) prompt, starting commands with / is neither necessary nor recommended. + +

+

+All expressions are like the ones used in Inform. + +

+

+You can perform casts to get the result in the form you want: + +

+
+ +
(number) expression +
+Use expression as if it were a number. Useful when you want to know the number of something, not the object, routine, or string information nitfol normally gives. +
(object) expression +
+Use expression as if it were an object. Most useful when printing the result, as it will show the object's attributes and properties. +
(routine) expression +
+Use expression as if it were the packed address of a routine. Useful if you have the packed address of a routine which you want to set a breakpoint at. +
(string) expression +
+Use expression as if it were the packed address of a string. Useful for printing it. +
(global) expression +
+Evaluates to the value of a numbered global. (global) 0 is the player location for version 3 Infocom games. +
(local) expression +
+Evaluates to the value of a numbered local. Not terribly useful unless you're debugging something without source. +
+ +

+Here are short descriptions of the debugger commands. See Info file `gdb', node `Top', for more information. Some of these were taken/adapted from GDB's help. + +

+
+ +
info breakpoints +
+ +
info breakpoints num +
+ +List breakpoints. An argument specifies a specific breakpoint to list. + +
quit +
+ +Exit nitfol. + +
show language +
+ +Show the current source language. + +
condition num exp +
+ +Set a condition for an existing breakpoint. + +
restore +
+ +Restore a saved game. + +
break linespec +
+ +
break linespec if exp +
+ +Set a breakpoint. An if clause specifies a condition. + +
stepi +
+ +
stepi num +
+ +Step exactly one instruction. An argument specifies a repeat count. + +
restart +
+ +Restart the game. + +
object-tree +
+ +
object-tree exp +
+ +Display the object tree. An argument says which object to use as the root of the tree. + +
disable display num +
+ +Temporarily disable an automatic display. + +
select-frame num +
+ +Select a specific stack frame. + +
alias name value +
+ +Add an alias + +
down-silently +
+ +
down-silently num +
+ +Silently select the child of the selected frame. An argument specifies how many frames down to go. + +
frame +
+ +
frame num +
+ +Show the selected stack frame. An argument specifies a stack frame to show. + +
give exp num +
+ +
give exp ~ num +
+ +Give an object an attribute. With a tilde clears the attribute instead of setting it. + +
set exp +
+ +Evaluate an expression without printing its value. + +
print exp +
+ +Evaluates an expression and prints the result. This can include function calls. + +
up +
+ +
up num +
+ +Select the parent of the selected frame. An argument specifies how many frames up to go. + +
# comment +
+ +Enter a comment + +
continue +
+ +
continue num +
+ +Continue execution. An argument sets the ignore count of the current breakpoint. + +
dumpmem file +
+ +Dump memory to a file + +
undo +
+ +Undo last move (not last debugger command). + +
display exp +
+ +Print value of an expression each time the program stops. + +
move exp to exp +
+ +Move an object around the object tree. + +
up-silently +
+ +
up-silently num +
+ +Select the parent of the selected frame silently. An argument specifies how many frames up to go. + +
show copying +
+ +Show licensing information. + +
recording off +
+ +Stop recording a script. + +
jump linespec +
+ +Continue execution at a new location. + +
recording on +
+ +Start recording a script. + +
ralias name value +
+ +Add a recursive alias + +
globals +
+ +
globals exp +
+ +List all global variables and their values. With an argument, list all only those with a specific value. + +
backtrace +
+ +
backtrace num +
+ +
backtrace - num +
+ +Display the parent functions of the current frame. An argument specifies how many frames back to show. If the argument is negative, start from the first frame instead of the current. + +
find +
+ +Find objects whose shortnames contain a string. + +
finish +
+ +An argument specifies a repeat count. + +
down +
+ +
down num +
+ +Select the child of the selected frame. An argument specifies how many frames down to go. + +
ignore num num +
+ +Set the ignore count for a breakpoint. + +
replay off +
+ +Halt replay. + +
nexti +
+ +
nexti num +
+ +Step one instruction, stepping over subroutine calls. Step a specified number of instructions, stepping over subroutine calls. + +
help +
+ +Print list of commands. + +
redo +
+ +Redo undid move. Only works immediately after an undo. + +
enable num +
+ +Re-enabled a breakpoint. + +
until +
+ +Resume execution until the program reaches a line number greater than the current line. + +
replay +
+ +Replay a recorded script. + +
unalias name +
+ +Remove an alias + +
remove exp +
+ +Remove an object from the object tree. + +
info sources +
+ +List source files. + +
delete num +
+ +Delete a breakpoint. + +
symbol-file file +
+ +Load debugging info from a file (usually `gameinfo.dbg'). + +
automap exp +
+ +Start automapping + +
show warranty +
+ +Show warranty information. + +
disable num +
+ +Temporarily disable a breakpoint. + +
undisplay num +
+ +Stop automatically displaying an expression. + +
enable display num +
+ +Re-enable an automatic display. + +
step +
+ +
step num +
+ +Step through program to a different source line. An argument specifies a repeat count. + +
info source +
+ +Get information on the current source file. + +
next +
+ +
next num +
+ +Step through program, stepping over subroutine calls. An argument specifies a repeat count. + +
+ +

+If you're on a UNIX and you don't like the GDB interface, you can compile cheapnitfol and run it as the inferior debugger under Emacs or DDD. You can also try compiling xnitfol with `-DSTDOUT_DEBUG' and trying that, but I haven't tested that much. + +

+

+ddd MyGame.z5 --debugger cheapnitfol -s gameinfo.dbg -prompt "(gdb) " + +

+ + +

5 Bugs

+ +

+A nitfol bug is any behaviour which makes nitfol reliably misbehave, with the exceptions of bugs in Glk libraries. These include: anything which makes nitfol crash (other than when nitfol reports `FATAL' errors), anything which causes nitfol to contradict the Z-machine standards documents (except for optional enhancements like spelling correction and debug mode), any buffer overflows, and anything which makes nitfol infinite loop other than infinite loops in the game itself. + +

+

+Before reporting a bug, make sure the bug is not listed below and your copy of nitfol is not compiled with `-DFAST'. Please report the version of nitfol, your system type and a series of commands which reliably cause the bug. + +

+

+Nitfol is lacking: + +

    +
  • Graphical font (Beyond Zork) (should use images for this) + +
  • Terminating character support (mostly Beyond Zork) + +
  • Reverse video, full color (should querry Glk more aggressively) + +
  • Unicode support + +
  • keypad character codes + +
  • its own random number generator (relies on system one) + +
+ +

+Nitfol does incorrectly: + +

    +
  • Play is not paused to wait for sounds to complete in The Lurking Horror. + +
  • Pictures and text are not placed correctly for v6 games. + +
  • block quotes are placed in the upper window, so cheapnitfol can't see them. + +
  • Corrupted save files may make nitfol do bad things. + +
  • Should figure out a way to handle buggy games like AMFV and Varicella which assume the upper window is 80 columns wide. + +
  • Doesn't catch header writing other than @storeb and @storew. + +
+ +

+Debugger problems: + +

    +
  • Sometimes says there's no code at a location where it could be clever and + + find some. +
  • ofclass, superclass not implemented. + +
  • Should perform more sanity checks everywhere. + +
  • Lots of useful commands not yet implemented. + +
  • object.function is handled incorrectly, both for assignments and calls. + +
  • Assumes you know what you're doing, so quit, run, etc., don't prompt you for confirmation. + +
+ +

+Automapping problems: + +

    +
  • Doesn't work well for random destinations (the forest in Advent) + +
  • @get_cursor doesn't return the correct value during automapping since output is disabled. + +
  • Requires too much work for the end-user; should put in stuff to make it figure out the location global in 95% of games. + +
  • Doesn't really work if multiple locations are coded as being in the same room (long road in Enchanter). + +
  • Doesn't show exits which go nowhere, but change the game. + +
  • Perhaps should use graphics windows when available. + +
  • Movement causing teleportation confuses it. + +
  • Reincarnation handling isn't optimal. + +
  • Still very buggy. + +
  • It's too slow. + +
  • Should realize it can add extra bends (especially in one-way passages). + +
  • Should be able to output nice-looking Postscript. + +
  • Should store map in saved games (wait until automapping code stabilizes). + +
+ + + +

6 Thanks

+ +

+The following people have given comments, suggestions, bug reports, answered questions, or helped port nitfol (in alphabetical order): + +

    +
  • John Cater + +
  • Paul David Doherty + +
  • Martin Frost + +
  • Doug Jones + +
  • David Picton + +
  • Andrew Plotkin + +
  • Andrew Pontious + +
  • L. Ross Raszewski + +
  • Dan Shiovitz + +
+ + + +

7 Games Cited

+ +

+Wishbringer Copyright © 1985, 1988 Infocom Inc. + +

+

+
+Zork I Copyright © 1981-1986 Infocom Inc. + +

+

+
+Curses Copyright © 1993-1994 Graham Nelson. + +

+

+http://ifarchive.org/if-archive/games/zcode/curses.z5 + +

+

+
+Beyond Zork Copyright © 1987 Infocom Inc. + +

+

+
+Enchanter Copyright © 1983, 1984, 1986 Infocom Inc. + +

+

+
+Varicella by Adam Cadre 1999. + +

+

+http://adamcadre.ac/content/vgame.z8 + +

+

+
+Spider And Web Copyright © 1997-1998 Andrew Plotkin. + +

+

+http://ifarchive.org/if-archive/games/zcode/Tangle.z5 + +

+


+This document was generated on 24 October 1999 using the +texi2html +translator version 1.54.

+ + diff --git a/interpreters/nitfol/nitfol.info b/interpreters/nitfol/nitfol.info new file mode 100644 index 0000000..00e9a49 --- /dev/null +++ b/interpreters/nitfol/nitfol.info @@ -0,0 +1,1075 @@ +This is Info file nitfol.info, produced by Makeinfo version 1.68 from +the input file nitfol.texi. + +INFO-DIR-SECTION Games +START-INFO-DIR-ENTRY +* nitfol: (nitfol). Z-code interpreter and debugger. +END-INFO-DIR-ENTRY + + +File: nitfol.info, Node: Top, Next: Invoking nitfol, Prev: (dir), Up: (dir) + +Introduction +************ + + Nitfol is a portable interpreter for Z-machine code, the game format +used by Infocom and more recently, +Inform (http://www.gnelson.demon.co.uk/inform.html). Nitfol handles +versions one through eight of the format, and attempts to comply with +version 1.0 of the Z-machine specification. + + You will need game files to use nitfol. The "if-archive" contains a +large collection of these, available at +`ftp://ftp.gmd.de/if-archive/games/zcode/' or at the USA mirror +`http://ifarchive.org/indexes/if-archiveXgamesXzcode.html'. + + This manual describes how to use nitfol and how it differs from +other Z-machine interpreters. This manual was written with UNIX-like +systems in mind; ignore that which does not apply to your platform. +Comments on and corrections for this manual and nitfol are appreciated +and should be sent to . + +* Menu: + +* Invoking nitfol:: How to start nitfol under UNIX +* Features:: Useful extras +* Debugger:: Built in source-level debugger + +* Bugs:: What I know is wrong +* Thanks:: List of people who've helped +* Games Cited:: The games used as examples in this manual + + +File: nitfol.info, Node: Invoking nitfol, Next: Features, Prev: Top, Up: Top + +Invoking nitfol +*************** + + Invoke nitfol with the game filename, and options. If you omit the +game filename, nitfol will prompt you for one. The following options +are recognized: + +`-ignore' +`-no-ignore' +`-i' + Ignore Z-machine strictness errors. Normally nitfol checks for + illegal and undefined Z-machine behaviour and alerts the user. If + you're playing someone else's buggy game, this can be annoying and + you should use this option. + +`-fullname' +`-no-fullname' +`-f' + For running under Emacs or DDD. Tells nitfol to give + machine-recognizeable markers when stack frames are displayed + instead of displaying the line. Only useful if you are using + nitfol as an inferior debugger. + +`-command FILE' +`-x FILE' + Read commands from this file. Load a script from a file for + playback in the game. + +`-pirate' +`-no-pirate' +`-P' + Aye, matey. Make the piracy opcode not branch, letting the game + know the game disc is not "genuine." Infocom never used this + opcode and neither should you, but if obscurity amuses you... + +`-quiet' +`-no-quiet' +`-q' + Do not print introductory messages. For GDB compatibility. + +`-spell' +`-no-spell' + Perform spelling correction. Normally Z-machine games are + unforgiving of typos (though they do have an `oops' command). If + you type in a word which isn't in the game's dictionary and have + typo correction enabled, nitfol will search for a word which is + off by one letter by a substitution, deletion, insertion or + transposition. + +`-expand' +`-no-expand' + Expand one letter abbreviations. Early Z-machine games don't + include `x' as a synonym for `examine' and such. If the first + word in a sentence is one letter long and not recognized by the + game, this will attempt to expand it to a full word. + +`-symbols FILE' +`-s FILE' + Specify symbol file for game. If you want to perform source-level + debugging, you will need to specify a symbol file, which contains + the names of variables and tells nitfol how source code + corresponds to object code. + +`-tandy' +`-no-tandy' +`-t' + Censors some Infocom games. Some version 3 games perform minor + wording changes when this bit is set to appease the sensitivity of + Tandy Corporation. + +`-transcript WFILE' +`-T WFILE' + Write transcript to this file. This transcript begins as soon as + the game starts. + +`-debug' +`-no-debug' +`-d' + Enter debugger immediatly. Imitate GDB by not automatically + starting the story. + +`-prompt STRING' + Specify debugging prompt. This is the prompt nitfol prints when + it is waiting for a debugging command (as opposed to the > the + game story prints when waiting for a game command). DDD requires + this to be `(gdb) '. + +`-path STRING' + Look for games in this directory. If nitfol cannot find the + requested game in the current directory, it looks through the + directories listed in the given colon separated string. The + directories specified here are also used to search for gamefiles + from saved games. If this option is not used, nitfol looks at + the `INFOCOM_PATH' environment variable. + +`-autoundo' +`-no-autoundo' + Ensure `@save_undo' is called every turn. If a turn passes with + no `@save_undo' between, this option performs the `@save_undo' + automagically. Could cause problems with some games which have a + different concept of a turn. + +`-stacklimit NUMBER' +`-S NUMBER' + Exit when the stack is this deep. If a game is infinitely + recursing, nitfol will allocate large amounts of memory and take a + long time before the problem is reported. This option makes it + fatal to recurse more than the given number of stack frames. + Setting this to 0 makes nitfol allow as many as fit contiguously + in memory. The Z-machine Standards Document recommends games use + no more than 1024 words of total stack (frames and pushed data) in + ZIP, which roughly works out to 90 routine calls deep. + +`-alias STRING' +`-a STRING' + Specify an alias. Adds an alias which will be expanded in read + lines before tokenisation. The alias is of the form NAME VALUE; + you will need to use quotes around it on the commandline. + +`-ralias STRING' + Specify an recursive alias. Adds an alias whose result is checked + for further alias expansion. Identical syntax to adding a normal + alias. + +`-unalias STRING' + Remove an alias. Removes an alias previously added by -alias. + Useful for removing aliases in preference files. + +`-random NUMBER' +`-r NUMBER' + Set random seed. Normally the random number generator is + initialized with the time of day. If this option is used with a + non-zero argument, the given number will be used to initialize the + generator and for `@random 0'. + +`-mapsym STRING' + Specify mapping glyphs. Nitfol draws maps using ASCII characters; + you can choose which characters it uses to draw rooms. Defaults + to `*udb@UDB+', which is, in order: empty room, room with down + exit, room with up exit, room with up and down exits, room with + player, room with player and up exit, room with player and down + exit, room with player and up and down exits, bend symbol. + +`-mapsize NUMBER' + Specify map size. Determines the number of lines to be used for + the map. + +`-maploc STRING' + Specify map location. Nitfol creates a Glk window for the map it + generates. The map can be placed `above', `below', to the + `right', or the `left', of the main game window. Follow this + option with one those locations. + +`-terpnum NUMBER' + Specify interpreter number. Each port of Infocom's Z-machine + interpreter was given a number. `1' for their own DECSystem-20, + `2' for Apple IIe, `3' for Macintosh, `4' for Amiga, `5' for Atari + ST, `6' for IBM PC, `7' for Commodore 128, `8' for Commodore 64, + `9' for Apple IIc, `10' for Apple IIgs, `11' for Tandy Color. + Giving this option makes nitfol claim to be running on the + specified system. A few games change their behaviour slightly + depending on which machine type they run. By default nitfol + claims to be on an Apple IIe, as this makes Beyond Zork not do + character graphics. + +`-terpver STRING' + Specify interpreter version. Infocom's interpreters were given + versions, typically a capital letter. Nitfol defaults to `N', + Frotz uses `F', and ZIP uses `B'. Any single character version is + allowed. Multicharacter options are read as a number instead of + an ASCII character. Only known effect upon games is the letter + printed by banners and the `version' command. Version 6 games + interpret this as a number instead of a letter. + + +File: nitfol.info, Node: Features, Next: Debugger, Prev: Invoking nitfol, Up: Top + +Features +******** + +* Menu: + +* Preferences:: Store options in `~/.nitfolrc' +* Infinite undo/redo:: Erase your mistakes and bad luck +* Aliases:: Abbreviate long/common words +* Abbreviation Expansion:: Expand one letter commands +* Typo correction:: Nitfol uses a smart tokeniser +* Automapping:: Automatically generate an on-screen map +* Quetzal:: Save files are in Quetzal format +* Blorb:: Nitfol supports Blorb resources + + +File: nitfol.info, Node: Preferences, Next: Infinite undo/redo, Prev: Features, Up: Features + +Preferences +=========== + + If you don't like the default options and don't want to recompile, +you can set your preferences by writing a `.nitfolrc' in your home +directory. + + Each line should be of the form `OPTION=VALUE'. Blank lines and +lines starting with a `#' are ignored. If you want to specify +different options for different copies of nitfol, you can put those +options in a block which will only be read by copies of nitfol with a +specific name. + + Here's an example `.nitfolrc': + path=/usr/local/games/infocom + alias=v verbose + alias=asierra tone cordial. ask sierra about + ignore=true + + [strictnitfol] + ignore=false + spell=false + expand=false + autoundo=false + unalias=v + unalias=asierra + + [xnitfol] + tandy=true + pirate=true + + Nitfol will look in `/usr/local/games/infocom' for game files. +Copies of nitfol named `strictnitfol' will report Z-machine strictness +errors, perform strict tokenisation, and not automatically +`@save_undo'. All others will ignore strictness errors and have two +aliases. `xnitfol' will set the Tandy bit and branch on the piracy +opcode. + + Options specified in the preference file may be overruled by +environment variables and command line options. + + +File: nitfol.info, Node: Infinite undo/redo, Next: Aliases, Prev: Preferences, Up: Features + +Infinite undo/redo +================== + + Multiple `@restore_undo' opcodes with no intervening `@save_undo' +will restore earlier and earlier saved states. However, Inform games +will not do this, so if you want infinite undo, you must enter the +commands `/undo' and `/redo'. The `/...' commands are part of the +debugger, so you will need to compile in debugger support to use this +feature. + + Z-machine games prior to version 5 do not provide undo (none of them +provide redo), and some version 5 games don't use it (like +`Wishbringer'). If the game performs two `@read' opcodes with no +intervening `@save_undo' or `@restore_undo', nitfol will perform a +`@save_undo'. + + +File: nitfol.info, Node: Aliases, Next: Abbreviation Expansion, Prev: Infinite undo/redo, Up: Features + +Aliases +======= + + If the game has long words which you wish to abbreviate, you can use +aliases. Use the command `/alias NAME VALUE'. All instances of NAME +in line input will be replaced with VALUE. NAME may not contain +whitespace. + + Unlike abbreviation expansion and typo correction, alias expansion +modifies the text buffer, inserting the requested text. This is +necessary to allow multiple commands to be given in an alias through +the use of periods. + + Aliases are not expanded recursively, so you could do something +clever like this: + >/alias e w + /alias w e + /alias nw ne + /alias ne nw + /alias sw se + /alias se sw + + And your east-west movement will be swapped (`e' will do a `w', +though `east' will still do `east'). Aliases expand on all input the +game receives using `@read', including transcripts and directions from +the automapper. + + If you want the expansion of the alias to be checked for further +aliases, you must use the `/ralias' command. This expansion is stopped +when an alias would expand itself. + >/ralias e w + /ralias w e + + Would do nothing, as `e' is expanded to `w', which is expanded to +`e', and then it stops because the rule for expanding `e' has already +taken place. + + >/ralias hanoi2 move src to extra. move src to dest. move extra to dest + /alias src left + /alias dest center + /alias extra right + hanoi2 + You move the small disc from the left peg to the right peg. + You move the medium disc from the left peg to the middle peg. + You move the small disc from the right peg to the middle peg. + >move left to right + You move the large disc from the left peg to the right peg. + >/alias src center + /alias dest right + /alias extra left + hanoi2 + You move the small disc from the middle peg to the left peg. + You move the medium disc from the middle peg to the right peg. + You move the small disc from the left peg to the right peg. + + Ideally you should be able to define an alias which recursively +solves any depth by relying on lower levels being solvable, but this +isn't yet possible. You must keep the expansion of aliases to a +reasonable size, since Inform has a fairly small buffer size. + + You can remove aliases using the `/unalias' command. + >/unalias hanoi2 + + Aliases do not effect `/...' commands; if they did, it wouldn't be +possible to `/unalias'. + + +File: nitfol.info, Node: Abbreviation Expansion, Next: Typo correction, Prev: Aliases, Up: Features + +Abbreviation Expansion +====================== + + Early Infocom games don't provide abbreviations like `x' for +`examine'. If you enable abbreviation expansion, nitfol will attempt +to expand one letter words at the beginning of inputs which are not in +the game's dictionary. + + Nitfol supports the following expansions (note that some are +non-standard): + +c close d down e east g again +i inventory k attack l look n north +o oops p open q quit r drop +s south t take u up w west +x examine y yes z wait + + From `Zork I': + *West of House* + You are standing in an open field west of a white house, with a + boarded front door. + There is a small mailbox here. + + >x mailbox + [x -> examine] + The small mailbox is closed. + + >p it + [p -> open] + Opening the small mailbox reveals a leaflet. + + >t leaflet + [t -> take] + Taken. + + +File: nitfol.info, Node: Typo correction, Next: Automapping, Prev: Abbreviation Expansion, Up: Features + +Typo correction +=============== + + In the Z-machine, the `@read' opcode provides the interpreter with a +dictionary to search in order to do tokenisation and word matching. If +you enable typo correction and enter a word not in the provided +dictionary, nitfol will search for near misses. + + From `Curses': + >ask jemmia about gloves + [jemmia -> jemima] + "Those are my gloves." + + Nitfol takes the following steps to correct typos: + + 1. If the entered word is in the dictionary, behave as normal. + + 2. If the length of the word is less than 3 letters long, give up. + We don't want to make assumptions about what so short words might + be. + + 3. If the word is the same as a dictionary word with one + transposition, assume it is that word. `exmaine' becomes + `examine'. + + 4. If it is a dictionary word with one deleted letter, assume it is + that word. `botle' becomes `bottle'. + + 5. If it is a dictionary word with one inserted letter, assume it is + that word. `tastey' becomes `tasty'. + + 6. If it is a dictionary word with one substitution, assume it is + that word. `opin' becomes `open'. + + This behavior can be annoying when nitfol "corrects" intentionally +entered words which are similar to dictionary words. Usually this has +no effect upon the game, perhaps slightly changing the game's error +message, but may have negative effects when it causes an undesired +action. Games like `Beyond Zork' expect you to type words not in their +dictionary to name things. Nitfol might "correct" your entered word to +a dictionary word, which the game might complain about. + + If typo correction is getting in your way, run nitfol with +`-no-smart', compile it without applying `-DSMART_TOKENISER', or edit +`nitfol.opt' to change the compile-time default. + + +File: nitfol.info, Node: Automapping, Next: Quetzal, Prev: Typo correction, Up: Features + +Automapping +=========== + + Nitfol has the ability to display an on-screen map showing visited +rooms and their connections on the current floor. Below is a map +generated from `Enchanter'. + *-* * + | | + u-*-*-*-*-------*---* + | | + * * * | *---* | + |/ \ / | /|\ / \ | + * * * / | X * \ * + / \ | / |/ v| \ | + / * *-*-*-*-*---*---u---*-*-*-@ + / | / | |\ |\ ^| / | + *-* * * | | \ | X * / *-* + \ |/ | | \|/ \ / | + * * * * *---* * + \ / | | | + * u-d-*-----------*-u + | + * + \ + * + + The `*'s designate rooms; the `@' the current room. Rooms +containing staircases are shown with a `u' or `d', or `b' if the +staircase is bi-directional. If the current room contains a staircase, +nitfol draws it with a `U', `D', or `B'. Passageways are shown with +lines; the `X's are crossed lines. One-way passages are shown as lines +with arrows. Nitfol uses `v', `^', `<', and `>' for arrow heads. + + In Glks which provide mouse events, you can click on rooms and it +will display the room name (and number) in the upper left hand corner +of the map. Note that XGlk is slightly broken, so you need to click on +the left-hand side of the room. Clicking on an empty map space clears +the name. + + In order to use automapping, you must tell nitfol how to calculate +the current location. You do this by specifying an Inform expression, +so you must have debugging enabled. + + Typically the current location is available in a global. In Z-code +versions 3 and prior, the current location is always stored in global +zero, so typing `/automap (global) 0' should work. In later versions, +you must figure out an expression which evaluates to the current +location. + + First, find out where the player object is. Typically, the player +object is named `self', `cretin', `self-object', or the name of the PC. +You can use the `find' command to search object names. If this all +fails, try `object-tree' to find the location number. + + Once you have found the number of the location, you need to figure +out which global keeps track of the location. You can use the +`globals' command to search the globals. + + From `Spider And Web': + >/find self + 20 "(self object)" + 25 "yourself" in 91 "End of Alley" + 26 "yourself" in 48 "chair" + /globals 91 + G15 G36 G39 + s + + *Mouth of Alley* + You're in the entrance of a narrow brick alley, which runs further + in to the north. To the south a broad street courses by, congested + with traffic and bicycles, although none of them seem to notice you. + + >/find self + 20 "(self object)" + 25 "yourself" in 94 "Mouth of Alley" + 26 "yourself" in 48 "chair" + /globals 94 + G15 G36 G39 + /automap (global) 15 + + Obviously we have 3 globals tracking the player location. Typically +there are only two, but some games have more. In this, we just picked +the first one, which is probably the Inform `location' variable; +another is probably the `real_location' variable. Depending on how you +want automapping to behave in the dark, or when dealing with +game-specific stuff, you may want to pick a different one. + + To figure out what is in which direction, nitfol checks the current +location, tells the game to go north, checks the new location, undoes +the north movement, tries to go northeast, and so on. During all of +this, output is disabled. + + Drawing the map is more complicated. First nitfol looks for cycles +in the graph and makes the cycles connect properly. Then it draws the +map. If parts of the map overlapp, it finds a path connecting the +overlapping bits and tries increasing the length of each passage in +this path by one, and recalculates cycle connections. If this solves +the problem, it's done; otherwise, it tries increasing the length of +two passages, or of one of the passages by two. If this fails, it +gives up. + + This technique isn't perfect. The implementation of this technique +isn't perfect either. So expect nitfol to misbehave a lot on complex +maps, and a little on simple maps. If you clever ideas on how to +improve it, let me know. + + Nitfol makes an effort to simplify the map. If multiple exits go +from the barn to cornfield and you've been to both places, nitfol will +draw a single two-way passage if possible. If both up and west go up +the stairs and nitfol knows east returns from the top of the stairs, +nitfol will draw it as a simple west-east passage ignoring the up/down. +If east doesn't return from the top of the staircase, nitfol will draw +it as up/down, leaving out the west passage. + + If you've been north of a gate, and come up to the gate from the +south, and unlock the gate, nitfol will draw it as a one-way passage +since last time it was north of the gate, it couldn't go south. + + Some games feature reincarnation, perhaps moving you to a new +location. If movement leads to your death, this makes nitfol think the +reincarnation location is in that direction. Nitfol watches for three +asterisks in a row and will assume they mean death instead of a normal +passage. + + Some of these problems could be avoided by having nitfol explore +each neighboring room, but this would make automapping even slower. + + +File: nitfol.info, Node: Quetzal, Next: Blorb, Prev: Automapping, Up: Features + +Quetzal +======= + + Nitfol uses Quetzal version 1.4 for its save format, so you can use +your saves between different computers and interpreters. More +information about Quetzal is available at +`http://www.geocities.com/SiliconValley/Vista/6631/'. + + If you specify a save-file on the command-line on UNIX, nitfol uses +a `UNIX' `IntD' chunk to locate the game file associated with the save +name. This chunk is included in save games when nitfol can figure out +the current filename. If you compile nitfol with -D__USE_GNU, +-D__USE_BSD, or -D__USE_XOPEN_EXTENDED, nitfol will canonicalize the +file name, so you don't have to worry about relative file name +specifications no longer working if you invoke nitfol from a different +directory. + + On MacOS, nitfol uses alias records from a `MACS' `IntD' chunk to +locate the game file. This won't work for games built-in to the +interpreter. + + If no `IntD' chunk is included, nitfol searches the environment +variable `INFOCOM_PATH' for a game with matching release number, serial +number, and checksum. + + Looking for games without an `IntD' chunk isn't foolproof, but it +should work most of the time. Serial numbers are basically the date +and it's extremely unlikely more than ten games will be compiled on the +same day (the only time lots of games are compiled on same day is right +before competition time). Assuming they all have the same release +number, there's still only a .0686% chance that at least two of these +ten will share the same checksum. If someone reports this as a +problem, I'll make nitfol ensure the game contains a `save' opcode +right before the restored PC. + + +File: nitfol.info, Node: Blorb, Prev: Quetzal, Up: Features + +Blorb +===== + + If you wish to hear sounds or see graphics in your games, they must +be packaged in Blorb files. The Z-machine game may included in the +Blorb file or may be specified separately. Nitfol does not support the +traditional Infocom `.mg1' and `.snd' files. + + Note that graphics are displayed incorrectly, and sound has not yet +been tested. + + +File: nitfol.info, Node: Debugger, Next: Bugs, Prev: Features, Up: Top + +Debugger +******** + + Nitfol debugging mode tries to imitate the GDB interface. If you're +familiar with that, you should have no problem using nitfol (other than +dealing with the current incompleteness). + + You need inform 6.21 or later, as earlier versions don't produce +correct infix files without a patch. You then need to compile infix +information for your game. I recommend doing: + + `inform -k -~S -~X -~D MYGAME.inf' + + Then your debug information will be in `gameinfo.dbg'. If you have +a command-line on your platform, run nitfol like `nitfol MYGAME.z5 +-symbols gameinfo.dbg'. Otherwise, start up your game and type +`/symbol-file gameinfo.dbg' the first time you get a prompt. + + When the game stops to read a line of text, you can begin that line +with `/' to give a debug command. If you want to pass a line beginning +with a `/' to the game, double the `/' and the game will be passed the +second one. When at a `(nitfol) ' prompt, starting commands with `/' +is neither necessary nor recommended. + + All expressions are like the ones used in Inform. + + You can perform casts to get the result in the form you want: + +`(number) EXPRESSION' + Use EXPRESSION as if it were a number. Useful when you want to + know the number of something, not the object, routine, or string + information nitfol normally gives. + +`(object) EXPRESSION' + Use EXPRESSION as if it were an object. Most useful when printing + the result, as it will show the object's attributes and properties. + +`(routine) EXPRESSION' + Use EXPRESSION as if it were the packed address of a routine. + Useful if you have the packed address of a routine which you want + to set a breakpoint at. + +`(string) EXPRESSION' + Use EXPRESSION as if it were the packed address of a string. + Useful for printing it. + +`(global) EXPRESSION' + Evaluates to the value of a numbered global. `(global) 0' is the + player location for version 3 Infocom games. + +`(local) EXPRESSION' + Evaluates to the value of a numbered local. Not terribly useful + unless you're debugging something without source. + + Here are short descriptions of the debugger commands. *note +(gdb)Top::, for more information. Some of these were taken/adapted +from GDB's help. + +`info breakpoints' +`info breakpoints NUM' + List breakpoints. An argument specifies a specific breakpoint to + list. + +`quit' + Exit nitfol. + +`show language' + Show the current source language. + +`condition NUM EXP' + Set a condition for an existing breakpoint. + +`restore' + Restore a saved game. + +`break LINESPEC' +`break LINESPEC if EXP' + Set a breakpoint. An `if' clause specifies a condition. + +`stepi' +`stepi NUM' + Step exactly one instruction. An argument specifies a repeat + count. + +`restart' + Restart the game. + +`object-tree' +`object-tree EXP' + Display the object tree. An argument says which object to use as + the root of the tree. + +`disable display NUM' + Temporarily disable an automatic display. + +`select-frame NUM' + Select a specific stack frame. + +`alias NAME VALUE' + Add an alias + +`down-silently' +`down-silently NUM' + Silently select the child of the selected frame. An argument + specifies how many frames down to go. + +`frame' +`frame NUM' + Show the selected stack frame. An argument specifies a stack + frame to show. + +`give EXP NUM' +`give EXP ~ NUM' + Give an object an attribute. With a tilde clears the attribute + instead of setting it. + +`set EXP' + Evaluate an expression without printing its value. + +`print EXP' + Evaluates an expression and prints the result. This can include + function calls. + +`up' +`up NUM' + Select the parent of the selected frame. An argument specifies + how many frames up to go. + +`# comment' + Enter a comment + +`continue' +`continue NUM' + Continue execution. An argument sets the ignore count of the + current breakpoint. + +`dumpmem FILE' + Dump memory to a file + +`undo' + Undo last move (not last debugger command). + +`display EXP' + Print value of an expression each time the program stops. + +`move EXP to EXP' + Move an object around the object tree. + +`up-silently' +`up-silently NUM' + Select the parent of the selected frame silently. An argument + specifies how many frames up to go. + +`show copying' + Show licensing information. + +`recording off' + Stop recording a script. + +`jump LINESPEC' + Continue execution at a new location. + +`recording on' + Start recording a script. + +`ralias NAME VALUE' + Add a recursive alias + +`globals' +`globals EXP' + List all global variables and their values. With an argument, + list all only those with a specific value. + +`backtrace' +`backtrace NUM' +`backtrace - NUM' + Display the parent functions of the current frame. An argument + specifies how many frames back to show. If the argument is + negative, start from the first frame instead of the current. + +`find' + Find objects whose shortnames contain a string. + +`finish' + An argument specifies a repeat count. + +`down' +`down NUM' + Select the child of the selected frame. An argument specifies how + many frames down to go. + +`ignore NUM NUM' + Set the ignore count for a breakpoint. + +`replay off' + Halt replay. + +`nexti' +`nexti NUM' + Step one instruction, stepping over subroutine calls. Step a + specified number of instructions, stepping over subroutine calls. + +`help' + Print list of commands. + +`redo' + Redo undid move. Only works immediately after an `undo'. + +`enable NUM' + Re-enabled a breakpoint. + +`until' + Resume execution until the program reaches a line number greater + than the current line. + +`replay' + Replay a recorded script. + +`unalias NAME' + Remove an alias + +`remove EXP' + Remove an object from the object tree. + +`info sources' + List source files. + +`delete NUM' + Delete a breakpoint. + +`symbol-file FILE' + Load debugging info from a file (usually `gameinfo.dbg'). + +`automap EXP' + Start automapping + +`show warranty' + Show warranty information. + +`disable NUM' + Temporarily disable a breakpoint. + +`undisplay NUM' + Stop automatically displaying an expression. + +`enable display NUM' + Re-enable an automatic display. + +`step' +`step NUM' + Step through program to a different source line. An argument + specifies a repeat count. + +`info source' + Get information on the current source file. + +`next' +`next NUM' + Step through program, stepping over subroutine calls. An argument + specifies a repeat count. + + If you're on a UNIX and you don't like the GDB interface, you can +compile cheapnitfol and run it as the inferior debugger under Emacs or +DDD. You can also try compiling `xnitfol' with `-DSTDOUT_DEBUG' and +trying that, but I haven't tested that much. + +`ddd MYGAME.z5 --debugger cheapnitfol -s gameinfo.dbg -prompt "(gdb) "' + + +File: nitfol.info, Node: Bugs, Next: Thanks, Prev: Debugger, Up: Top + +Bugs +**** + + A nitfol bug is any behaviour which makes nitfol reliably misbehave, +with the exceptions of bugs in Glk libraries. These include: anything +which makes nitfol crash (other than when nitfol reports `FATAL' +errors), anything which causes nitfol to contradict the Z-machine +standards documents (except for optional enhancements like spelling +correction and debug mode), any buffer overflows, and anything which +makes nitfol infinite loop other than infinite loops in the game itself. + + Before reporting a bug, make sure the bug is not listed below and +your copy of nitfol is not compiled with `-DFAST'. Please report the +version of nitfol, your system type and a series of commands which +reliably cause the bug. + + Nitfol is lacking: + - Graphical font (`Beyond Zork') (should use images for this) + + - Terminating character support (mostly `Beyond Zork') + + - Reverse video, full color (should querry Glk more aggressively) + + - Unicode support + + - keypad character codes + + - its own random number generator (relies on system one) + + Nitfol does incorrectly: + - Play is not paused to wait for sounds to complete in `The Lurking + Horror'. + + - Pictures and text are not placed correctly for v6 games. + + - block quotes are placed in the upper window, so `cheapnitfol' + can't see them. + + - Corrupted save files may make nitfol do bad things. + + - Should figure out a way to handle buggy games like `AMFV' and + `Varicella' which assume the upper window is 80 columns wide. + + - Doesn't catch header writing other than `@storeb' and `@storew'. + + Debugger problems: + - Sometimes says there's no code at a location where it could be + clever and find some. + + - `ofclass', superclass not implemented. + + - Should perform more sanity checks everywhere. + + - Lots of useful commands not yet implemented. + + - OBJECT.FUNCTION is handled incorrectly, both for assignments and + calls. + + - Assumes you know what you're doing, so `quit', `run', etc., don't + prompt you for confirmation. + + Automapping problems: + - Doesn't work well for random destinations (the forest in `Advent') + + - `@get_cursor' doesn't return the correct value during automapping + since output is disabled. + + - Requires too much work for the end-user; should put in stuff to + make it figure out the location global in 95% of games. + + - Doesn't really work if multiple locations are coded as being in + the same room (long road in `Enchanter'). + + - Doesn't show exits which go nowhere, but change the game. + + - Perhaps should use graphics windows when available. + + - Movement causing teleportation confuses it. + + - Reincarnation handling isn't optimal. + + - Still very buggy. + + - It's too slow. + + - Should realize it can add extra bends (especially in one-way + passages). + + - Should be able to output nice-looking Postscript. + + - Should store map in saved games (wait until automapping code + stabilizes). + + +File: nitfol.info, Node: Thanks, Next: Games Cited, Prev: Bugs, Up: Top + +Thanks +****** + + The following people have given comments, suggestions, bug reports, +answered questions, or helped port nitfol (in alphabetical order): + - John Cater + + - Paul David Doherty + + - Martin Frost + + - Doug Jones + + - David Picton + + - Andrew Plotkin + + - Andrew Pontious + + - L. Ross Raszewski + + - Dan Shiovitz + + +File: nitfol.info, Node: Games Cited, Prev: Thanks, Up: Top + +Games Cited +*********** + +`Wishbringer' Copyright (C) 1985, 1988 Infocom Inc. + +`Zork I' Copyright (C) 1981-1986 Infocom Inc. + +`Curses' Copyright (C) 1993-1994 Graham Nelson. + + `http://ifarchive.org/if-archive/games/zcode/curses.z5' + +`Beyond Zork' Copyright (C) 1987 Infocom Inc. + +`Enchanter' Copyright (C) 1983, 1984, 1986 Infocom Inc. + +`Varicella' by Adam Cadre 1999. + + `http://adamcadre.ac/content/vgame.z8' + +`Spider And Web' Copyright (C) 1997-1998 Andrew Plotkin. + + `http://ifarchive.org/if-archive/games/zcode/Tangle.z5' + + + +Tag Table: +Node: Top230 +Node: Invoking nitfol1572 +Node: Features8446 +Node: Preferences9091 +Node: Infinite undo/redo10452 +Node: Aliases11221 +Node: Abbreviation Expansion13740 +Node: Typo correction14870 +Node: Automapping16779 +Node: Quetzal22563 +Node: Blorb24279 +Node: Debugger24700 +Node: Bugs31673 +Node: Thanks34727 +Node: Games Cited35142 + +End Tag Table diff --git a/interpreters/nitfol/nitfol.opt b/interpreters/nitfol/nitfol.opt new file mode 100644 index 0000000..c622c59 --- /dev/null +++ b/interpreters/nitfol/nitfol.opt @@ -0,0 +1,81 @@ +# Blank lines and lines beginning with # are ignored +# Lines alternate between a table describing the command and the texinfo description of the command. +# You can alter the 'default' part to change the game's default options. + +# Longest name Long short description type default code +"Ignore errors" ignore i "Ignore Z-machine strictness errors" flag 1 { ignore_errors = flag; } +Normally nitfol checks for illegal and undefined Z-machine behaviour and alerts the user. If you're playing someone else's buggy game, this can be annoying and you should use this option. + +"Inferior debugger" fullname f "For running under Emacs or DDD" flag 0 { fullname = flag; } +Tells nitfol to give machine-recognizeable markers when stack frames are displayed instead of displaying the line. Only useful if you are using nitfol as an inferior debugger. + +"Input script" command x "Read commands from this file" file NULL { if(stream) input_stream1 = stream; } +Load a script from a file for playback in the game. + +"Piracy flag" pirate P "Aye, matey" flag 0 { aye_matey = flag; } +Make the piracy opcode not branch, letting the game know the game disc is not ``genuine.'' Infocom never used this opcode and neither should you, but if obscurity amuses you... + +"Quiet mode" quiet q "Do not print introductory messages" flag 1 { quiet = flag; } +For GDB compatibility. + +"Typo correction" spell - "Perform spelling correction" flag 1 { do_spell_correct = flag; } +Normally Z-machine games are unforgiving of typos (though they do have an @code{oops} command). If you type in a word which isn't in the game's dictionary and have typo correction enabled, nitfol will search for a word which is off by one letter by a substitution, deletion, insertion or transposition. + +"Expand abbreviations" expand - "Expand one letter abbreviations" flag 1 { do_expand = flag; } +Early Z-machine games don't include @kbd{x} as a synonym for @kbd{examine} and such. If the first word in a sentence is one letter long and not recognized by the game, this will attempt to expand it to a full word. + +"Symbol file" symbols s "Specify symbol file for game" file NULL { if(stream) init_infix(stream); } +If you want to perform source-level debugging, you will need to specify a symbol file, which contains the names of variables and tells nitfol how source code corresponds to object code. + +"Tandy bit" tandy t "Censors some Infocom games" flag 0 { do_tandy = flag; } +Some version 3 games perform minor wording changes when this bit is set to appease the sensitivity of Tandy Corporation. + +"Transcript" transcript T "Write transcript to this file" wfile NULL { if(stream) set_transcript(stream); } +This transcript begins as soon as the game starts. + +"Start in debugger" debug d "Enter debugger immediatly" flag 0 { enter_debugger = flag; do_check_watches = flag; } +Imitate GDB by not automatically starting the story. + +"Debugger prompt" prompt - "Specify debugging prompt" string "(nitfol) " { n_free(db_prompt); db_prompt = n_strdup(string); } +This is the prompt nitfol prints when it is waiting for a debugging command (as opposed to the > the game story prints when waiting for a game command). DDD requires this to be @samp{(gdb) }. + +"Search path" path - "Look for games in this directory" string NULL { n_free(search_path); search_path = n_strdup(string); } +If nitfol cannot find the requested game in the current directory, it looks through the directories listed in the given colon separated string. The directories specified here are also used to search for gamefiles from saved games. If this option is not used, nitfol looks at the @code{INFOCOM_PATH} environment variable. + +"auto-@save_undo" autoundo - "Ensure @code{@@save_undo} is called every turn" flag 1 { auto_save_undo = flag; } +If a turn passes with no @code{@@save_undo} between, this option performs the @code{@@save_undo} automagically. Could cause problems with some games which have a different concept of a turn. + +# Disabled since it doesn't work +#"char auto-@save_undo" charundo - "Ensure @code{@@save_undo} is called every character input" flag 0 { auto_save_undo_char = flag; } +#If two consecutive @code{@@read_char} pass without a @code{@@save_undo}, perform the @code{@@save_undo} automagically. Useful to get undo in Z-machine abuses. Press @kbd{Ctrl-u} to undo at character input. + +"Maximum stack size" stacklimit S "Exit when the stack is this deep" number 0 { stacklimit = number; } +If a game is infinitely recursing, nitfol will allocate large amounts of memory and take a long time before the problem is reported. This option makes it fatal to recurse more than the given number of stack frames. Setting this to 0 makes nitfol allow as many as fit contiguously in memory. The Z-machine Standards Document recommends games use no more than 1024 words of total stack (frames and pushed data) in ZIP, which roughly works out to 90 routine calls deep. + +"Add alias" alias a "Specify an alias" string NULL { if(string) parse_new_alias(string, FALSE); } +Adds an alias which will be expanded in read lines before tokenisation. The alias is of the form @var{name} @var{value}; you will need to use quotes around it on the commandline. + +"Add recursive alias" ralias - "Specify an recursive alias" string NULL { if(string) parse_new_alias(string, TRUE); } +Adds an alias whose result is checked for further alias expansion. Identical syntax to adding a normal alias. + +"Remove alias" unalias - "Remove an alias" string NULL { if(string) remove_alias(string); } +Removes an alias previously added by -alias. Useful for removing aliases in preference files. + +"Random seed" random r "Set random seed" number 0 { faked_random_seed = number; } +Normally the random number generator is initialized with the time of day. If this option is used with a non-zero argument, the given number will be used to initialize the generator and for @code{@@random 0}. + +"Automap symbols" mapsym - "Specify mapping glyphs" string "*udb@UDB+" { n_free(roomsymbol); roomsymbol = n_strdup(string); } +Nitfol draws maps using ASCII characters; you can choose which characters it uses to draw rooms. Defaults to @samp{*udb@@UDB+}, which is, in order: empty room, room with down exit, room with up exit, room with up and down exits, room with player, room with player and up exit, room with player and down exit, room with player and up and down exits, bend symbol. + +"Automap size" mapsize - "Specify map size" number 12 { automap_size = number; } +Determines the number of lines to be used for the map. + +"Automap location" maploc - "Specify map location" string "above" { switch(glk_char_to_lower(*string)) { case 'a': case 't': case 'u': automap_split = winmethod_Above; break; case 'b': case 'd': automap_split = winmethod_Below; break; case 'l': automap_split = winmethod_Left; break; case 'r': automap_split = winmethod_Right; } } +Nitfol creates a Glk window for the map it generates. The map can be placed @samp{above}, @samp{below}, to the @samp{right}, or the @samp{left}, of the main game window. Follow this option with one those locations. + +"Interpreter number" terpnum - "Specify interpreter number" number 2 { interp_num = number; } +Each port of Infocom's Z-machine interpreter was given a number. @samp{1} for their own DECSystem-20, @samp{2} for Apple IIe, @samp{3} for Macintosh, @samp{4} for Amiga, @samp{5} for Atari ST, @samp{6} for IBM PC, @samp{7} for Commodore 128, @samp{8} for Commodore 64, @samp{9} for Apple IIc, @samp{10} for Apple IIgs, @samp{11} for Tandy Color. Giving this option makes nitfol claim to be running on the specified system. A few games change their behaviour slightly depending on which machine type they run. By default nitfol claims to be on an Apple IIe, as this makes Beyond Zork not do character graphics. + +"Interpreter version" terpver - "Specify interpreter version" string "N" { if(string) { if(n_strlen(string) == 1) interp_ver = *string; else interp_ver = n_strtol(string, NULL, 10); } } +Infocom's interpreters were given versions, typically a capital letter. Nitfol defaults to @samp{N}, Frotz uses @samp{F}, and ZIP uses @samp{B}. Any single character version is allowed. Multicharacter options are read as a number instead of an ASCII character. Only known effect upon games is the letter printed by banners and the @samp{version} command. Version 6 games interpret this as a number instead of a letter. + diff --git a/interpreters/nitfol/nitfol.texi b/interpreters/nitfol/nitfol.texi new file mode 100644 index 0000000..68d35b5 --- /dev/null +++ b/interpreters/nitfol/nitfol.texi @@ -0,0 +1,523 @@ +\input texinfo @c -*-texinfo-*- +@setfilename nitfol.info +@settitle Nitfol + +@ifinfo +@dircategory Games +@direntry +* nitfol: (nitfol). Z-code interpreter and debugger. +@end direntry +@end ifinfo + +@contents + +@node Top , Invoking nitfol , (dir) , (dir) +@top Introduction + +@c Workaround for bug in texi2html +@ifnotinfo +@ifnottex +@node Introduction , Top , (dir) , (dir) +@chapter Introduction +@end ifnottex +@end ifnotinfo + +Nitfol is a portable interpreter for Z-machine code, the game format used by Infocom and more recently, @uref{http://www.gnelson.demon.co.uk/inform.html, Inform}. Nitfol handles versions one through eight of the format, and attempts to comply with version 1.0 of the Z-machine specification. + +You will need game files to use nitfol. The ``if-archive'' contains a large collection of these, available at @uref{ftp://ftp.gmd.de/if-archive/games/zcode/} or at the USA mirror @uref{http://ifarchive.org/indexes/if-archiveXgamesXzcode.html}. + +This manual describes how to use nitfol and how it differs from other Z-machine interpreters. This manual was written with @sc{unix}-like systems in mind; ignore that which does not apply to your platform. Comments on and corrections for this manual and nitfol are appreciated and should be sent to @email{nitfol@@my-deja.com}. + +@menu +* Invoking nitfol:: How to start nitfol under @sc{unix} +* Features:: Useful extras +* Debugger:: Built in source-level debugger + +* Bugs:: What I know is wrong +* Thanks:: List of people who've helped +* Games Cited:: The games used as examples in this manual +@end menu + + + +@node Invoking nitfol , Features , Top, Top +@chapter Invoking nitfol + +Invoke nitfol with the game filename, and options. If you omit the game filename, nitfol will prompt you for one. The following options are recognized: + +@table @code +@include options.texi +@end table + +@node Features , Debugger , Invoking nitfol , Top +@chapter Features + +@menu +* Preferences:: Store options in @file{~/.nitfolrc} +* Infinite undo/redo:: Erase your mistakes and bad luck +* Aliases:: Abbreviate long/common words +* Abbreviation Expansion:: Expand one letter commands +* Typo correction:: Nitfol uses a smart tokeniser +* Automapping:: Automatically generate an on-screen map +* Quetzal:: Save files are in Quetzal format +* Blorb:: Nitfol supports Blorb resources +@end menu + +@node Preferences , Infinite undo/redo , Features , Features +@section Preferences + +If you don't like the default options and don't want to recompile, you can set your preferences by writing a @file{.nitfolrc} in your home directory. + +Each line should be of the form @code{@var{option}=@var{value}}. Blank lines and lines starting with a @code{#} are ignored. If you want to specify different options for different copies of nitfol, you can put those options in a block which will only be read by copies of nitfol with a specific name. + +Here's an example @file{.nitfolrc}: +@example +path=/usr/local/games/infocom +alias=v verbose +alias=asierra tone cordial. ask sierra about +ignore=true + +[strictnitfol] +ignore=false +spell=false +expand=false +autoundo=false +unalias=v +unalias=asierra + +[xnitfol] +tandy=true +pirate=true +@end example + +Nitfol will look in @file{/usr/local/games/infocom} for game files. Copies of nitfol named @file{strictnitfol} will report Z-machine strictness errors, perform strict tokenisation, and not automatically @code{@@save_undo}. All others will ignore strictness errors and have two aliases. @file{xnitfol} will set the Tandy bit and branch on the piracy opcode. + +Options specified in the preference file may be overruled by environment variables and command line options. + + +@node Infinite undo/redo , Aliases , Preferences , Features +@section Infinite undo/redo + +Multiple @code{@@restore_undo} opcodes with no intervening @code{@@save_undo} will restore earlier and earlier saved states. However, Inform games will not do this, so if you want infinite undo, you must enter the commands @code{/undo} and @code{/redo}. The @code{/@dots{}} commands are part of the debugger, so you will need to compile in debugger support to use this feature. + +Z-machine games prior to version 5 do not provide undo (none of them provide redo), and some version 5 games don't use it (like @cite{Wishbringer}). If the game performs two @code{@@read} opcodes with no intervening @code{@@save_undo} or @code{@@restore_undo}, nitfol will perform a @code{@@save_undo}. + + +@node Aliases , Abbreviation Expansion , Infinite undo/redo , Features +@section Aliases + +If the game has long words which you wish to abbreviate, you can use aliases. Use the command @code{/alias @var{name} @var{value}}. All instances of @var{name} in line input will be replaced with @var{value}. @var{name} may not contain whitespace. + +Unlike abbreviation expansion and typo correction, alias expansion modifies the text buffer, inserting the requested text. This is necessary to allow multiple commands to be given in an alias through the use of periods. + +Aliases are not expanded recursively, so you could do something clever like this: +@example +>@kbd{/alias e w} +@kbd{/alias w e} +@kbd{/alias nw ne} +@kbd{/alias ne nw} +@kbd{/alias sw se} +@kbd{/alias se sw} +@end example + +And your east-west movement will be swapped (@kbd{e} will do a @kbd{w}, though @kbd{east} will still do @kbd{east}). Aliases expand on all input the game receives using @code{@@read}, including transcripts and directions from the automapper. + +If you want the expansion of the alias to be checked for further aliases, you must use the @code{/ralias} command. This expansion is stopped when an alias would expand itself. +@example +>@kbd{/ralias e w} +@kbd{/ralias w e} +@end example + +Would do nothing, as @kbd{e} is expanded to @kbd{w}, which is expanded to @kbd{e}, and then it stops because the rule for expanding @kbd{e} has already taken place. + +@example +>@kbd{/ralias hanoi2 move src to extra. move src to dest. move extra to dest} +@kbd{/alias src left} +@kbd{/alias dest center} +@kbd{/alias extra right} +@kbd{hanoi2} +You move the small disc from the left peg to the right peg. +You move the medium disc from the left peg to the middle peg. +You move the small disc from the right peg to the middle peg. +>move left to right +You move the large disc from the left peg to the right peg. +>@kbd{/alias src center} +@kbd{/alias dest right} +@kbd{/alias extra left} +@kbd{hanoi2} +You move the small disc from the middle peg to the left peg. +You move the medium disc from the middle peg to the right peg. +You move the small disc from the left peg to the right peg. +@end example + +Ideally you should be able to define an alias which recursively solves any depth by relying on lower levels being solvable, but this isn't yet possible. You must keep the expansion of aliases to a reasonable size, since Inform has a fairly small buffer size. + +You can remove aliases using the @code{/unalias} command. +@example +>@kbd{/unalias hanoi2} +@end example + +Aliases do not effect @code{/@dots{}} commands; if they did, it wouldn't be possible to @code{/unalias}. + + +@node Abbreviation Expansion , Typo correction , Aliases , Features +@section Abbreviation Expansion + +Early Infocom games don't provide abbreviations like @kbd{x} for @kbd{examine}. If you enable abbreviation expansion, nitfol will attempt to expand one letter words at the beginning of inputs which are not in the game's dictionary. + +Nitfol supports the following expansions (note that some are non-standard): + +@multitable {i} {inventory} {i} {inventory} {i} {inventory} {i} {inventory} +@item c @tab close + @tab d @tab down + @tab e @tab east + @tab g @tab again +@item i @tab inventory + @tab k @tab attack + @tab l @tab look + @tab n @tab north +@item o @tab oops + @tab p @tab open + @tab q @tab quit + @tab r @tab drop +@item s @tab south + @tab t @tab take + @tab u @tab up + @tab w @tab west +@item x @tab examine + @tab y @tab yes + @tab z @tab wait +@end multitable + +From @cite{Zork I}: +@example +@group +@strong{West of House} +You are standing in an open field west of a white house, with a +boarded front door. +There is a small mailbox here. + +>@kbd{x mailbox} +[x -> examine] +The small mailbox is closed. +@end group + +@group +>@kbd{p it} +[p -> open] +Opening the small mailbox reveals a leaflet. +@end group + +@group +>@kbd{t leaflet} +[t -> take] +Taken. +@end group +@end example + + +@node Typo correction , Automapping , Abbreviation Expansion , Features +@section Typo correction + +In the Z-machine, the @code{@@read} opcode provides the interpreter with a dictionary to search in order to do tokenisation and word matching. If you enable typo correction and enter a word not in the provided dictionary, nitfol will search for near misses. + +From @cite{Curses}: +@example +@group +>@kbd{ask jemmia about gloves} +[jemmia -> jemima] +"Those are my gloves." +@end group +@end example + +Nitfol takes the following steps to correct typos: + +@enumerate +@item +If the entered word is in the dictionary, behave as normal. + +@item +If the length of the word is less than 3 letters long, give up. We don't want to make assumptions about what so short words might be. + +@item +If the word is the same as a dictionary word with one transposition, assume it is that word. @kbd{exmaine} becomes @kbd{examine}. + +@item +If it is a dictionary word with one deleted letter, assume it is that word. @kbd{botle} becomes @kbd{bottle}. + +@item +If it is a dictionary word with one inserted letter, assume it is that word. @kbd{tastey} becomes @kbd{tasty}. + +@item +If it is a dictionary word with one substitution, assume it is that word. @kbd{opin} becomes @kbd{open}. +@end enumerate + +This behavior can be annoying when nitfol ``corrects'' intentionally entered words which are similar to dictionary words. Usually this has no effect upon the game, perhaps slightly changing the game's error message, but may have negative effects when it causes an undesired action. Games like @cite{Beyond Zork} expect you to type words not in their dictionary to name things. Nitfol might ``correct'' your entered word to a dictionary word, which the game might complain about. + +If typo correction is getting in your way, run nitfol with @samp{-no-smart}, compile it without applying @samp{-DSMART_TOKENISER}, or edit @file{nitfol.opt} to change the compile-time default. + + +@node Automapping , Quetzal , Typo correction , Features +@section Automapping + +Nitfol has the ability to display an on-screen map showing visited rooms and their connections on the current floor. Below is a map generated from @cite{Enchanter}. +@example +@group + *-* * + | | + u-*-*-*-*-------*---* + | | + * * * | *---* | + |/ \ / | /|\ / \ | + * * * / | X * \ * + / \ | / |/ v| \ | + / * *-*-*-*-*---*---u---*-*-*-@@ + / | / | |\ |\ ^| / | + *-* * * | | \ | X * / *-* + \ |/ | | \|/ \ / | + * * * * *---* * + \ / | | | + * u-d-*-----------*-u + | + * + \ + * +@end group +@end example + +The @samp{*}s designate rooms; the @samp{@@} the current room. Rooms containing staircases are shown with a @samp{u} or @samp{d}, or @samp{b} if the staircase is bi-directional. If the current room contains a staircase, nitfol draws it with a @samp{U}, @samp{D}, or @samp{B}. Passageways are shown with lines; the @samp{X}s are crossed lines. One-way passages are shown as lines with arrows. Nitfol uses @samp{v}, @samp{^}, @samp{<}, and @samp{>} for arrow heads. + +In Glks which provide mouse events, you can click on rooms and it will display the room name (and number) in the upper left hand corner of the map. Note that XGlk is slightly broken, so you need to click on the left-hand side of the room. Clicking on an empty map space clears the name. + +In order to use automapping, you must tell nitfol how to calculate the current location. You do this by specifying an Inform expression, so you must have debugging enabled. + +Typically the current location is available in a global. In Z-code versions 3 and prior, the current location is always stored in global zero, so typing @kbd{/automap (global) 0} should work. In later versions, you must figure out an expression which evaluates to the current location. + +First, find out where the player object is. Typically, the player object is named @samp{self}, @samp{cretin}, @samp{self-object}, or the name of the PC. You can use the @code{find} command to search object names. If this all fails, try @code{object-tree} to find the location number. + +Once you have found the number of the location, you need to figure out which global keeps track of the location. You can use the @code{globals} command to search the globals. + +From @cite{Spider And Web}: +@example +@group +>@kbd{/find self} +20 "(self object)" +25 "yourself" in 91 "End of Alley" +26 "yourself" in 48 "chair" +@kbd{/globals 91} +G15 G36 G39 +@end group +@group +@kbd{s} + +@strong{Mouth of Alley} +You're in the entrance of a narrow brick alley, which runs further +in to the north. To the south a broad street courses by, congested +with traffic and bicycles, although none of them seem to notice you. +@end group + +@group +>@kbd{/find self} +20 "(self object)" +25 "yourself" in 94 "Mouth of Alley" +26 "yourself" in 48 "chair" +@end group +@group +@kbd{/globals 94} +G15 G36 G39 +@end group +@kbd{/automap (global) 15} +@end example + +Obviously we have 3 globals tracking the player location. Typically there are only two, but some games have more. In this, we just picked the first one, which is probably the Inform @code{location} variable; another is probably the @code{real_location} variable. Depending on how you want automapping to behave in the dark, or when dealing with game-specific stuff, you may want to pick a different one. + +To figure out what is in which direction, nitfol checks the current location, tells the game to go north, checks the new location, undoes the north movement, tries to go northeast, and so on. During all of this, output is disabled. + +Drawing the map is more complicated. First nitfol looks for cycles in the graph and makes the cycles connect properly. Then it draws the map. If parts of the map overlapp, it finds a path connecting the overlapping bits and tries increasing the length of each passage in this path by one, and recalculates cycle connections. If this solves the problem, it's done; otherwise, it tries increasing the length of two passages, or of one of the passages by two. If this fails, it gives up. + +This technique isn't perfect. The implementation of this technique isn't perfect either. So expect nitfol to misbehave a lot on complex maps, and a little on simple maps. If you clever ideas on how to improve it, let me know. + +Nitfol makes an effort to simplify the map. If multiple exits go from the barn to cornfield and you've been to both places, nitfol will draw a single two-way passage if possible. If both up and west go up the stairs and nitfol knows east returns from the top of the stairs, nitfol will draw it as a simple west-east passage ignoring the up/down. If east doesn't return from the top of the staircase, nitfol will draw it as up/down, leaving out the west passage. + +If you've been north of a gate, and come up to the gate from the south, +and unlock the gate, nitfol will draw it as a one-way passage since last +time it was north of the gate, it couldn't go south. + +Some games feature reincarnation, perhaps moving you to a new location. If movement leads to your death, this makes nitfol think the reincarnation location is in that direction. Nitfol watches for three asterisks in a row and will assume they mean death instead of a normal passage. + +Some of these problems could be avoided by having nitfol explore each neighboring room, but this would make automapping even slower. + + +@node Quetzal , Blorb , Automapping , Features +@section Quetzal + +Nitfol uses Quetzal version 1.4 for its save format, so you can use your saves between different computers and interpreters. More information about Quetzal is available at @uref{http://www.geocities.com/SiliconValley/Vista/6631/}. + +If you specify a save-file on the command-line on @sc{unix}, nitfol uses a @code{UNIX} @code{IntD} chunk to locate the game file associated with the save name. This chunk is included in save games when nitfol can figure out the current filename. If you compile nitfol with -D__USE_GNU, -D__USE_BSD, or -D__USE_XOPEN_EXTENDED, nitfol will canonicalize the file name, so you don't have to worry about relative file name specifications no longer working if you invoke nitfol from a different directory. + +On MacOS, nitfol uses alias records from a @code{MACS} @code{IntD} chunk to locate the game file. This won't work for games built-in to the interpreter. + +If no @code{IntD} chunk is included, nitfol searches the environment variable @code{INFOCOM_PATH} for a game with matching release number, serial number, and checksum. + +Looking for games without an @code{IntD} chunk isn't foolproof, but it should work most of the time. Serial numbers are basically the date and it's extremely unlikely more than ten games will be compiled on the same day (the only time lots of games are compiled on same day is right before competition time). Assuming they all have the same release number, there's still only a .0686% chance that at least two of these ten will share the same checksum. If someone reports this as a problem, I'll make nitfol ensure the game contains a @code{save} opcode right before the restored PC. + + +@node Blorb , , Quetzal , Features +@section Blorb + +If you wish to hear sounds or see graphics in your games, they must be packaged in Blorb files. The Z-machine game may included in the Blorb file or may be specified separately. Nitfol does not support the traditional Infocom @file{.mg1} and @file{.snd} files. + +Note that graphics are displayed incorrectly, and sound has not yet been tested. + +@node Debugger , Bugs , Features , Top +@chapter Debugger + +Nitfol debugging mode tries to imitate the GDB interface. If you're familiar with that, you should have no problem using nitfol (other than dealing with the current incompleteness). + +You need inform 6.21 or later, as earlier versions don't produce correct infix files without a patch. You then need to compile infix information for your game. I recommend doing: + +@code{inform -k -~S -~X -~D @var{MyGame}.inf} + +Then your debug information will be in @file{gameinfo.dbg}. If you have a command-line on your platform, run nitfol like @code{nitfol @var{MyGame}.z5 -symbols gameinfo.dbg}. Otherwise, start up your game and type @code{/symbol-file gameinfo.dbg} the first time you get a prompt. + +When the game stops to read a line of text, you can begin that line with @kbd{/} to give a debug command. If you want to pass a line beginning with a @kbd{/} to the game, double the @kbd{/} and the game will be passed the second one. When at a @code{(nitfol) } prompt, starting commands with @kbd{/} is neither necessary nor recommended. + +All expressions are like the ones used in Inform. + +You can perform casts to get the result in the form you want: + +@table @code +@item (number) @var{expression} +Use @var{expression} as if it were a number. Useful when you want to know the number of something, not the object, routine, or string information nitfol normally gives. +@item (object) @var{expression} +Use @var{expression} as if it were an object. Most useful when printing the result, as it will show the object's attributes and properties. +@item (routine) @var{expression} +Use @var{expression} as if it were the packed address of a routine. Useful if you have the packed address of a routine which you want to set a breakpoint at. +@item (string) @var{expression} +Use @var{expression} as if it were the packed address of a string. Useful for printing it. +@item (global) @var{expression} +Evaluates to the value of a numbered global. @code{(global) 0} is the player location for version 3 Infocom games. +@item (local) @var{expression} +Evaluates to the value of a numbered local. Not terribly useful unless you're debugging something without source. +@end table + +Here are short descriptions of the debugger commands. @inforef{Top,,gdb}, for more information. Some of these were taken/adapted from GDB's help. + +@ftable @code +@include dbg_help.texi +@end ftable + +If you're on a @sc{unix} and you don't like the GDB interface, you can compile cheapnitfol and run it as the inferior debugger under Emacs or DDD. You can also try compiling @code{xnitfol} with @samp{-DSTDOUT_DEBUG} and trying that, but I haven't tested that much. + +@noindent +@code{ddd @var{MyGame}.z5 --debugger cheapnitfol -s gameinfo.dbg -prompt "(gdb) "} + +@node Bugs , Thanks , Debugger , Top +@chapter Bugs + +A nitfol bug is any behaviour which makes nitfol reliably misbehave, with the exceptions of bugs in Glk libraries. These include: anything which makes nitfol crash (other than when nitfol reports @samp{FATAL} errors), anything which causes nitfol to contradict the Z-machine standards documents (except for optional enhancements like spelling correction and debug mode), any buffer overflows, and anything which makes nitfol infinite loop other than infinite loops in the game itself. + +Before reporting a bug, make sure the bug is not listed below and your copy of nitfol is not compiled with @samp{-DFAST}. Please report the version of nitfol, your system type and a series of commands which reliably cause the bug. + +Nitfol is lacking: +@itemize @minus +@item Graphical font (@cite{Beyond Zork}) (should use images for this) +@item Terminating character support (mostly @cite{Beyond Zork}) +@item Reverse video, full color (should querry Glk more aggressively) +@item Unicode support +@item keypad character codes +@item its own random number generator (relies on system one) +@end itemize + +Nitfol does incorrectly: +@itemize @minus +@item Play is not paused to wait for sounds to complete in @cite{The Lurking Horror}. +@item Pictures and text are not placed correctly for v6 games. +@item block quotes are placed in the upper window, so @code{cheapnitfol} can't see them. +@item Corrupted save files may make nitfol do bad things. +@item Should figure out a way to handle buggy games like @cite{AMFV} and @cite{Varicella} which assume the upper window is 80 columns wide. +@item Doesn't catch header writing other than @code{@@storeb} and @code{@@storew}. +@end itemize + +Debugger problems: +@itemize @minus +@item Sometimes says there's no code at a location where it could be clever and + find some. +@item @code{ofclass}, superclass not implemented. +@item Should perform more sanity checks everywhere. +@item Lots of useful commands not yet implemented. +@item @var{object}.@var{function} is handled incorrectly, both for assignments and calls. +@item Assumes you know what you're doing, so @code{quit}, @code{run}, etc., don't prompt you for confirmation. +@end itemize + +Automapping problems: +@itemize @minus +@item Doesn't work well for random destinations (the forest in @cite{Advent}) +@item @code{@@get_cursor} doesn't return the correct value during automapping since output is disabled. +@item Requires too much work for the end-user; should put in stuff to make it figure out the location global in 95% of games. +@item Doesn't really work if multiple locations are coded as being in the same room (long road in @cite{Enchanter}). +@item Doesn't show exits which go nowhere, but change the game. +@item Perhaps should use graphics windows when available. +@item Movement causing teleportation confuses it. +@item Reincarnation handling isn't optimal. +@item Still very buggy. +@item It's too slow. +@item Should realize it can add extra bends (especially in one-way passages). +@item Should be able to output nice-looking Postscript. +@item Should store map in saved games (wait until automapping code stabilizes). +@end itemize + +@node Thanks , Games Cited , Bugs , Top +@chapter Thanks + +The following people have given comments, suggestions, bug reports, answered questions, or helped port nitfol (in alphabetical order): +@itemize @minus +@item John Cater +@item Paul David Doherty +@item Martin Frost +@item Doug Jones +@item David Picton +@item Andrew Plotkin +@item Andrew Pontious +@item L. Ross Raszewski +@item Dan Shiovitz +@end itemize + +@node Games Cited , , Thanks , Top +@chapter Games Cited + +@noindent +@cite{Wishbringer} Copyright @copyright{} 1985, 1988 Infocom Inc. + +@* +@noindent +@cite{Zork I} Copyright @copyright{} 1981-1986 Infocom Inc. + +@* +@noindent +@cite{Curses} Copyright @copyright{} 1993-1994 Graham Nelson. + +@uref{http://ifarchive.org/if-archive/games/zcode/curses.z5} + +@* +@noindent +@cite{Beyond Zork} Copyright @copyright{} 1987 Infocom Inc. + +@* +@noindent +@cite{Enchanter} Copyright @copyright{} 1983, 1984, 1986 Infocom Inc. + +@* +@noindent +@cite{Varicella} by Adam Cadre 1999. + +@uref{http://adamcadre.ac/content/vgame.z8} + +@* +@noindent +@cite{Spider And Web} Copyright @copyright{} 1997-1998 Andrew Plotkin. + +@uref{http://ifarchive.org/if-archive/games/zcode/Tangle.z5} + +@bye + diff --git a/interpreters/nitfol/no_blorb.c b/interpreters/nitfol/no_blorb.c new file mode 100644 index 0000000..0798545 --- /dev/null +++ b/interpreters/nitfol/no_blorb.c @@ -0,0 +1,34 @@ +#include "nitfol.h" +#include "gi_blorb.h" + +/* Link this in only if your glk doesn't support blorb */ + + +giblorb_err_t wrap_gib_create_map(strid_t file, giblorb_map_t **newmap) +{ + return giblorb_err_CompileTime; +} + + +giblorb_err_t wrap_gib_destroy_map(giblorb_map_t *map) +{ + return giblorb_err_CompileTime; +} + + +giblorb_err_t wrap_gib_load_resource(giblorb_map_t *map, glui32 method, giblorb_result_t *res, glui32 usage, glui32 resnum) +{ + return giblorb_err_CompileTime; +} + + +giblorb_err_t wrap_gib_count_resources(giblorb_map_t *map, glui32 usage, glui32 *num, glui32 *min, glui32 *max) +{ + return giblorb_err_CompileTime; +} + + +giblorb_err_t wrap_gib_set_resource_map(strid_t file) +{ + return giblorb_err_CompileTime; +} diff --git a/interpreters/nitfol/no_blorb.h b/interpreters/nitfol/no_blorb.h new file mode 100644 index 0000000..8d0f661 --- /dev/null +++ b/interpreters/nitfol/no_blorb.h @@ -0,0 +1,17 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i no_blorb.c' */ +#ifndef CFH_NO_BLORB_H +#define CFH_NO_BLORB_H + +/* From `no_blorb.c': */ +giblorb_err_t wrap_gib_create_map (strid_t file , giblorb_map_t **newmap ); +giblorb_err_t wrap_gib_destroy_map (giblorb_map_t *map ); +giblorb_err_t wrap_gib_load_resource (giblorb_map_t *map , glui32 method , giblorb_result_t *res , glui32 usage , glui32 resnum ); +giblorb_err_t wrap_gib_count_resources (giblorb_map_t *map , glui32 usage , glui32 *num , glui32 *min , glui32 *max ); +giblorb_err_t wrap_gib_set_resource_map (strid_t file ); + +#endif /* CFH_NO_BLORB_H */ diff --git a/interpreters/nitfol/no_graph.c b/interpreters/nitfol/no_graph.c new file mode 100644 index 0000000..3cd3ba0 --- /dev/null +++ b/interpreters/nitfol/no_graph.c @@ -0,0 +1,23 @@ +#include "nitfol.h" + +/* Link this in only if your glk doesn't support graphics */ + +glui32 wrap_glk_image_draw(winid_t win, glui32 image, glsi32 val1, glsi32 val2) +{ + return FALSE; +} + + +glui32 wrap_glk_image_draw_scaled(winid_t win, glui32 image, glsi32 val1, glsi32 val2, glui32 width, glui32 height) +{ + return FALSE; +} + + +glui32 wrap_glk_image_get_info(glui32 image, glui32 *width, glui32 *height) +{ + *width = 0; *height = 0; + return FALSE; +} + + diff --git a/interpreters/nitfol/no_graph.h b/interpreters/nitfol/no_graph.h new file mode 100644 index 0000000..4263fdd --- /dev/null +++ b/interpreters/nitfol/no_graph.h @@ -0,0 +1,15 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i no_graph.c' */ +#ifndef CFH_NO_GRAPH_H +#define CFH_NO_GRAPH_H + +/* From `no_graph.c': */ +glui32 wrap_glk_image_draw (winid_t win , glui32 image , glsi32 val1 , glsi32 val2 ); +glui32 wrap_glk_image_draw_scaled (winid_t win , glui32 image , glsi32 val1 , glsi32 val2 , glui32 width , glui32 height ); +glui32 wrap_glk_image_get_info (glui32 image , glui32 *width , glui32 *height ); + +#endif /* CFH_NO_GRAPH_H */ diff --git a/interpreters/nitfol/no_snd.c b/interpreters/nitfol/no_snd.c new file mode 100644 index 0000000..064bc75 --- /dev/null +++ b/interpreters/nitfol/no_snd.c @@ -0,0 +1,21 @@ +#include "nitfol.h" + +void init_sound(void) +{ + ; +} + +void kill_sound(void) +{ + ; +} + +void op_sound_effect(void) +{ + ; +} + +void check_sound(event_t unused) +{ + ; +} diff --git a/interpreters/nitfol/no_snd.h b/interpreters/nitfol/no_snd.h new file mode 100644 index 0000000..9cb3b81 --- /dev/null +++ b/interpreters/nitfol/no_snd.h @@ -0,0 +1,16 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i no_snd.c' */ +#ifndef CFH_NO_SND_H +#define CFH_NO_SND_H + +/* From `no_snd.c': */ +void init_sound (void); +void kill_sound (void); +void op_sound_effect (void); +void check_sound (event_t unused ); + +#endif /* CFH_NO_SND_H */ diff --git a/interpreters/nitfol/objects.c b/interpreters/nitfol/objects.c new file mode 100644 index 0000000..185a781 --- /dev/null +++ b/interpreters/nitfol/objects.c @@ -0,0 +1,1117 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +/* attribute/8 will be the byte offset into the object entry for attribute */ +#define ATTRIBUTE(n, a) z_memory[z_objecttable + n * OBJSIZE + a / 8] + +#define OBJ_ADDR(n) (z_objecttable + (n) * OBJSIZE) + +#define PARENTP(n) (OBJ_ADDR(n) + oPARENT) +#define SIBLINGP(n) (OBJ_ADDR(n) + oSIBLING) +#define CHILDP(n) (OBJ_ADDR(n) + oCHILD) +#define PROPSP(n) (OBJ_ADDR(n) + oPROPS) + +#define PARENT(n) LOWO(PARENTP(n)) +#define SIBLING(n) LOWO(SIBLINGP(n)) +#define CHILD(n) LOWO(CHILDP(n)) +#define PROPS(n) LOWORD(PROPSP(n)) + + +static zword OBJSIZE; +static zword oPARENT, oSIBLING, oCHILD, oPROPS; +static zword PROP_NUM_MASK, ATTR_COUNT; + +static BOOL object_property_loop(zword object, zword *propnum, + zword *location, zword *len); + + +zword LOWO(zword p) +{ + if(zversion >= 4) + return LOWORD(p); + return LOBYTE(p); +} + +void LOWOcopy(zword a, zword b) +{ + if(zversion >= 4) { + LOWORDcopy(a, b); + } else { + LOBYTEcopy(a, b); + } +} + +void LOWOwrite(zword p, zword n) +{ + if(zversion >= 4) { + LOWORDwrite(p, n); + } else { + LOBYTEwrite(p, n); + } +} + + +#ifdef FAST +#define check_obj_valid(obj) TRUE +#define check_attr_valid(attr) TRUE +#else +static BOOL check_obj_valid(zword object) +{ + if(object > object_count) { /* Object past the first property table entry */ + if(object > maxobjs) { /* Object past the end of dynamic memory */ + n_show_error(E_OBJECT, "object number too large", object); + return FALSE; + } + n_show_warn(E_OBJECT, "object number probably too large", object); + } + if(object == 0) { + n_show_error(E_OBJECT, "vile object 0 error from hell", object); + return FALSE; + } + return TRUE; +} + +static BOOL check_attr_valid(zword attr) +{ + if(attr > ATTR_COUNT) { + n_show_error(E_OBJECT, "attempt to access illegal attribute", attr); + return FALSE; + } + return TRUE; +} +#endif + + + +void objects_init(void) +{ + zword object; + + if(zversion >= 4) { + OBJSIZE = 14; + oPARENT = 6; + oSIBLING = 8; + oCHILD = 10; + oPROPS = 12; + PROP_NUM_MASK = 0x3f; + ATTR_COUNT = 47; + } else { + OBJSIZE = 9; + oPARENT = 4; + oSIBLING = 5; + oCHILD = 6; + oPROPS = 7; + PROP_NUM_MASK = 0x1f; + ATTR_COUNT = 31; + } + + /* Address of objects; we want this to be one before the first object + because there's no object 0 */ + z_objecttable = z_propdefaults + PROP_NUM_MASK * ZWORD_SIZE - OBJSIZE; + maxobjs = (dynamic_size - z_objecttable) / OBJSIZE; + + obj_first_prop_addr = ZWORD_MASK; + obj_last_prop_addr = 0; + + prop_table_start = ZWORD_MASK; + prop_table_end = 0; + + /* Count objects in tree, assuming objects end where proptables begin */ + for(object = 1; OBJ_ADDR(object) < prop_table_start; object++) { + zword propnum, location, len; + + if(PROPS(object) < prop_table_start) + prop_table_start = PROPS(object); + + location = 0; + while(object_property_loop(object, &propnum, &location, &len)) { + if(location < obj_first_prop_addr) + obj_first_prop_addr = location; + if(location > obj_last_prop_addr) + obj_last_prop_addr = location; + + if(location + len > prop_table_end) + prop_table_end = location + len; + } + } + + object_count = object - 1; + + if(object_count > maxobjs) + object_count = maxobjs; +} + + + + +void op_get_child(void) +{ + zword child; + if(!check_obj_valid(operand[0])) { + mop_store_result(0); + mop_skip_branch(); + return; + } + + debug_object(operand[0], OBJ_GET_INFO); + + child = CHILD(operand[0]); + mop_store_result(child); + mop_cond_branch(child != 0); +} + + +void op_get_parent(void) +{ + zword parent; + if(!check_obj_valid(operand[0])) { + mop_store_result(0); + return; + } + + debug_object(operand[0], OBJ_GET_INFO); + + parent = PARENT(operand[0]); + mop_store_result(parent); +} + + +void op_get_sibling(void) +{ + zword sibling; + if(!check_obj_valid(operand[0])) { + mop_store_result(0); + mop_skip_branch(); + return; + } + + debug_object(operand[0], OBJ_GET_INFO); + + sibling = SIBLING(operand[0]); + mop_store_result(sibling); + mop_cond_branch(sibling != 0); +} + + + +void op_insert_obj(void) +{ + zword object = operand[0], dest = operand[1]; + + if(!check_obj_valid(object) || !check_obj_valid(dest)) + return; + +#ifndef FAST + { + zword child = object; + int depth = 0; + while(child) { + if(child == dest) { + n_show_error(E_OBJECT, "attempt to place an object inside itself", object); + return; + } + child = CHILD(child); + depth++; + if(depth > maxobjs) { + n_show_error(E_OBJECT, "found objects inside themselves", child); + break; + } + } + } +#endif + + + op_remove_obj(); /* Remove object operand[0] from object tree */ + + debug_object(operand[1], OBJ_RECEIVE); + + LOWOwrite(PARENTP(object), dest); + LOWOcopy(SIBLINGP(object), CHILDP(dest)); + LOWOwrite(CHILDP(dest), object); +} + + +void op_jin(void) +{ + if(!check_obj_valid(operand[0])) { + mop_skip_branch(); + return; + } + + debug_object(operand[0], OBJ_GET_INFO); + debug_object(operand[1], OBJ_GET_INFO); + + mop_cond_branch(PARENT(operand[0]) == operand[1]); +} + + +void op_remove_obj(void) +{ + zword parent, sibling, nextsibling; + zword object = operand[0]; + + if(!check_obj_valid(object)) + return; + parent = PARENT(object); + + if(!parent) /* If no parent, do nothing with no error message */ + return; + if(!check_obj_valid(parent)) + return; + + debug_object(operand[0], OBJ_MOVE); + + nextsibling = CHILD(parent); + + if(nextsibling == object) { /* if it's the first child */ + LOWOcopy(CHILDP(parent), SIBLINGP(object)); + } else { + unsigned width = 0; + do { /* Loops until the SIBLING(sibling)==object so we can skip over it */ + sibling = nextsibling; + + if(!check_obj_valid(sibling)) + return; + + nextsibling = SIBLING(sibling); + +#ifndef FAST + if(width++ > maxobjs) { /* If we've looped more times than reasonable */ + n_show_error(E_OBJECT, "looped sibling list", parent); + return; + } +#endif + } while(nextsibling != object); + + LOWOcopy(SIBLINGP(sibling), SIBLINGP(object));/*make the list skip object*/ + } + + LOWOwrite(PARENTP(object), 0); + LOWOwrite(SIBLINGP(object), 0); +} + +offset object_name(zword object) +{ + if(LOBYTE(PROPS(object))) + return PROPS(object) + 1; + else + return 0; +} + + +void op_print_obj(void) +{ + offset short_name_off = object_name(operand[0]); + if(short_name_off) + decodezscii(short_name_off, output_char); + else + n_show_error(E_OBJECT, "attempt to print name of nameless object", operand[0]); +} + + +/* attribute opcodes: */ + +void op_clear_attr(void) +{ + if(!check_obj_valid(operand[0]) || !check_attr_valid(operand[1])) + return; + debug_attrib(operand[1], operand[0]); + + ATTRIBUTE(operand[0], operand[1]) &= ~(b10000000 >> (operand[1] & b0111)); + /* shift top bit right to select the appropriate bit and make + a mask from the complement */ +} + + +void op_set_attr(void) +{ + if(!check_obj_valid(operand[0]) || !check_attr_valid(operand[1])) + return; + debug_attrib(operand[1], operand[0]); + /* select the bit to be set */ + ATTRIBUTE(operand[0], operand[1]) |= (b10000000 >> (operand[1] & b0111)); +} + + +void op_test_attr(void) +{ + if(!check_obj_valid(operand[0]) || !check_attr_valid(operand[1])) { + mop_skip_branch(); + return; + } + debug_attrib(operand[1], operand[0]); + /* select the bit to be tested */ + if(ATTRIBUTE(operand[0], operand[1]) & (b10000000 >> (operand[1] & b0111))) + mop_take_branch(); + else + mop_skip_branch(); +} + + +/* property table opcodes: */ + +/* Helper functions */ + +/* + * Given the location of the sizebyte, returns the length of the following + * property and sets *size_length to the size of the sizebyte + */ +static zword get_prop_length(zword propoffset, int *size_length) +{ + zword prop_length; + + zbyte size_byte = LOBYTE(propoffset); + if(zversion >= 4) { + if(size_byte & b10000000) { /* top bit set means two bytes of size info */ + *size_length = 2; + prop_length = LOBYTE(propoffset + 1) & b00111111; + if(prop_length == 0) + prop_length = 64; + } else { /* one byte of size info */ + *size_length = 1; + if(size_byte & b01000000) + prop_length = 2; + else + prop_length = 1; + } + } else { + prop_length = (size_byte >> 5) + 1; + *size_length = 1; + } + return prop_length; +} + + +/* + * Loops over all properties of an object, returning FALSE if no more + * Before first call, set *location = 0; + */ +static BOOL object_property_loop(zword object, zword *propnum, + zword *location, zword *len) +{ + zword proptable; + int size_byte, size_length; + + if(!*location) { + *location = proptable = PROPS(object); + *len = LOBYTE(proptable) * 2 + 1; /* skip the header */ + } + + proptable = *location; + proptable += *len; + + size_byte = LOBYTE(proptable); + *propnum = size_byte & PROP_NUM_MASK; + if(*propnum) { + *len = get_prop_length(proptable, &size_length); + proptable += size_length; + + *location = proptable; + return TRUE; + } + return FALSE; +} + + + +static zword get_prop_offset(zword object, zword property, zword *length) +{ + zword propnum, location; + location = 0; + while(object_property_loop(object, &propnum, &location, length)) { + if(propnum == property) + return location; + } + return 0; +} + + +void op_get_next_prop(void) +{ + zword proptable = 0; + zword prop_len; + zword prop_num; + + if(!check_obj_valid(operand[0])) { + mop_store_result(0); + return; + } + + if(operand[1] == 0) { + if(object_property_loop(operand[0], &prop_num, &proptable, &prop_len)) + mop_store_result(prop_num); + else + mop_store_result(0); + return; + } + + while(object_property_loop(operand[0], &prop_num, &proptable, &prop_len)) { + if(prop_num == operand[1]) { + if(object_property_loop(operand[0], &prop_num, &proptable, &prop_len)) + mop_store_result(prop_num); + else + mop_store_result(0); + return; + } + } + + n_show_error(E_OBJECT, "get_next_prop on nonexistent property", operand[1]); + mop_store_result(0); + return; +} + + +void op_get_prop_addr(void) +{ + zword proptable; + zword prop_len; + if(!check_obj_valid(operand[0])) { + mop_store_result(0); + return; + } + + proptable = get_prop_offset(operand[0], operand[1], &prop_len); + + mop_store_result(proptable); +} + + +void op_get_prop_len(void) +{ + int size_length; + zword prop_length; + +#ifndef FAST + if(operand[0] < obj_first_prop_addr || + operand[0] > obj_last_prop_addr) { + if(operand[0] < 64) { + n_show_error(E_OBJECT, "get property length in header", operand[0]); + mop_store_result(0); + return; + } + n_show_warn(E_OBJECT, "get property length at probably bad address", operand[0]); + } +#endif + + operand[0]--; /* Skip back a byte for the size byte */ + + if(zversion >= 4) + if(LOBYTE(operand[0]) & 0x80) { /* test top bit - two bytes of size info */ + operand[0]--; /* Skip back another byte */ + } + + prop_length = get_prop_length(operand[0], &size_length); + + mop_store_result(prop_length); +} + + +void op_get_prop(void) +{ + zword prop_length; + zword proptable; + if(!check_obj_valid(operand[0])) { + mop_store_result(0); + return; + } + + proptable = get_prop_offset(operand[0], operand[1], &prop_length); + + if(proptable == 0) { /* property not provided; use property default */ + mop_store_result(LOWORD(z_propdefaults + (operand[1]-1) * ZWORD_SIZE)); + return; + } + + switch(prop_length) { + case 1: + mop_store_result(LOBYTE(proptable)); break; +#ifndef FAST + default: + n_show_port(E_OBJECT, "get_prop on property with bad length", operand[1]); +#endif + case ZWORD_SIZE: + mop_store_result(LOWORD(proptable)); + } +} + + +void op_put_prop(void) +{ + zword prop_length; + zword proptable; + if(!check_obj_valid(operand[0])) { + mop_store_result(0); + return; + } + + proptable = get_prop_offset(operand[0], operand[1], &prop_length); + +#ifndef FAST + if(proptable == 0) { + n_show_error(E_OBJECT, "attempt to write to nonexistent property", operand[1]); + return; + } +#endif + + switch(prop_length) { + case 1: + LOBYTEwrite(proptable, operand[2]); break; +#ifndef FAST + default: + n_show_port(E_OBJECT, "put_prop on property with bad length", operand[1]); +#endif + case ZWORD_SIZE: + LOWORDwrite(proptable, operand[2]); break; + } +} + + + +#ifdef DEBUGGING + + +BOOL infix_property_loop(zword object, zword *propnum, zword *location, zword *len, zword *nonindivloc, zword *nonindivlen) +{ + BOOL status; + + if(*location && *propnum > PROP_NUM_MASK) { + *location += *len; + *propnum = LOWORD(*location); + *location += ZWORD_SIZE; + *len = LOBYTE(*location); + (*location)++; + + if(*propnum) + return TRUE; + + *propnum = 0; + *location = *nonindivloc; + *len = *nonindivlen; + *nonindivloc = 0; + *nonindivlen = 0; + } + + status = object_property_loop(object, propnum, location, len); + if(!status) + return FALSE; + + if(*propnum == 3) { /* Individual property table */ + zword iproptable = LOWORD(*location); + zword ilen; + + *propnum = LOWORD(iproptable); + iproptable += ZWORD_SIZE; + ilen = LOBYTE(iproptable); + iproptable++; + if(!*propnum) + return infix_property_loop(object, propnum, location, len, nonindivloc, nonindivlen); + + *nonindivloc = *location; + *nonindivlen = *len; + *location = iproptable; + *len = ilen; + } + + return TRUE; +} + + +void infix_move(zword dest, zword object) +{ + zword to1 = operand[0], to2 = operand[1]; + operand[0] = object; operand[1] = dest; + op_insert_obj(); + operand[0] = to1; operand[1] = to2; +} + +void infix_remove(zword object) +{ + zword to1 = operand[0]; + operand[0] = object; + op_remove_obj(); + operand[0] = to1; +} + +zword infix_parent(zword object) +{ + return PARENT(object); +} + +zword infix_child(zword object) +{ + return CHILD(object); +} + +zword infix_sibling(zword object) +{ + return SIBLING(object); +} + +void infix_set_attrib(zword object, zword attrib) +{ + zword to1 = operand[0], to2 = operand[1]; + operand[0] = object; operand[1] = attrib; + op_set_attr(); + operand[0] = to1; operand[1] = to2; +} + +void infix_clear_attrib(zword object, zword attrib) +{ + zword to1 = operand[0], to2 = operand[1]; + operand[0] = object; operand[1] = attrib; + op_clear_attr(); + operand[0] = to1; operand[1] = to2; +} + + + +static void infix_property_display(unsigned prop, + offset proptable, unsigned prop_length) +{ + BOOL do_number = TRUE; + BOOL do_name = TRUE; + + unsigned i; + + /* things we know to be objects/strings/routines */ + static const char *decode_me_names[] = { + "n_to", "nw_to", "w_to", "sw_to", "s_to", "se_to", "e_to", "ne_to", + "in_to", "out_to", "u_to", "d_to", + "add_to_scope", "after", "article", "articles", "before", "cant_go", + "daemon", "describe", "door_dir", "door_to", "each_turn", "found_in", + "grammar", "initial", "inside_description", "invent", "life", "orders", + "parse_name", "plural", "react_after", "react_before", + "short_name", "short_name_indef", "time_out", "when_closed", "when_open", + "when_on", "when_off", "with_key", + "obj", + "description", + "ofclass" + }; + + /* things we know to be just plain numbers */ + static const char *dont_decode_names[] = { + "capacity", "number", "time_left" + }; + + z_typed p; + const char *propname; + + p.v = prop; p.t = Z_PROP; + propname = infix_get_name(p); + + if(prop == 2) + propname = "ofclass"; + + infix_print_string(", "); + + if(propname) + infix_print_string(propname); + else { + infix_print_string("P"); + infix_print_number(prop); + } + infix_print_string(" ="); + + if(prop == 2) { + for(i = 0; i < prop_length; i+=ZWORD_SIZE) { + offset short_name_off = object_name(LOWORD(proptable + i)); + if(short_name_off) { + infix_print_char(' '); + decodezscii(short_name_off, infix_print_char); + } else { + infix_print_string(" "); + } + } + return; + } + + if(propname) { + if(n_strcmp(propname, "name") == 0) { + for(i = 0; i < prop_length; i+=ZWORD_SIZE) { + infix_print_string(" '"); + decodezscii(LOWORD(proptable + i), infix_print_char); + infix_print_char('\''); + } + return; + } + + for(i = 0; i < sizeof(decode_me_names) / sizeof(*decode_me_names); i++) + if(n_strcmp(decode_me_names[i], propname) == 0) + do_number = FALSE; + + for(i = 0; i < sizeof(dont_decode_names) / sizeof(*dont_decode_names); i++) + if(n_strcmp(dont_decode_names[i], propname) == 0) + do_name = FALSE; + } + + if(prop_length % ZWORD_SIZE || LOWORD(proptable) == 0) { + do_number = TRUE; + do_name = FALSE; + } + + if(do_number) { + switch(prop_length) { + case 1: + infix_print_char(' '); + infix_print_znumber(LOBYTE(proptable)); + break; + case ZWORD_SIZE: + infix_print_char(' '); + infix_print_znumber(LOWORD(proptable)); + break; + default: + for(i = 0; i < prop_length; i++) { + infix_print_char(' '); + infix_print_znumber(LOBYTE(proptable + i)); + } + } + } + + if(do_name) { + for(i = 0; i < prop_length; i += ZWORD_SIZE) { + zword val = LOWORD(proptable + i); + const char *name = debug_decode_number(val); + + if(name) { + infix_print_char(' '); + if(do_number) + infix_print_char('('); + infix_print_string(name); + if(do_number) + infix_print_char(')'); + } else { + if(!do_number) { + infix_print_char(' '); + infix_print_znumber(val); + } + if(val <= object_count) { + offset short_name_off = object_name(val); + if(short_name_off) { + infix_print_char(' '); + infix_print_char('('); + decodezscii(short_name_off, infix_print_char); + infix_print_char(')'); + } + } + } + } + } +} + + +static void infix_show_object(zword object) +{ + const char *name; + if(!object) { + infix_print_string("0"); + } else { + offset short_name_off; + z_typed o; + o.t = Z_OBJECT; o.v = object; + name = infix_get_name(o); + if(name) { + infix_print_string(name); + } else { + infix_print_number(object); + } + + short_name_off = object_name(object); + if(short_name_off) { + infix_print_string(" \""); + decodezscii(short_name_off, infix_print_char); + infix_print_char('"'); + } else if(!name) { + infix_print_string(" "); + } + } +} + +zword infix_get_proptable(zword object, zword prop, zword *length) +{ + zword propnum, location, nloc, nlen; + + location = 0; + while(infix_property_loop(object, &propnum, &location, length, &nloc, &nlen)) { + if(propnum == prop) + return location; + } + + return 0; +} + + +zword infix_get_prop(zword object, zword prop) +{ + zword prop_length; + zword proptable = infix_get_proptable(object, prop, &prop_length); + + if(!proptable) { + if(prop < PROP_NUM_MASK) { /* property defaults */ + proptable = z_propdefaults + (prop - 1) * ZWORD_SIZE; + prop_length = ZWORD_SIZE; + } else { + return 0; + } + } + + switch(prop_length) { + case 1: + return LOBYTE(proptable); + default: + case ZWORD_SIZE: + return LOWORD(proptable); + } +} + + +void infix_put_prop(zword object, zword prop, zword val) +{ + zword prop_length; + zword proptable = infix_get_proptable(object, prop, &prop_length); + + if(!proptable) + return; + + switch(prop_length) { + case 1: + LOBYTEwrite(proptable, val); + default: + case ZWORD_SIZE: + LOWORDwrite(proptable, val); + } +} + + +BOOL infix_test_attrib(zword object, zword attrib) +{ + if(!check_obj_valid(object) || !check_attr_valid(attrib)) { + return FALSE; + } + + /* select the bit to be tested */ + if(ATTRIBUTE(object, attrib) & (b10000000 >> (attrib & b0111))) + return TRUE; + else + return FALSE; +} + + +static char *trunk = NULL; +static int trunksize = 128; + +static void infix_draw_trunk(int depth) +{ + int i; + for(i = 1; i < depth; i++) { + if(trunk[i]) + infix_print_fixed_string(" | "); + else + infix_print_fixed_string(" "); + } +} + +static void infix_draw_branch(int depth) +{ + infix_draw_trunk(depth); + if(depth) + infix_print_fixed_string(" +->"); +} + + +static void infix_draw_object(zword object, int depth) +{ + zword c; + unsigned width; + + if(depth >= trunksize) { + trunksize *= 2; + trunk = (char *) n_realloc(trunk, trunksize); + } + + infix_draw_branch(depth); + infix_show_object(object); + infix_print_char(10); + + /* Do a sanity check before we print anything to avoid screenfulls of junk */ + width = 0; + for(c = CHILD(object); c; c = SIBLING(c)) { + if(width++ > maxobjs) { + infix_print_string("looped sibling list.\n"); + return; + } + } + + for(c = CHILD(object); c; c = SIBLING(c)) { + if(PARENT(c) != object) { /* Section 12.5 (b) */ + infix_print_string("object "); + infix_print_number(c); + infix_print_string(" is a child of object "); + infix_print_number(object); + infix_print_string(" but has "); + infix_print_number(PARENT(c)); + infix_print_string(" listed as its parent.\n"); + return; + } + + trunk[depth+1] = (SIBLING(c) != 0); + + infix_draw_object(c, depth+1); + } +} + +void infix_object_tree(zword object) +{ + trunk = (char *) n_malloc(trunksize); + + if(object != 0) { + infix_draw_object(object, 0); + n_free(trunk); + return; + } + + for(object = 1; object <= object_count; object++) { + if(!PARENT(object)) { + if(SIBLING(object)) { /* Section 12.5 (a) */ + infix_print_string("Object "); + infix_print_number(object); + infix_print_string(" has no parent, but has sibling "); + infix_print_number(SIBLING(object)); + infix_print_string(".\n"); + return; + } + infix_draw_object(object, 0); + } + } + + n_free(trunk); + +} + + +/* Contrary to the zspec, short names may be arbitrarily long because of + abbreviations, so use realloc */ + +static char *short_name; +static unsigned short_name_length; +static unsigned short_name_i; + +static void infix_copy_short_name(int ch) +{ + if(short_name_i + 1 >= short_name_length ) { + char *p; + short_name_length *= 2; + p = (char *) n_realloc(short_name, short_name_length); + short_name = p; + } + short_name[short_name_i++] = ch; +} + +void infix_object_find(const char *description) +{ + zword object; + char *desc = n_strdup(description); + n_strlower(desc); + for(object = 1; object <= object_count; object++) { + offset short_name_off = object_name(object); + if(short_name_off) { + short_name_length = 512; + short_name = (char *) n_malloc(short_name_length); + short_name_i = 0; + decodezscii(short_name_off, infix_copy_short_name); + short_name[short_name_i] = 0; + n_strlower(short_name); + if(n_strstr(short_name, desc)) { + infix_show_object(object); + if(PARENT(object)) { + infix_print_string(" in "); + infix_show_object(PARENT(object)); + } + infix_print_char(10); + } + n_free(short_name); + } + } +} + + +void infix_object_display(zword object) +{ + offset short_name_off; + zword propnum, location, length, nloc, nlen; + unsigned n; + BOOL did; + + if(object == 0) { + infix_print_string("nothing"); + return; + } + + if(!check_obj_valid(object)) { + infix_print_string("invalid object"); + return; + } + + infix_print_char('{'); + + short_name_off = object_name(object); + if(short_name_off) { + infix_print_string("short_name = \""); + decodezscii(short_name_off, infix_print_char); + infix_print_string("\", attrib ="); + } + + did = FALSE; + for(n = 0; n < ATTR_COUNT; n++) { + if(infix_test_attrib(object, n)) { + z_typed a; + const char *attrname; + a.t = Z_ATTR; a.v = n; + attrname = infix_get_name(a); + infix_print_char(' '); + if(attrname) + infix_print_string(attrname); + else + infix_print_number(n); + did = TRUE; + } + } + if(!did) + infix_print_string(" "); + + infix_print_string(", parent = "); + infix_show_object(PARENT(object)); + + infix_print_string(", sibling = "); + infix_show_object(SIBLING(object)); + + infix_print_string(", child = "); + infix_show_object(CHILD(object)); + + + location = 0; + while(infix_property_loop(object, &propnum, &location, &length, &nloc, &nlen)) { + infix_property_display(propnum, location, length); + } + + infix_print_char('}'); +} + +#endif /* DEBUGGING */ diff --git a/interpreters/nitfol/objects.h b/interpreters/nitfol/objects.h new file mode 100644 index 0000000..98bc812 --- /dev/null +++ b/interpreters/nitfol/objects.h @@ -0,0 +1,51 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i objects.c' */ +#ifndef CFH_OBJECTS_H +#define CFH_OBJECTS_H + +/* From `objects.c': */ +zword LOWO (zword p ); +void LOWOcopy (zword a , zword b ); +void LOWOwrite (zword p , zword n ); +void objects_init (void); +void op_get_child (void); +void op_get_parent (void); +void op_get_sibling (void); +void op_insert_obj (void); +void op_jin (void); +void op_remove_obj (void); +offset object_name (zword object ); +void op_print_obj (void); +void op_clear_attr (void); +void op_set_attr (void); +void op_test_attr (void); +void op_get_next_prop (void); +void op_get_prop_addr (void); +void op_get_prop_len (void); +void op_get_prop (void); +void op_put_prop (void); + +#ifdef DEBUGGING +BOOL infix_property_loop (zword object , zword *propnum , zword *location , zword *len , zword *nonindivloc , zword *nonindivlen ); +void infix_move (zword dest , zword object ); +void infix_remove (zword object ); +zword infix_parent (zword object ); +zword infix_child (zword object ); +zword infix_sibling (zword object ); +void infix_set_attrib (zword object , zword attrib ); +void infix_clear_attrib (zword object , zword attrib ); +zword infix_get_proptable (zword object , zword prop , zword *length ); +zword infix_get_prop (zword object , zword prop ); +void infix_put_prop (zword object , zword prop , zword val ); +BOOL infix_test_attrib (zword object , zword attrib ); +void infix_object_tree (zword object ); +void infix_object_find (const char *description ); +void infix_object_display (zword object ); + +#endif /* DEBUGGING */ + +#endif /* CFH_OBJECTS_H */ diff --git a/interpreters/nitfol/op_call.c b/interpreters/nitfol/op_call.c new file mode 100644 index 0000000..ebedf4f --- /dev/null +++ b/interpreters/nitfol/op_call.c @@ -0,0 +1,98 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +void mop_call(zword dest, unsigned num_args, zword *args, int result_var) +{ + unsigned i; + offset destPC; + unsigned num_local; + + if(dest == 0) { + check_set_var(result_var, 0); + return; + } + + destPC = UNPACKR(dest); + + /* printf("call %x -> %x\n", oldPC, destPC); */ + +#ifndef FAST + if(destPC > game_size) { + n_show_error(E_INSTR, "call past end of story", dest); + check_set_var(result_var, 0); + return; + } +#endif + + num_local = HIBYTE(destPC); /* first byte is # of local variables */ + +#ifndef FAST + if(num_local > 15) { + n_show_error(E_INSTR, "call to non-function (initial byte > 15)", dest); + check_set_var(result_var, 0); + return; + } +#endif + + destPC++; /* Go on past the variable count */ + + if(zversion < 5) { + for(i = num_args; i < num_local; i++) + args[i] = HIWORD(destPC + i * ZWORD_SIZE); /* Load default locals */ + destPC += num_local * ZWORD_SIZE; + } else { + for(i = num_args; i < num_local; i++) + args[i] = 0; /* locals default to zero */ + } + + add_stack_frame(PC, num_local, args, num_args, result_var); + + PC = destPC; + + /*n_show_debug(E_DEBUG, "function starting", dest);*/ +} + +void op_call_n(void) +{ + mop_call(operand[0], numoperands - 1, operand + 1, -1); +} + +void op_call_s(void) +{ + zbyte ret_var = HIBYTE(PC); + PC++; + mop_call(operand[0], numoperands - 1, operand + 1, ret_var); +} + +void op_ret(void) +{ + mop_func_return(operand[0]); +} + +void op_rfalse(void) +{ + mop_func_return(0); +} + +void op_rtrue(void) +{ + mop_func_return(1); +} diff --git a/interpreters/nitfol/op_call.h b/interpreters/nitfol/op_call.h new file mode 100644 index 0000000..942ed0f --- /dev/null +++ b/interpreters/nitfol/op_call.h @@ -0,0 +1,18 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i op_call.c' */ +#ifndef CFH_OP_CALL_H +#define CFH_OP_CALL_H + +/* From `op_call.c': */ +void mop_call (zword dest , unsigned num_args , zword *args , int result_var ); +void op_call_n (void); +void op_call_s (void); +void op_ret (void); +void op_rfalse (void); +void op_rtrue (void); + +#endif /* CFH_OP_CALL_H */ diff --git a/interpreters/nitfol/op_jmp.c b/interpreters/nitfol/op_jmp.c new file mode 100644 index 0000000..9da59de --- /dev/null +++ b/interpreters/nitfol/op_jmp.c @@ -0,0 +1,162 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + + +N_INLINE static void skip_branch(zbyte branch) +{ + if(!(branch & b01000000)) /* Bit 6 clear means 'branch occupies two bytes' */ + PC++; +} + +N_INLINE static void take_branch(zbyte branch) +{ + int o = branch & b00111111; + + if(!(branch & b01000000)) {/* Bit 6 clear means 'branch occupies two bytes'*/ + o = (o << 8) + HIBYTE(PC); + PC++; + if(branch & b00100000) + o = -((1 << 14) - o); + } + + if(o == 0) + mop_func_return(0); + else if(o == 1) + mop_func_return(1); + else + PC += o - 2; + +#ifndef FAST + if(PC > game_size) { + n_show_error(E_INSTR, "attempt to conditionally jump outside of story", o - 2); + PC -= o - 2; + return; + } +#endif + + /* printf("cjmp %x -> %x\n", oldPC, PC); */ +} + + +void mop_skip_branch(void) +{ + zbyte branch = HIBYTE(PC); + PC++; + + if(branch & b10000000) /* Bit 7 set means 'branch when true' */ + skip_branch(branch); + else + take_branch(branch); + +} + +void mop_take_branch(void) +{ + zbyte branch = HIBYTE(PC); + PC++; + + if(branch & b10000000) /* Bit 7 set means 'branch when true' */ + take_branch(branch); + else + skip_branch(branch); +} + +void mop_cond_branch(BOOL cond) +{ + zbyte branch = HIBYTE(PC); + PC++; + if((branch >> 7) ^ cond) + skip_branch(branch); + else + take_branch(branch); +} + + +void op_je(void) +{ + int i; + for(i = 1; i < numoperands; i++) + if(operand[0] == operand[i]) { + mop_take_branch(); + return; + } + + mop_skip_branch(); +} + +void op_jg(void) +{ + if(is_greaterthan(operand[0], operand[1])) + mop_take_branch(); + else + mop_skip_branch(); +} + +void op_jl(void) +{ + if(is_lessthan(operand[0], operand[1])) + mop_take_branch(); + else + mop_skip_branch(); +} + +void op_jump(void) +{ + operand[0] -= 2; /* not documented in zspec */ + if(is_neg(operand[0])) { +#ifndef FAST + if(neg(operand[0]) > PC) { + n_show_error(E_INSTR, "attempt to jump before beginning of story", -neg(operand[0])); + return; + } +#endif + PC -= neg(operand[0]); + } + else { + PC += operand[0]; + if(PC > game_size) { + n_show_error(E_INSTR, "attempt to jump past end of story", operand[0]); + PC -= operand[0]; + return; + } + } + /* printf("jump %x -> %x\n", oldPC, PC); */ +} + +void op_jz(void) +{ + mop_cond_branch(operand[0] == 0); +} + +void op_test(void) +{ + mop_cond_branch((operand[0] & operand[1]) == operand[1]); +} + +void op_verify(void) +{ + mop_cond_branch(LOWORD(HD_CHECKSUM) == z_checksum); +} + +void op_piracy(void) +{ + mop_cond_branch(aye_matey); +} diff --git a/interpreters/nitfol/op_jmp.h b/interpreters/nitfol/op_jmp.h new file mode 100644 index 0000000..86dc41f --- /dev/null +++ b/interpreters/nitfol/op_jmp.h @@ -0,0 +1,23 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i op_jmp.c' */ +#ifndef CFH_OP_JMP_H +#define CFH_OP_JMP_H + +/* From `op_jmp.c': */ +void mop_skip_branch (void); +void mop_take_branch (void); +void mop_cond_branch (BOOL cond ); +void op_je (void); +void op_jg (void); +void op_jl (void); +void op_jump (void); +void op_jz (void); +void op_test (void); +void op_verify (void); +void op_piracy (void); + +#endif /* CFH_OP_JMP_H */ diff --git a/interpreters/nitfol/op_math.c b/interpreters/nitfol/op_math.c new file mode 100644 index 0000000..923bcde --- /dev/null +++ b/interpreters/nitfol/op_math.c @@ -0,0 +1,253 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + + +void op_load(void) +{ + mop_store_result(get_var(operand[0])); +} + +void op_store(void) +{ + set_var(operand[0], operand[1]); +} + +void op_add(void) +{ + mop_store_result(ARITHMASK(operand[0] + operand[1])); +} + +void op_sub(void) +{ + mop_store_result(ARITHMASK(operand[0] + neg(operand[1]))); +} + + +void op_and(void) +{ + mop_store_result(operand[0] & operand[1]); +} + +void op_or(void) +{ + mop_store_result(operand[0] | operand[1]); +} + +void op_not(void) +{ + mop_store_result(ARITHMASK(~operand[0])); +} + + +void op_art_shift(void) +{ + if(is_neg(operand[1])) { + if(is_neg(operand[0])) { + zword i; + zword foo = operand[0]; + /* FIXME: is there a better way? */ + for(i = 0; i < neg(operand[1]); i++) { + foo >>= 1; + foo |= ZWORD_TOPBITMASK; + } + mop_store_result(foo); + } else { + mop_store_result(operand[0] >> neg(operand[1])); + } + } else { + mop_store_result(ARITHMASK(operand[0] << operand[1])); + } +} + +void op_log_shift(void) +{ + if(is_neg(operand[1])) + mop_store_result(operand[0] >> neg(operand[1]) ); + else + mop_store_result(ARITHMASK(operand[0] << operand[1])); +} + + +void op_dec(void) +{ + zword val = ARITHMASK(get_var(operand[0]) - 1); + set_var(operand[0], val); +} + +void op_dec_chk(void) +{ + zword val = ARITHMASK(get_var(operand[0]) - 1); + set_var(operand[0], val); + + if(is_lessthan(val, operand[1])) + mop_take_branch(); + else + mop_skip_branch(); +} + +void op_inc(void) +{ + zword val = ARITHMASK(get_var(operand[0]) + 1); + set_var(operand[0], val); +} + +void op_inc_chk(void) +{ + unsigned val = ARITHMASK(get_var(operand[0]) + 1); + set_var(operand[0], val); + + if(is_greaterthan(val, operand[1])) + mop_take_branch(); + else + mop_skip_branch(); +} + + +zword z_mult(zword a, zword b) +{ + int sign = 0; + + if(is_neg(a)) { + a = neg(a); + sign = 1; + } + if(is_neg(b)) { + b = neg(b); + sign ^= 1; + } + + if(sign) + return ARITHMASK(neg(a * b)); + else + return ARITHMASK(a * b); + +} + + +zword z_div(zword a, zword b) +{ + int sign = 0; + + if(b == 0) { + n_show_error(E_MATH, "division by zero", a); + return ZWORD_MAX; + } + + if(is_neg(a)) { + a = neg(a); + sign = 1; + } + if(is_neg(b)) { + b = neg(b); + sign ^= 1; + } + + if(sign) + return neg(a / b); + else + return a / b; +} + + +zword z_mod(zword a, zword b) +{ + int sign = 0; + + if(b == 0) { + n_show_error(E_MATH, "modulo by zero", a); + return 0; + } + + if(is_neg(a)) { + a = neg(a); + sign = 1; + } + if(is_neg(b)) { + b = neg(b); + } + + if(sign) + return neg(a % b); + else + return a % b; +} + + +void op_div(void) +{ + mop_store_result(z_div(operand[0], operand[1])); +} + + +void op_mod(void) +{ + mop_store_result(z_mod(operand[0], operand[1])); +} + + +void op_mul(void) +{ + mop_store_result(z_mult(operand[0], operand[1])); +} + + +/* FIXME: use our own rng */ +zword z_random(zword num) +{ + static BOOL rising = FALSE; + static unsigned r = 0; + zword result = 0; + + if(num == 0) { + if(faked_random_seed) + srand(faked_random_seed); + else + srand(time(NULL)); + rising = FALSE; + } else if(is_neg(num)) { + if(neg(num) < 1000) { + r = 0; + rising = TRUE; + } else { + srand(neg(num)); + rising = FALSE; + } + } else { + if(rising) { + r++; + if(r > num) + r = 1; + result = r; + } else { + result = (1 + (rand() % num)); + } + } + return result; +} + +void op_random(void) +{ + if(operand[0] == 0) + n_show_port(E_MATH, "some interpreters don't like @random 0", operand[0]); + + mop_store_result(z_random(operand[0])); +} + diff --git a/interpreters/nitfol/op_math.h b/interpreters/nitfol/op_math.h new file mode 100644 index 0000000..f03adc9 --- /dev/null +++ b/interpreters/nitfol/op_math.h @@ -0,0 +1,33 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i op_math.c' */ +#ifndef CFH_OP_MATH_H +#define CFH_OP_MATH_H + +/* From `op_math.c': */ +void op_load (void); +void op_store (void); +void op_add (void); +void op_sub (void); +void op_and (void); +void op_or (void); +void op_not (void); +void op_art_shift (void); +void op_log_shift (void); +void op_dec (void); +void op_dec_chk (void); +void op_inc (void); +void op_inc_chk (void); +zword z_mult (zword a , zword b ); +zword z_div (zword a , zword b ); +zword z_mod (zword a , zword b ); +void op_div (void); +void op_mod (void); +void op_mul (void); +zword z_random (zword num ); +void op_random (void); + +#endif /* CFH_OP_MATH_H */ diff --git a/interpreters/nitfol/op_save.c b/interpreters/nitfol/op_save.c new file mode 100644 index 0000000..1ccef32 --- /dev/null +++ b/interpreters/nitfol/op_save.c @@ -0,0 +1,256 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +BOOL savegame(void) +{ + BOOL result; + strid_t file; + + if(automap_unexplore()) + return FALSE; + + file = n_file_prompt(fileusage_SavedGame | fileusage_BinaryMode, + filemode_Write); + if(!file) + return FALSE; + + result = savequetzal(file); + + glk_stream_close(file, NULL); + + return result; +} + + +void op_save1(void) +{ + if(!savegame()) { + mop_skip_branch(); + } else { + mop_take_branch(); + } +} + + +void op_save4(void) +{ + if(!savegame()) { + mop_store_result(0); + } else { + mop_store_result(1); + } +} + + +void op_save5(void) +{ + unsigned i; + char filename[256]; + unsigned length; + strid_t file = NULL; + offset end; + switch(numoperands) { + case 0: + op_save4(); + return; + case 1: + n_show_error(E_INSTR, "call save with bad number of operands", numoperands); + mop_store_result(0); + return; + case 2: + file = n_file_prompt(fileusage_Data | fileusage_BinaryMode, + filemode_Write); + break; + default: + length = LOBYTE(operand[2]); + if(length > 13) + n_show_port(E_INSTR, "save with filename > 13 characters", length); + for(i = 0; i < length; i++) + filename[i] = glk_char_to_upper(LOBYTE(operand[2] + 1 + i)); + filename[length] = 0; + file = n_file_name(fileusage_Data | fileusage_BinaryMode, + filemode_Write, filename); + break; + } + if(!file) { + mop_store_result(0); + return; + } + end = ((offset) operand[0]) + operand[1]; + if(end > 65535 || end > total_size) { + n_show_error(E_MEMORY, "attempt to save data out of range", end); + mop_store_result(0); + return; + } + + w_glk_put_buffer_stream(file, (char *) z_memory + operand[0], operand[1]); + glk_stream_close(file, NULL); + + mop_store_result(1); +} + + +BOOL restoregame(void) +{ + BOOL result; + strid_t file; + + if(automap_unexplore()) + return FALSE; + + file = n_file_prompt(fileusage_SavedGame | fileusage_BinaryMode, + filemode_Read); + if(!file) + return FALSE; + + result = restorequetzal(file); + + glk_stream_close(file, NULL); + + if(result) { + glui32 wid, hei; + z_find_size(&wid, &hei); + set_header(wid, hei); + } + + return result; +} + + +void op_restore1(void) +{ + if(!restoregame()) + mop_skip_branch(); + else + mop_take_branch(); +} + +void op_restore4(void) +{ + if(!restoregame()) + mop_store_result(0); + else + mop_store_result(2); +} + + +void op_restore5(void) +{ + int i; + char filename[256]; + int length; + strid_t file; + offset end; + switch(numoperands) { + case 0: + op_restore4(); + return; + case 1: + n_show_error(E_INSTR, "call restore with bad number of operands", numoperands); + mop_store_result(0); + return; + case 2: + file = n_file_prompt(fileusage_Data | fileusage_BinaryMode, + filemode_Read); + break; + default: + length = LOBYTE(operand[2]); + if(length > 13) + n_show_port(E_INSTR, "save with filename > 13 characters", length); + for(i = 0; i < length; i++) + filename[i] = glk_char_to_upper(LOBYTE(operand[2] + 1 + i)); + filename[length] = 0; + file = n_file_name(fileusage_Data | fileusage_BinaryMode, + filemode_Read, filename); + } + if(!file) { + mop_store_result(0); + return; + } + end = ((offset) operand[0]) + operand[1]; + if(end > 65535 || end > dynamic_size) { + n_show_error(E_MEMORY, "attempt to restore data out of range", end); + mop_store_result(0); + return; + } + + length = glk_get_buffer_stream(file, (char *) z_memory + operand[0], + operand[1]); + glk_stream_close(file, NULL); + mop_store_result(length); +} + + +void op_restart(void) +{ + if(automap_unexplore()) + return; + z_init(current_zfile); +} + + +void op_save_undo(void) +{ + if(saveundo(TRUE)) { + mop_store_result(1); + } else { + mop_store_result(0); + } +} + + +void op_restore_undo(void) +{ + if(!restoreundo()) + mop_store_result(0); +} + + +void op_quit(void) +{ + if(automap_unexplore()) + return; + z_close(); + /* puts("@quit\n"); */ + glk_exit(); +} + + +BOOL check_game_for_save(strid_t gamefile, zword release, const char serial[6], + zword checksum) +{ + int i; + unsigned char header[64]; + glk_stream_set_position(gamefile, 0, seekmode_Start); + if(glk_get_buffer_stream(gamefile, (char *) header, 64) != 64) + return FALSE; + if(header[HD_ZVERSION] == 0 || header[HD_ZVERSION] > 8) + return FALSE; + if(MSBdecodeZ(header + HD_RELNUM) != release) + return FALSE; + if(MSBdecodeZ(header + HD_CHECKSUM) != checksum) + return FALSE; + for(i = 0; i < 6; i++) { + if(header[HD_SERNUM + i] != serial[i]) + return FALSE; + } + return TRUE; +} diff --git a/interpreters/nitfol/op_save.h b/interpreters/nitfol/op_save.h new file mode 100644 index 0000000..1c2922d --- /dev/null +++ b/interpreters/nitfol/op_save.h @@ -0,0 +1,25 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i op_save.c' */ +#ifndef CFH_OP_SAVE_H +#define CFH_OP_SAVE_H + +/* From `op_save.c': */ +BOOL savegame (void); +void op_save1 (void); +void op_save4 (void); +void op_save5 (void); +BOOL restoregame (void); +void op_restore1 (void); +void op_restore4 (void); +void op_restore5 (void); +void op_restart (void); +void op_save_undo (void); +void op_restore_undo (void); +void op_quit (void); +BOOL check_game_for_save (strid_t gamefile , zword release , const char serial[6] , zword checksum ); + +#endif /* CFH_OP_SAVE_H */ diff --git a/interpreters/nitfol/op_table.c b/interpreters/nitfol/op_table.c new file mode 100644 index 0000000..9c2e54c --- /dev/null +++ b/interpreters/nitfol/op_table.c @@ -0,0 +1,170 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +void op_copy_table(void) +{ + offset i; + zword first = operand[0]; + zword second = operand[1]; + zword size = operand[2]; + + if(second == 0) { /* zero 'first' bytes */ + for(i = 0; i < size; i++) + LOBYTEwrite(first + i, 0); + } else { + if(first > second || is_neg(size)) { + if(is_neg(size)) + size = neg(size); + + for(i = 0; i < size; i++) /* copy forward */ + LOBYTEcopy(second + i, first + i); + } + else { + for(i = 0; i < size; i++) /* copy backward */ + LOBYTEcopy(second + size - i - 1, first + size - i - 1); + } + } +} + + +void op_scan_table(void) +{ + zword i; + int form = operand[3]; + zword address = operand[1]; + if(numoperands < 4) + form = 0x82; /* default form - scan for words, increment by two bytes */ + + if(form & b10000000) { /* Bit 8 set means scan for words */ + for(i = 0; i < operand[2]; i++) { + if(LOWORD(address) == operand[0]) { + mop_store_result(address); + mop_take_branch(); + return; + } + address += form & b01111111; /* Bottom 7 bits give amount to increment */ + } + mop_store_result(0); + mop_skip_branch(); + } else { /* Bit 8 clear means scan for bytes */ + for(i = 0; i < operand[2]; i++) { + if(LOBYTE(address) == operand[0]) { + mop_store_result(address); + mop_take_branch(); + return; + } + address += form & b01111111; + } + mop_store_result(0); + mop_skip_branch(); + } +} + +void op_loadb(void) +{ + mop_store_result(LOBYTE(operand[0] + operand[1])); +} + +void op_loadw(void) +{ + mop_store_result(LOWORD(operand[0] + operand[1] * ZWORD_SIZE)); +} + +static void z_write_header(zword i, zbyte val) +{ + zbyte diff = LOBYTE(i) ^ val; + if(diff == 0) + return; + if(i >= 0x40) { + LOBYTEwrite(i, val); + return; + } + if(i != HD_FLAGS2 + 1) { + n_show_error(E_MEMORY, "attempt to write to non-dynamic byte in header", i); + return; + } + if(diff > b00000111) { + n_show_error(E_MEMORY, "attempt to change non-dynamic bits in flags2", val); + return; + } + LOBYTEwrite(i, val); + if(diff & b00000001) { + if(val & b00000001) { + operand[0] = 2; + op_output_stream(); + } else { + operand[0] = neg(2); + op_output_stream(); + } + } + if(diff & b00000010) { + if(val & b00000010) { + set_fixed(TRUE); + } else { + set_fixed(FALSE); + } + } +} + +void op_storeb(void) +{ + zword addr = operand[0] + operand[1]; + if(addr < 0x40) + z_write_header(addr, (zbyte) operand[2]); + else + LOBYTEwrite(addr, operand[2]); +} + +void op_storew(void) +{ + zword addr = operand[0] + operand[1] * ZWORD_SIZE; + if(addr < 0x40) { + z_write_header(addr, (zbyte) (operand[2] >> 8)); + z_write_header(addr + 1, (zbyte) (operand[2] & 0xff)); + } else { + LOWORDwrite(addr, operand[2]); + } +} + + +void header_extension_write(zword w, zword val) +{ + w *= ZWORD_SIZE; + if(z_headerext == 0) + return; + + if(LOWORD(z_headerext) < w) + return; + + LOWORDwrite(z_headerext + w, val); +} + +zword header_extension_read(unsigned w) +{ + w *= ZWORD_SIZE; + if(z_headerext == 0) + return 0; + + if(LOWORD(z_headerext) < w) + return 0; + + return LOWORD(z_headerext + w); +} diff --git a/interpreters/nitfol/op_table.h b/interpreters/nitfol/op_table.h new file mode 100644 index 0000000..b519047 --- /dev/null +++ b/interpreters/nitfol/op_table.h @@ -0,0 +1,20 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i op_table.c' */ +#ifndef CFH_OP_TABLE_H +#define CFH_OP_TABLE_H + +/* From `op_table.c': */ +void op_copy_table (void); +void op_scan_table (void); +void op_loadb (void); +void op_loadw (void); +void op_storeb (void); +void op_storew (void); +void header_extension_write (zword w , zword val ); +zword header_extension_read (unsigned w ); + +#endif /* CFH_OP_TABLE_H */ diff --git a/interpreters/nitfol/op_v6.c b/interpreters/nitfol/op_v6.c new file mode 100644 index 0000000..b641c3a --- /dev/null +++ b/interpreters/nitfol/op_v6.c @@ -0,0 +1,345 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + + +typedef enum { z6_text, z6_picture, z6_rectangle } z6_type; + +struct graph_piece { + struct graph_piece *next; + struct graph_piece *prev; + + int window; + zword x, y; + zword width, height; + z6_type type; + + char *text; + zword picnum; + int color; /* -1 means erase to background color */ +}; + +static struct graph_piece *older_pieces; + + +static zwinid lower_win; + +static zword window_props[8][16]; +static int current_window; + +typedef enum { w_y_coord, w_x_coord, w_y_size, w_x_size, + w_y_cursor, w_x_cursor, w_l_margin, w_r_margin, + w_nl_routine, w_int_count, w_text_style, w_colour_data, + w_font_number, w_font_size, w_attributes, w_line_count +} win_prop_names; + +typedef enum { wa_wrapping, wa_scrolling, wa_transcript, wa_buffered } win_attributes; + + +static int get_window_num(int arg) +{ + if(numoperands <= arg || operand[arg] == neg(3)) + return current_window; + if(operand[arg] > 7) { + n_show_error(E_OUTPUT, "illegal window number", operand[arg]); + return current_window; + } + return operand[arg]; +} + +static BOOL check_window_prop(int prop_num) +{ + if(prop_num > 15) { + n_show_error(E_OUTPUT, "illegal wind_prop number", prop_num); + return FALSE; + } + return TRUE; +} + + +int is_in_bounds(glui32 x1, glui32 y1, glui32 width1, glui32 height1, + glui32 x2, glui32 y2, glui32 width2, glui32 height2) +{ + return (x1 >= x2 && y1 >= y2 && + x1 + width1 <= x2 + width2 && y1 + height1 <= y2 + height2); +} + + +static void add_piece(struct graph_piece new_piece) +{ + struct graph_piece *p, *q; + + return; + + if(new_piece.x + new_piece.width > + window_props[current_window][w_x_coord] + + window_props[current_window][w_x_size]) { + new_piece.width = window_props[current_window][w_x_coord] + + window_props[current_window][w_x_size] - + new_piece.x; + } + + if(new_piece.y + new_piece.height > + window_props[current_window][w_y_coord] + + window_props[current_window][w_y_size]) { + new_piece.height = window_props[current_window][w_y_coord] + + window_props[current_window][w_y_size] - + new_piece.y; + } + + q = NULL; + p = older_pieces; + while(p) { + if(is_in_bounds(p->x, p->y, p->width, p->height, + new_piece.x, new_piece.y, + new_piece.width, new_piece.height)) { + if(q) { + q->next = p->next; + n_free(p); + p = q->next; + } else { + LEremove(older_pieces); + p = older_pieces; + } + } else { + p = p-> next; + } + q = p; + } + + + LEadd(older_pieces, new_piece); + + + +} + + +void v6_main_window_is(zwinid win) +{ + lower_win = win; +} + + +void op_set_window6(void) +{ + int win = get_window_num(0); + current_window = win; +} + + +void op_set_margins(void) +{ + int win = get_window_num(2); + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "set_margins w=", operand[2]); + n_show_debug(E_OUTPUT, "set_margins l=", operand[0]); + n_show_debug(E_OUTPUT, "set_margins r=", operand[1]); +#endif + + window_props[win][w_l_margin] = operand[0]; + window_props[win][w_r_margin] = operand[1]; + + /* FIXME: move cursor */ +} + + +void op_move_window(void) +{ + int win = get_window_num(0); + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "move_window w=", operand[0]); + n_show_debug(E_OUTPUT, "move_window y=", operand[1]); + n_show_debug(E_OUTPUT, "move_window x=", operand[2]); +#endif + + window_props[win][w_y_coord] = operand[1]; + window_props[win][w_x_coord] = operand[2]; +} + + +void op_window_size(void) +{ + int win = get_window_num(0); + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "window_size w=", operand[0]); + n_show_debug(E_OUTPUT, "window_size y=", operand[1]); + n_show_debug(E_OUTPUT, "window_size x=", operand[2]); +#endif + + window_props[win][w_y_size] = operand[1]; + window_props[win][w_x_size] = operand[2]; +} + + +void op_window_style(void) +{ + zword attr; + int win = get_window_num(0); + + if(numoperands < 3) + operand[2] = 0; + + attr = window_props[win][w_attributes]; + switch(operand[2]) { + case 0: attr = operand[1]; break; + case 1: attr |= operand[1]; break; + case 2: attr &= ~(operand[1]); break; + case 3: attr ^= operand[1]; break; + default: n_show_error(E_OUTPUT, "invalid flag operation", operand[2]); + } + + window_props[win][w_attributes] = attr; +} + + +void op_get_wind_prop(void) +{ + int win = get_window_num(0); + + if(!check_window_prop(operand[1])) { + mop_store_result(0); + return; + } + mop_store_result(window_props[win][operand[1]]); +} + + +void op_put_wind_prop(void) +{ + int win = get_window_num(0); + + if(!check_window_prop(operand[1])) { + mop_store_result(0); + return; + } + window_props[win][operand[1]] = operand[2]; +} + + +void op_scroll_window(void) +{ + ; +} + + +void op_read_mouse(void) +{ + ; +} + + +void op_mouse_window(void) +{ + ; +} + + +void op_print_form(void) +{ + ; +} + + +void op_make_menu(void) +{ + mop_skip_branch(); +} + + +void op_picture_table(void) +{ + ; /* Glk contains no image prefetching facilities, so nothing to do here */ +} + + +void op_draw_picture(void) +{ + struct graph_piece new_piece; + glui32 width, height; + + z_put_char(lower_win, 13); /* Work around a bug in xglk */ + draw_intext_picture(lower_win, operand[0], imagealign_MarginLeft); + + + /* + + new_piece.window = current_window; + new_piece.x = operand[2] + window_props[current_window][w_x_coord]; + new_piece.y = operand[1] + window_props[current_window][w_y_coord]; + + wrap_glk_image_get_info(operand[0], &width, &height); + + new_piece.width = width; + new_piece.height = height; + + new_piece.type = z6_picture; + new_piece.picnum = operand[0]; + + add_piece(new_piece); + + */ +} + + +void op_picture_data(void) +{ + if(glk_gestalt(gestalt_Graphics, 0)) { + glui32 width, height; + + if(operand[0] == 0) { + LOWORDwrite(operand[1], imagecount); + LOWORDwrite(operand[1], 42); /* FIXME: where do I get picture release? */ + } + else if(wrap_glk_image_get_info(operand[0], &width, &height)) { + LOWORDwrite(operand[1], height); + LOWORDwrite(operand[1] + ZWORD_SIZE, width); + mop_take_branch(); + return; + } + } + mop_skip_branch(); +} + + +void op_erase_picture(void) +{ + struct graph_piece new_piece; + glui32 width, height; + + new_piece.window = current_window; + new_piece.x = operand[2] + window_props[current_window][w_x_coord]; + new_piece.y = operand[1] + window_props[current_window][w_y_coord]; + + wrap_glk_image_get_info(operand[0], &width, &height); + + new_piece.width = width; + new_piece.height = height; + + new_piece.type = z6_rectangle; + new_piece.color = -1; + + add_piece(new_piece); + +} + diff --git a/interpreters/nitfol/op_v6.h b/interpreters/nitfol/op_v6.h new file mode 100644 index 0000000..16c92ef --- /dev/null +++ b/interpreters/nitfol/op_v6.h @@ -0,0 +1,30 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i op_v6.c' */ +#ifndef CFH_OP_V6_H +#define CFH_OP_V6_H + +/* From `op_v6.c': */ +int is_in_bounds (glui32 x1 , glui32 y1 , glui32 width1 , glui32 height1 , glui32 x2 , glui32 y2 , glui32 width2 , glui32 height2 ); +void v6_main_window_is (zwinid win ); +void op_set_window6 (void); +void op_set_margins (void); +void op_move_window (void); +void op_window_size (void); +void op_window_style (void); +void op_get_wind_prop (void); +void op_put_wind_prop (void); +void op_scroll_window (void); +void op_read_mouse (void); +void op_mouse_window (void); +void op_print_form (void); +void op_make_menu (void); +void op_picture_table (void); +void op_draw_picture (void); +void op_picture_data (void); +void op_erase_picture (void); + +#endif /* CFH_OP_V6_H */ diff --git a/interpreters/nitfol/oplist.c b/interpreters/nitfol/oplist.c new file mode 100644 index 0000000..454e02a --- /dev/null +++ b/interpreters/nitfol/oplist.c @@ -0,0 +1,222 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +static void op_nop(void) { ; } + +static void op_XXX(void) +{ + zbyte ins = HIBYTE(oldPC); + n_show_error(E_INSTR, "illegal instruction", ins); +} + +#ifdef HEADER + +/* offsets in opcodetable for various types */ +#define OFFSET_0OP 0x00 +#define OFFSET_1OP 0x10 +#define OFFSET_2OP 0x20 +#define OFFSET_VAR 0x40 +#define OFFSET_EXT 0x60 +#define OFFSET_END 0x80 + +typedef void (*op_func)(void); + +#endif + +op_func opcodetable[] = { + /* 0OP */ + op_rtrue, op_rfalse, op_print, op_print_ret, + op_nop, op_save4, op_restore4, op_restart, + op_ret_popped, op_catch, op_quit, op_new_line, + op_show_status, op_verify, op_XXX, op_piracy, + /* 1OP */ + op_jz, op_get_sibling, op_get_child, op_get_parent, + op_get_prop_len, op_inc, op_dec, op_print_addr, + op_call_s, op_remove_obj, op_print_obj, op_ret, + op_jump, op_print_paddr, op_load, op_call_n, + /* 2OP */ + op_XXX, op_je, op_jl, op_jg, + op_dec_chk, op_inc_chk, op_jin, op_test, + op_or, op_and, op_test_attr, op_set_attr, + op_clear_attr, op_store, op_insert_obj, op_loadw, + op_loadb, op_get_prop, op_get_prop_addr, op_get_next_prop, + op_add, op_sub, op_mul, op_div, + op_mod, op_call_s, op_call_n, op_set_colour, + op_throw, op_XXX, op_XXX, op_XXX, + /* VAR */ + op_call_s, op_storew, op_storeb, op_put_prop, + op_aread, op_print_char, op_print_num, op_random, + op_push, op_pull, op_split_window, op_set_window, + op_call_s, op_erase_window, op_erase_line, op_set_cursor, + op_get_cursor, op_set_text_style,op_buffer_mode, op_output_stream, + op_input_stream, op_sound_effect, op_read_char, op_scan_table, + op_not, op_call_n, op_call_n, op_tokenise, + op_encode_text, op_copy_table, op_print_table, op_check_arg_count, + /* EXT */ + op_save5, op_restore5, op_log_shift, op_art_shift, + op_set_font, op_draw_picture, op_picture_data, op_erase_picture, + op_set_margins, op_save_undo, op_restore_undo, op_print_unicode, + op_check_unicode, op_XXX, op_XXX, op_XXX, + op_move_window, op_window_size, op_window_style, op_get_wind_prop, + op_scroll_window, op_pop_stack, op_read_mouse, op_mouse_window, + op_push_stack, op_put_wind_prop, op_print_form, op_make_menu, + op_picture_table, op_XXX, op_XXX, op_XXX +}; + +#ifdef DEBUGGING + +opcodeinfo opcodelist[] = { + /* 0OP */ + { "rtrue", v1, vM, 0, 0, opNONE, 0 }, + { "rfalse", v1, vM, 0, 0, opNONE, 0 }, + { "print", v1, vM, 0, 0, opTEXTINLINE, 0 }, + { "print_ret", v1, vM, 0, 0, opTEXTINLINE, 0 }, + { "nop", v1, vM, 0, 0, opNONE, 0 }, + { "save", v4, v4, 0, 0, opSTORES, 0 }, + { "restore", v4, v4, 0, 0, opSTORES, 0 }, + { "restart", v1, vM, 0, 0, opNONE, 0 }, + { "ret_popped", v1, vM, 0, 0, opNONE, 0 }, + { "catch", v5, vM, 0, 0, opSTORES, 0 }, + { "quit", v1, vM, 0, 0, opNONE, 0 }, + { "new_line", v1, vM, 0, 0, opNONE, 0 }, + { "show_status", v3, v3, 0, 0, opNONE, 0 }, + { "verify", v3, vM, 0, 0, opBRANCHES, 0 }, + { "extended", v5, vM, 0, 0, opNONE, 0 }, + { "piracy", v5, vM, 0, 0, opBRANCHES, 0 }, + /* 1OP */ + { "jz", v1, vM, 1, 1, opBRANCHES, 0 }, + { "get_sibling", v1, vM, 1, 1, opSTORES | opBRANCHES, 0 }, + { "get_child", v1, vM, 1, 1, opSTORES | opBRANCHES, 0 }, + { "get_parent", v1, vM, 1, 1, opSTORES, 0 }, + { "get_prop_len", v1, vM, 1, 1, opSTORES, 0 }, + { "inc", v1, vM, 1, 1, opNONE, 0 }, + { "dec", v1, vM, 1, 1, opNONE, 0 }, + { "print_addr", v1, vM, 1, 1, opNONE, 0 }, + { "call_1s", v4, vM, 1, 1, opSTORES, 0 }, + { "remove_obj", v1, vM, 1, 1, opNONE, 0 }, + { "print_obj", v1, vM, 1, 1, opNONE, 0 }, + { "ret", v1, vM, 1, 1, opNONE, 0 }, + { "jump", v1, vM, 1, 1, opJUMPS, 0 }, + { "print_paddr", v1, vM, 1, 1, opNONE, 0 }, + { "load", v1, vM, 1, 1, opSTORES, 0 }, + { "call_1n", v5, vM, 1, 1, opNONE, 0 }, + /* 2OP, 0 */ + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + { "je", v1, vM, 1, 4, opBRANCHES, 0 }, + { "jl", v1, vM, 2, 2, opBRANCHES, 0 }, + { "jg", v1, vM, 2, 2, opBRANCHES, 0 }, + { "dec_chk", v1, vM, 2, 2, opBRANCHES, 0 }, + { "inc_chk", v1, vM, 2, 2, opBRANCHES, 0 }, + { "jin", v1, vM, 2, 2, opBRANCHES, 0 }, + { "test", v1, vM, 2, 2, opBRANCHES, 0 }, + { "or", v1, vM, 2, 2, opNONE, 0 }, + { "and", v1, vM, 2, 2, opNONE, 0 }, + { "test_attr", v1, vM, 2, 2, opBRANCHES, 0 }, + { "set_attr", v1, vM, 2, 2, opNONE, 0 }, + { "clear_attr", v1, vM, 2, 2, opNONE, 0 }, + { "store", v1, vM, 2, 2, opNONE, 0 }, + { "insert_obj", v1, vM, 2, 2, opNONE, 0 }, + { "loadw", v1, vM, 2, 2, opSTORES, 0 }, + { "loadb", v1, vM, 2, 2, opSTORES, 0 }, + { "get_prop", v1, vM, 2, 2, opSTORES, 0 }, + { "get_prop_addr", v1, vM, 2, 2, opSTORES, 0 }, + { "get_next_prop", v1, vM, 2, 2, opSTORES, 0 }, + { "add", v1, vM, 2, 2, opSTORES, 0 }, + { "sub", v1, vM, 2, 2, opSTORES, 0 }, + { "mul", v1, vM, 2, 2, opSTORES, 0 }, + { "div", v1, vM, 2, 2, opSTORES, 0 }, + { "mod", v1, vM, 2, 2, opSTORES, 0 }, + { "call_2s", v4, vM, 2, 2, opSTORES, 0 }, + { "call_2n", v5, vM, 2, 2, opNONE, 0 }, + { "set_colour", v5, vM, 2, 2, opNONE, 0 }, + { "throw", v5, vM, 2, 2, opNONE, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + /* VAR */ + { "call_vs", v1, vM, 1, 4, opSTORES, 0 }, + { "storew", v1, vM, 3, 3, opNONE, 0 }, + { "storeb", v1, vM, 3, 3, opNONE, 0 }, + { "put_prop", v1, vM, 3, 3, opNONE, 0 }, + { "aread", v5, vM, 0, 4, opSTORES, 0 }, + { "print_char", v1, vM, 1, 1, opNONE, 0 }, + { "print_num", v1, vM, 1, 1, opNONE, 0 }, + { "random", v1, vM, 1, 1, opSTORES, 0 }, + { "push", v1, vM, 1, 1, opNONE, 0 }, + { "pull", v1, vM, 1, 1, opNONE, 0 }, + { "split_window", v3, vM, 1, 1, opNONE, 0 }, + { "set_window", v3, vM, 1, 1, opNONE, 0 }, + { "call_vs2", v4, vM, 1, 8, opSTORES, 0 }, + { "erase_window", v4, vM, 1, 1, opNONE, 0 }, + { "erase_line", v4, vM, 1, 1, opNONE, 0 }, + { "set_cursor", v4, vM, 2, 2, opNONE, 0 }, + { "get_cursor", v4, vM, 1, 1, opNONE, 0 }, + { "set_text_style", v4, vM, 1, 1, opNONE, 0 }, + { "buffer_mode", v4, vM, 1, 1, opNONE, 0 }, + { "output_stream", v5, vM, 1, 2, opNONE, 0 }, + { "input_stream", v3, vM, 1, 1, opNONE, 0 }, + { "sound_effect", v3, vM, 4, 4, opNONE, 0 }, + { "read_char", v4, vM, 1, 3, opSTORES, 0 }, + { "scan_table", v4, vM, 4, 4, opSTORES | opBRANCHES, 0 }, + { "not", v5, vM, 1, 1, opSTORES, 0 }, + { "call_vn", v5, vM, 1, 4, opNONE, 0 }, + { "call_vn2", v5, vM, 1, 8, opNONE, 0 }, + { "tokenise", v5, vM, 4, 4, opNONE, 0 }, + { "encode_text", v5, vM, 4, 4, opNONE, 0 }, + { "copy_table", v5, vM, 3, 3, opNONE, 0 }, + { "print_table", v5, vM, 4, 4, opNONE, 0 }, + { "check_arg_count", v5, vM, 1, 1, opBRANCHES, 0 }, + /* EXT */ + { "save", v5, vM, 0, 3, opSTORES, 0 }, + { "restore", v5, vM, 0, 3, opSTORES, 0 }, + { "log_shift", v5, vM, 2, 2, opSTORES, 0 }, + { "art_shift", v5, vM, 2, 2, opSTORES, 0 }, + { "set_font", v5, vM, 1, 1, opSTORES, 0 }, + { "draw_picture", v6, vM, 3, 3, opNONE, 0 }, + { "picture_data", v6, vM, 2, 2, opBRANCHES, 0 }, + { "erase_picture", v6, vM, 3, 3, opNONE, 0 }, + { "set_margins", v6, vM, 3, 3, opNONE, 0 }, + { "save_undo", v5, vM, 0, 0, opSTORES, 0 }, + { "restore_undo", v5, vM, 0, 0, opSTORES, 0 }, + { "print_unicode", v5, vM, 1, 1, opNONE, 0 }, + { "check_unicode", v5, vM, 1, 1, opSTORES, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + { "move_window", v6, vM, 3, 3, opNONE, 0 }, + { "window_size", v6, vM, 3, 3, opNONE, 0 }, + { "window_style", v6, vM, 2, 3, opNONE, 0 }, + { "get_wind_prop", v6, vM, 2, 2, opSTORES, 0 }, + { "scroll_window", v6, vM, 2, 2, opNONE, 0 }, + { "pop_stack", v6, vM, 1, 2, opNONE, 0 }, + { "read_mouse", v6, vM, 1, 1, opNONE, 0 }, + { "mouse_window", v6, vM, 1, 1, opNONE, 0 }, + { "push_stack", v6, vM, 1, 2, opBRANCHES, 0 }, + { "put_wind_prop", v6, vM, 3, 3, opNONE, 0 }, + { "print_form", v6, vM, 1, 1, opNONE, 0 }, + { "make_menu", v6, vM, 2, 2, opBRANCHES, 0 }, + { "picture_table", v6, vM, 1, 1, opNONE, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, + { "XXX", v1, vM, 0, 8, opNONE, 0 }, +}; + +#endif diff --git a/interpreters/nitfol/oplist.h b/interpreters/nitfol/oplist.h new file mode 100644 index 0000000..3b2f329 --- /dev/null +++ b/interpreters/nitfol/oplist.h @@ -0,0 +1,25 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i oplist.c' */ +#ifndef CFH_OPLIST_H +#define CFH_OPLIST_H + +/* From `oplist.c': */ +#define OFFSET_0OP 0x00 +#define OFFSET_1OP 0x10 +#define OFFSET_2OP 0x20 +#define OFFSET_VAR 0x40 +#define OFFSET_EXT 0x60 +#define OFFSET_END 0x80 +typedef void (*op_func)(void); +extern op_func opcodetable[]; + +#ifdef DEBUGGING +extern opcodeinfo opcodelist[]; + +#endif + +#endif /* CFH_OPLIST_H */ diff --git a/interpreters/nitfol/opt2glkc.pl b/interpreters/nitfol/opt2glkc.pl new file mode 100644 index 0000000..6f60a64 --- /dev/null +++ b/interpreters/nitfol/opt2glkc.pl @@ -0,0 +1,1055 @@ +#!/usr/local/bin/perl -w +use strict; + +my $header = "nitfol.h"; +my $appname = "nitfol"; +my $appmajor = "NITFOL_MAJOR"; +my $appminor = "NITFOL_MINOR"; +my $texinfo = "nitfol.texi"; +my $author = "Evin Robertson"; +my $email = "nitfol\@my-deja.com"; +my $search_path = "INFOCOM_PATH"; +my @man_see_also = ( "frotz (6)", "txd (1)" ); +my $mac_creator = "niTf"; +my $mac_savefile = "IFZS"; +my $mac_datafile = "ZipD"; +my @mac_gamefile = ( "ZCOD", "IFRS", $mac_savefile ); + +# The following are modified by make_glk_* as appropriate +my $dirsep = "/"; +my $configdir = "configdir = n_strdup(getenv(\"HOME\"));"; +my $configname = "configname = \"${dirsep}.${appname}rc\";"; + + +# opt2glkc.pl - Generates Glk startup code and some documentation +# Copyright (C) 1999 Evin Robertson +# +# 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. +# +# The author can be reached at nitfol@my-deja.com + +my @LoL; +my $argtype; +my $systemtype; + +my %optionlist = ( unix => [ "startunix.c", \&make_glk_unix ], + dos => [ "startdos.c", \&make_glk_dos ], + win => [ "startwin.c", \&make_glk_win ], + mac => [ "startmac.c", \&make_glk_mac ], + info => [ "options.texi", \&make_info ], + man => [ "$appname.6", \&make_man ] + ); + +$_ = $ARGV[0]; + +if(/^-(.*)$/) { + $systemtype = $1; + shift; +} else { + $systemtype = "unix"; +} + +read_in_optfile(); + +$optionlist{$systemtype} || die "Unknown switch '-$systemtype'\n"; + +open("MYOUTPUT", ">$optionlist{$systemtype}[0]") || die "cannot write to $optionlist{$systemtype}[0]\n"; + +select "MYOUTPUT"; +&{ $optionlist{$systemtype}[1] }(); + +close "MYOUTPUT"; + + + + +sub read_in_optfile +{ + my $longdesc; + my $i = 1; + while(<>) { + if(/^\#/ || /^$/) { + ; + } + # 1 2 3 4 5 6 9 + elsif(/^\s*\"(.*?)\"\s+(\S+)\s+(\S)\s+\"(.*?)\"\s+(\S+)\s+((\S+)|(\".*?\"))\s+(\{.*)$/) { + $longdesc = <>; + push @LoL, [ ( $1, $2, $3, $4, $5, $6, $9, $i, $ARGV, $longdesc ) ]; + $i++; + } else { + die "Error in file $ARGV line $i."; + } + $i++; + } +} + + + +sub make_info +{ + foreach my $soption (@LoL) { + my @option = @{ $soption }; + if($option[4] eq "flag") { + print "\@item -$option[1]\n"; + print "\@itemx -no-$option[1]\n"; + if($option[2] ne "-") { + print "\@itemx -$option[2]\n"; + } + } else { + print "\@item -$option[1] \@var{$option[4]}\n"; + if($option[2] ne "-") { + print "\@itemx -$option[2] \@var{$option[4]}\n"; + } + } + print "$option[3]. $option[9]\n"; + } +} + +sub texi2roff +{ + $_ = $_[0]; + s/\@itemize \@minus\n//; + s/\@end itemize/.PP/; + s/\@item /.IP \\\(bu\n/; + + s/\@code\{(.*?)\}/\\fB$1\\fP/g; + s/\@samp\{(.*?)\}/\`$1\'/g; + s/\@var\{(.*?)\}/\\fI$1\\fP/g; + s/\@kbd\{(.*?)\}/\`$1\'/g; + s/\@file\{(.*?)\}/\`$1\'/g; + s/\@cite\{(.*?)\}/\\fI$1\\fP/g; + s/\@uref\{(.*?)(, (.*?))\}/$3 \<$1\>/g; + s/\@uref\{(.*?)\}/\<$1\>/g; + s/\@email\{(.*?)\}/\<$1\>/g; + s/\@sc\{(.*?)\}/$1/g; + s/\@\@/\@/g; + + return $_; +} + +sub texi2txt +{ + $_ = $_[0]; + s/\@code\{(.*?)\}/\'$1\'/g; + s/\@file\{(.*?)\}/\'$1\'/g; + s/\@\@/\@/g; + return $_; +} + + +sub make_man +{ + open("MYTEXINFO", "<$texinfo") || die "unable to read '$texinfo'\n"; + print ".TH ", uc $appname, " 6\n"; + print ".SH NAME\n"; + while(($_ = ) !~ /^\* $appname: \($appname\). *.*/i) { + ; + } + /^\* $appname: \($appname\). *(.*)/i; + print "$appname \\- $1\n"; + print ".SH SYNOPSIS\n"; + print ".B $appname\n"; + print ".I \"[options] file\"\n"; + print ".SH DESCRIPTION\n"; + print "This manpage was generated from bits of the info page. See the info page for complete documentation.\n"; + while( !~ /^\@chapter introduction/i) { + ; + } + while(($_ = ) !~ /^(\@node)|(\@bye)|(\@menu)/) { + if($_ !~ /^\@.*/) { + print texi2roff($_); + } + } + print ".SH OPTIONS\n"; + foreach my $soption (@LoL) { + my @option = @{ $soption }; + print ".TP\n"; + print ".B"; + if($option[4] eq "flag") { + print " \\-$option[1]"; + print ", \\-no\\-$option[1]"; + if($option[2] ne "-") { + print ", \\-$option[2]"; + } + } else { + print " \\-$option[1] \\fI$option[4]\\fB"; + if($option[2] ne "-") { + print ", \\-$option[2] \\fI$option[4]"; + } + } + print "\n"; + print texi2roff($option[3]), ". ", texi2roff($option[9]); + } + + print ".SH BUGS\n"; + while( !~ /^\@chapter bugs/i) { + ; + } + while(($_ = ) !~ /^(\@node)|(\@bye)/) { + print texi2roff($_); + } + + print ".SH \"SEE ALSO\"\n"; + print ".RB \"`\\|\" $appname \"\\|'\"\n"; + print "entry in\n"; + print ".B\n"; + print "info;\n"; + + my $flag = 0; + foreach my $also (@man_see_also) { + if($flag) { + print ",\n"; + } + $flag = 1; + print ".BR $also"; + } + print ".\n"; + + print ".SH AUTHOR\n"; + print "$appname was written by $author, who can be reached at $email.\n"; + close("MYTEXINFO"); +} + +sub make_glk_unix +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +#ifdef DEBUGGING +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include \"$header\" +#include \"glkstart.h\" + +static char *game_filename = NULL; + +static void set_game_filename(const char *name) +{ + n_free(game_filename); + game_filename = 0; + +#if defined(_GNU_SOURCE) + game_filename = canonicalize_file_name(name); +#else +#if defined(_BSD_SOURCE) || defined(_XOPEN_SOURCE) + game_filename = (char *) n_malloc(PATH_MAX); + if(!realpath(name, game_filename)) { + n_free(game_filename); + game_filename = 0; + } +#else +#ifdef __DJGPP__ + game_filename = (char *) n_malloc(FILENAME_MAX); + _fixpath(name, game_filename); +#endif +#endif +#endif + + if(!game_filename) + game_filename = n_strdup(name); +} + + +strid_t startup_findfile(void) +{ + static DIR *dir = NULL; + static char *pathstart = NULL; + static char *path = NULL; + strid_t stream; + struct dirent *d; + char *name = NULL; + + if(!pathstart) { + char *p = search_path; + if(!p) + return 0; + pathstart = n_strdup(p); + if(!(path = n_strtok(pathstart, \":\"))) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + + do { + if(!dir) { + dir = opendir(path); + if(!dir) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + d = readdir(dir); + if(!d) { + closedir(dir); + dir = NULL; + if(!(path = n_strtok(NULL, \":\"))) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + } while(!dir); + + name = (char *) n_malloc(n_strlen(path) + n_strlen(d->d_name) + 2); + n_strcpy(name, path); + n_strcat(name, \"$dirsep\"); + n_strcat(name, d->d_name); + stream = glkunix_stream_open_pathname(name, fileusage_Data | + fileusage_BinaryMode, 0); + if(stream) + set_game_filename(name); + n_free(name); + return stream; +} + + +strid_t intd_filehandle_open(strid_t savefile, glui32 operating_id, + glui32 contents_id, glui32 interp_id, + glui32 length) +{ + char *name; + strid_t str; + if(operating_id != ", string_to_iff("UNIX"), ") + return 0; + if(contents_id != 0) + return 0; + if(interp_id != ", string_to_iff(" "), ") + return 0; + + name = (char *) n_malloc(length+1); + glk_get_buffer_stream(savefile, name, length); + name[length] = 0; + str = glkunix_stream_open_pathname(name, fileusage_Data | + fileusage_BinaryMode, 0); + if(str) + set_game_filename(name); + n_free(name); + return str; +} + +void intd_filehandle_make(strid_t savefile) +{ + if(!game_filename) + return; + w_glk_put_string_stream(savefile, \"UNIX\"); + glk_put_char_stream(savefile, b00000010); /* Flags */ + glk_put_char_stream(savefile, 0); /* Contents ID */ + glk_put_char_stream(savefile, 0); /* Reserved */ + glk_put_char_stream(savefile, 0); /* Reserved */ + w_glk_put_string_stream(savefile, \" \"); /* Interpreter ID */ + w_glk_put_string_stream(savefile, game_filename); +} + +glui32 intd_get_size(void) +{ + if(!game_filename) + return 0; + return n_strlen(game_filename) + 12; +} + +strid_t startup_open(const char *name) +{ + strid_t str; + + str = glkunix_stream_open_pathname((char *) name, fileusage_Data | fileusage_BinaryMode, 0); + if(str) { + set_game_filename(name); + } else { + char *path = search_path; + if(path) { + char *p; + char *newname = (char *) n_malloc(strlen(path) + strlen(name) + 2); + path = n_strdup(path); + for(p = n_strtok(path, \":\"); p; p = n_strtok(NULL, \":\")) { + n_strcpy(newname, p); + n_strcat(newname, \"$dirsep\"); + n_strcat(newname, name); + str = glkunix_stream_open_pathname((char *) newname, fileusage_Data | + fileusage_BinaryMode, 0); + if(str) { + set_game_filename(newname); + break; + } + } + n_free(path); + } + } + + if(!str) + fprintf(stderr, \"Cannot open '%s'\\n\", name); + + return str; +} + +"; + + make_generic_startup_wopen(); + make_useless_command_structure(); + make_functions(); + make_useful_command_structure(); + make_default_setter(); + make_textpref_reader(); + make_help_printer(); + make_command_parser(); + + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " + +#ifdef DEBUGGING +static void sighandle(int unused); + +static void sighandle(int unused) +{ +/* signal(SIGINT, sighandle); */ /* SysV resets default behaviour - foil it */ + enter_debugger = TRUE; +} +#endif + +#ifdef __cplusplus +extern \"C\" { +#endif +int glkunix_startup_code(glkunix_startup_t *data) +{ + strid_t pref; + const char *configname; + char *configdir, *prefname; + char *execname; + char *p; + username = getenv(\"LOGNAME\"); /* SysV */ + if(!username) + username = getenv(\"USER\"); /* BSD */ + +#ifdef DEBUGGING +/* signal(SIGINT, sighandle); */ +#endif + + execname = n_strrchr(data->argv[0], '$dirsep'); + + if(execname) + execname++; + else + execname = data->argv[0]; + + set_defaults(); + $configdir + $configname + prefname = n_malloc(n_strlen(configdir) + n_strlen(configname) + 1); + n_strcpy(prefname, configdir); + n_strcat(prefname, configname); + pref = glkunix_stream_open_pathname(prefname, fileusage_Data | fileusage_TextMode, 0); + n_free(configdir); + n_free(prefname); + read_textpref(pref, execname); + + p = getenv(\"$search_path\"); + if(p) { + free(search_path); + search_path = n_strdup(p); + } + + return parse_commands(data->argc, data->argv); +} +#ifdef __cplusplus +} +#endif +"; +} + +sub make_glk_dos +{ + $dirsep = "/"; + $configdir = "configdir = n_strdup(data->argv[0]); if(n_strrchr(configdir, '$dirsep')) *n_strrchr(configdir, '$dirsep') = 0;"; + $configname = "configname = \"${dirsep}${appname}.cfg\";"; + make_glk_unix(); +} + + +sub make_glk_win +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +#include \"$header\" +#include \"WinGlk.h\" + +"; + make_generic_intd(); + make_generic_findfile(); + make_generic_startup_open(); + make_generic_startup_wopen(); + make_functions(); + make_useful_command_structure(); + make_default_setter(); + make_help_printer(); + make_command_parser(); + + + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +void shift_string_left(char *str) +{ + int len = strlen(str); + int i; + for(i = 0; i < len; i++) + str[i] = str[i+1]; +} + +int winglk_startup_code(void) +{ + BOOL status; + char *commandline = strdup(GetCommandLine()); + char **argv = (char **) n_malloc(sizeof(char *) * strlen(commandline)); + int argc = 0; + + int i; + + while(*commandline) { + while(*commandline && isspace(*commandline)) + commandline++; + + argv[argc++] = commandline; + + while(*commandline && !isspace(*commandline)) { + if(*commandline == '\"') { + shift_string_left(commandline); + while(*commandline && *commandline != '\"') + commandline++; + shift_string_left(commandline); + } else { + commandline++; + } + } + + *commandline++ = 0; + } + + argv[argc] = NULL; + + status = parse_commands(argc, argv); + + n_free(argv); + n_free(commandline); + + winglk_app_set_name(\"$appname\"); + winglk_window_set_title(\"$appname\"); + set_defaults(); + + return status; +} +"; +} + + + +sub string_to_iff +{ + my ($id) = @_; + my $i; + my $val; + $val = 0; + for($i=0; $i < length $id; $i++) { + $val = $val * 0x100 + ord substr $id, $i, 1; + } + return sprintf("0x%x /* '$id' */", $val); +} + + + +sub make_glk_mac +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +#include \"$header\" +#include \"macglk_startup.h\" + +static strid_t mac_gamefile; + +static BOOL hashandle = FALSE; +static AliasHandle gamehandle; +"; + make_generic_findfile(); + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +strid_t intd_filehandle_open(strid_t savefile, glui32 operating_id, + glui32 contents_id, glui32 interp_id, + glui32 length) +{ + FSSpec file; + Boolean wasChanged; + if(operating_id != ", string_to_iff("MACS"), ") + return 0; + if(contents_id != 0) + return 0; + if(interp_id != ", string_to_iff(" "), ") + return 0; + + gamehandle = NewHandle(length); + glk_get_buffer_stream(savefile, *gamehandle, length); + hashandle = TRUE; + ResolveAlias(NULL, gamehandle, &file, &wasChanged); + return macglk_stream_open_fsspec(&file, 0, 0); +} + +void intd_filehandle_make(strid_t savefile) +{ + if(!hashandle) + return; + glk_put_string_stream(savefile, \"MACS\"); + glk_put_char_stream(savefile, b00000010); /* Flags */ + glk_put_char_stream(savefile, 0); /* Contents ID */ + glk_put_char_stream(savefile, 0); /* Reserved */ + glk_put_char_stream(savefile, 0); /* Reserved */ + glk_put_string_stream(savefile, \" \");/* Interpreter ID */ + glk_put_buffer_stream(savefile, *gamehandle, *gamehandle->aliasSize); +} + +glui32 intd_get_size(void) +{ + if(!hashandle) + return 0; + return *gamehandle->aliasSize + 12; +} + +static Boolean mac_whenselected(FSSpec *file, OSType filetype) +{ + NewAlias(NULL, file, &gamehandle); + hashandle = TRUE; + return game_use_file(mac_gamefile); +} + +static Boolean mac_whenbuiltin() +{ + return game_use_file(mac_gamefile); +} + +Boolean macglk_startup_code(macglk_startup_t *data) +{ + OSType mac_gamefile_types[] = { "; + + my $flag = 0; + foreach my $filetype (@mac_gamefile) { + if($flag) { + print ", "; + } + $flag = 1; + print string_to_iff($filetype); + } + + print " }; + + data->startup_model = macglk_model_ChooseOrBuiltIn; + data->app_creator = ", string_to_iff($mac_creator), "; + data->gamefile_types = mac_gamefile_types; + data->num_gamefile_types = sizeof(mac_gamefile_types) / sizeof(*mac_gamefile_types); + data->savefile_type = ", string_to_iff($mac_savefile), "; + data->datafile_type = ", string_to_iff($mac_datafile), "; + data->gamefile = &mac_gamefile; + data->when_selected = mac_whenselected; + data->when_builtin = mac_whenbuiltin; + + return TRUE; +} +"; +} + +sub make_generic_intd +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +strid_t intd_filehandle_open(strid_t savefile, glui32 operating_id, + glui32 contents_id, glui32 interp_id, + glui32 length) +{ + return 0; +} + +void intd_filehandle_make(strid_t savefile) +{ + ; +} + +glui32 intd_get_size(void) +{ + return 0; +} +"; +} + + +sub make_generic_findfile +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +strid_t startup_findfile(void) +{ + ; +} +"; +} + + +sub make_generic_startup_open +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +strid_t startup_open(const char *name) +{ + return n_file_name(fileusage_Data | fileusage_BinaryMode, + filemode_Read, name); +} +"; +} + + +sub make_generic_startup_wopen +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +static strid_t startup_wopen(const char *name) +{ + return n_file_name(fileusage_Data | fileusage_BinaryMode, + filemode_Write, name); +} +"; +} + + + +sub make_functions +{ + foreach my $soption (@LoL) { + my @option = @{ $soption }; + if($option[4] eq "flag") { + $argtype = "int flag"; + } + if($option[4] eq "file" || $option[4] eq "wfile") { + $argtype = "strid_t stream"; + } + if($option[4] eq "number") { + $argtype = "int number"; + } + if($option[4] eq "string") { + $argtype = "const char *string"; + } + + print "static void code_$option[1]($argtype)\n"; + print "#line $option[7] \"$option[8]\"\n"; + print "$option[6]\n\n"; + } +} + + + +sub make_useful_command_structure +{ + # + # Write structure so we can actually parse the options + # + my ( $int_func, $defint, $str_func, $defstr, $string_func, $defstring ); + + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"'; + print "\ntypedef enum { option_flag, option_file, option_wfile, option_number, option_string } option_type;\n"; + print "typedef struct { const char *longname; char shortname; const char *description; option_type type; void (*int_func)(int); int defint; void (*str_func)(strid_t); strid_t defstream; void (*string_func)(const char *); const char *defstring; } option_option;\n\n"; + + print "static option_option options[] = {\n"; + + my $flag = 0; + foreach my $soption (@LoL) { + my @option = @{ $soption }; + if($flag) { + print ",\n"; + } + $flag = 1; + + $int_func = "NULL"; + $defint = "0"; + + $str_func = "NULL"; + $defstr = "NULL"; + + $string_func = "NULL"; + $defstring = "NULL"; + + if($option[4] eq "flag") { + $defint = $option[5]; + $int_func = "code_" . $option[1]; + } + if($option[4] eq "file" || $option[4] eq "wfile") { + $defstr = $option[5]; + $str_func = "code_" . $option[1]; + } + if($option[4] eq "number") { + $defint = $option[5]; + $int_func = "code_" . $option[1]; + } + if($option[4] eq "string") { + $defstring = $option[5]; + $string_func = "code_" . $option[1]; + } + + print " { \"$option[1]\", '$option[2]', \"", texi2txt($option[3]), "\", option_$option[4], $int_func, $defint, $str_func, $defstr, $string_func, $defstring }"; + } + + print "\n};\n\n"; + +} + + + +sub make_default_setter +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +static void set_defaults(void) +{ + unsigned n; + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(options[n].int_func) + options[n].int_func(options[n].defint); + if(options[n].str_func) + options[n].str_func(options[n].defstream); + if(options[n].string_func) + options[n].string_func(options[n].defstring); + } +} +\n"; +} + +sub make_textpref_reader +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +static void read_textpref(strid_t pref, const char *progname) +{ + unsigned n; + char buffer[1024]; + int prognamelen = n_strlen(progname); + if(!pref) + return; + while(glk_get_line_stream(pref, buffer, sizeof(buffer))) { + char *optname; + char *optval; + long int optnum; + + if(buffer[0] == '#') + continue; + while(buffer[0] == '[') { + if(n_strncasecmp(buffer+1, progname, prognamelen) != 0 + || buffer[1+prognamelen] != ']') { + while(glk_get_line_stream(pref, buffer, sizeof(buffer))) + if(buffer[0] == '[') + break; + } else { + glk_get_line_stream(pref, buffer, sizeof(buffer)); + } + } + + optname = buffer; + while(isspace(*optname)) + optname++; + if((optval = n_strchr(optname, '=')) != NULL) { + char *p; + *optval = 0; + optval++; + + if((p = n_strchr(optname, ' ')) != NULL) + *p = 0; + + while(isspace(*optval)) + optval++; + + while(isspace(optval[strlen(optval)-1])) + optval[strlen(optval)-1] = 0; + + optnum = n_strtol(optval, NULL, 0); + if(n_strcasecmp(optval, \"false\") == 0 + || n_strcasecmp(optval, \"f\") == 0) + optnum = FALSE; + if(n_strcasecmp(optval, \"true\") == 0 + || n_strcasecmp(optval, \"t\") == 0) + optnum = TRUE; + + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(n_strcmp(options[n].longname, optname) == 0) { + switch(options[n].type) { + case option_flag: + case option_number: + options[n].int_func(optnum); + break; + case option_file: + options[n].str_func(startup_open(optval)); + break; + case option_wfile: + options[n].str_func(startup_wopen(optval)); + break; + case option_string: + options[n].string_func(optval); + break; + } + break; + } + } + } + } + glk_stream_close(pref, NULL); +} +\n"; +} + + +sub make_help_printer +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +static void show_help(void) +{ + unsigned n; + printf(\"Usage: $appname [OPTIONS] gamefile\\n\"); + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(options[n].shortname != '-') + printf(\" -%c, \", options[n].shortname); + else + printf(\" \"); + printf(\"-%-15s %s\\n\", options[n].longname, options[n].description); + } +} +\n"; +} + +sub make_command_parser +{ + print "#line " . (__LINE__+1) . ' "' . __FILE__ . '"' . " +static BOOL parse_commands(int argc, char **argv) +{ + int i; + unsigned n; + + for(i = 1; i < argc; i++) { + BOOL flag = TRUE; + + const char *p = argv[i]; + + if(p[0] == '-') { + BOOL found = FALSE; + + while(*p == '-') + p++; + if(n_strncmp(p, \"no-\", 3) == 0) { + flag = FALSE; + p+=3; + } + + if(n_strcasecmp(p, \"help\") == 0) { + show_help(); + exit(0); + } + if(n_strcasecmp(p, \"version\") == 0) { + printf(\"$appname version %d.%d\\n\", $appmajor, $appminor); + exit(0); + } + + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if((n_strlen(p) == 1 && *p == options[n].shortname) || + n_strcmp(options[n].longname, p) == 0) { + found = TRUE; + + switch(options[n].type) { + case option_flag: + options[n].int_func(flag); + break; + + case option_file: + i++; + options[n].str_func(startup_open(argv[i])); + break; + + case option_wfile: + i++; + options[n].str_func(startup_wopen(argv[i])); + break; + + case option_number: + i++; + options[n].int_func(n_strtol(argv[i], NULL, 0)); + break; + + case option_string: + i++; + options[n].string_func(argv[i]); + break; + } + } + } + + if(!found) + return FALSE; + + } else { + strid_t s = startup_open(argv[i]); + if(!s) + return FALSE; + if(!game_use_file(s)) + return FALSE; + } + } + + return TRUE; +} +\n"; +} + + + + +sub make_useless_command_structure +{ + print "glkunix_argumentlist_t glkunix_arguments[] = {\n"; + + print " { (char *) \"\", glkunix_arg_ValueCanFollow, (char *) \"filename\tfile to load\" },\n"; + + print " { (char *) \"-help\", glkunix_arg_NoValue, (char *) \"list command-line options\" },\n"; + print " { (char *) \"--help\", glkunix_arg_NoValue, (char *) \"list command-line options\" },\n"; + print " { (char *) \"-version\", glkunix_arg_NoValue, (char *) \"get version number\" },\n"; + print " { (char *) \"--version\", glkunix_arg_NoValue, (char *) \"get version number\" },\n"; + + + foreach my $soption (@LoL) { + my @option = @{ $soption }; + if($option[4] eq "flag") { + $argtype = "glkunix_arg_NoValue"; + } + if($option[4] eq "file" || $option[4] eq "wfile") { + $argtype = "glkunix_arg_ValueFollows"; + } + if($option[4] eq "number") { + $argtype = "glkunix_arg_NumberValue"; + } + if($option[4] eq "string") { + $argtype = "glkunix_arg_ValueFollows"; + } + + if($option[2] ne "-") { + print " { (char *) \"-$option[2]\", $argtype, (char *) \"-$option[2]\" },\n"; + } + + if($option[4] eq "flag") { + print " { (char *) \"-no-$option[1]\", $argtype, (char *) \"-no-$option[1]\" },\n"; + print " { (char *) \"--no-$option[1]\", $argtype, (char *) \"--no-$option[1]\" },\n"; + } + + print " { (char *) \"-$option[1]\", $argtype, (char *) \"-$option[1]\" },\n"; + print " { (char *) \"--$option[1]\", $argtype, (char *) \"--$option[1]\t$option[3]\" },\n"; + } + + print " { NULL, glkunix_arg_End, NULL }\n"; + print "};\n\n"; + +} + + diff --git a/interpreters/nitfol/options.texi b/interpreters/nitfol/options.texi new file mode 100644 index 0000000..fd8e146 --- /dev/null +++ b/interpreters/nitfol/options.texi @@ -0,0 +1,93 @@ +@item -ignore +@itemx -no-ignore +@itemx -i +Ignore Z-machine strictness errors. Normally nitfol checks for illegal and undefined Z-machine behaviour and alerts the user. If you're playing someone else's buggy game, this can be annoying and you should use this option. + +@item -fullname +@itemx -no-fullname +@itemx -f +For running under Emacs or DDD. Tells nitfol to give machine-recognizeable markers when stack frames are displayed instead of displaying the line. Only useful if you are using nitfol as an inferior debugger. + +@item -command @var{file} +@itemx -x @var{file} +Read commands from this file. Load a script from a file for playback in the game. + +@item -pirate +@itemx -no-pirate +@itemx -P +Aye, matey. Make the piracy opcode not branch, letting the game know the game disc is not ``genuine.'' Infocom never used this opcode and neither should you, but if obscurity amuses you... + +@item -quiet +@itemx -no-quiet +@itemx -q +Do not print introductory messages. For GDB compatibility. + +@item -spell +@itemx -no-spell +Perform spelling correction. Normally Z-machine games are unforgiving of typos (though they do have an @code{oops} command). If you type in a word which isn't in the game's dictionary and have typo correction enabled, nitfol will search for a word which is off by one letter by a substitution, deletion, insertion or transposition. + +@item -expand +@itemx -no-expand +Expand one letter abbreviations. Early Z-machine games don't include @kbd{x} as a synonym for @kbd{examine} and such. If the first word in a sentence is one letter long and not recognized by the game, this will attempt to expand it to a full word. + +@item -symbols @var{file} +@itemx -s @var{file} +Specify symbol file for game. If you want to perform source-level debugging, you will need to specify a symbol file, which contains the names of variables and tells nitfol how source code corresponds to object code. + +@item -tandy +@itemx -no-tandy +@itemx -t +Censors some Infocom games. Some version 3 games perform minor wording changes when this bit is set to appease the sensitivity of Tandy Corporation. + +@item -transcript @var{wfile} +@itemx -T @var{wfile} +Write transcript to this file. This transcript begins as soon as the game starts. + +@item -debug +@itemx -no-debug +@itemx -d +Enter debugger immediatly. Imitate GDB by not automatically starting the story. + +@item -prompt @var{string} +Specify debugging prompt. This is the prompt nitfol prints when it is waiting for a debugging command (as opposed to the > the game story prints when waiting for a game command). DDD requires this to be @samp{(gdb) }. + +@item -path @var{string} +Look for games in this directory. If nitfol cannot find the requested game in the current directory, it looks through the directories listed in the given colon separated string. The directories specified here are also used to search for gamefiles from saved games. If this option is not used, nitfol looks at the @code{INFOCOM_PATH} environment variable. + +@item -autoundo +@itemx -no-autoundo +Ensure @code{@@save_undo} is called every turn. If a turn passes with no @code{@@save_undo} between, this option performs the @code{@@save_undo} automagically. Could cause problems with some games which have a different concept of a turn. + +@item -stacklimit @var{number} +@itemx -S @var{number} +Exit when the stack is this deep. If a game is infinitely recursing, nitfol will allocate large amounts of memory and take a long time before the problem is reported. This option makes it fatal to recurse more than the given number of stack frames. Setting this to 0 makes nitfol allow as many as fit contiguously in memory. The Z-machine Standards Document recommends games use no more than 1024 words of total stack (frames and pushed data) in ZIP, which roughly works out to 90 routine calls deep. + +@item -alias @var{string} +@itemx -a @var{string} +Specify an alias. Adds an alias which will be expanded in read lines before tokenisation. The alias is of the form @var{name} @var{value}; you will need to use quotes around it on the commandline. + +@item -ralias @var{string} +Specify an recursive alias. Adds an alias whose result is checked for further alias expansion. Identical syntax to adding a normal alias. + +@item -unalias @var{string} +Remove an alias. Removes an alias previously added by -alias. Useful for removing aliases in preference files. + +@item -random @var{number} +@itemx -r @var{number} +Set random seed. Normally the random number generator is initialized with the time of day. If this option is used with a non-zero argument, the given number will be used to initialize the generator and for @code{@@random 0}. + +@item -mapsym @var{string} +Specify mapping glyphs. Nitfol draws maps using ASCII characters; you can choose which characters it uses to draw rooms. Defaults to @samp{*udb@@UDB+}, which is, in order: empty room, room with down exit, room with up exit, room with up and down exits, room with player, room with player and up exit, room with player and down exit, room with player and up and down exits, bend symbol. + +@item -mapsize @var{number} +Specify map size. Determines the number of lines to be used for the map. + +@item -maploc @var{string} +Specify map location. Nitfol creates a Glk window for the map it generates. The map can be placed @samp{above}, @samp{below}, to the @samp{right}, or the @samp{left}, of the main game window. Follow this option with one those locations. + +@item -terpnum @var{number} +Specify interpreter number. Each port of Infocom's Z-machine interpreter was given a number. @samp{1} for their own DECSystem-20, @samp{2} for Apple IIe, @samp{3} for Macintosh, @samp{4} for Amiga, @samp{5} for Atari ST, @samp{6} for IBM PC, @samp{7} for Commodore 128, @samp{8} for Commodore 64, @samp{9} for Apple IIc, @samp{10} for Apple IIgs, @samp{11} for Tandy Color. Giving this option makes nitfol claim to be running on the specified system. A few games change their behaviour slightly depending on which machine type they run. By default nitfol claims to be on an Apple IIe, as this makes Beyond Zork not do character graphics. + +@item -terpver @var{string} +Specify interpreter version. Infocom's interpreters were given versions, typically a capital letter. Nitfol defaults to @samp{N}, Frotz uses @samp{F}, and ZIP uses @samp{B}. Any single character version is allowed. Multicharacter options are read as a number instead of an ASCII character. Only known effect upon games is the letter printed by banners and the @samp{version} command. Version 6 games interpret this as a number instead of a letter. + diff --git a/interpreters/nitfol/portfunc.c b/interpreters/nitfol/portfunc.c new file mode 100644 index 0000000..9c5c552 --- /dev/null +++ b/interpreters/nitfol/portfunc.c @@ -0,0 +1,935 @@ +/* Various functions for people who are lacking/have a poor libc + Copyright (C) 1999 Evin Robertson. The last part Copyright (C) FSF + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ + + +#include /* For malloc */ +#include /* For LONG_MAX, LONG_MIN, ... */ +#include /* For isspace, isdigit, isalpha */ +#include "nitfol.h" + +/* Nitfol malloc/realloc wrappers - never return NULL */ +void *n_malloc(int size) +{ + if(size != 0) { + void *m = malloc(size); + if(m != NULL) + return m; + while(free_undo()) { + m = malloc(size); + if(m) + return m; + } + n_show_fatal(E_MEMORY, "not enough memory for malloc", size); + } else { + n_show_fatal(E_MEMORY, "malloc(0)", size); + } + + glk_exit(); + return NULL; +} + +void *n_calloc(int nmemb, int size) +{ + int totalsize = nmemb * size; + void *m = n_malloc(totalsize); + n_memset(m, 0, totalsize); + return m; +} + +void *n_realloc(void *ptr, int size) +{ + void *m; + if(size == 0) { + n_free(ptr); + return NULL; + } + m = realloc(ptr, size); + if(m != NULL || size == 0) + return m; + while(free_undo()) { + m = realloc(ptr, size); + if(m) + return m; + } + n_free(ptr); + + glk_exit(); + return NULL; +} + +void n_free(void *ptr) +{ + free(ptr); +} + +typedef struct rmmalloc_entry rmmalloc_entry; + +struct rmmalloc_entry { + rmmalloc_entry *next; + void *data; +}; + +static rmmalloc_entry *rmmalloc_list = NULL; + +/* This malloc maintains a list of malloced data, which can all be freed at + once */ +void *n_rmmalloc(int size) +{ + rmmalloc_entry newentry; + newentry.data = n_malloc(size); + LEadd(rmmalloc_list, newentry); + return newentry.data; +} + +void n_rmfree(void) +{ + rmmalloc_entry *p; + for(p=rmmalloc_list; p; p=p->next) + n_free(p->data); + LEdestroy(rmmalloc_list); +} + +void n_rmfreeone(void *m) +{ + rmmalloc_entry *p, *t; + LEsearchremove(rmmalloc_list, p, t, p->data == m, n_free(p->data)); +} + + +/* Returns true if target is a null-terminated string identical to the + first len characters of starting */ +BOOL n_strmatch(const char *target, const char *starting, unsigned len) +{ + if(target && + n_strlen(target) == len && + n_strncasecmp(target, starting, len) == 0) + return TRUE; + return FALSE; +} + +/* Write 'n' in decimal to 'dest' - assume there is enough space in buffer */ +int n_to_decimal(char *buffer, unsigned n) +{ + int i = 0; + if(n == 0) { + buffer[0] = '0'; + return 1; + } + while(n) { + unsigned c = n % 10; + buffer[i++] = '0' + c; + n = (n - c) / 10; + if(i >= 12) + return i; + } + return i; +} + +const char *n_static_number(const char *preface, glui32 n) +{ + static char *buffer = NULL; + char number[12]; + int preflen = n_strlen(preface); + int numlen; + int i; + + buffer = (char *) n_realloc(buffer, preflen + 12 + 2); + n_strcpy(buffer, preface); + numlen = n_to_decimal(number, n); + for(i = 0; i < numlen; i++) + buffer[preflen + i] = number[numlen - i - 1]; + buffer[preflen + i] = 0; + return buffer; +} + +/* n_strdup(NULL) works, unlike strdup(NULL) which segfaults */ +char *n_strdup(const char *s) +{ + char *n; + if(s == NULL) + return NULL; + n = (char *) n_malloc(n_strlen(s) + 1); + n_strcpy(n, s); + return n; +} + +/* Swap n bytes between a and b */ +void n_memswap(void *a, void *b, int n) +{ + int i; + unsigned char *c = (unsigned char *) a; + unsigned char *d = (unsigned char *) b; + unsigned char t; + + for(i = 0; i < n; i++) { + t = d[i]; + d[i] = c[i]; + c[i] = t; + } +} + +/* Wrappers to hide ugliness of Glk file opening functions */ +strid_t n_file_prompt(glui32 usage, glui32 fmode) +{ + frefid_t r = glk_fileref_create_by_prompt(usage, fmode, 0); + if(r) { + strid_t s; + if((fmode & filemode_Read) && !glk_fileref_does_file_exist(r)) + return NULL; + s = glk_stream_open_file(r, fmode, 0); + glk_fileref_destroy(r); + return s; + } + return NULL; +} + +strid_t n_file_name(glui32 usage, glui32 fmode, const char *name) +{ + frefid_t r = glk_fileref_create_by_name(usage, (char *) name, 0); + if(r) { + strid_t s; + if((fmode & filemode_Read) && !glk_fileref_does_file_exist(r)) + return NULL; + s = glk_stream_open_file(r, fmode, 0); + glk_fileref_destroy(r); + return s; + } + return NULL; +} + +/* If given name is more than whitespace, open by name; else by prompt */ +strid_t n_file_name_or_prompt(glui32 usage, glui32 fmode, const char *name) +{ + const char *c; + for(c = name; *c; c++) { + if(*c != ' ') + return n_file_name(usage, fmode, name); + } + return n_file_prompt(usage, fmode); +} + + + +/* Trivial wrappers to fix implicit (const char *) to (char *) cast warnings */ +void w_glk_put_string(const char *s) +{ + glk_put_string((char *) s); +} + +void w_glk_put_string_stream(strid_t str, const char *s) +{ + glk_put_string_stream(str, (char *) s); +} + +void w_glk_put_buffer(const char *buf, glui32 len) +{ + glk_put_buffer((char *) buf, len); +} + +void w_glk_put_buffer_stream(strid_t str, const char *buf, glui32 len) +{ + glk_put_buffer_stream(str, (char *) buf, len); +} + +void w_glk_put_char(int ch) +{ + glk_put_char(ch); +} + + + +/* The rest of the functions in this conform to ANSI/BSD/POSIX/whatever and + can be replaced with standard functions with appropriate #defines + They are included here only for systems lacking a proper libc. No effort + has been made to tune their speeds. */ + +#ifdef HEADER + +#ifndef NULL +#define NULL 0 +#endif + +/* FIXME: use autoconf for these someday */ + +#ifndef NO_LIBC +/* ISO 9899 functions */ +#include +#define n_strlen(s) strlen(s) +#define n_strcpy(d, s) strcpy(d, s) +#define n_strncpy(d, s, n) strncpy(d, s, n) +#define n_memcpy(d, s, n) memcpy(d, s, n) +#define n_memmove(d, s, n) memmove(d, s, n) +#define n_memset(s, c, n) memset(s, c, n) +#define n_strcmp(a, b) strcmp(a, b) +#define n_strncmp(a, b, n) strncmp(a, b, n) +#define n_memcmp(a, b, n) memcmp(a, b, n) +#define n_strchr(a, c) strchr(a, c) +#define n_strcat(d, s) strcat(d, s) +#define n_strpbrk(s, a) strpbrk(s, a) +#define n_strspn(s, a) strspn(s, a) +#define n_strtok(s, d) strtok(s, d) +#define n_strstr(h, n) strstr(h, n) +#define n_strtol(n, e, b) strtol(n, e, b) +#define n_strrchr(s, c) strrchr(s, c) + +#include +#define n_qsort(b, n, s, c) qsort(b, n, s, c) +#define n_bsearch(k, b, n, s, c) bsearch(k, b, n, s, c) +#endif + +#if defined(__USE_BSD) || defined(__USE_GNU) +#define n_strcasecmp(a, b) strcasecmp(a, b) +#define n_strncasecmp(a, b, n) strncasecmp(a, b, n) +#endif + +#ifdef __USE_GNU +#define n_lfind(k, b, n, s, c) lfind(k, b, n, s, c) +#endif + +#endif /* HEADER */ + + +#ifndef n_strlen +unsigned n_strlen(const char *s) +{ + int i = 0; + while(*s++) + i++; + return i; +} +#endif + +#ifndef n_strcpy +char *n_strcpy(char *dest, const char *src) +{ + while(*src) { + *dest++ = *src++; + } + *dest = 0; + return dest; +} +#endif + +#ifndef n_strncpy +char *n_strncpy(char *dest, const char *src, int len) +{ + while(*src && len) { + *dest++ = *src++; + len--; + } + while(len--) + *dest++ = 0; + return dest; +} +#endif + +#ifndef n_memcpy +void *n_memcpy(void *dest, const void *src, int n) +{ + int i; + unsigned char *a = (unsigned char *) dest; + unsigned const char *b = (const unsigned char *) src; + for(i = 0; i < n; i++) + a[i] = b[i]; + return dest; +} +#endif + +#ifndef n_memmove +void *n_memmove(void *dest, const void *src, int n) +{ + int i; + unsigned char *a = (unsigned char *) dest; + unsigned char *b = (unsigned char *) src; + if(a < b) + for(i = 0; i < n; i++) + a[i] = b[i]; + else + for(i = n-1; i >= 0; i--) + a[i] = b[i]; + return a; +} +#endif + +#ifndef n_memset +void *n_memset(void *s, int c, int n) +{ + int i; + unsigned char *a = (unsigned char *) s; + for(i = 0; i < n; i++) + a[i] = c; + return s; +} +#endif + +#ifndef n_strcmp +int n_strcmp(const char *a, const char *b) +{ + for(;;) { + if(*a != *b) + return *a - *b; + if(*a == 0) + break; + a++; b++; + } + return 0; +} +#endif + +#ifndef n_strncmp +int n_strncmp(const char *a, const char *b, int n) +{ + for(; n; n--) { + if(*a != *b) + return *a - *b; + if(*a == 0) + break; + a++; b++; + } + return 0; +} +#endif + +#ifndef n_memcmp +int n_memcmp(const void *s1, const void *s2, int n) +{ + const unsigned char *a = (unsigned char *) s1; + const unsigned char *b = (unsigned char *) s2; + for(; n; n--) { + if(*a != *b) + return *a - *b; + a++; b++; + } + return 0; +} +#endif + +#ifndef n_strcasecmp +int n_strcasecmp(const char *a, const char *b) +{ + for(;;) + { + if(*a != *b) { + char c1 = glk_char_to_lower(*a); + char c2 = glk_char_to_lower(*b); + if(c1 != c2) + return c1 - c2; + } + if(*a == 0) + break; + a++; b++; + } + return 0; +} +#endif + +#ifndef n_strncasecmp +int n_strncasecmp(const char *a, const char *b, int n) +{ + for(; n; n--) + { + if(*a != *b) { + char c1 = glk_char_to_lower(*a); + char c2 = glk_char_to_lower(*b); + if(c1 != c2) + return c1 - c2; + } + if(*a == 0) + break; + a++; b++; + } + return 0; +} +#endif + +#ifndef n_strlower +char *n_strlower(char *s) +{ + char *b = s; + while(*b) { + *b = glk_char_to_lower(*b); + b++; + } + return s; +} +#endif + +#ifndef n_strupper +char *n_strupper(char *s) +{ + char *b = s; + while(*b) { + *b = glk_char_to_upper(*b); + b++; + } + return s; +} +#endif + +#ifndef n_strchr +char *n_strchr(const char *s, int c) +{ + const unsigned char *a = (const unsigned char *) s; + while(*a != c) { + if(*a == 0) + return NULL; + a++; + } + return (char *) a; +} +#endif + +#ifndef n_lfind +void *n_lfind(const void *key, const void *base, int *nmemb, int size, + int (*compar)(const void *, const void *)) +{ + int i; + char *t = (char *) base; + for(i = 0; i < *nmemb; i++) { + if((*compar)(t, key) == 0) + return (void *) t; + t += size; + } + return NULL; +} +#endif + + +#ifndef n_qsort + +/* Modified by Evin Robertson for nitfolness Aug 4 1999 */ + +/******************************************************************/ +/* qsort.c -- Non-Recursive ANSI Quicksort function */ +/* */ +/* Public domain by Raymond Gardner, Englewood CO February 1991 */ +/* */ +/* Usage: */ +/* qsort(base, nbr_elements, width_bytes, compare_function); */ +/* void *base; */ +/* size_t nbr_elements, width_bytes; */ +/* int (*compare_function)(const void *, const void *); */ +/* */ +/* Sorts an array starting at base, of length nbr_elements, each */ +/* element of size width_bytes, ordered via compare_function, */ +/* which is called as (*compare_function)(ptr_to_element1, */ +/* ptr_to_element2) and returns < 0 if element1 < element2, */ +/* 0 if element1 = element2, > 0 if element1 > element2. */ +/* Most refinements are due to R. Sedgewick. See "Implementing */ +/* Quicksort Programs", Comm. ACM, Oct. 1978, and Corrigendum, */ +/* Comm. ACM, June 1979. */ +/******************************************************************/ + +/* prototypes */ +static void swap_chars(char *, char *, int); + +#define SWAP(a, b) (swap_chars((char *)(a), (char *)(b), size)) + + +#define COMP(a, b) ((*comp)((void *)(a), (void *)(b))) + +#define T 7 /* subfiles of T or fewer elements will */ + /* be sorted by a simple insertion sort */ + /* Note! T must be at least 3 */ + +void n_qsort(void *basep, int nelems, int size, + int (*comp)(const void *, const void *)) +{ + char *stack[40], **sp; /* stack and stack pointer */ + char *i, *j, *limit; /* scan and limit pointers */ + int thresh; /* size of T elements in bytes */ + char *base; /* base pointer as char * */ + + base = (char *)basep; /* set up char * base pointer */ + thresh = T * size; /* init threshold */ + sp = stack; /* init stack pointer */ + limit = base + nelems * size;/* pointer past end of array */ + for ( ;; ) { /* repeat until break... */ + if ( limit - base > thresh ) { /* if more than T elements */ + /* swap base with middle */ + SWAP((((limit-base)/size)/2)*size+base, base); + i = base + size; /* i scans left to right */ + j = limit - size; /* j scans right to left */ + if ( COMP(i, j) > 0 ) /* Sedgewick's */ + SWAP(i, j); /* three-element sort */ + if ( COMP(base, j) > 0 ) /* sets things up */ + SWAP(base, j); /* so that */ + if ( COMP(i, base) > 0 ) /* *i <= *base <= *j */ + SWAP(i, base); /* *base is pivot element */ + for ( ;; ) { /* loop until break */ + do /* move i right */ + i += size; /* until *i >= pivot */ + while ( COMP(i, base) < 0 ); + do /* move j left */ + j -= size; /* until *j <= pivot */ + while ( COMP(j, base) > 0 ); + if ( i > j ) /* if pointers crossed */ + break; /* break loop */ + SWAP(i, j); /* else swap elements, keep scanning*/ + } + SWAP(base, j); /* move pivot into correct place */ + if ( j - base > limit - i ) { /* if left subfile larger */ + sp[0] = base; /* stack left subfile base */ + sp[1] = j; /* and limit */ + base = i; /* sort the right subfile */ + } else { /* else right subfile larger*/ + sp[0] = i; /* stack right subfile base */ + sp[1] = limit; /* and limit */ + limit = j; /* sort the left subfile */ + } + sp += 2; /* increment stack pointer */ + } else { /* else subfile is small, use insertion sort */ + for ( j = base, i = j+size; i < limit; j = i, i += size ) + for ( ; COMP(j, j+size) > 0; j -= size ) { + SWAP(j, j+size); + if ( j == base ) + break; + } + if ( sp != stack ) { /* if any entries on stack */ + sp -= 2; /* pop the base and limit */ + base = sp[0]; + limit = sp[1]; + } else /* else stack empty, done */ + break; + } + } +} + +/* +** swap nbytes between a and b +*/ + +static void swap_chars(char *a, char *b, int nbytes) +{ + char tmp; + do { + tmp = *a; *a++ = *b; *b++ = tmp; + } while ( --nbytes ); +} + +#endif + + + + +/* These last several were adapted from glibc, GNU LGPLed, copyright FSF */ +/* For nitfol, these are licensed under the GPL per section 3 of the LGPL */ + +#ifndef n_strcat +char *n_strcat(char *dest, const char *src) +{ + char *s1 = dest; + const char *s2 = src; + char c; + + /* Find the end of the string. */ + do + c = *s1++; + while (c != '\0'); + + /* Make S1 point before the next character, so we can increment + it while memory is read (wins on pipelined cpus). */ + s1 -= 2; + + do + { + c = *s2++; + *++s1 = c; + } + while (c != '\0'); + + return dest; +} +#endif + +#ifndef n_strpbrk +char *n_strpbrk(const char *s, const char *accept) +{ + while (*s != '\0') + if (n_strchr(accept, *s) == NULL) + ++s; + else + return (char *) s; + + return NULL; +} +#endif + +#ifndef n_strspn +int n_strspn(const char *s, const char *accept) +{ + const char *p; + const char *a; + int count = 0; + + for (p = s; *p != '\0'; ++p) + { + for (a = accept; *a != '\0'; ++a) + if (*p == *a) + break; + if (*a == '\0') + return count; + else + ++count; + } + + return count; +} +#endif + +#ifndef n_strtok +char *n_strtok(char *s, const char *delim) +{ + static char *olds = NULL; + char *token; + + if (s == NULL) + { + if (olds == NULL) + { + /* errno = EINVAL; */ + return NULL; + } + else + s = olds; + } + + /* Scan leading delimiters. */ + s += n_strspn(s, delim); + if (*s == '\0') + { + olds = NULL; + return NULL; + } + + /* Find the end of the token. */ + token = s; + s = n_strpbrk(token, delim); + if (s == NULL) + /* This token finishes the string. */ + olds = NULL; + else + { + /* Terminate the token and make OLDS point past it. */ + *s = '\0'; + olds = s + 1; + } + return token; +} +#endif + +#ifndef n_strstr +char *n_strstr(const char *haystack, const char *needle) +{ + const char *needle_end = n_strchr(needle, '\0'); + const char *haystack_end = n_strchr(haystack, '\0'); + const unsigned needle_len = needle_end - needle; + const unsigned needle_last = needle_len - 1; + const char *begin; + + if (needle_len == 0) + return (char *) haystack; /* ANSI 4.11.5.7, line 25. */ + if ((size_t) (haystack_end - haystack) < needle_len) + return NULL; + + for (begin = &haystack[needle_last]; begin < haystack_end; ++begin) + { + const char *n = &needle[needle_last]; + const char *h = begin; + + do + if (*h != *n) + goto loop; /* continue for loop */ + while (--n >= needle && --h >= haystack); + + return (char *) h; + + loop:; + } + + return NULL; +} +#endif + +#ifndef n_strtol +long int n_strtol (const char *nptr, char **endptr, int base) +{ + int negative; + unsigned long int cutoff; + unsigned int cutlim; + unsigned long int i; + const char *s; + unsigned char c; + const char *save; + int overflow; + + if (base < 0 || base == 1 || base > 36) + base = 10; + + s = nptr; + + /* Skip white space. */ + while (isspace (*s)) + ++s; + if (*s == '\0') + goto noconv; + + /* Check for a sign. */ + if (*s == '-') + { + negative = 1; + ++s; + } + else if (*s == '+') + { + negative = 0; + ++s; + } + else + negative = 0; + + if (base == 16 && s[0] == '0' && glk_char_to_upper (s[1]) == 'X') + s += 2; + + /* If BASE is zero, figure it out ourselves. */ + if (base == 0) + if (*s == '0') + { + if (glk_char_to_upper (s[1]) == 'X') + { + s += 2; + base = 16; + } + else + base = 8; + } + else + base = 10; + + /* Save the pointer so we can check later if anything happened. */ + save = s; + + cutoff = ULONG_MAX / (unsigned long int) base; + cutlim = ULONG_MAX % (unsigned long int) base; + + overflow = 0; + i = 0; + for (c = *s; c != '\0'; c = *++s) + { + if (isdigit (c)) + c -= '0'; + else if (isalpha (c)) + c = glk_char_to_upper (c) - 'A' + 10; + else + break; + if (c >= base) + break; + /* Check for overflow. */ + if (i > cutoff || (i == cutoff && c > cutlim)) + overflow = 1; + else + { + i *= (unsigned long int) base; + i += c; + } + } + + /* Check if anything actually happened. */ + if (s == save) + goto noconv; + + /* Store in ENDPTR the address of one character + past the last character we converted. */ + if (endptr != NULL) + *endptr = (char *) s; + + /* Check for a value that is within the range of + `unsigned long int', but outside the range of `long int'. */ + if (i > (negative ? + -(unsigned long int) LONG_MIN : (unsigned long int) LONG_MAX)) + overflow = 1; + + if (overflow) + { + /* errno = ERANGE; */ + return negative ? LONG_MIN : LONG_MAX; + } + + /* Return the result of the appropriate sign. */ + return (negative ? -i : i); + +noconv: + /* There was no number to convert. */ + if (endptr != NULL) + *endptr = (char *) nptr; + return 0L; +} +#endif + +#ifndef n_strrchr +char *n_strrchr(const char *s, int c) +{ + const char *found, *p; + + c = (unsigned char) c; + + /* Since strchr is fast, we use it rather than the obvious loop. */ + + if (c == '\0') + return n_strchr(s, '\0'); + + found = NULL; + while ((p = n_strchr(s, c)) != NULL) + { + found = p; + s = p + 1; + } + + return (char *) found; +} +#endif + +#ifndef n_bsearch +void *n_bsearch(const void *key, const void *base, int nmemb, int size, + int (*compar)(const void *, const void *)) +{ + int l, u, idx; + void *p; + int comparison; + + l = 0; + u = nmemb; + while (l < u) + { + idx = (l + u) / 2; + p = (void *) (((const char *) base) + (idx * size)); + comparison = (*compar)(key, p); + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + return (void *) p; + } + + return NULL; +} +#endif + diff --git a/interpreters/nitfol/portfunc.h b/interpreters/nitfol/portfunc.h new file mode 100644 index 0000000..d3a9711 --- /dev/null +++ b/interpreters/nitfol/portfunc.h @@ -0,0 +1,193 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i portfunc.c' */ +#ifndef CFH_PORTFUNC_H +#define CFH_PORTFUNC_H + +/* From `portfunc.c': */ +void * n_malloc (int size ); +void * n_calloc (int nmemb , int size ); +void * n_realloc (void *ptr , int size ); +void n_free (void *ptr ); +void * n_rmmalloc (int size ); +void n_rmfree (void); +void n_rmfreeone (void *m ); +BOOL n_strmatch (const char *target , const char *starting , unsigned len ); +int n_to_decimal (char *buffer , unsigned n ); +const char * n_static_number (const char *preface , glui32 n ); +char * n_strdup (const char *s ); +void n_memswap (void *a , void *b , int n ); +strid_t n_file_prompt (glui32 usage , glui32 fmode ); +strid_t n_file_name (glui32 usage , glui32 fmode , const char *name ); +strid_t n_file_name_or_prompt (glui32 usage , glui32 fmode , const char *name ); +void w_glk_put_string (const char *s ); +void w_glk_put_string_stream (strid_t str , const char *s ); +void w_glk_put_buffer (const char *buf , glui32 len ); +void w_glk_put_buffer_stream (strid_t str , const char *buf , glui32 len ); +void w_glk_put_char (int ch ); + +#ifndef NULL +#define NULL 0 + +#endif + +#ifndef NO_LIBC +#include +#define n_strlen(s) strlen(s) +#define n_strcpy(d, s) strcpy(d, s) +#define n_strncpy(d, s, n) strncpy(d, s, n) +#define n_memcpy(d, s, n) memcpy(d, s, n) +#define n_memmove(d, s, n) memmove(d, s, n) +#define n_memset(s, c, n) memset(s, c, n) +#define n_strcmp(a, b) strcmp(a, b) +#define n_strncmp(a, b, n) strncmp(a, b, n) +#define n_memcmp(a, b, n) memcmp(a, b, n) +#define n_strchr(a, c) strchr(a, c) +#define n_strcat(d, s) strcat(d, s) +#define n_strpbrk(s, a) strpbrk(s, a) +#define n_strspn(s, a) strspn(s, a) +#define n_strtok(s, d) strtok(s, d) +#define n_strstr(h, n) strstr(h, n) +#define n_strtol(n, e, b) strtol(n, e, b) +#define n_strrchr(s, c) strrchr(s, c) +#include +#define n_qsort(b, n, s, c) qsort(b, n, s, c) +#define n_bsearch(k, b, n, s, c) bsearch(k, b, n, s, c) + +#endif + +#if defined(__USE_BSD) || defined(__USE_GNU) +#define n_strcasecmp(a, b) strcasecmp(a, b) +#define n_strncasecmp(a, b, n) strncasecmp(a, b, n) + +#endif + +#ifdef __USE_GNU +#define n_lfind(k, b, n, s, c) lfind(k, b, n, s, c) + +#endif + +#ifndef n_strlen +unsigned n_strlen (const char *s ); + +#endif + +#ifndef n_strcpy +char * n_strcpy (char *dest , const char *src ); + +#endif + +#ifndef n_strncpy +char * n_strncpy (char *dest , const char *src , int len ); + +#endif + +#ifndef n_memcpy +void * n_memcpy (void *dest , const void *src , int n ); + +#endif + +#ifndef n_memmove +void * n_memmove (void *dest , const void *src , int n ); + +#endif + +#ifndef n_memset +void * n_memset (void *s , int c , int n ); + +#endif + +#ifndef n_strcmp +int n_strcmp (const char *a , const char *b ); + +#endif + +#ifndef n_strncmp +int n_strncmp (const char *a , const char *b , int n ); + +#endif + +#ifndef n_memcmp +int n_memcmp (const void *s1 , const void *s2 , int n ); + +#endif + +#ifndef n_strcasecmp +int n_strcasecmp (const char *a , const char *b ); + +#endif + +#ifndef n_strncasecmp +int n_strncasecmp (const char *a , const char *b , int n ); + +#endif + +#ifndef n_strlower +char * n_strlower (char *s ); + +#endif + +#ifndef n_strupper +char * n_strupper (char *s ); + +#endif + +#ifndef n_strchr +char * n_strchr (const char *s , int c ); + +#endif + +#ifndef n_lfind +void * n_lfind (const void *key , const void *base , int *nmemb , int size , int ( *compar ) ( const void * , const void * ) ); + +#endif + +#ifndef n_qsort +void n_qsort (void *basep , int nelems , int size , int ( *comp ) ( const void * , const void * ) ); + +#endif + +#ifndef n_strcat +char * n_strcat (char *dest , const char *src ); + +#endif + +#ifndef n_strpbrk +char * n_strpbrk (const char *s , const char *accept ); + +#endif + +#ifndef n_strspn +int n_strspn (const char *s , const char *accept ); + +#endif + +#ifndef n_strtok +char * n_strtok (char *s , const char *delim ); + +#endif + +#ifndef n_strstr +char * n_strstr (const char *haystack , const char *needle ); + +#endif + +#ifndef n_strtol +long int n_strtol (const char *nptr , char **endptr , int base ); + +#endif + +#ifndef n_strrchr +char * n_strrchr (const char *s , int c ); + +#endif + +#ifndef n_bsearch +void * n_bsearch (const void *key , const void *base , int nmemb , int size , int ( *compar ) ( const void * , const void * ) ); + +#endif + +#endif /* CFH_PORTFUNC_H */ diff --git a/interpreters/nitfol/quetzal.c b/interpreters/nitfol/quetzal.c new file mode 100644 index 0000000..0ddbf36 --- /dev/null +++ b/interpreters/nitfol/quetzal.c @@ -0,0 +1,357 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +/* Note that quetzal stack save/restore is handled at the bottom of stack.c */ + + +/* Sets *diff to a malloced quetzal diff of the first length bytes of a and b + * *diff_length is set to the length of *diff. Returns successfulness. */ +BOOL quetzal_diff(const zbyte *a, const zbyte *b, glui32 length, + zbyte **diff, glui32 *diff_length, BOOL do_utf8) +{ + /* Worst case: every other byte is the same as in the original, so we have + to store 1.5 times the original length. Allocate a couple bytes extra + to be on the safe side. (yes, I realize it could actually be twice the + original length if you use a really bad algorithm, but I don't) */ + zbyte *attempt = (zbyte *) n_malloc((length * 3) / 2 + 2); + + glui32 attempt_len = 0; + glui32 same_len; + + *diff = NULL; + + while(length) { + /* Search through consecutive identical bytes */ + for(same_len = 0; same_len < length && a[same_len] == b[same_len]; same_len++) + ; + a += same_len; b += same_len; length -= same_len; + + if(length) { + /* If we hit the end of the region, we don't have to record that the + bytes at the end are the same */ + while(same_len) { + attempt[attempt_len++] = 0; + same_len--; /* We always store length-1 */ + if(do_utf8) { + if(same_len <= 0x7f) { + attempt[attempt_len++] = same_len; + same_len = 0; + } else { + if(same_len <= 0x7fff) { + attempt[attempt_len++] = (same_len & 0x7f) | 0x80; + attempt[attempt_len++] = (same_len & 0x7f80) >> 7; + same_len = 0; + } else { + attempt[attempt_len++] = (0x7fff & 0x7f) | 0x80; + attempt[attempt_len++] = (0x7fff & 0x7f80) >> 7; + same_len -= 0x7fff; + } + } + } else { + if(same_len <= 0xff) { + attempt[attempt_len++] = same_len; + same_len = 0; + } else { + attempt[attempt_len++] = 0xff; + same_len -= 0xff; + } + } + } + + attempt[attempt_len++] = *a++ ^ *b++; + length--; + } + } + + *diff = (zbyte *) n_realloc(attempt, attempt_len); + *diff_length = attempt_len; + return TRUE; +} + +/* Applies a quetzal diff to dest */ +BOOL quetzal_undiff(zbyte *dest, glui32 length, + const zbyte *diff, glui32 diff_length, BOOL do_utf8) +{ + glui32 iz = 0; + glui32 id; + + for(id = 0; id < diff_length; id++) { + if(diff[id] == 0) { + unsigned runlen; + if(++id >= diff_length) + return FALSE; /* Incomplete run */ + runlen = diff[id]; + if(do_utf8 && diff[id] & 0x80) { + if(++id >= diff_length) + return FALSE; /* Incomplete extended run */ + runlen = (runlen & 0x7f) | (((unsigned) diff[id]) << 7); + } + iz += runlen + 1; + } else { + dest[iz] ^= diff[id]; + iz++; + } + if(iz >= length) + return FALSE; /* Too long */ + } + return TRUE; +} + + +static unsigned qifhd[] = { 2, 6, 2, 3, 0 }; +enum qifhdnames { qrelnum, qsernum, qchecksum = qsernum + 6, qinitPC }; + +BOOL savequetzal(strid_t stream) +{ + unsigned n; + glui32 start_loc, last_loc; + glui32 qs[13]; + glui32 hdrsize, memsize, stksize, padding, chunksize, intdsize; + zbyte *original = (zbyte *) n_malloc(dynamic_size); + zbyte *diff = NULL; + + glk_stream_set_position(current_zfile, zfile_offset, seekmode_Start); + glk_get_buffer_stream(current_zfile, (char *) original, dynamic_size); + + if(!quetzal_diff(original, z_memory, dynamic_size, &diff, &memsize, FALSE) + || memsize >= dynamic_size) { /* If we're losing try uncompressed */ + if(diff) + free(diff); + diff = NULL; + memsize = dynamic_size; + } + + hdrsize = 13; + stksize = get_quetzal_stack_size(); + intdsize = intd_get_size(); + padding = 8 + (hdrsize & 1) + + 8 + (memsize & 1) + + 8 + (stksize & 1); + if(intdsize) + padding += 8 + (intdsize & 1); + chunksize = 4 + hdrsize + memsize + stksize + intdsize + padding; + + + iffputchunk(stream, "FORM", chunksize); + start_loc = glk_stream_get_position(stream); + + w_glk_put_buffer_stream(stream, "IFZS", 4); + + iffputchunk(stream, "IFhd", hdrsize); + last_loc = glk_stream_get_position(stream); + qs[qrelnum] = LOWORD(HD_RELNUM); + for(n = 0; n < 6; n++) + qs[qsernum + n] = LOBYTE(HD_SERNUM + n); + qs[qchecksum] = LOWORD(HD_CHECKSUM); + qs[qinitPC] = PC; + emptystruct(stream, qifhd, qs); + + if(glk_stream_get_position(stream) - last_loc != hdrsize) { + n_show_error(E_SAVE, "header size miscalculation", glk_stream_get_position(stream) - last_loc); + return FALSE; + } + + if(intdsize) { + iffputchunk(stream, "IntD", intdsize); + + last_loc = glk_stream_get_position(stream); + intd_filehandle_make(stream); + + if(glk_stream_get_position(stream) - last_loc != intdsize) { + n_show_error(E_SAVE, "IntD size miscalculation", glk_stream_get_position(stream) - last_loc); + return FALSE; + } + } + + + if(diff) { + iffputchunk(stream, "CMem", memsize); + last_loc = glk_stream_get_position(stream); + w_glk_put_buffer_stream(stream, (char *) diff, memsize); + } else { + iffputchunk(stream, "UMem", memsize); + last_loc = glk_stream_get_position(stream); + w_glk_put_buffer_stream(stream, (char *) z_memory, dynamic_size); + } + + if(glk_stream_get_position(stream) - last_loc != memsize) { + n_show_error(E_SAVE, "memory size miscalculation", glk_stream_get_position(stream) - last_loc); + return FALSE; + } + + iffputchunk(stream, "Stks", stksize); + last_loc = glk_stream_get_position(stream); + quetzal_stack_save(stream); + + if(glk_stream_get_position(stream) - last_loc != stksize) { + n_show_error(E_SAVE, "stack miscalculation", glk_stream_get_position(stream) - last_loc); + return FALSE; + } + + if(glk_stream_get_position(stream) - start_loc != chunksize) { + n_show_error(E_SAVE, "chunks size miscalculation", glk_stream_get_position(stream) - last_loc); + return FALSE; + } + + return TRUE; +} + + +BOOL restorequetzal(strid_t stream) +{ + char desttype[4]; + glui32 chunksize; + glui32 start; + + if(!ifffindchunk(stream, "FORM", &chunksize, 0)) { + n_show_error(E_SAVE, "no FORM chunk", 0); + return FALSE; + } + + glk_get_buffer_stream(stream, desttype, 4); + if(n_strncmp(desttype, "IFZS", 4) != 0) { + n_show_error(E_SAVE, "FORM chunk not IFZS; this isn't a quetzal file", 0); + return FALSE; + } + + start = glk_stream_get_position(stream); + + if(!ifffindchunk(stream, "IFhd", &chunksize, start)) { + n_show_error(E_SAVE, "no IFhd chunk", 0); + return FALSE; + } else { + unsigned n; + glui32 qsifhd[10]; + + fillstruct(stream, qifhd, qsifhd, NULL); + + if(qsifhd[qrelnum] != LOWORD(HD_RELNUM)) { + n_show_error(E_SAVE, "release number does not match", qsifhd[qrelnum]); + return FALSE; + } + for(n = 0; n < 6; n++) { + if(qsifhd[qsernum + n] != LOBYTE(HD_SERNUM + n)) { + n_show_error(E_SAVE, "serial number does not match", n); + return FALSE; + } + } + if(qsifhd[qchecksum] != LOWORD(HD_CHECKSUM)) { + n_show_error(E_SAVE, "checksum does not match", qsifhd[qchecksum]); + return FALSE; + } + if(qsifhd[qinitPC] > total_size) { + n_show_error(E_SAVE, "PC past end of memory", qsifhd[qinitPC]); + return FALSE; + } + + PC = qsifhd[qinitPC]; + } + if(!ifffindchunk(stream, "UMem", &chunksize, start)) { + if(!ifffindchunk(stream, "CMem", &chunksize, start)) { + n_show_error(E_SAVE, "no memory chunk (UMem or CMem)", 0); + return FALSE; + } else { + zbyte *compressed_chunk = (zbyte *) malloc(chunksize); + glk_get_buffer_stream(stream, (char *) compressed_chunk, chunksize); + + glk_stream_set_position(current_zfile, zfile_offset, seekmode_Start); + glk_get_buffer_stream(current_zfile, (char *) z_memory, dynamic_size); + + if(!quetzal_undiff(z_memory, dynamic_size, + compressed_chunk, chunksize, FALSE)) { + n_show_error(E_SAVE, "error in compressed data", 0); + return FALSE; + } + } + } else { + if(chunksize != dynamic_size) { + n_show_error(E_SAVE, "uncompressed memory chunk not expected size", + chunksize); + return FALSE; + } + glk_get_buffer_stream(stream, (char *) z_memory, chunksize); + } + + if(!ifffindchunk(stream, "Stks", &chunksize, start)) { + n_show_error(E_SAVE, "no Stks chunk", 0); + return FALSE; + } else { + if(!quetzal_stack_restore(stream, chunksize)) + return FALSE; + } + + return TRUE; +} + +static unsigned qintd[] = { 4, 1, 1, 2, 4 }; +enum qintdnames { qopid, qflags, qcontid, qresrvd, qintid }; + +strid_t quetzal_findgamefile(strid_t stream) +{ + char desttype[4]; + glui32 chunksize; + glui32 start; + + if(!ifffindchunk(stream, "FORM", &chunksize, 0)) + return 0; + + glk_get_buffer_stream(stream, desttype, 4); + if(n_strncmp(desttype, "IFZS", 4) != 0) + return 0; + + start = glk_stream_get_position(stream); + + if(ifffindchunk(stream, "IntD", &chunksize, start)) { + glui32 qsintd[6]; + strid_t file; + fillstruct(stream, qintd, qsintd, NULL); + file = intd_filehandle_open(stream, qsintd[qopid], + qsintd[qcontid], qsintd[qintid], + chunksize - 12); + if(file) + return file; + } + + if(ifffindchunk(stream, "IFhd", &chunksize, start)) { + unsigned n; + glui32 qsifhd[10]; + strid_t file = 0; + char serial[6]; + + fillstruct(stream, qifhd, qsifhd, NULL); + + for(n = 0; n < 6; n++) + serial[n] = qsifhd[qsernum + n]; + + do { + file = startup_findfile(); + if(file) { + if(check_game_for_save(file, qsifhd[qrelnum], serial, + qsifhd[qchecksum])) + return file; + } + } while(file); + } + + return 0; +} + + diff --git a/interpreters/nitfol/quetzal.h b/interpreters/nitfol/quetzal.h new file mode 100644 index 0000000..3b517fb --- /dev/null +++ b/interpreters/nitfol/quetzal.h @@ -0,0 +1,17 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i quetzal.c' */ +#ifndef CFH_QUETZAL_H +#define CFH_QUETZAL_H + +/* From `quetzal.c': */ +BOOL quetzal_diff (const zbyte *a , const zbyte *b , glui32 length , zbyte **diff , glui32 *diff_length , BOOL do_utf8 ); +BOOL quetzal_undiff (zbyte *dest , glui32 length , const zbyte *diff , glui32 diff_length , BOOL do_utf8 ); +BOOL savequetzal (strid_t stream ); +BOOL restorequetzal (strid_t stream ); +strid_t quetzal_findgamefile (strid_t stream ); + +#endif /* CFH_QUETZAL_H */ diff --git a/interpreters/nitfol/rg_qsort.h b/interpreters/nitfol/rg_qsort.h new file mode 100644 index 0000000..be1cbe4 --- /dev/null +++ b/interpreters/nitfol/rg_qsort.h @@ -0,0 +1,12 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i rg_qsort.c' */ +#ifndef CFH_RG_QSORT_H +#define CFH_RG_QSORT_H + +/* From `rg_qsort.c': */ + +#endif /* CFH_RG_QSORT_H */ diff --git a/interpreters/nitfol/solve.c b/interpreters/nitfol/solve.c new file mode 100644 index 0000000..33a3e30 --- /dev/null +++ b/interpreters/nitfol/solve.c @@ -0,0 +1,471 @@ +/* solve.c: solve cycles for the automapper + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ + + +#include "nitfol.h" + + +/* Rational number stuff - doesn't handle overflow conditions */ + +typedef struct rational rational; +struct rational { + int num; + int den; +}; + +static int gcd(int a, int b) +{ + if(a == 0) + return b; + return gcd(b % a, a); +} + +static int lcm(int a, int b) +{ + return a * b / gcd(a, b); +} + +static rational rational_reduce(rational r) +{ + int g = gcd(r.num, r.den); + if(g == 0) + return r; + r.num /= g; + r.den /= g; + if(r.den < 0) { + r.num = -r.num; + r.den = -r.den; + } + return r; +} + +static BOOL rational_iszero(rational r) +{ + return r.num == 0; +} + +static BOOL rational_isone(rational r) +{ + return r.num == r.den; +} + +static rational rational_int(int i) +{ + rational r; + r.num = i; + r.den = 1; + return r; +} + +static rational rational_add(rational a, rational b) +{ + rational r; + r.num = a.num * b.den + a.den * b.num; + r.den = a.den * b.den; + return rational_reduce(r); +} + +static rational rational_sub(rational a, rational b) +{ + rational r; + r.num = a.num * b.den - a.den * b.num; + r.den = a.den * b.den; + return rational_reduce(r); +} + +static rational rational_mult(rational a, rational b) +{ + a.num *= b.num; + a.den *= b.den; + return rational_reduce(a); +} + +static rational rational_div(rational a, rational b) +{ + a.num *= b.den; + a.den *= b.num; + return rational_reduce(a); +} + + +#ifdef HEADER +typedef struct cycleequation cycleequation; +struct cycleequation { + cycleequation *next; + + int *var; + const int *min; + int xcoefficient; + int ycoefficient; +}; +#endif + +typedef struct equation equation; +struct equation { + equation *next; + + int *var; + const int *min; + rational coefficient; + int count; /* Number of times variable is used */ +}; + +typedef struct equalist equalist; +struct equalist { + equalist *next; + equation *eq; +}; + +static equalist *equats = NULL; + +void automap_add_cycle(cycleequation *cycle) +{ + equation *newx = NULL; + equation *newy = NULL; + equalist newlist; + cycleequation *p; + + for(p = cycle; p; p=p->next) { + equation newnode; + newnode.var = p->var; + newnode.min = p->min; + newnode.coefficient.den = 1; + newnode.coefficient.num = p->xcoefficient; + LEadd(newx, newnode); + + newnode.coefficient.num = p->ycoefficient; + LEadd(newy, newnode); + } + + newlist.eq = newx; + LEadd(equats, newlist); + newlist.eq = newy; + LEadd(equats, newlist); + + LEdestroy(cycle); +} + +void automap_delete_cycles(void) +{ + equalist *p; + for(p = equats; p; p=p->next) + LEdestroy(p->eq); + LEdestroy(equats); +} + + +/* Do Gauss-Jordan elimination. */ +/* This could be done with lists instead of arrays with clever hash stuff, but + this is simpler, and the elimination stage takes O(n^3) anyway */ +void automap_cycle_elimination(void) +{ + equalist *p; + equation *q, *v; + equation *variablelist = NULL; + int width = 0, height = 0; + rational *array; + rational *r, *s; + int row, column, i, n; + + + /* First, figure out how many variables we're dealing with */ + for(p = equats; p; p=p->next) { + for(q = p->eq; q; q=q->next) { + for(v = variablelist; v; v=v->next) + if(v->var == q->var) + break; + if(!v) { + LEadd(variablelist, *q); + width++; + } + } + height++; + } + + if(height == 0 || width == 0) + return; + + /* An array implementation is easier to think about than a linked-list one */ + array = (rational *) n_malloc(width * height * sizeof(rational)); + + /* Fill the array */ + r = array; + for(p = equats; p; p=p->next) { + for(v = variablelist; v; v=v->next) { + for(q = p->eq; q; q=q->next) + if(q->var == v->var) + break; + *r++ = q ? q->coefficient : rational_int(0); + } + } + + /* Do the actual elimination */ + row = 0; column = 0; + r = array; + while(row < height && column < width) { + rational c; + /* If zero, swap with a non-zero row */ + if(rational_iszero(r[column])) { + BOOL found = FALSE; + for(i = row+1; i < height; i++) { + if(!rational_iszero(array[i * width + column])) { + n_memswap(&array[row * width], &array[i * width], + width * sizeof(*array)); + found = TRUE; + break; + } + } + if(!found) { /* Zeroed column; try next */ + column++; + continue; + } + } + + /* Divide row by leading coefficient */ + c = r[column]; + for(n = 0; n < width; n++) + r[n] = rational_div(r[n], c); + + /* Eliminate other entries in current column */ + s = array; + for(i = 0; i < height; i++) { + if(i != row && !rational_iszero(s[column])) { + c = s[column]; + for(n = 0; n < width; n++) + s[n] = rational_sub(s[n], rational_mult(r[n], c)); + } + s += width; + } + + r += width; + row++; column++; + } + + /* Delete the old lists */ + automap_delete_cycles(); + + /* Count how many times each variable is used */ + for(v = variablelist; v; v=v->next) + v->count = 0; + r = array; + for(i = 0; i < height; i++) { + for(v = variablelist; v; v=v->next) { + if(!rational_iszero(*r)) + v->count++; + r++; + } + } + + /* Make new lists from the array */ + r = array; + for(i = 0; i < height; i++) { + equation *neweq = NULL; + for(v = variablelist; v; v=v->next) { + if(!rational_iszero(*r)) { + equation newnode; + newnode = *v; + newnode.coefficient = *r; + LEadd(neweq, newnode); + } + r++; + } + if(neweq) { + equalist newlist; + newlist.eq = neweq; + LEadd(equats, newlist); + } + } + + n_free(array); +} + + +/* Find an edge to lengthen which would cause the least amount of lengthening + to edges in other cycles */ +static equation *select_edge(equation *cycle, int *need_recalc) +{ + equation *help = NULL; /* Increasing its length will help other cycles */ + equation *solitary = NULL; /* Only in one cycle */ + equation *nonharm = NULL; /* Increasing its length won't destroy things */ + BOOL is_harmful_past = FALSE; + + equation *p; + + for(p = cycle; p; p=p->next) { + if(p->next && p->coefficient.num < 0) { + equalist *t; + BOOL pastthis = FALSE; + BOOL is_found = FALSE; + BOOL is_harmful = FALSE; + BOOL is_past = FALSE; + BOOL is_help = FALSE; + BOOL is_compensator = FALSE; + for(t = equats; t; t=t->next) { + if(t->eq == cycle) + pastthis = TRUE; + else { + rational sum = rational_int(0); + equation *r, *foundme = NULL; + BOOL first_find = TRUE; + for(r = t->eq; r; r=r->next) { + if(r->next) { + int value = *(r->var) ? *(r->var) : *(r->min); + sum = rational_add(sum, rational_mult(r->coefficient, + rational_int(value))); + if(r->count == 1 && r->coefficient.num < 0) + is_compensator = TRUE; + if(r->var == p->var) { + if(foundme) + first_find = FALSE; + foundme = r; + is_past = pastthis && (is_past || !is_found); + is_found = TRUE; + if(r->coefficient.num > 0) + is_harmful = TRUE; + } + } else if(pastthis && foundme && -sum.num < *(r->min) && first_find + && foundme->coefficient.num < 0) + is_help = TRUE; + } + } + } + if(is_help && !is_harmful && !help) + help = p; + if(!is_found) { + solitary = p; + } else if(!is_harmful || is_compensator) { + if(!nonharm || is_past) { + is_harmful_past = !is_past; + nonharm = p; + } + } + } + } + + if(help) return help; + if(solitary) return solitary; + if(nonharm) { if(is_harmful_past) *need_recalc = 2; return nonharm; } + + return NULL; +} + + + +/* Fill variables with valid numbers. Assumes Gauss-Jordan elimination has + already happened. */ +void automap_cycles_fill_values(void) +{ + equalist *p; + equation *q; + int calccount; + int recalculate = 0; + + for(p = equats; p; p=p->next) + for(q = p->eq; q; q=q->next) + *(q->var) = 0; + + for(calccount = 0; calccount <= recalculate; calccount++) { + recalculate = 0; + + /* Last variable in each list is the dependant; all others are independant */ + /* Fill independant variables with their minimums, then calculate the + dependant one; if it's less than its minimum, play with independants */ + for(p = equats; p; p=p->next) { + rational sum = rational_int(0); + for(q = p->eq; q; q=q->next) { + if(q->next) { /* Independant */ + int value = *(q->var) ? *(q->var) : *(q->min); + sum = rational_add(sum, rational_mult(q->coefficient, + rational_int(value))); + *(q->var) = value; + } else { /* Dependant */ + int value = -sum.num; + if(!rational_isone(q->coefficient)) + n_show_error(E_SYSTEM, "last coefficient not 1", q->coefficient.num); + if(sum.den != 1) + n_show_error(E_SYSTEM, "unimplemented case denominator != 1", sum.den); + else if(value < *(q->min)) { + /* Edge is not long enough - try increasing lengths of another edge + in cycle to lengthen it */ + equation *m = select_edge(p->eq, &recalculate); + + if(m) { + rational oldval = rational_mult(m->coefficient, + rational_int(*(m->var))); + rational newval; + int diff = value - *(q->min); + sum = rational_sub(sum, oldval); + if(oldval.den != 1) + n_show_error(E_SYSTEM, "unimplemented case denominator != 1", oldval.den); + diff += oldval.num; + newval = rational_div(rational_int(diff), m->coefficient); + *(m->var) = newval.num; + sum = rational_add(sum, rational_mult(m->coefficient, newval)); + value = -sum.num; + } + if(value > *(q->min)) + n_show_error(E_SYSTEM, "met more than needed", sum.num); + } + if(value < *(q->min)) + n_show_error(E_SYSTEM, "failed to meet needs", sum.num); + *(q->var) = value; + sum = rational_add(sum, rational_mult(q->coefficient, + rational_int(*(q->var)))); + if(!rational_iszero(sum)) + n_show_error(E_SYSTEM, "doesn't add up", sum.num); + + } + } + } +#if 0 + { + rational checksum = rational_int(0); + equation *cq; + for(cq = p->eq; cq; cq=cq->next) { + checksum = rational_add(checksum,rational_mult(cq->coefficient, + rational_int(*(cq->var)))); + } + if(checksum.num != sum.num || checksum.den != sum.den) { + n_show_error(E_SYSTEM, "correction for correction incorrect", checksum.num); + sum = checksum; + } + } +#endif + } + + +#if 0 + for(p = equats; p; p=p->next) { + rational sum = rational_int(0); + for(q = p->eq; q; q=q->next) { + if(*(q->var) < *(q->min)) + n_show_error(E_SYSTEM, "variable less than minimum", *(q->var)); + sum = rational_add(sum, rational_mult(q->coefficient, + rational_int(*(q->var)))); + } + if(!rational_iszero(sum)) + n_show_error(E_SYSTEM, "equation not equal", sum.num / sum.den); + } +#endif + + +} + + diff --git a/interpreters/nitfol/solve.h b/interpreters/nitfol/solve.h new file mode 100644 index 0000000..89061aa --- /dev/null +++ b/interpreters/nitfol/solve.h @@ -0,0 +1,26 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i solve.c' */ +#ifndef CFH_SOLVE_H +#define CFH_SOLVE_H + +/* From `solve.c': */ +typedef struct cycleequation cycleequation; +struct cycleequation { + cycleequation *next; + + int *var; + const int *min; + int xcoefficient; + int ycoefficient; +} +; +void automap_add_cycle (cycleequation *cycle ); +void automap_delete_cycles (void); +void automap_cycle_elimination (void); +void automap_cycles_fill_values (void); + +#endif /* CFH_SOLVE_H */ diff --git a/interpreters/nitfol/sound.c b/interpreters/nitfol/sound.c new file mode 100644 index 0000000..763afcd --- /dev/null +++ b/interpreters/nitfol/sound.c @@ -0,0 +1,113 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +/* Link this in only if your glk supports sound */ + +#ifdef GLK_MODULE_SOUND + +static schanid_t channel; + +void init_sound(void) +{ + channel = glk_schannel_create(0); +} + +void kill_sound(void) +{ + glk_schannel_destroy(channel); +} + +static void set_volume(zword zvolume) +{ + glui32 vol; + switch(zvolume) { + case 1: vol = 0x02000; break; + case 2: vol = 0x04000; break; + case 3: vol = 0x06000; break; + case 4: vol = 0x08000; break; + case 5: vol = 0x0a000; break; + case 6: vol = 0x0c000; break; + case 7: vol = 0x0e000; break; + case 8: vol = 0x10000; break; + case 255: vol = 0x20000; break; + default: n_show_error(E_SOUND, "illegal volume", zvolume); + } + glk_schannel_set_volume(channel, vol); +} + + +void check_sound(event_t moo) /* called from main event loop */ +{ + if(moo.type == evtype_SoundNotify) { + zword dummylocals[16]; + in_timer = TRUE; + mop_call(moo.val2, 0, dummylocals, -2); /* add a special stack frame */ + decode(); /* start interpreting the routine */ + in_timer = FALSE; + exit_decoder = FALSE; + } +} + + +void op_sound_effect(void) +{ + zword number = operand[0], effect = operand[1]; + zword volume = operand[2], routine = operand[3]; + glui32 repeats; + + if(!glk_gestalt(gestalt_Sound, 0)) + return; + + if(numoperands < 1) + number = 1; + if(numoperands < 2) + effect = 2; + if(numoperands < 3) + volume = 8; + if(numoperands < 4) + routine = 0; + + repeats = (volume >> 8) & 0xff; + if(repeats == 0xff) + repeats = 0xffffffff; + else + repeats++; + + volume &= 0xff; + + switch(effect) { + case 1: + glk_sound_load_hint(number, 1); + break; + case 2: + glk_schannel_play_ext(channel, number, repeats, routine); + set_volume(volume); + break; + case 3: + glk_schannel_stop(channel); + break; + case 4: + glk_sound_load_hint(number, 0); + } + +} + +#endif diff --git a/interpreters/nitfol/sound.h b/interpreters/nitfol/sound.h new file mode 100644 index 0000000..1f42a56 --- /dev/null +++ b/interpreters/nitfol/sound.h @@ -0,0 +1,20 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i sound.c' */ +#ifndef CFH_SOUND_H +#define CFH_SOUND_H + +/* From `sound.c': */ + +#ifdef GLK_MODULE_SOUND +void init_sound (void); +void kill_sound (void); +void check_sound (event_t moo ); +void op_sound_effect (void); + +#endif + +#endif /* CFH_SOUND_H */ diff --git a/interpreters/nitfol/stack.c b/interpreters/nitfol/stack.c new file mode 100644 index 0000000..08195c2 --- /dev/null +++ b/interpreters/nitfol/stack.c @@ -0,0 +1,599 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +/* Contains everything which references the stack directly. */ + +/* Uses two stacks because I don't want to worry about alignment issues */ + +typedef struct Stack_frame Stack_frame; + +struct Stack_frame { + offset stack_stack_start; /* points into stack_stack to local variables + for this frame, which are followed by + stack for pushing/popping */ + offset return_PC; + int num_locals; + zword arguments; /* Number of arguments used for check_count */ + int result_variable; /* Where to place the result upon returning */ +}; + +static zword *stack_stack = NULL; /* Holds local variables and pushed values */ +static offset stack_pointer; /* Current offset into stack_stack */ +static offset stack_min; /* Minimum for stack_pointer (how much we can pop) */ +static offset stack_max; /* Maximum for stack_pointer (size of stack) */ +static zword *local_vars; /* Pointer to local variables for current frame */ + +static Stack_frame *stack_frames = NULL; +static zword frame_count; /* number of frames on the stack */ +static zword frame_max; + + +void init_stack(offset initialstack_stack_size, zword initialframe_size) +{ + n_free(stack_stack); + stack_stack = (zword *) n_malloc(sizeof(*stack_stack) * + initialstack_stack_size); + stack_pointer = 0; + stack_min = 0; + stack_max = initialstack_stack_size; + + n_free(stack_frames); + stack_frames = (Stack_frame *) n_malloc(sizeof(*stack_frames) * + initialframe_size); + frame_count = 0; + if(stacklimit && initialframe_size > stacklimit) + frame_max = stacklimit; + else + frame_max = initialframe_size; + + stack_frames[frame_count].stack_stack_start = 0; + stack_frames[frame_count].return_PC = 0; + stack_frames[frame_count].num_locals = 0; + stack_frames[frame_count].arguments = 0; + stack_frames[frame_count].result_variable = -2; + local_vars = stack_stack + stack_frames[frame_count].stack_stack_start; +} + +void kill_stack(void) +{ + n_free(stack_stack); + n_free(stack_frames); + stack_stack = 0; + stack_frames = 0; +} + +/* Perform various sanity checks on the stack to make sure all is well in + Denmark. */ +BOOL verify_stack(void) +{ + zword f; + if(frame_count > frame_max) { + n_show_error(E_STACK, "more frames than max", frame_count); + return FALSE; + } + if(!stack_frames) { + n_show_error(E_STACK, "no frames", 0); + return FALSE; + } + for(f = 0; f < frame_count; f++) { + if(stack_frames[f].stack_stack_start > stack_pointer) { + n_show_error(E_STACK, "stack start after end", f); + return FALSE; + } + if(stack_frames[f].return_PC > total_size) { + n_show_error(E_STACK, "PC after end of game", f); + return FALSE; + } + if(stack_frames[f].num_locals > 15) { + n_show_error(E_STACK, "too many locals", f); + return FALSE; + } + if(stack_frames[f].arguments > 7) { + n_show_error(E_STACK, "too many arguments", f); + return FALSE; + } + if(stack_frames[f].result_variable > 255) { + n_show_error(E_STACK, "result variable too big", f); + return FALSE; + } + if(stack_frames[f].result_variable < -2) { + n_show_error(E_STACK, "unknown magic result variable", f); + return FALSE; + } + } + return TRUE; +} + + +/* Insure we have at least addsize more zwords available on the stack, and + * if not, allocate more space + */ +static void check_stack_stack(offset addsize) +{ + if(stack_pointer + addsize >= stack_max) { + stack_max *= 2; + stack_stack = (zword *) n_realloc(stack_stack, + sizeof(*stack_stack) * stack_max); + + n_show_port(E_STACK, "stack larger than available on some interps", stack_max); + + local_vars = stack_stack + stack_frames[frame_count].stack_stack_start; + } +} + + +void add_stack_frame(offset return_PC, unsigned num_locals, zword *locals, + unsigned num_args, int result_var) +{ + unsigned n; + /* Don't increment the frame yet because we have error messages yet to + show which need to receive a valid frame to output local variables */ + if(frame_count+1 >= frame_max) { + frame_max *= 2; + if(stacklimit && frame_max > stacklimit) { + frame_max = stacklimit; + if(frame_count+1 >= frame_max) { + n_show_fatal(E_STACK, "recursed deeper than allowed", frame_count+1); + } + } + stack_frames = (Stack_frame *) + n_realloc(stack_frames, sizeof(*stack_frames) * frame_max); + n_show_port(E_STACK, "deep recursion not available on some 'terps", frame_max); + } + frame_count++; + stack_frames[frame_count].stack_stack_start = stack_pointer; + stack_frames[frame_count].return_PC = return_PC; + stack_frames[frame_count].num_locals = num_locals; + stack_frames[frame_count].arguments = num_args; + stack_frames[frame_count].result_variable = result_var; + + + check_stack_stack(num_locals); + for(n = 0; n < num_locals; n++) + stack_stack[stack_pointer++] = locals[n]; + + stack_min = stack_pointer; + + local_vars = stack_stack + stack_frames[frame_count].stack_stack_start; +} + + +void remove_stack_frame(void) +{ +#ifndef FAST + if(frame_count == 0) { + n_show_error(E_STACK, "attempt to remove initial stack frame", 0); + return; + } +#endif + stack_pointer = stack_frames[frame_count].stack_stack_start; + frame_count--; + stack_min = stack_frames[frame_count].stack_stack_start + + stack_frames[frame_count].num_locals; + local_vars = stack_stack + stack_frames[frame_count].stack_stack_start; +} + + +void check_set_var(int var, zword val) +{ + switch(var) { + default: set_var(var, val); break; + case -2: exit_decoder = TRUE; time_ret = val; /* timer junk */ + case -1: ; + } +} + + +void mop_func_return(zword ret_val) +{ + int var; + PC = stack_frames[frame_count].return_PC; + var = stack_frames[frame_count].result_variable; + remove_stack_frame(); + check_set_var(var, ret_val); + /* printf("retn %x\n", PC); */ +} + + +void op_catch(void) +{ + mop_store_result(frame_count); +} + + +unsigned stack_get_numlocals(int frame) +{ + if(stack_frames) + return stack_frames[frame].num_locals; + return 0; +} + + +void op_throw(void) +{ +#ifndef FAST + if(operand[1] > frame_count) { + n_show_error(E_STACK, "attempting to throw away non-existent frames", operand[1]); + return; + } +#endif + if(operand[1] != 0) { + frame_count = operand[1]; + mop_func_return(operand[0]); + } else { + n_show_error(E_STACK, "attempting to throw away initial frame", operand[0]); + } +} + +void op_check_arg_count(void) +{ + if(stack_frames[frame_count].arguments >= operand[0]) + mop_take_branch(); + else + mop_skip_branch(); +} + + +static zword stack_pop(void) +{ +#ifndef FAST + if(stack_pointer <= stack_min) { + n_show_error(E_STACK, "underflow - excessive popping", stack_pointer); + return 0; + } +#endif + return stack_stack[--stack_pointer]; +} + + +static void stack_push(zword n) +{ + check_stack_stack(1); + stack_stack[stack_pointer++] = n; +} + + +void op_push(void) +{ + stack_push(operand[0]); +} + + +void op_pop(void) +{ + stack_pop(); +} + + +void op_pull(void) +{ + if(zversion == 6) { /* v6 uses user stacks */ + if(numoperands == 0 || operand[0] == 0) + mop_store_result(stack_pop()); + else { + zword space = LOWORD(operand[0]) + 1; /* One more slot is free */ + LOWORDwrite(operand[0], space); + mop_store_result(LOWORD(operand[0] + space * ZWORD_SIZE)); + } + } else { + zword val = stack_pop(); + set_var(operand[0], val); + } +} + + +void op_pop_stack(void) +{ + zword i; + if(numoperands < 2 || operand[1] == 0) { + for(i = 0; i < operand[0]; i++) + stack_pop(); + } else { + zword space = LOWORD(operand[1]) + operand[0]; + LOWORDwrite(operand[1], space); + } +} + + +void op_push_stack(void) +{ + zword space = LOWORD(operand[1]); + if(space) { + LOWORDwrite(operand[1] + space * ZWORD_SIZE, operand[0]); + LOWORDwrite(operand[1], space - 1); + mop_take_branch(); + } else { + mop_skip_branch(); + } +} + + +void mop_store_result(zword val) +{ + set_var(HIBYTE(PC), val); + PC++; +} + + +void op_ret_popped(void) +{ + mop_func_return(stack_pop()); +} + +unsigned stack_get_depth(void) +{ + return frame_count; +} + +BOOL frame_is_valid(unsigned frame) +{ + return frame <= frame_count; +} + +offset frame_get_PC(unsigned frame) +{ + if(frame == frame_count) { + return PC; + } + return stack_frames[frame+1].return_PC; +} + +zword frame_get_var(unsigned frame, int var) +{ + if(var == 0 || var > 0x10) { + n_show_error(E_STACK, "variable not readable from arbitrary frame", var); + return 0; + } + + if(var > stack_frames[frame].num_locals) + n_show_error(E_STACK, "reading nonexistant local", var); + + return stack_stack[stack_frames[frame].stack_stack_start + (var - 1)]; +} + + +void frame_set_var(unsigned frame, int var, zword val) +{ + if(var == 0 || var > 0x10) { + n_show_error(E_STACK, "variable not writable from arbitrary frame", var); + return; + } + + if(var > stack_frames[frame].num_locals) + n_show_error(E_STACK, "writing nonexistant local", var); + + stack_stack[stack_frames[frame].stack_stack_start + (var - 1)] = val;; +} + +N_INLINE zword get_var(int var) +{ + if(var < 0x10) { + if(var != 0) { +#ifndef FAST + if(var > stack_frames[frame_count].num_locals) + n_show_error(E_INSTR, "reading nonexistant local", var); +#endif + return local_vars[var - 1]; + } + return stack_pop(); + } + return LOWORD(z_globaltable + (var - 0x10) * ZWORD_SIZE); +} + +N_INLINE void set_var(int var, zword val) +{ + if(var < 0x10) { + if(var != 0) { +#ifndef FAST + if(var > stack_frames[frame_count].num_locals) + n_show_error(E_INSTR, "setting nonexistant local", var); +#endif + local_vars[var - 1] = val; + } else { + stack_push(val); + } + } else { + LOWORDwrite(z_globaltable + (var - 0x10) * ZWORD_SIZE, val); + } +} + + +const unsigned qstackframe[] = { 3, 1, 1, 1, 2, 0 }; +enum qstacknames { qreturnPC, qflags, qvar, qargs, qeval }; + +BOOL quetzal_stack_restore(strid_t stream, glui32 qsize) +{ + glui32 i = 0; + int num_frames = 0; + + kill_stack(); + init_stack(1024, 128); + + while(i < qsize) { + unsigned n; + unsigned num_locals; + zword locals[16]; + int num_args; + int var; + + glui32 qframe[5]; + i += fillstruct(stream, qstackframe, qframe, NULL); + + if(qframe[qreturnPC] > total_size) { + n_show_error(E_SAVE, "function return PC past end of memory", + qframe[qreturnPC]); + return FALSE; + } + + if((qframe[qflags] & b11100000) != 0) { + n_show_error(E_SAVE, "expected top bits of flag to be zero", qframe[qflags]); + return FALSE; + } + + var = qframe[qvar]; + if(qframe[qflags] & b00010000) /* from a call_n */ + var = -1; + + num_locals = qframe[qflags] & b00001111; + + if(num_locals > 15) { + n_show_error(E_SAVE, "too many locals", num_locals); + return FALSE; + } + + num_args = 0; + switch(qframe[qargs]) { + default: + n_show_error(E_SAVE, "invalid argument count", qframe[qargs]); + return FALSE; + case b01111111: num_args++; + case b00111111: num_args++; + case b00011111: num_args++; + case b00001111: num_args++; + case b00000111: num_args++; + case b00000011: num_args++; + case b00000001: num_args++; + case b00000000: ; + } + + for(n = 0; n < num_locals; n++) { + unsigned char v[ZWORD_SIZE]; + glk_get_buffer_stream(stream, (char *) v, ZWORD_SIZE); + locals[n] = MSBdecodeZ(v); + i+=ZWORD_SIZE; + } + + if(zversion != 6 && num_frames == 0) + ; /* dummy stack frame; don't really use it */ + else + add_stack_frame(qframe[qreturnPC], + num_locals, locals, + num_args, var); + + for(n = 0; n < qframe[qeval]; n++) { + unsigned char v[ZWORD_SIZE]; + glk_get_buffer_stream(stream, (char *) v, ZWORD_SIZE); + stack_push(MSBdecodeZ(v)); + i += ZWORD_SIZE; + } + + num_frames++; + } + if(!verify_stack()) { + n_show_error(E_SAVE, "restored stack fails verification", 0); + return FALSE; + } + return TRUE; +} + + +glui32 get_quetzal_stack_size(void) +{ + glui32 framespace; + glui32 stackspace; + framespace = frame_count * 8; + stackspace = stack_pointer * ZWORD_SIZE; + if(zversion != 6) + framespace += 8; /* for the dummy frame */ + return framespace + stackspace; +} + + +BOOL quetzal_stack_save(strid_t stream) +{ + unsigned frame_num = 0; + + if(zversion == 6) + frame_num++; + + if(!verify_stack()) { + n_show_error(E_SAVE, "stack did not pass verification before saving", 0); + return FALSE; + } + + /* We have to look one ahead to see how much stack space a frame uses; when + we get to the last frame, there will be no next frame, so this won't work + if there wasn't a frame there earlier with the correct info. Add and + remove a frame to make things happy */ + add_stack_frame(0, 0, NULL, 0, 0); + remove_stack_frame(); + + for(; frame_num <= frame_count; frame_num++) { + unsigned n; + int num_locals; + unsigned stack_size; + + glui32 qframe[5]; + + const unsigned char argarray[8] = { + b00000000, b00000001, b00000011, b00000111, + b00001111, b00011111, b00111111, b01111111 + }; + + qframe[qreturnPC] = stack_frames[frame_num].return_PC; + + qframe[qvar] = stack_frames[frame_num].result_variable; + + num_locals = stack_frames[frame_num].num_locals; + + if(num_locals > 15) { + n_show_error(E_SAVE, "num_locals too big", num_locals); + return FALSE; + } + + qframe[qflags] = num_locals; + + if(stack_frames[frame_num].result_variable == -1) { + qframe[qflags] |= b00010000; + qframe[qvar] = 0; + } + + if(stack_frames[frame_num].arguments > 7) { + n_show_error(E_SAVE, "too many function arguments", stack_frames[frame_num].arguments); + return FALSE; + } + + qframe[qargs] = argarray[stack_frames[frame_num].arguments]; + + stack_size = (stack_frames[frame_num+1].stack_stack_start - + stack_frames[frame_num].stack_stack_start - + num_locals); + + qframe[qeval] = stack_size; + + if(frame_num == 0) { + qframe[qreturnPC] = 0; + qframe[qflags] = 0; + qframe[qvar] = 0; + qframe[qargs] = 0; + } + + emptystruct(stream, qstackframe, qframe); + + for(n = 0; n < num_locals + stack_size; n++) { + unsigned char v[ZWORD_SIZE]; + zword z = stack_stack[stack_frames[frame_num].stack_stack_start + n]; + MSBencodeZ(v, z); + w_glk_put_buffer_stream(stream, (char *) v, ZWORD_SIZE); + } + } + return TRUE; +} diff --git a/interpreters/nitfol/stack.h b/interpreters/nitfol/stack.h new file mode 100644 index 0000000..5636b36 --- /dev/null +++ b/interpreters/nitfol/stack.h @@ -0,0 +1,41 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i stack.c' */ +#ifndef CFH_STACK_H +#define CFH_STACK_H + +/* From `stack.c': */ +void init_stack (offset initialstack_stack_size , zword initialframe_size ); +void kill_stack (void); +BOOL verify_stack (void); +void add_stack_frame (offset return_PC , unsigned num_locals , zword *locals , unsigned num_args , int result_var ); +void remove_stack_frame (void); +void check_set_var (int var , zword val ); +void mop_func_return (zword ret_val ); +void op_catch (void); +unsigned stack_get_numlocals (int frame ); +void op_throw (void); +void op_check_arg_count (void); +void op_push (void); +void op_pop (void); +void op_pull (void); +void op_pop_stack (void); +void op_push_stack (void); +void mop_store_result (zword val ); +void op_ret_popped (void); +unsigned stack_get_depth (void); +BOOL frame_is_valid (unsigned frame ); +offset frame_get_PC (unsigned frame ); +zword frame_get_var (unsigned frame , int var ); +void frame_set_var (unsigned frame , int var , zword val ); +N_INLINE zword get_var (int var ); +N_INLINE void set_var (int var , zword val ); +extern const unsigned qstackframe[]; +BOOL quetzal_stack_restore (strid_t stream , glui32 qsize ); +glui32 get_quetzal_stack_size (void); +BOOL quetzal_stack_save (strid_t stream ); + +#endif /* CFH_STACK_H */ diff --git a/interpreters/nitfol/startdos.c b/interpreters/nitfol/startdos.c new file mode 100644 index 0000000..4455078 --- /dev/null +++ b/interpreters/nitfol/startdos.c @@ -0,0 +1,637 @@ +#line 228 "opt2glkc.pl" +#ifdef DEBUGGING +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "nitfol.h" +#include "glkstart.h" + +static char *game_filename = NULL; + +static void set_game_filename(const char *name) +{ + n_free(game_filename); + game_filename = 0; + +#if defined(_GNU_SOURCE) + game_filename = canonicalize_file_name(name); +#else +#if defined(_BSD_SOURCE) || defined(_XOPEN_SOURCE) + game_filename = (char *) n_malloc(PATH_MAX); + if(!realpath(name, game_filename)) { + n_free(game_filename); + game_filename = 0; + } +#else +#ifdef __DJGPP__ + game_filename = (char *) n_malloc(FILENAME_MAX); + _fixpath(name, game_filename); +#endif +#endif +#endif + + if(!game_filename) + game_filename = n_strdup(name); +} + + +strid_t startup_findfile(void) +{ + static DIR *dir = NULL; + static char *pathstart = NULL; + static char *path = NULL; + strid_t stream; + struct dirent *d; + char *name = NULL; + + if(!pathstart) { + char *p = search_path; + if(!p) + return 0; + pathstart = n_strdup(p); + if(!(path = n_strtok(pathstart, ":"))) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + + do { + if(!dir) { + dir = opendir(path); + if(!dir) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + d = readdir(dir); + if(!d) { + closedir(dir); + dir = NULL; + if(!(path = n_strtok(NULL, ":"))) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + } while(!dir); + + name = (char *) n_malloc(n_strlen(path) + n_strlen(d->d_name) + 2); + n_strcpy(name, path); + n_strcat(name, "/"); + n_strcat(name, d->d_name); + stream = glkunix_stream_open_pathname(name, fileusage_Data | + fileusage_BinaryMode, 0); + if(stream) + set_game_filename(name); + n_free(name); + return stream; +} + + +strid_t intd_filehandle_open(strid_t savefile, glui32 operating_id, + glui32 contents_id, glui32 interp_id, + glui32 length) +{ + char *name; + strid_t str; + if(operating_id != 0x554e4958 /* 'UNIX' */) + return 0; + if(contents_id != 0) + return 0; + if(interp_id != 0x20202020 /* ' ' */) + return 0; + + name = (char *) n_malloc(length+1); + glk_get_buffer_stream(savefile, name, length); + name[length] = 0; + str = glkunix_stream_open_pathname(name, fileusage_Data | + fileusage_BinaryMode, 0); + if(str) + set_game_filename(name); + n_free(name); + return str; +} + +void intd_filehandle_make(strid_t savefile) +{ + if(!game_filename) + return; + w_glk_put_string_stream(savefile, "UNIX"); + glk_put_char_stream(savefile, b00000010); /* Flags */ + glk_put_char_stream(savefile, 0); /* Contents ID */ + glk_put_char_stream(savefile, 0); /* Reserved */ + glk_put_char_stream(savefile, 0); /* Reserved */ + w_glk_put_string_stream(savefile, " "); /* Interpreter ID */ + w_glk_put_string_stream(savefile, game_filename); +} + +glui32 intd_get_size(void) +{ + if(!game_filename) + return 0; + return n_strlen(game_filename) + 12; +} + +strid_t startup_open(const char *name) +{ + strid_t str; + + str = glkunix_stream_open_pathname((char *) name, fileusage_Data | fileusage_BinaryMode, 0); + if(str) { + set_game_filename(name); + } else { + char *path = search_path; + if(path) { + char *p; + char *newname = (char *) n_malloc(strlen(path) + strlen(name) + 2); + path = n_strdup(path); + for(p = n_strtok(path, ":"); p; p = n_strtok(NULL, ":")) { + n_strcpy(newname, p); + n_strcat(newname, "/"); + n_strcat(newname, name); + str = glkunix_stream_open_pathname((char *) newname, fileusage_Data | + fileusage_BinaryMode, 0); + if(str) { + set_game_filename(newname); + break; + } + } + n_free(path); + } + } + + if(!str) + fprintf(stderr, "Cannot open '%s'\n", name); + + return str; +} + +#line 717 "opt2glkc.pl" +static strid_t startup_wopen(const char *name) +{ + return n_file_name(fileusage_Data | fileusage_BinaryMode, + filemode_Write, name); +} +glkunix_argumentlist_t glkunix_arguments[] = { + { (char *) "", glkunix_arg_ValueCanFollow, (char *) "filename file to load" }, + { (char *) "-help", glkunix_arg_NoValue, (char *) "list command-line options" }, + { (char *) "--help", glkunix_arg_NoValue, (char *) "list command-line options" }, + { (char *) "-version", glkunix_arg_NoValue, (char *) "get version number" }, + { (char *) "--version", glkunix_arg_NoValue, (char *) "get version number" }, + { (char *) "-i", glkunix_arg_NoValue, (char *) "-i" }, + { (char *) "-no-ignore", glkunix_arg_NoValue, (char *) "-no-ignore" }, + { (char *) "--no-ignore", glkunix_arg_NoValue, (char *) "--no-ignore" }, + { (char *) "-ignore", glkunix_arg_NoValue, (char *) "-ignore" }, + { (char *) "--ignore", glkunix_arg_NoValue, (char *) "--ignore Ignore Z-machine strictness errors" }, + { (char *) "-f", glkunix_arg_NoValue, (char *) "-f" }, + { (char *) "-no-fullname", glkunix_arg_NoValue, (char *) "-no-fullname" }, + { (char *) "--no-fullname", glkunix_arg_NoValue, (char *) "--no-fullname" }, + { (char *) "-fullname", glkunix_arg_NoValue, (char *) "-fullname" }, + { (char *) "--fullname", glkunix_arg_NoValue, (char *) "--fullname For running under Emacs or DDD" }, + { (char *) "-x", glkunix_arg_ValueFollows, (char *) "-x" }, + { (char *) "-command", glkunix_arg_ValueFollows, (char *) "-command" }, + { (char *) "--command", glkunix_arg_ValueFollows, (char *) "--command Read commands from this file" }, + { (char *) "-P", glkunix_arg_NoValue, (char *) "-P" }, + { (char *) "-no-pirate", glkunix_arg_NoValue, (char *) "-no-pirate" }, + { (char *) "--no-pirate", glkunix_arg_NoValue, (char *) "--no-pirate" }, + { (char *) "-pirate", glkunix_arg_NoValue, (char *) "-pirate" }, + { (char *) "--pirate", glkunix_arg_NoValue, (char *) "--pirate Aye, matey" }, + { (char *) "-q", glkunix_arg_NoValue, (char *) "-q" }, + { (char *) "-no-quiet", glkunix_arg_NoValue, (char *) "-no-quiet" }, + { (char *) "--no-quiet", glkunix_arg_NoValue, (char *) "--no-quiet" }, + { (char *) "-quiet", glkunix_arg_NoValue, (char *) "-quiet" }, + { (char *) "--quiet", glkunix_arg_NoValue, (char *) "--quiet Do not print introductory messages" }, + { (char *) "-no-spell", glkunix_arg_NoValue, (char *) "-no-spell" }, + { (char *) "--no-spell", glkunix_arg_NoValue, (char *) "--no-spell" }, + { (char *) "-spell", glkunix_arg_NoValue, (char *) "-spell" }, + { (char *) "--spell", glkunix_arg_NoValue, (char *) "--spell Perform spelling correction" }, + { (char *) "-no-expand", glkunix_arg_NoValue, (char *) "-no-expand" }, + { (char *) "--no-expand", glkunix_arg_NoValue, (char *) "--no-expand" }, + { (char *) "-expand", glkunix_arg_NoValue, (char *) "-expand" }, + { (char *) "--expand", glkunix_arg_NoValue, (char *) "--expand Expand one letter abbreviations" }, + { (char *) "-s", glkunix_arg_ValueFollows, (char *) "-s" }, + { (char *) "-symbols", glkunix_arg_ValueFollows, (char *) "-symbols" }, + { (char *) "--symbols", glkunix_arg_ValueFollows, (char *) "--symbols Specify symbol file for game" }, + { (char *) "-t", glkunix_arg_NoValue, (char *) "-t" }, + { (char *) "-no-tandy", glkunix_arg_NoValue, (char *) "-no-tandy" }, + { (char *) "--no-tandy", glkunix_arg_NoValue, (char *) "--no-tandy" }, + { (char *) "-tandy", glkunix_arg_NoValue, (char *) "-tandy" }, + { (char *) "--tandy", glkunix_arg_NoValue, (char *) "--tandy Censors some Infocom games" }, + { (char *) "-T", glkunix_arg_ValueFollows, (char *) "-T" }, + { (char *) "-transcript", glkunix_arg_ValueFollows, (char *) "-transcript" }, + { (char *) "--transcript", glkunix_arg_ValueFollows, (char *) "--transcript Write transcript to this file" }, + { (char *) "-d", glkunix_arg_NoValue, (char *) "-d" }, + { (char *) "-no-debug", glkunix_arg_NoValue, (char *) "-no-debug" }, + { (char *) "--no-debug", glkunix_arg_NoValue, (char *) "--no-debug" }, + { (char *) "-debug", glkunix_arg_NoValue, (char *) "-debug" }, + { (char *) "--debug", glkunix_arg_NoValue, (char *) "--debug Enter debugger immediatly" }, + { (char *) "-prompt", glkunix_arg_ValueFollows, (char *) "-prompt" }, + { (char *) "--prompt", glkunix_arg_ValueFollows, (char *) "--prompt Specify debugging prompt" }, + { (char *) "-path", glkunix_arg_ValueFollows, (char *) "-path" }, + { (char *) "--path", glkunix_arg_ValueFollows, (char *) "--path Look for games in this directory" }, + { (char *) "-no-autoundo", glkunix_arg_NoValue, (char *) "-no-autoundo" }, + { (char *) "--no-autoundo", glkunix_arg_NoValue, (char *) "--no-autoundo" }, + { (char *) "-autoundo", glkunix_arg_NoValue, (char *) "-autoundo" }, + { (char *) "--autoundo", glkunix_arg_NoValue, (char *) "--autoundo Ensure @code{@@save_undo} is called every turn" }, + { (char *) "-S", glkunix_arg_NumberValue, (char *) "-S" }, + { (char *) "-stacklimit", glkunix_arg_NumberValue, (char *) "-stacklimit" }, + { (char *) "--stacklimit", glkunix_arg_NumberValue, (char *) "--stacklimit Exit when the stack is this deep" }, + { (char *) "-a", glkunix_arg_ValueFollows, (char *) "-a" }, + { (char *) "-alias", glkunix_arg_ValueFollows, (char *) "-alias" }, + { (char *) "--alias", glkunix_arg_ValueFollows, (char *) "--alias Specify an alias" }, + { (char *) "-ralias", glkunix_arg_ValueFollows, (char *) "-ralias" }, + { (char *) "--ralias", glkunix_arg_ValueFollows, (char *) "--ralias Specify an recursive alias" }, + { (char *) "-unalias", glkunix_arg_ValueFollows, (char *) "-unalias" }, + { (char *) "--unalias", glkunix_arg_ValueFollows, (char *) "--unalias Remove an alias" }, + { (char *) "-r", glkunix_arg_NumberValue, (char *) "-r" }, + { (char *) "-random", glkunix_arg_NumberValue, (char *) "-random" }, + { (char *) "--random", glkunix_arg_NumberValue, (char *) "--random Set random seed" }, + { (char *) "-mapsym", glkunix_arg_ValueFollows, (char *) "-mapsym" }, + { (char *) "--mapsym", glkunix_arg_ValueFollows, (char *) "--mapsym Specify mapping glyphs" }, + { (char *) "-mapsize", glkunix_arg_NumberValue, (char *) "-mapsize" }, + { (char *) "--mapsize", glkunix_arg_NumberValue, (char *) "--mapsize Specify map size" }, + { (char *) "-maploc", glkunix_arg_ValueFollows, (char *) "-maploc" }, + { (char *) "--maploc", glkunix_arg_ValueFollows, (char *) "--maploc Specify map location" }, + { (char *) "-terpnum", glkunix_arg_NumberValue, (char *) "-terpnum" }, + { (char *) "--terpnum", glkunix_arg_NumberValue, (char *) "--terpnum Specify interpreter number" }, + { (char *) "-terpver", glkunix_arg_ValueFollows, (char *) "-terpver" }, + { (char *) "--terpver", glkunix_arg_ValueFollows, (char *) "--terpver Specify interpreter version" }, + { NULL, glkunix_arg_End, NULL } +}; + +static void code_ignore(int flag) +#line 6 "nitfol.opt" +{ ignore_errors = flag; } + +static void code_fullname(int flag) +#line 9 "nitfol.opt" +{ fullname = flag; } + +static void code_command(strid_t stream) +#line 12 "nitfol.opt" +{ if(stream) input_stream1 = stream; } + +static void code_pirate(int flag) +#line 15 "nitfol.opt" +{ aye_matey = flag; } + +static void code_quiet(int flag) +#line 18 "nitfol.opt" +{ quiet = flag; } + +static void code_spell(int flag) +#line 21 "nitfol.opt" +{ do_spell_correct = flag; } + +static void code_expand(int flag) +#line 24 "nitfol.opt" +{ do_expand = flag; } + +static void code_symbols(strid_t stream) +#line 27 "nitfol.opt" +{ if(stream) init_infix(stream); } + +static void code_tandy(int flag) +#line 30 "nitfol.opt" +{ do_tandy = flag; } + +static void code_transcript(strid_t stream) +#line 33 "nitfol.opt" +{ if(stream) set_transcript(stream); } + +static void code_debug(int flag) +#line 36 "nitfol.opt" +{ enter_debugger = flag; do_check_watches = flag; } + +static void code_prompt(const char *string) +#line 39 "nitfol.opt" +{ n_free(db_prompt); db_prompt = n_strdup(string); } + +static void code_path(const char *string) +#line 42 "nitfol.opt" +{ n_free(search_path); search_path = n_strdup(string); } + +static void code_autoundo(int flag) +#line 45 "nitfol.opt" +{ auto_save_undo = flag; } + +static void code_stacklimit(int number) +#line 52 "nitfol.opt" +{ stacklimit = number; } + +static void code_alias(const char *string) +#line 55 "nitfol.opt" +{ if(string) parse_new_alias(string, FALSE); } + +static void code_ralias(const char *string) +#line 58 "nitfol.opt" +{ if(string) parse_new_alias(string, TRUE); } + +static void code_unalias(const char *string) +#line 61 "nitfol.opt" +{ if(string) remove_alias(string); } + +static void code_random(int number) +#line 64 "nitfol.opt" +{ faked_random_seed = number; } + +static void code_mapsym(const char *string) +#line 67 "nitfol.opt" +{ n_free(roomsymbol); roomsymbol = n_strdup(string); } + +static void code_mapsize(int number) +#line 70 "nitfol.opt" +{ automap_size = number; } + +static void code_maploc(const char *string) +#line 73 "nitfol.opt" +{ switch(glk_char_to_lower(*string)) { case 'a': case 't': case 'u': automap_split = winmethod_Above; break; case 'b': case 'd': automap_split = winmethod_Below; break; case 'l': automap_split = winmethod_Left; break; case 'r': automap_split = winmethod_Right; } } + +static void code_terpnum(int number) +#line 76 "nitfol.opt" +{ interp_num = number; } + +static void code_terpver(const char *string) +#line 79 "nitfol.opt" +{ if(string) { if(n_strlen(string) == 1) interp_ver = *string; else interp_ver = n_strtol(string, NULL, 10); } } + +#line 760 "opt2glkc.pl" +typedef enum { option_flag, option_file, option_wfile, option_number, option_string } option_type; +typedef struct { const char *longname; char shortname; const char *description; option_type type; void (*int_func)(int); int defint; void (*str_func)(strid_t); strid_t defstream; void (*string_func)(const char *); const char *defstring; } option_option; + +static option_option options[] = { + { "ignore", 'i', "Ignore Z-machine strictness errors", option_flag, code_ignore, 0, NULL, NULL, NULL, NULL }, + { "fullname", 'f', "For running under Emacs or DDD", option_flag, code_fullname, 0, NULL, NULL, NULL, NULL }, + { "command", 'x', "Read commands from this file", option_file, NULL, 0, code_command, NULL, NULL, NULL }, + { "pirate", 'P', "Aye, matey", option_flag, code_pirate, 0, NULL, NULL, NULL, NULL }, + { "quiet", 'q', "Do not print introductory messages", option_flag, code_quiet, 1, NULL, NULL, NULL, NULL }, + { "spell", '-', "Perform spelling correction", option_flag, code_spell, 1, NULL, NULL, NULL, NULL }, + { "expand", '-', "Expand one letter abbreviations", option_flag, code_expand, 1, NULL, NULL, NULL, NULL }, + { "symbols", 's', "Specify symbol file for game", option_file, NULL, 0, code_symbols, NULL, NULL, NULL }, + { "tandy", 't', "Censors some Infocom games", option_flag, code_tandy, 0, NULL, NULL, NULL, NULL }, + { "transcript", 'T', "Write transcript to this file", option_wfile, NULL, 0, code_transcript, NULL, NULL, NULL }, + { "debug", 'd', "Enter debugger immediatly", option_flag, code_debug, 0, NULL, NULL, NULL, NULL }, + { "prompt", '-', "Specify debugging prompt", option_string, NULL, 0, NULL, NULL, code_prompt, "(nitfol) " }, + { "path", '-', "Look for games in this directory", option_string, NULL, 0, NULL, NULL, code_path, NULL }, + { "autoundo", '-', "Ensure '@save_undo' is called every turn", option_flag, code_autoundo, 1, NULL, NULL, NULL, NULL }, + { "stacklimit", 'S', "Exit when the stack is this deep", option_number, code_stacklimit, 0, NULL, NULL, NULL, NULL }, + { "alias", 'a', "Specify an alias", option_string, NULL, 0, NULL, NULL, code_alias, NULL }, + { "ralias", '-', "Specify an recursive alias", option_string, NULL, 0, NULL, NULL, code_ralias, NULL }, + { "unalias", '-', "Remove an alias", option_string, NULL, 0, NULL, NULL, code_unalias, NULL }, + { "random", 'r', "Set random seed", option_number, code_random, 0, NULL, NULL, NULL, NULL }, + { "mapsym", '-', "Specify mapping glyphs", option_string, NULL, 0, NULL, NULL, code_mapsym, "*udb@UDB+" }, + { "mapsize", '-', "Specify map size", option_number, code_mapsize, 12, NULL, NULL, NULL, NULL }, + { "maploc", '-', "Specify map location", option_string, NULL, 0, NULL, NULL, code_maploc, "above" }, + { "terpnum", '-', "Specify interpreter number", option_number, code_terpnum, 2, NULL, NULL, NULL, NULL }, + { "terpver", '-', "Specify interpreter version", option_string, NULL, 0, NULL, NULL, code_terpver, "N" } +}; + +#line 811 "opt2glkc.pl" +static void set_defaults(void) +{ + unsigned n; + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(options[n].int_func) + options[n].int_func(options[n].defint); + if(options[n].str_func) + options[n].str_func(options[n].defstream); + if(options[n].string_func) + options[n].string_func(options[n].defstring); + } +} + +#line 829 "opt2glkc.pl" +static void read_textpref(strid_t pref, const char *progname) +{ + unsigned n; + char buffer[1024]; + int prognamelen = n_strlen(progname); + if(!pref) + return; + while(glk_get_line_stream(pref, buffer, sizeof(buffer))) { + char *optname; + char *optval; + long int optnum; + + if(buffer[0] == '#') + continue; + while(buffer[0] == '[') { + if(n_strncasecmp(buffer+1, progname, prognamelen) != 0 + || buffer[1+prognamelen] != ']') { + while(glk_get_line_stream(pref, buffer, sizeof(buffer))) + if(buffer[0] == '[') + break; + } else { + glk_get_line_stream(pref, buffer, sizeof(buffer)); + } + } + + optname = buffer; + while(isspace(*optname)) + optname++; + if((optval = n_strchr(optname, '=')) != NULL) { + char *p; + *optval = 0; + optval++; + + if((p = n_strchr(optname, ' ')) != NULL) + *p = 0; + + while(isspace(*optval)) + optval++; + + while(isspace(optval[strlen(optval)-1])) + optval[strlen(optval)-1] = 0; + + optnum = n_strtol(optval, NULL, 0); + if(n_strcasecmp(optval, "false") == 0 + || n_strcasecmp(optval, "f") == 0) + optnum = FALSE; + if(n_strcasecmp(optval, "true") == 0 + || n_strcasecmp(optval, "t") == 0) + optnum = TRUE; + + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(n_strcmp(options[n].longname, optname) == 0) { + switch(options[n].type) { + case option_flag: + case option_number: + options[n].int_func(optnum); + break; + case option_file: + options[n].str_func(startup_open(optval)); + break; + case option_wfile: + options[n].str_func(startup_wopen(optval)); + break; + case option_string: + options[n].string_func(optval); + break; + } + break; + } + } + } + } + glk_stream_close(pref, NULL); +} + +#line 910 "opt2glkc.pl" +static void show_help(void) +{ + unsigned n; + printf("Usage: nitfol [OPTIONS] gamefile\n"); + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(options[n].shortname != '-') + printf(" -%c, ", options[n].shortname); + else + printf(" "); + printf("-%-15s %s\n", options[n].longname, options[n].description); + } +} + +#line 928 "opt2glkc.pl" +static BOOL parse_commands(int argc, char **argv) +{ + int i; + unsigned n; + + for(i = 1; i < argc; i++) { + BOOL flag = TRUE; + + const char *p = argv[i]; + + if(p[0] == '-') { + BOOL found = FALSE; + + while(*p == '-') + p++; + if(n_strncmp(p, "no-", 3) == 0) { + flag = FALSE; + p+=3; + } + + if(n_strcasecmp(p, "help") == 0) { + show_help(); + exit(0); + } + if(n_strcasecmp(p, "version") == 0) { + printf("nitfol version %d.%d\n", NITFOL_MAJOR, NITFOL_MINOR); + exit(0); + } + + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if((n_strlen(p) == 1 && *p == options[n].shortname) || + n_strcmp(options[n].longname, p) == 0) { + found = TRUE; + + switch(options[n].type) { + case option_flag: + options[n].int_func(flag); + break; + + case option_file: + i++; + options[n].str_func(startup_open(argv[i])); + break; + + case option_wfile: + i++; + options[n].str_func(startup_wopen(argv[i])); + break; + + case option_number: + i++; + options[n].int_func(n_strtol(argv[i], NULL, 0)); + break; + + case option_string: + i++; + options[n].string_func(argv[i]); + break; + } + } + } + + if(!found) + return FALSE; + + } else { + strid_t s = startup_open(argv[i]); + if(!s) + return FALSE; + if(!game_use_file(s)) + return FALSE; + } + } + + return TRUE; +} + +#line 415 "opt2glkc.pl" + +#ifdef DEBUGGING +static void sighandle(int unused); + +static void sighandle(int unused) +{ +/* signal(SIGINT, sighandle); */ /* SysV resets default behaviour - foil it */ + enter_debugger = TRUE; +} +#endif + +#ifdef __cplusplus +extern "C" { +#endif +int glkunix_startup_code(glkunix_startup_t *data) +{ + strid_t pref; + const char *configname; + char *configdir, *prefname; + char *execname; + char *p; + username = getenv("LOGNAME"); /* SysV */ + if(!username) + username = getenv("USER"); /* BSD */ + +#ifdef DEBUGGING +/* signal(SIGINT, sighandle); */ +#endif + + execname = n_strrchr(data->argv[0], '/'); + + if(execname) + execname++; + else + execname = data->argv[0]; + + set_defaults(); + configdir = n_strdup(data->argv[0]); if(n_strrchr(configdir, '/')) *n_strrchr(configdir, '/') = 0; + configname = "/nitfol.cfg"; + prefname = n_malloc(n_strlen(configdir) + n_strlen(configname) + 1); + n_strcpy(prefname, configdir); + n_strcat(prefname, configname); + pref = glkunix_stream_open_pathname(prefname, fileusage_Data | fileusage_TextMode, 0); + n_free(configdir); + n_free(prefname); + read_textpref(pref, execname); + + p = getenv("INFOCOM_PATH"); + if(p) { + free(search_path); + search_path = n_strdup(p); + } + + return parse_commands(data->argc, data->argv); +} +#ifdef __cplusplus +} +#endif diff --git a/interpreters/nitfol/startmac.c b/interpreters/nitfol/startmac.c new file mode 100644 index 0000000..94bdfd9 --- /dev/null +++ b/interpreters/nitfol/startmac.c @@ -0,0 +1,82 @@ +#line 576 "opt2glkc.pl" +#include "nitfol.h" +#include "macglk_startup.h" + +static strid_t mac_gamefile; + +static BOOL hashandle = FALSE; +static AliasHandle gamehandle; +#line 694 "opt2glkc.pl" +strid_t startup_findfile(void) +{ + ; +} +#line 586 "opt2glkc.pl" +strid_t intd_filehandle_open(strid_t savefile, glui32 operating_id, + glui32 contents_id, glui32 interp_id, + glui32 length) +{ + FSSpec file; + Boolean wasChanged; + if(operating_id != 0x4d414353 /* 'MACS' */) + return 0; + if(contents_id != 0) + return 0; + if(interp_id != 0x20202020 /* ' ' */) + return 0; + + gamehandle = NewHandle(length); + glk_get_buffer_stream(savefile, *gamehandle, length); + hashandle = TRUE; + ResolveAlias(NULL, gamehandle, &file, &wasChanged); + return macglk_stream_open_fsspec(&file, 0, 0); +} + +void intd_filehandle_make(strid_t savefile) +{ + if(!hashandle) + return; + glk_put_string_stream(savefile, "MACS"); + glk_put_char_stream(savefile, b00000010); /* Flags */ + glk_put_char_stream(savefile, 0); /* Contents ID */ + glk_put_char_stream(savefile, 0); /* Reserved */ + glk_put_char_stream(savefile, 0); /* Reserved */ + glk_put_string_stream(savefile, " ");/* Interpreter ID */ + glk_put_buffer_stream(savefile, *gamehandle, *gamehandle->aliasSize); +} + +glui32 intd_get_size(void) +{ + if(!hashandle) + return 0; + return *gamehandle->aliasSize + 12; +} + +static Boolean mac_whenselected(FSSpec *file, OSType filetype) +{ + NewAlias(NULL, file, &gamehandle); + hashandle = TRUE; + return game_use_file(mac_gamefile); +} + +static Boolean mac_whenbuiltin() +{ + return game_use_file(mac_gamefile); +} + +Boolean macglk_startup_code(macglk_startup_t *data) +{ + OSType mac_gamefile_types[] = { 0x5a434f44 /* 'ZCOD' */, 0x49465253 /* 'IFRS' */, 0x49465a53 /* 'IFZS' */ }; + + data->startup_model = macglk_model_ChooseOrBuiltIn; + data->app_creator = 0x6e695466 /* 'niTf' */; + data->gamefile_types = mac_gamefile_types; + data->num_gamefile_types = sizeof(mac_gamefile_types) / sizeof(*mac_gamefile_types); + data->savefile_type = 0x49465a53 /* 'IFZS' */; + data->datafile_type = 0x5a697044 /* 'ZipD' */; + data->gamefile = &mac_gamefile; + data->when_selected = mac_whenselected; + data->when_builtin = mac_whenbuiltin; + + return TRUE; +} diff --git a/interpreters/nitfol/startunix.c b/interpreters/nitfol/startunix.c new file mode 100644 index 0000000..945ac3e --- /dev/null +++ b/interpreters/nitfol/startunix.c @@ -0,0 +1,614 @@ +#line 228 "opt2glkc.pl" +#include "nitfol.h" +#include "glkstart.h" +#ifdef DEBUGGING +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +static char *game_filename = NULL; + +static void set_game_filename(const char *name) +{ + n_free(game_filename); + game_filename = 0; + +#if defined(_GNU_SOURCE) + game_filename = canonicalize_file_name(name); +#else +#if defined(_BSD_SOURCE) || defined(_XOPEN_SOURCE) + game_filename = (char *) n_malloc(PATH_MAX); + if(!realpath(name, game_filename)) { + n_free(game_filename); + game_filename = 0; + } +#else +#ifdef __DJGPP__ + game_filename = (char *) n_malloc(FILENAME_MAX); + _fixpath(name, game_filename); +#endif +#endif +#endif + + if(!game_filename) + game_filename = n_strdup(name); +} + + +strid_t startup_findfile(void) +{ + static DIR *dir = NULL; + static char *pathstart = NULL; + static char *path = NULL; + strid_t stream; + struct dirent *d; + char *name = NULL; + + if(!pathstart) { + char *p = search_path; + if(!p) + return 0; + pathstart = n_strdup(p); + if(!(path = n_strtok(pathstart, ":"))) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + + do { + if(!dir) { + dir = opendir(path); + if(!dir) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + d = readdir(dir); + if(!d) { + closedir(dir); + dir = NULL; + if(!(path = n_strtok(NULL, ":"))) { + n_free(pathstart); + pathstart = 0; + return 0; + } + } + } while(!dir); + + name = (char *) n_malloc(n_strlen(path) + n_strlen(d->d_name) + 2); + n_strcpy(name, path); + n_strcat(name, "/"); + n_strcat(name, d->d_name); + stream = glkunix_stream_open_pathname(name, fileusage_Data | + fileusage_BinaryMode, 0); + if(stream) + set_game_filename(name); + n_free(name); + return stream; +} + + +strid_t intd_filehandle_open(strid_t savefile, glui32 operating_id, + glui32 contents_id, glui32 interp_id, + glui32 length) +{ + char *name; + strid_t str; + if(operating_id != 0x554e4958 /* 'UNIX' */) + return 0; + if(contents_id != 0) + return 0; + if(interp_id != 0x20202020 /* ' ' */) + return 0; + + name = (char *) n_malloc(length+1); + glk_get_buffer_stream(savefile, name, length); + name[length] = 0; + str = glkunix_stream_open_pathname(name, fileusage_Data | + fileusage_BinaryMode, 0); + if(str) + set_game_filename(name); + n_free(name); + return str; +} + +void intd_filehandle_make(strid_t savefile) +{ + if(!game_filename) + return; + w_glk_put_string_stream(savefile, "UNIX"); + glk_put_char_stream(savefile, b00000010); /* Flags */ + glk_put_char_stream(savefile, 0); /* Contents ID */ + glk_put_char_stream(savefile, 0); /* Reserved */ + glk_put_char_stream(savefile, 0); /* Reserved */ + w_glk_put_string_stream(savefile, " "); /* Interpreter ID */ + w_glk_put_string_stream(savefile, game_filename); +} + +glui32 intd_get_size(void) +{ + if(!game_filename) + return 0; + return n_strlen(game_filename) + 12; +} + +strid_t startup_open(const char *name) +{ + strid_t str; + char *s; + + str = glkunix_stream_open_pathname((char *) name, fileusage_Data | fileusage_BinaryMode, 0); + if(str) { + set_game_filename(name); + s = strrchr(name, '\\'); + if (!s) s = strrchr(name, '/'); + garglk_set_story_name(s ? s + 1 : name); + } else { + char *path = search_path; + if(path) { + char *p; + char *newname = (char *) n_malloc(strlen(path) + strlen(name) + 2); + path = n_strdup(path); + for(p = n_strtok(path, ":"); p; p = n_strtok(NULL, ":")) { + n_strcpy(newname, p); + n_strcat(newname, "/"); + n_strcat(newname, name); + str = glkunix_stream_open_pathname((char *) newname, fileusage_Data | + fileusage_BinaryMode, 0); + if(str) { + set_game_filename(newname); + s = strrchr(newname, '\\'); + if (!s) s = strrchr(newname, '/'); + garglk_set_story_name(s ? s + 1 : newname); + break; + } + } + n_free(path); + } + } + + if(!str) + fprintf(stderr, "Cannot open '%s'\n", name); + + return str; +} + +#line 717 "opt2glkc.pl" +static strid_t startup_wopen(const char *name) +{ + return n_file_name(fileusage_Data | fileusage_BinaryMode, + filemode_Write, name); +} +glkunix_argumentlist_t glkunix_arguments[] = { + { (char *) "", glkunix_arg_ValueCanFollow, (char *) "filename file to load" }, + { (char *) "-help", glkunix_arg_NoValue, (char *) "list command-line options" }, + { (char *) "--help", glkunix_arg_NoValue, (char *) "list command-line options" }, + { (char *) "-version", glkunix_arg_NoValue, (char *) "get version number" }, + { (char *) "--version", glkunix_arg_NoValue, (char *) "get version number" }, + { (char *) "-i", glkunix_arg_NoValue, (char *) "-i" }, + { (char *) "-no-ignore", glkunix_arg_NoValue, (char *) "-no-ignore" }, + { (char *) "--no-ignore", glkunix_arg_NoValue, (char *) "--no-ignore" }, + { (char *) "-ignore", glkunix_arg_NoValue, (char *) "-ignore" }, + { (char *) "--ignore", glkunix_arg_NoValue, (char *) "--ignore Ignore Z-machine strictness errors" }, + { (char *) "-f", glkunix_arg_NoValue, (char *) "-f" }, + { (char *) "-no-fullname", glkunix_arg_NoValue, (char *) "-no-fullname" }, + { (char *) "--no-fullname", glkunix_arg_NoValue, (char *) "--no-fullname" }, + { (char *) "-fullname", glkunix_arg_NoValue, (char *) "-fullname" }, + { (char *) "--fullname", glkunix_arg_NoValue, (char *) "--fullname For running under Emacs or DDD" }, + { (char *) "-x", glkunix_arg_ValueFollows, (char *) "-x" }, + { (char *) "-command", glkunix_arg_ValueFollows, (char *) "-command" }, + { (char *) "--command", glkunix_arg_ValueFollows, (char *) "--command Read commands from this file" }, + { (char *) "-P", glkunix_arg_NoValue, (char *) "-P" }, + { (char *) "-no-pirate", glkunix_arg_NoValue, (char *) "-no-pirate" }, + { (char *) "--no-pirate", glkunix_arg_NoValue, (char *) "--no-pirate" }, + { (char *) "-pirate", glkunix_arg_NoValue, (char *) "-pirate" }, + { (char *) "--pirate", glkunix_arg_NoValue, (char *) "--pirate Aye, matey" }, + { (char *) "-q", glkunix_arg_NoValue, (char *) "-q" }, + { (char *) "-no-quiet", glkunix_arg_NoValue, (char *) "-no-quiet" }, + { (char *) "--no-quiet", glkunix_arg_NoValue, (char *) "--no-quiet" }, + { (char *) "-quiet", glkunix_arg_NoValue, (char *) "-quiet" }, + { (char *) "--quiet", glkunix_arg_NoValue, (char *) "--quiet Do not print introductory messages" }, + { (char *) "-no-spell", glkunix_arg_NoValue, (char *) "-no-spell" }, + { (char *) "--no-spell", glkunix_arg_NoValue, (char *) "--no-spell" }, + { (char *) "-spell", glkunix_arg_NoValue, (char *) "-spell" }, + { (char *) "--spell", glkunix_arg_NoValue, (char *) "--spell Perform spelling correction" }, + { (char *) "-no-expand", glkunix_arg_NoValue, (char *) "-no-expand" }, + { (char *) "--no-expand", glkunix_arg_NoValue, (char *) "--no-expand" }, + { (char *) "-expand", glkunix_arg_NoValue, (char *) "-expand" }, + { (char *) "--expand", glkunix_arg_NoValue, (char *) "--expand Expand one letter abbreviations" }, + { (char *) "-s", glkunix_arg_ValueFollows, (char *) "-s" }, + { (char *) "-symbols", glkunix_arg_ValueFollows, (char *) "-symbols" }, + { (char *) "--symbols", glkunix_arg_ValueFollows, (char *) "--symbols Specify symbol file for game" }, + { (char *) "-t", glkunix_arg_NoValue, (char *) "-t" }, + { (char *) "-no-tandy", glkunix_arg_NoValue, (char *) "-no-tandy" }, + { (char *) "--no-tandy", glkunix_arg_NoValue, (char *) "--no-tandy" }, + { (char *) "-tandy", glkunix_arg_NoValue, (char *) "-tandy" }, + { (char *) "--tandy", glkunix_arg_NoValue, (char *) "--tandy Censors some Infocom games" }, + { (char *) "-T", glkunix_arg_ValueFollows, (char *) "-T" }, + { (char *) "-transcript", glkunix_arg_ValueFollows, (char *) "-transcript" }, + { (char *) "--transcript", glkunix_arg_ValueFollows, (char *) "--transcript Write transcript to this file" }, + { (char *) "-d", glkunix_arg_NoValue, (char *) "-d" }, + { (char *) "-no-debug", glkunix_arg_NoValue, (char *) "-no-debug" }, + { (char *) "--no-debug", glkunix_arg_NoValue, (char *) "--no-debug" }, + { (char *) "-debug", glkunix_arg_NoValue, (char *) "-debug" }, + { (char *) "--debug", glkunix_arg_NoValue, (char *) "--debug Enter debugger immediatly" }, + { (char *) "-prompt", glkunix_arg_ValueFollows, (char *) "-prompt" }, + { (char *) "--prompt", glkunix_arg_ValueFollows, (char *) "--prompt Specify debugging prompt" }, + { (char *) "-path", glkunix_arg_ValueFollows, (char *) "-path" }, + { (char *) "--path", glkunix_arg_ValueFollows, (char *) "--path Look for games in this directory" }, + { (char *) "-no-autoundo", glkunix_arg_NoValue, (char *) "-no-autoundo" }, + { (char *) "--no-autoundo", glkunix_arg_NoValue, (char *) "--no-autoundo" }, + { (char *) "-autoundo", glkunix_arg_NoValue, (char *) "-autoundo" }, + { (char *) "--autoundo", glkunix_arg_NoValue, (char *) "--autoundo Ensure @code{@@save_undo} is called every turn" }, + { (char *) "-S", glkunix_arg_NumberValue, (char *) "-S" }, + { (char *) "-stacklimit", glkunix_arg_NumberValue, (char *) "-stacklimit" }, + { (char *) "--stacklimit", glkunix_arg_NumberValue, (char *) "--stacklimit Exit when the stack is this deep" }, + { (char *) "-a", glkunix_arg_ValueFollows, (char *) "-a" }, + { (char *) "-alias", glkunix_arg_ValueFollows, (char *) "-alias" }, + { (char *) "--alias", glkunix_arg_ValueFollows, (char *) "--alias Specify an alias" }, + { (char *) "-ralias", glkunix_arg_ValueFollows, (char *) "-ralias" }, + { (char *) "--ralias", glkunix_arg_ValueFollows, (char *) "--ralias Specify an recursive alias" }, + { (char *) "-unalias", glkunix_arg_ValueFollows, (char *) "-unalias" }, + { (char *) "--unalias", glkunix_arg_ValueFollows, (char *) "--unalias Remove an alias" }, + { (char *) "-r", glkunix_arg_NumberValue, (char *) "-r" }, + { (char *) "-random", glkunix_arg_NumberValue, (char *) "-random" }, + { (char *) "--random", glkunix_arg_NumberValue, (char *) "--random Set random seed" }, + { (char *) "-mapsym", glkunix_arg_ValueFollows, (char *) "-mapsym" }, + { (char *) "--mapsym", glkunix_arg_ValueFollows, (char *) "--mapsym Specify mapping glyphs" }, + { (char *) "-mapsize", glkunix_arg_NumberValue, (char *) "-mapsize" }, + { (char *) "--mapsize", glkunix_arg_NumberValue, (char *) "--mapsize Specify map size" }, + { (char *) "-maploc", glkunix_arg_ValueFollows, (char *) "-maploc" }, + { (char *) "--maploc", glkunix_arg_ValueFollows, (char *) "--maploc Specify map location" }, + { (char *) "-terpnum", glkunix_arg_NumberValue, (char *) "-terpnum" }, + { (char *) "--terpnum", glkunix_arg_NumberValue, (char *) "--terpnum Specify interpreter number" }, + { (char *) "-terpver", glkunix_arg_ValueFollows, (char *) "-terpver" }, + { (char *) "--terpver", glkunix_arg_ValueFollows, (char *) "--terpver Specify interpreter version" }, + { NULL, glkunix_arg_End, NULL } +}; + +static void code_ignore(int flag) +#line 6 "nitfol.opt" +{ ignore_errors = flag; } + +static void code_fullname(int flag) +#line 9 "nitfol.opt" +{ fullname = flag; } + +static void code_command(strid_t stream) +#line 12 "nitfol.opt" +{ if(stream) input_stream1 = stream; } + +static void code_pirate(int flag) +#line 15 "nitfol.opt" +{ aye_matey = flag; } + +static void code_quiet(int flag) +#line 18 "nitfol.opt" +{ quiet = flag; } + +static void code_spell(int flag) +#line 21 "nitfol.opt" +{ do_spell_correct = flag; } + +static void code_expand(int flag) +#line 24 "nitfol.opt" +{ do_expand = flag; } + +static void code_symbols(strid_t stream) +#line 27 "nitfol.opt" +{ if(stream) init_infix(stream); } + +static void code_tandy(int flag) +#line 30 "nitfol.opt" +{ do_tandy = flag; } + +static void code_transcript(strid_t stream) +#line 33 "nitfol.opt" +{ if(stream) set_transcript(stream); } + +static void code_debug(int flag) +#line 36 "nitfol.opt" +{ enter_debugger = flag; do_check_watches = flag; } + +static void code_prompt(const char *string) +#line 39 "nitfol.opt" +{ n_free(db_prompt); db_prompt = n_strdup(string); } + +static void code_path(const char *string) +#line 42 "nitfol.opt" +{ n_free(search_path); search_path = n_strdup(string); } + +static void code_autoundo(int flag) +#line 45 "nitfol.opt" +{ auto_save_undo = flag; } + +static void code_stacklimit(int number) +#line 52 "nitfol.opt" +{ stacklimit = number; } + +static void code_alias(const char *string) +#line 55 "nitfol.opt" +{ if(string) parse_new_alias(string, FALSE); } + +static void code_ralias(const char *string) +#line 58 "nitfol.opt" +{ if(string) parse_new_alias(string, TRUE); } + +static void code_unalias(const char *string) +#line 61 "nitfol.opt" +{ if(string) remove_alias(string); } + +static void code_random(int number) +#line 64 "nitfol.opt" +{ faked_random_seed = number; } + +static void code_mapsym(const char *string) +#line 67 "nitfol.opt" +{ n_free(roomsymbol); roomsymbol = n_strdup(string); } + +static void code_mapsize(int number) +#line 70 "nitfol.opt" +{ automap_size = number; } + +static void code_maploc(const char *string) +#line 73 "nitfol.opt" +{ switch(glk_char_to_lower(*string)) { case 'a': case 't': case 'u': automap_split = winmethod_Above; break; case 'b': case 'd': automap_split = winmethod_Below; break; case 'l': automap_split = winmethod_Left; break; case 'r': automap_split = winmethod_Right; } } + +static void code_terpnum(int number) +#line 76 "nitfol.opt" +{ interp_num = number; } + +static void code_terpver(const char *string) +#line 79 "nitfol.opt" +{ if(string) { if(n_strlen(string) == 1) interp_ver = *string; else interp_ver = n_strtol(string, NULL, 10); } } + +#line 760 "opt2glkc.pl" +typedef enum { option_flag, option_file, option_wfile, option_number, option_string } option_type; +typedef struct { const char *longname; char shortname; const char *description; option_type type; void (*int_func)(int); int defint; void (*str_func)(strid_t); strid_t defstream; void (*string_func)(const char *); const char *defstring; } option_option; + +static option_option options[] = { + { "ignore", 'i', "Ignore Z-machine strictness errors", option_flag, code_ignore, 1, NULL, NULL, NULL, NULL }, + { "fullname", 'f', "For running under Emacs or DDD", option_flag, code_fullname, 0, NULL, NULL, NULL, NULL }, + { "command", 'x', "Read commands from this file", option_file, NULL, 0, code_command, NULL, NULL, NULL }, + { "pirate", 'P', "Aye, matey", option_flag, code_pirate, 0, NULL, NULL, NULL, NULL }, + { "quiet", 'q', "Do not print introductory messages", option_flag, code_quiet, 1, NULL, NULL, NULL, NULL }, + { "spell", '-', "Perform spelling correction", option_flag, code_spell, 1, NULL, NULL, NULL, NULL }, + { "expand", '-', "Expand one letter abbreviations", option_flag, code_expand, 1, NULL, NULL, NULL, NULL }, + { "symbols", 's', "Specify symbol file for game", option_file, NULL, 0, code_symbols, NULL, NULL, NULL }, + { "tandy", 't', "Censors some Infocom games", option_flag, code_tandy, 0, NULL, NULL, NULL, NULL }, + { "transcript", 'T', "Write transcript to this file", option_wfile, NULL, 0, code_transcript, NULL, NULL, NULL }, + { "debug", 'd', "Enter debugger immediatly", option_flag, code_debug, 0, NULL, NULL, NULL, NULL }, + { "prompt", '-', "Specify debugging prompt", option_string, NULL, 0, NULL, NULL, code_prompt, "(nitfol) " }, + { "path", '-', "Look for games in this directory", option_string, NULL, 0, NULL, NULL, code_path, NULL }, + { "autoundo", '-', "Ensure '@save_undo' is called every turn", option_flag, code_autoundo, 1, NULL, NULL, NULL, NULL }, + { "stacklimit", 'S', "Exit when the stack is this deep", option_number, code_stacklimit, 0, NULL, NULL, NULL, NULL }, + { "alias", 'a', "Specify an alias", option_string, NULL, 0, NULL, NULL, code_alias, NULL }, + { "ralias", '-', "Specify an recursive alias", option_string, NULL, 0, NULL, NULL, code_ralias, NULL }, + { "unalias", '-', "Remove an alias", option_string, NULL, 0, NULL, NULL, code_unalias, NULL }, + { "random", 'r', "Set random seed", option_number, code_random, 0, NULL, NULL, NULL, NULL }, + { "mapsym", '-', "Specify mapping glyphs", option_string, NULL, 0, NULL, NULL, code_mapsym, "*udb@UDB+" }, + { "mapsize", '-', "Specify map size", option_number, code_mapsize, 12, NULL, NULL, NULL, NULL }, + { "maploc", '-', "Specify map location", option_string, NULL, 0, NULL, NULL, code_maploc, "above" }, + { "terpnum", '-', "Specify interpreter number", option_number, code_terpnum, 2, NULL, NULL, NULL, NULL }, + { "terpver", '-', "Specify interpreter version", option_string, NULL, 0, NULL, NULL, code_terpver, "N" } +}; + +#line 811 "opt2glkc.pl" +static void set_defaults(void) +{ + unsigned n; + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(options[n].int_func) + options[n].int_func(options[n].defint); + if(options[n].str_func) + options[n].str_func(options[n].defstream); + if(options[n].string_func) + options[n].string_func(options[n].defstring); + } +} + +#line 829 "opt2glkc.pl" +static void read_textpref(strid_t pref, const char *progname) +{ + unsigned n; + char buffer[1024]; + int prognamelen = n_strlen(progname); + if(!pref) + return; + while(glk_get_line_stream(pref, buffer, sizeof(buffer))) { + char *optname; + char *optval; + long int optnum; + + if(buffer[0] == '#') + continue; + while(buffer[0] == '[') { + if(n_strncasecmp(buffer+1, progname, prognamelen) != 0 + || buffer[1+prognamelen] != ']') { + while(glk_get_line_stream(pref, buffer, sizeof(buffer))) + if(buffer[0] == '[') + break; + } else { + glk_get_line_stream(pref, buffer, sizeof(buffer)); + } + } + + optname = buffer; + while(isspace(*optname)) + optname++; + if((optval = n_strchr(optname, '=')) != NULL) { + char *p; + *optval = 0; + optval++; + + if((p = n_strchr(optname, ' ')) != NULL) + *p = 0; + + while(isspace(*optval)) + optval++; + + while(isspace(optval[strlen(optval)-1])) + optval[strlen(optval)-1] = 0; + + optnum = n_strtol(optval, NULL, 0); + if(n_strcasecmp(optval, "false") == 0 + || n_strcasecmp(optval, "f") == 0) + optnum = FALSE; + if(n_strcasecmp(optval, "true") == 0 + || n_strcasecmp(optval, "t") == 0) + optnum = TRUE; + + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(n_strcmp(options[n].longname, optname) == 0) { + switch(options[n].type) { + case option_flag: + case option_number: + options[n].int_func(optnum); + break; + case option_file: + options[n].str_func(startup_open(optval)); + break; + case option_wfile: + options[n].str_func(startup_wopen(optval)); + break; + case option_string: + options[n].string_func(optval); + break; + } + break; + } + } + } + } + glk_stream_close(pref, NULL); +} + +#line 910 "opt2glkc.pl" +static void show_help(void) +{ + unsigned n; + printf("Usage: nitfol [OPTIONS] gamefile\n"); + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(options[n].shortname != '-') + printf(" -%c, ", options[n].shortname); + else + printf(" "); + printf("-%-15s %s\n", options[n].longname, options[n].description); + } +} + +#line 928 "opt2glkc.pl" +static BOOL parse_commands(int argc, char **argv) +{ + int i; + unsigned n; + + for(i = 1; i < argc; i++) { + BOOL flag = TRUE; + + const char *p = argv[i]; + + if(p[0] == '-') { + BOOL found = FALSE; + + while(*p == '-') + p++; + if(n_strncmp(p, "no-", 3) == 0) { + flag = FALSE; + p+=3; + } + + if(n_strcasecmp(p, "help") == 0) { + show_help(); + exit(0); + } + if(n_strcasecmp(p, "version") == 0) { + printf("nitfol version %d.%d\n", NITFOL_MAJOR, NITFOL_MINOR); + exit(0); + } + + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if((n_strlen(p) == 1 && *p == options[n].shortname) || + n_strcmp(options[n].longname, p) == 0) { + found = TRUE; + + switch(options[n].type) { + case option_flag: + options[n].int_func(flag); + break; + + case option_file: + i++; + options[n].str_func(startup_open(argv[i])); + break; + + case option_wfile: + i++; + options[n].str_func(startup_wopen(argv[i])); + break; + + case option_number: + i++; + options[n].int_func(n_strtol(argv[i], NULL, 0)); + break; + + case option_string: + i++; + options[n].string_func(argv[i]); + break; + } + } + } + + if(!found) + return FALSE; + + } else { + strid_t s = startup_open(argv[i]); + if(!s) + return FALSE; + if(!game_use_file(s)) + return FALSE; + } + } + + return TRUE; +} + +#line 415 "opt2glkc.pl" + +#ifdef DEBUGGING +static void sighandle(int unused); + +static void sighandle(int unused) +{ +/* signal(SIGINT, sighandle); */ /* SysV resets default behaviour - foil it */ + enter_debugger = TRUE; +} +#endif + +#ifdef __cplusplus +extern "C" { +#endif +int glkunix_startup_code(glkunix_startup_t *data) +{ + set_defaults(); + + garglk_set_program_name("Nitfol 0.5"); + garglk_set_program_info( + "Nitfol 0.5 by Evin Robertson\n" + "With countless patches by other people.\n"); + + return parse_commands(data->argc, data->argv); +} +#ifdef __cplusplus +} +#endif diff --git a/interpreters/nitfol/startwin.c b/interpreters/nitfol/startwin.c new file mode 100644 index 0000000..5e64c80 --- /dev/null +++ b/interpreters/nitfol/startwin.c @@ -0,0 +1,322 @@ +#line 488 "opt2glkc.pl" +#include "nitfol.h" +#include "WinGlk.h" + +#line 671 "opt2glkc.pl" +strid_t intd_filehandle_open(strid_t savefile, glui32 operating_id, + glui32 contents_id, glui32 interp_id, + glui32 length) +{ + return 0; +} + +void intd_filehandle_make(strid_t savefile) +{ + ; +} + +glui32 intd_get_size(void) +{ + return 0; +} +#line 694 "opt2glkc.pl" +strid_t startup_findfile(void) +{ + ; +} +#line 705 "opt2glkc.pl" +strid_t startup_open(const char *name) +{ + return n_file_name(fileusage_Data | fileusage_BinaryMode, + filemode_Read, name); +} +#line 717 "opt2glkc.pl" +static strid_t startup_wopen(const char *name) +{ + return n_file_name(fileusage_Data | fileusage_BinaryMode, + filemode_Write, name); +} +static void code_ignore(int flag) +#line 6 "nitfol.opt" +{ ignore_errors = flag; } + +static void code_fullname(int flag) +#line 9 "nitfol.opt" +{ fullname = flag; } + +static void code_command(strid_t stream) +#line 12 "nitfol.opt" +{ if(stream) input_stream1 = stream; } + +static void code_pirate(int flag) +#line 15 "nitfol.opt" +{ aye_matey = flag; } + +static void code_quiet(int flag) +#line 18 "nitfol.opt" +{ quiet = flag; } + +static void code_spell(int flag) +#line 21 "nitfol.opt" +{ do_spell_correct = flag; } + +static void code_expand(int flag) +#line 24 "nitfol.opt" +{ do_expand = flag; } + +static void code_symbols(strid_t stream) +#line 27 "nitfol.opt" +{ if(stream) init_infix(stream); } + +static void code_tandy(int flag) +#line 30 "nitfol.opt" +{ do_tandy = flag; } + +static void code_transcript(strid_t stream) +#line 33 "nitfol.opt" +{ if(stream) set_transcript(stream); } + +static void code_debug(int flag) +#line 36 "nitfol.opt" +{ enter_debugger = flag; do_check_watches = flag; } + +static void code_prompt(const char *string) +#line 39 "nitfol.opt" +{ n_free(db_prompt); db_prompt = n_strdup(string); } + +static void code_path(const char *string) +#line 42 "nitfol.opt" +{ n_free(search_path); search_path = n_strdup(string); } + +static void code_autoundo(int flag) +#line 45 "nitfol.opt" +{ auto_save_undo = flag; } + +static void code_stacklimit(int number) +#line 52 "nitfol.opt" +{ stacklimit = number; } + +static void code_alias(const char *string) +#line 55 "nitfol.opt" +{ if(string) parse_new_alias(string, FALSE); } + +static void code_ralias(const char *string) +#line 58 "nitfol.opt" +{ if(string) parse_new_alias(string, TRUE); } + +static void code_unalias(const char *string) +#line 61 "nitfol.opt" +{ if(string) remove_alias(string); } + +static void code_random(int number) +#line 64 "nitfol.opt" +{ faked_random_seed = number; } + +static void code_mapsym(const char *string) +#line 67 "nitfol.opt" +{ n_free(roomsymbol); roomsymbol = n_strdup(string); } + +static void code_mapsize(int number) +#line 70 "nitfol.opt" +{ automap_size = number; } + +static void code_maploc(const char *string) +#line 73 "nitfol.opt" +{ switch(glk_char_to_lower(*string)) { case 'a': case 't': case 'u': automap_split = winmethod_Above; break; case 'b': case 'd': automap_split = winmethod_Below; break; case 'l': automap_split = winmethod_Left; break; case 'r': automap_split = winmethod_Right; } } + +static void code_terpnum(int number) +#line 76 "nitfol.opt" +{ interp_num = number; } + +static void code_terpver(const char *string) +#line 79 "nitfol.opt" +{ if(string) { if(n_strlen(string) == 1) interp_ver = *string; else interp_ver = n_strtol(string, NULL, 10); } } + +#line 760 "opt2glkc.pl" +typedef enum { option_flag, option_file, option_wfile, option_number, option_string } option_type; +typedef struct { const char *longname; char shortname; const char *description; option_type type; void (*int_func)(int); int defint; void (*str_func)(strid_t); strid_t defstream; void (*string_func)(const char *); const char *defstring; } option_option; + +static option_option options[] = { + { "ignore", 'i', "Ignore Z-machine strictness errors", option_flag, code_ignore, 0, NULL, NULL, NULL, NULL }, + { "fullname", 'f', "For running under Emacs or DDD", option_flag, code_fullname, 0, NULL, NULL, NULL, NULL }, + { "command", 'x', "Read commands from this file", option_file, NULL, 0, code_command, NULL, NULL, NULL }, + { "pirate", 'P', "Aye, matey", option_flag, code_pirate, 0, NULL, NULL, NULL, NULL }, + { "quiet", 'q', "Do not print introductory messages", option_flag, code_quiet, 1, NULL, NULL, NULL, NULL }, + { "spell", '-', "Perform spelling correction", option_flag, code_spell, 1, NULL, NULL, NULL, NULL }, + { "expand", '-', "Expand one letter abbreviations", option_flag, code_expand, 1, NULL, NULL, NULL, NULL }, + { "symbols", 's', "Specify symbol file for game", option_file, NULL, 0, code_symbols, NULL, NULL, NULL }, + { "tandy", 't', "Censors some Infocom games", option_flag, code_tandy, 0, NULL, NULL, NULL, NULL }, + { "transcript", 'T', "Write transcript to this file", option_wfile, NULL, 0, code_transcript, NULL, NULL, NULL }, + { "debug", 'd', "Enter debugger immediatly", option_flag, code_debug, 0, NULL, NULL, NULL, NULL }, + { "prompt", '-', "Specify debugging prompt", option_string, NULL, 0, NULL, NULL, code_prompt, "(nitfol) " }, + { "path", '-', "Look for games in this directory", option_string, NULL, 0, NULL, NULL, code_path, NULL }, + { "autoundo", '-', "Ensure '@save_undo' is called every turn", option_flag, code_autoundo, 1, NULL, NULL, NULL, NULL }, + { "stacklimit", 'S', "Exit when the stack is this deep", option_number, code_stacklimit, 0, NULL, NULL, NULL, NULL }, + { "alias", 'a', "Specify an alias", option_string, NULL, 0, NULL, NULL, code_alias, NULL }, + { "ralias", '-', "Specify an recursive alias", option_string, NULL, 0, NULL, NULL, code_ralias, NULL }, + { "unalias", '-', "Remove an alias", option_string, NULL, 0, NULL, NULL, code_unalias, NULL }, + { "random", 'r', "Set random seed", option_number, code_random, 0, NULL, NULL, NULL, NULL }, + { "mapsym", '-', "Specify mapping glyphs", option_string, NULL, 0, NULL, NULL, code_mapsym, "*udb@UDB+" }, + { "mapsize", '-', "Specify map size", option_number, code_mapsize, 12, NULL, NULL, NULL, NULL }, + { "maploc", '-', "Specify map location", option_string, NULL, 0, NULL, NULL, code_maploc, "above" }, + { "terpnum", '-', "Specify interpreter number", option_number, code_terpnum, 2, NULL, NULL, NULL, NULL }, + { "terpver", '-', "Specify interpreter version", option_string, NULL, 0, NULL, NULL, code_terpver, "N" } +}; + +#line 811 "opt2glkc.pl" +static void set_defaults(void) +{ + unsigned n; + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(options[n].int_func) + options[n].int_func(options[n].defint); + if(options[n].str_func) + options[n].str_func(options[n].defstream); + if(options[n].string_func) + options[n].string_func(options[n].defstring); + } +} + +#line 910 "opt2glkc.pl" +static void show_help(void) +{ + unsigned n; + printf("Usage: nitfol [OPTIONS] gamefile\n"); + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if(options[n].shortname != '-') + printf(" -%c, ", options[n].shortname); + else + printf(" "); + printf("-%-15s %s\n", options[n].longname, options[n].description); + } +} + +#line 928 "opt2glkc.pl" +static BOOL parse_commands(int argc, char **argv) +{ + int i; + unsigned n; + + for(i = 1; i < argc; i++) { + BOOL flag = TRUE; + + const char *p = argv[i]; + + if(p[0] == '-') { + BOOL found = FALSE; + + while(*p == '-') + p++; + if(n_strncmp(p, "no-", 3) == 0) { + flag = FALSE; + p+=3; + } + + if(n_strcasecmp(p, "help") == 0) { + show_help(); + exit(0); + } + if(n_strcasecmp(p, "version") == 0) { + printf("nitfol version %d.%d\n", NITFOL_MAJOR, NITFOL_MINOR); + exit(0); + } + + for(n = 0; n < sizeof(options) / sizeof(*options); n++) { + if((n_strlen(p) == 1 && *p == options[n].shortname) || + n_strcmp(options[n].longname, p) == 0) { + found = TRUE; + + switch(options[n].type) { + case option_flag: + options[n].int_func(flag); + break; + + case option_file: + i++; + options[n].str_func(startup_open(argv[i])); + break; + + case option_wfile: + i++; + options[n].str_func(startup_wopen(argv[i])); + break; + + case option_number: + i++; + options[n].int_func(n_strtol(argv[i], NULL, 0)); + break; + + case option_string: + i++; + options[n].string_func(argv[i]); + break; + } + } + } + + if(!found) + return FALSE; + + } else { + strid_t s = startup_open(argv[i]); + if(!s) + return FALSE; + if(!game_use_file(s)) + return FALSE; + } + } + + return TRUE; +} + +#line 504 "opt2glkc.pl" +void shift_string_left(char *str) +{ + int len = strlen(str); + int i; + for(i = 0; i < len; i++) + str[i] = str[i+1]; +} + +int winglk_startup_code(void) +{ + BOOL status; + char *commandline = strdup(GetCommandLine()); + char **argv = (char **) n_malloc(sizeof(char *) * strlen(commandline)); + int argc = 0; + + int i; + + while(*commandline) { + while(*commandline && isspace(*commandline)) + commandline++; + + argv[argc++] = commandline; + + while(*commandline && !isspace(*commandline)) { + if(*commandline == '"') { + shift_string_left(commandline); + while(*commandline && *commandline != '"') + commandline++; + shift_string_left(commandline); + } else { + commandline++; + } + } + + *commandline++ = 0; + } + + argv[argc] = NULL; + + status = parse_commands(argc, argv); + + n_free(argv); + n_free(commandline); + + winglk_app_set_name("nitfol"); + winglk_window_set_title("nitfol"); + set_defaults(); + + return status; +} diff --git a/interpreters/nitfol/struct.c b/interpreters/nitfol/struct.c new file mode 100644 index 0000000..c0a58cf --- /dev/null +++ b/interpreters/nitfol/struct.c @@ -0,0 +1,85 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + + +glui32 fillstruct(strid_t stream, const unsigned *info, glui32 *dest, + glui32 (*special)(strid_t)) +{ + unsigned char buffer[4]; + unsigned e; + glui32 len = 0;; + + for(e = 0; info[e]; e++) { + if(info[e] == 0x8000) { + *dest++ = special(stream); + len++; + } + else if(info[e] > 4) { + unsigned i; + for(i = 0; i < info[e]; i++) { + *dest++ = glk_get_char_stream(stream); + len++; + } + } else { + glk_get_buffer_stream(stream, (char *) buffer, info[e]); + + switch(info[e]) { + case 1: *dest = MSBdecode1(buffer); break; + case 2: *dest = MSBdecode2(buffer); break; + case 3: *dest = MSBdecode3(buffer); break; + case 4: *dest = MSBdecode4(buffer); break; + } + dest++; + len+=info[e]; + } + } + return len; +} + +glui32 emptystruct(strid_t stream, const unsigned *info, const glui32 *src) +{ + unsigned char buffer[4]; + unsigned e; + glui32 len = 0;; + + for(e = 0; info[e]; e++) { + if(info[e] > 4) { + unsigned i; + for(i = 0; i < info[e]; i++) { + glk_put_char_stream(stream, *src++); + len++; + } + } else { + switch(info[e]) { + case 1: MSBencode1(buffer, *src); break; + case 2: MSBencode2(buffer, *src); break; + case 3: MSBencode3(buffer, *src); break; + case 4: MSBencode4(buffer, *src); break; + } + + w_glk_put_buffer_stream(stream, (char *) buffer, info[e]); + + src++; + len++; + } + } + return len; +} diff --git a/interpreters/nitfol/struct.h b/interpreters/nitfol/struct.h new file mode 100644 index 0000000..7ab9469 --- /dev/null +++ b/interpreters/nitfol/struct.h @@ -0,0 +1,14 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i struct.c' */ +#ifndef CFH_STRUCT_H +#define CFH_STRUCT_H + +/* From `struct.c': */ +glui32 fillstruct (strid_t stream , const unsigned *info , glui32 *dest , glui32 ( *special ) ( strid_t ) ); +glui32 emptystruct (strid_t stream , const unsigned *info , const glui32 *src ); + +#endif /* CFH_STRUCT_H */ diff --git a/interpreters/nitfol/test.inf b/interpreters/nitfol/test.inf new file mode 100644 index 0000000..f64205b --- /dev/null +++ b/interpreters/nitfol/test.inf @@ -0,0 +1,395 @@ +!! +!! Simple program to test various z-machine stuff. +!! Placed in the public domain by Evin Robertson. +!! + +[ assert actual expected a op b; + if(expected ~= actual) { + print "^"; + @check_arg_count 5 ?binary; + + print (string) op, a; + jump e; + + .binary; + print a, (string) op, b; + + .e; + print " expected ", expected, "; got ", actual, "^"; + @quit; + } +]; + +Global count; + +[ do_check_check_arg_count a b c d e f g n; + for(n = 1: n <= count: n++) { + @check_arg_count n ?~bad; + } + for(: n <= 7: n++) { + @check_arg_count n ?bad; + } + return; + + .bad; + + print "^claimed argument ", n, " was "; + if(n <= count) + print "not given when it was.^"; + else + print "given when it was not.^"; + @quit; +]; + +!Global mycount; +! +![ list_arguments a b c d e f g h i j k l m n o; +! for(mycount = 1: mycount <= 15: mycount++) { +! @check_arg_count mycount ?~noarg; +! +! print mycount, "="; +! +! @load [mycount] -> sp; +! @print_num sp; +! +! print "; "; +! +! .noarg; +! } +! new_line; +!]; + +[ do_add a b expect; + @add a b -> sp; + @call_vn2 assert sp expect a "+" b; +]; + +[ do_and a b expect; + @and a b -> sp; + @call_vn2 assert sp expect a "&" b; +]; + +[ do_art a b expect; + @art_shift a b -> sp; + @call_vn2 assert sp expect a "<<" b; +]; + +[ do_dec a expect; + @dec a; + @call_vn2 assert a expect a "--"; +]; + +[ do_div a b expect; + @div a b -> sp; + @call_vn2 assert sp expect a "/" b; +]; + +[ do_inc a expect; + @inc a; + @call_vn2 assert a expect a "++"; +]; + +[ do_log a b expect; + @log_shift a b -> sp; + @call_vn2 assert sp expect a "<<" b; +]; + +[ do_mod a b expect; + @mod a b -> sp; + @call_vn2 assert sp expect a "%" b; +]; + +[ do_mul a b expect; + @mul a b -> sp; + @call_vn2 assert sp expect a "*" b; +]; + +[ do_not a expect; + @"VAR:56S" a -> sp; ! @not a -> sp; (bug in inform) + @call_vn2 assert sp expect a "~"; +]; + +[ do_or a b expect; + @or a b -> sp; + @call_vn2 assert sp expect a "|" b; +]; + +[ do_sub a b expect; + @sub a b -> sp; + @call_vn2 assert sp expect a "-" b; +]; + + + +! I couldn't figure out how to do negative numbers in inform assembly, so +! here's constants for the numbers I use +Constant n1 -1; +Constant n2 -2; +Constant n3 -3; +Constant n4 -4; +Constant n5 -5; +Constant n500 -500; +Constant n32768 -32768; + +Constant Rand_Range 60; + +Array mytable -> 256; +Array mysecond -> 256; + + +[ Main n g max; +! mytable->0 = 200; +! mytable->1 = 0; +! @aread mytable 0 -> n; + + print "Testing... "; + + print "check_arg_count "; + count = 0; do_check_check_arg_count(); + count = 1; do_check_check_arg_count(1); + count = 2; do_check_check_arg_count(2, 1); + count = 3; do_check_check_arg_count(3, 2, 1); + count = 4; do_check_check_arg_count(4, 3, 2, 1); + count = 5; do_check_check_arg_count(5, 4, 3, 2, 1); + count = 6; do_check_check_arg_count(6, 5, 4, 3, 2, 1); + count = 7; do_check_check_arg_count(7, 6, 5, 4, 3, 2, 1); + + print "je "; + @je 5 5 ?~bad; + @je 5 n5 ?bad; + @je n5 5 ?bad; + @je n5 n5 ?~bad; + @je 32767 n32768 ?bad; + @je n32768 n32768 ?~bad; + + print "jg "; + @jg 5 5 ?bad; + @jg 1 0 ?~bad; + @jg 0 1 ?bad; + @jg n1 n2 ?~bad; + @jg n2 n1 ?bad; + @jg 1 n1 ?~bad; + @jg n1 1 ?bad; + + print "jl "; + @jl 5 5 ?bad; + @jl 1 0 ?bad; + @jl 0 1 ?~bad; + @jl n1 n2 ?bad; + @jl n2 n1 ?~bad; + @jl 1 n1 ?bad; + @jl n1 1 ?~bad; + + print "jz "; + @jz 0 ?~bad; + @jz 1 ?bad; + print "je "; + @je 42 42 ?~bad; + @je 15 5 ?bad; + + print "dec_chk "; + n = 3; + @dec_chk n 1000 ?~bad; ! 2 + @dec_chk n 1 ?bad; ! 1 + @dec_chk n 1 ?~bad; ! 0 + @dec_chk n 0 ?~bad; ! -1 + @dec_chk n n2 ?bad; ! -2 + @dec_chk n n2 ?~bad; ! -3 + @dec_chk n 1000 ?~bad; ! -4 + @dec_chk n n500 ?bad; ! -5 + + print "inc_chk "; + n = -6; + @inc_chk n n500 ?~bad; ! -5 + @inc_chk n 1000 ?bad; ! -4 + @inc_chk n n3 ?bad; ! -3 + @inc_chk n n3 ?~bad; ! -2 + @inc_chk n 0 ?bad; ! -1 + @inc_chk n 1 ?bad; ! 0 + @inc_chk n 1 ?bad; ! 1 + @inc_chk n 1 ?~bad; ! 2 + @inc_chk n 1000 ?bad; ! 3 + + print "test "; + @test $ffff $ffff ?~bad; + @test $ffff 0 ?~bad; + @test $1234 $4321 ?bad; + + print "add "; + do_add( 5, 3, 8); + do_add( 3, 5, 8); + do_add(-5, 3, -2); + do_add(-5, -3, -8); + do_add(-3, -5, -8); + do_add(-3, 5, 2); + do_add(32765, 6, -32765); + + print "and "; + do_and( 5, 3, 1); + do_and( 3, 5, 1); + do_and(-3, -3, -3); + do_and(-3, 5, 5); + do_and(-3, -5, -7); + + print "art_shift "; + do_art( 0, 1, 0); + do_art( 0, -1, 0); + do_art( 1, 5, 32); + do_art( 1, -1, 0); + do_art(85, 1, 170); + do_art(85, -2, 21); + do_art(-9, 5, -288); + do_art(-9, -5, -1); + + print "dec "; + do_dec( 5, 4); + do_dec( 0, -1); + do_dec(-8, -9); + do_dec(-32768, 32767); + + print "div "; + do_div(-11, 2, -5); + do_div(-11, -2, 5); + do_div( 11, -2, -5); + do_div( 5, 1, 5); + do_div( 5, 2, 2); + do_div( 5, 3, 1); + do_div( 5, 5, 1); + do_div( 5, 6, 0); + do_div(5, 32767, 0); + do_div(32767, -32768, 0); + do_div(-32768, 32767, -1); + do_div(-32768, -1, -32768); + + + print "inc "; + do_inc( 5, 6); + do_inc(-1, 0); + do_inc(-8, -7); + do_inc(32767, -32768); + + print "loadx/storex "; + @loadb mytable 0 -> n; + assert(n, 0); + @storeb mytable 0 123; + @loadb mytable 0 -> n; + assert(n, 123); + @loadw mytable 0 -> n; + assert(n, $7b00); + @storew mytable 0 $1234; + @loadw mytable 0 -> n; + assert(n, $1234); + @loadb mytable 0 -> n; + assert(n, $12); + @loadb mytable 1 -> n; + assert(n, $34); + + print "log_shift "; + do_log( 0, 1, 0); + do_log( 0, -1, 0); + do_log( 1, 5, 32); + do_log( 1, -1, 0); + do_log(85, 1, 170); + do_log(85, -2, 21); + do_log(-9, 5, -288); + do_log(-9, -5, 2047); + + print "mod "; + do_mod(-13, 5, -3); + do_mod( 13, -5, 3); + do_mod(-13, -5, -3); + do_mod( 5, 1, 0); + do_mod( 5, 2, 1); + do_mod( 5, 3, 2); + do_mod( 5, 5, 0); + do_mod( 5, 6, 5); + do_mod(5, 32767, 5); + do_mod(32767, -32768, 32767); + do_mod(-32768, 32767, -1); + do_mod(-32768, -1, 0); + + print "mul "; + do_mul( 0, 123, 0); + do_mul(123, 0, 0); + do_mul( 8, 9, 72); + do_mul( 9, 8, 72); + do_mul( 11, -5, -55); + do_mul(-11, 5, -55); + do_mul(-11, -5, 55); + do_mul(-32768, -1, -32768); + + print "not "; + do_not(0, ~0); + do_not(123, ~123); + do_not($5555, $aaaa); + do_not($aaaa, $5555); + + print "or "; + do_or($1234, $4321, $5335); + do_or($4321, $1234, $5335); + do_or($1234, 0, $1234); + do_or($1030, $ffff, $ffff); + do_or($ffff, $0204, $ffff); + + print "sub "; + do_sub(8, 5, 3); + do_sub(8, 3, 5); + do_sub(-2, -5, 3); + do_sub(-8, -5, -3); + do_sub(-8, -3, -5); + do_sub(2, -3, 5); + do_sub(-32765, 32765, 6); + + print "output_stream "; + @output_stream 3 mytable; + print "...looks "; + @output_stream 3 mysecond; + print " to me..."; + @output_stream -3; + print "good"; + @output_stream -3; + for(n = 0: n < mytable-->0: n++) + print (char) mytable->(n+2); + for(n = 0: n < mysecond-->0: n++) + print (char) mysecond->(n+2); + + print " random "; + for(n = 1: n <= Rand_Range: n++) + mytable-->n = 0; + + for(n = 0: n < 800: n++) { + @random Rand_Range -> g; + if(g <= 0 || g > Rand_Range) { + print "illegal return from result (", g, ").^"; + @quit; + } + (mytable-->g)++; + } + + new_line; + style fixed; + + max = 0; + for(n = 1: n <= Rand_Range: n++) { + if(mytable-->n > max) + max = mytable-->n; + } + + for(g = max: g > 0: g--) { + for(n = 1: n < Rand_Range: n++) { + if(mytable-->n >= g) + print (char) '#'; + else + print (char) ' '; + } + new_line; + } + + style roman; + + "Yay!"; + +.bad; + "bad!"; + +]; diff --git a/interpreters/nitfol/tokenise.c b/interpreters/nitfol/tokenise.c new file mode 100644 index 0000000..7db9db6 --- /dev/null +++ b/interpreters/nitfol/tokenise.c @@ -0,0 +1,373 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +static int tokenise(zword dictionarytable, const char *text, int length, + zword *parse_dest, int maxwords, + zword sepratortable, int numseparators, + BOOL write_unrecognized); + +static void addparsed(zword *parse_dest, int word_num, int length, int off) +{ + if(zversion <= 4) + off+=1; + else + off+=2; + + LOWORDwrite(*parse_dest, word_num); + *parse_dest+=ZWORD_SIZE; + LOBYTEwrite(*parse_dest, length); + *parse_dest+=1; + LOBYTEwrite(*parse_dest, off); + *parse_dest+=1; +} + +static int dictentry_len; + +static int cmpdictentry(const void *a, const void *b) +{ + return n_memcmp(a, b, dictentry_len); +} + + +static zword find_word(zword dictionarytable, const char *word, int length) +{ + zbyte zsciibuffer[12]; + int entry_length, word_length; + int num_entries; + void *p; + + entry_length = LOBYTE(dictionarytable); + dictionarytable++; + num_entries = LOWORD(dictionarytable); + dictionarytable+=ZWORD_SIZE; + + if(zversion <= 3) + word_length = 4; + else + word_length = 6; + + encodezscii(zsciibuffer, word_length, word_length, word, length); + + dictentry_len = word_length; + + if(is_neg(num_entries)) { /* Unordered dictionary */ + num_entries = neg(num_entries); + p = n_lfind(zsciibuffer, z_memory + dictionarytable, &num_entries, + entry_length, cmpdictentry); + } else { /* Ordered dictionary */ + p = n_bsearch(zsciibuffer, z_memory + dictionarytable, num_entries, + entry_length, cmpdictentry); + } + if(p) + return ((zbyte *) p) - z_memory; + + return 0; +} + + +#ifdef SMART_TOKENISER + +struct Typocorrection { + struct Typocorrection *next; + struct Typocorrection *prev; + char original[13]; + char changedto[13]; +}; + +struct Typocorrection *recent_corrections; /* Inform requests two parses of + each input; remember what + corrections we've made so + we don't print twice */ +void forget_corrections(void) +{ + LEdestroy(recent_corrections); +} + +static zword smart_tokeniser(zword dictionarytable, + const char *text, unsigned length, BOOL is_begin) +{ + zword word_num = 0; + unsigned tlength = (length < 12) ? length : 12; + char tbuffer[13]; + + /* Letter replacements are tried in this order - */ + const char fixmeletters[] = "abcdefghijklmnopqrstuvwxyz"; + /* char fixmeletters[] = "etaonrishdlfcmugpywbvkxjqz"; */ + + + word_num = find_word(dictionarytable, text, length); + + /* Some game files don't contain abbreviations for common commands */ + if(!word_num && do_expand && length == 1 && is_begin) { + const char * const abbrevs[26] = { + "a", "b", "close", "down", + "east", "f", "again", "h", + "inventory", "j", "attack", "look", + "m", "north", "oops", "open", + "quit", "drop", "south", "take", + "up", "v", "west", "examine", + "yes", "wait" + }; + if('a' <= text[0] && text[0] <= 'z') { + strcpy(tbuffer, abbrevs[text[0] - 'a']); + tlength = strlen(tbuffer); + word_num = find_word(dictionarytable, tbuffer, tlength); + } + } + + /* Check for various typing errors */ + + /* Don't attempt typo correction in very short words */ + if(do_spell_correct && length >= 3) { + + if(!word_num) { /* Check for transposes */ + /* To fix, try all possible transposes */ + unsigned position; + for(position = 1; position < tlength; position++) { + unsigned s; + for(s = 0; s < tlength; s++) + tbuffer[s] = text[s]; + + tbuffer[position - 1] = text[position]; + tbuffer[position] = text[position - 1]; + + word_num = find_word(dictionarytable, tbuffer, tlength); + if(word_num) + break; + } + } + + if(!word_num) { /* Check for deletions */ + /* To fix, try all possible insertions */ + unsigned position; + for(position = 0; position <= tlength; position++) { + unsigned s; + for(s = 0; s < position; s++) /* letters before the insertion */ + tbuffer[s] = text[s]; + + for(s = position; s < tlength; s++) /* after the insertion */ + tbuffer[s + 1] = text[s]; + + /* try each letter */ + for(s = 0; s < sizeof(fixmeletters); s++) { + tbuffer[position] = fixmeletters[s]; + word_num = find_word(dictionarytable, tbuffer, tlength + 1); + if(word_num) + break; + } + + if(word_num) { + tlength++; + break; + } + } + } + + if(!word_num) { /* Check for insertions */ + /* To fix, try all possible deletions */ + unsigned position; + for(position = 0; position < tlength; position++) { + unsigned s; + for(s = 0; s < position; s++) /* letters before the deletion */ + tbuffer[s] = text[s]; + + for(s = position + 1; s < tlength; s++) /* after the deletion */ + tbuffer[s - 1] = text[s]; + + word_num = find_word(dictionarytable, tbuffer, tlength - 1); + + if(word_num) { + tlength--; + break; + } + } + } + + if(!word_num) { /* Check for substitutions */ + /* To fix, try all possible substitutions */ + unsigned position; + for(position = 0; position < tlength; position++) { + unsigned s; + for(s = 0; s < tlength; s++) + tbuffer[s] = text[s]; + + /* try each letter */ + for(s = 0; s < sizeof(fixmeletters); s++) { + tbuffer[position] = fixmeletters[s]; + word_num = find_word(dictionarytable, tbuffer, tlength); + if(word_num) + break; + } + + if(word_num) + break; + } + } + } + + /* Report any corrections made */ + if(word_num) { + struct Typocorrection *p; + char original[13], changedto[13]; + n_strncpy(original, text, 13); + n_strncpy(changedto, tbuffer, 13); + if(length < 13) + original[length] = 0; + if(tlength < 13) + changedto[tlength] = 0; + + LEsearch(recent_corrections, p, ((n_strncmp(p->original, original, 13) == 0) && + (n_strncmp(p->changedto, changedto, 13) == 0))); + + /* Only print a correction if it hasn't yet been reported this turn */ + if(!p) { + struct Typocorrection newcorrection; + n_strncpy(newcorrection.original, original, 13); + n_strncpy(newcorrection.changedto, changedto, 13); + LEadd(recent_corrections, newcorrection); + + set_glk_stream_current(); + + if(allow_output) { + glk_put_char('['); + w_glk_put_buffer(text, length); + w_glk_put_string(" -> "); + w_glk_put_buffer(tbuffer, tlength); + glk_put_char(']'); + glk_put_char(10); + } + } + } + + return word_num; +} + +#endif + +static void handle_word(zword dictionarytable, const char *text, + zword word_start, int length, + zword *parse_dest, + BOOL write_unrecognized, int *parsed_words) +{ + + zword word_num; + + word_num = find_word(dictionarytable, text + word_start, length); + +#ifdef SMART_TOKENISER + if(!word_num) + word_num = smart_tokeniser(dictionarytable, text + word_start, length, + *parsed_words == 0); +#endif + + if(!word_num && !write_unrecognized) + *parse_dest += ZWORD_SIZE + 2; + else + addparsed(parse_dest, word_num, length, word_start); + + (*parsed_words)++; +} + + +static int tokenise(zword dictionarytable, const char *text, int length, + zword *parse_dest, int maxwords, + zword separatortable, int numseparators, + BOOL write_unrecognized) +{ + int i; + int parsed_words = 0; + int word_start = 0; + for(i = 0; i <= length && parsed_words < maxwords; i++) { + BOOL do_tokenise = FALSE; + BOOL do_add_separator = FALSE; + if((i == length) || text[i] == ' ') { /* A space or at the end */ + do_tokenise = TRUE; + } else { + int j; + for(j = 0; j < numseparators; j++) { + if(text[i] == (char) LOBYTE(separatortable + j)) { + do_tokenise = TRUE; + do_add_separator = TRUE; + break; + } + } + } + + if(do_tokenise) { + int wordlength = i - word_start; + if(wordlength > 0) { + handle_word(dictionarytable, text, word_start, wordlength, + parse_dest, write_unrecognized, &parsed_words); + } + word_start = i + 1; + } + if(do_add_separator && parsed_words < maxwords) { + handle_word(dictionarytable, text, i, 1, + parse_dest, write_unrecognized, &parsed_words); + + } + } + return parsed_words; +} + + +void z_tokenise(const char *text, int length, zword parse_dest, + zword dictionarytable, BOOL write_unrecognized) +{ + zword separatortable; + zword numparsedloc; + int numseparators; + int maxwords, parsed_words; + + if(parse_dest > dynamic_size || parse_dest < 64) { + n_show_error(E_OUTPUT, "parse table in invalid location", parse_dest); + return; + } + + numseparators = LOBYTE(dictionarytable); + separatortable = dictionarytable + 1; + dictionarytable += numseparators + 1; + + maxwords = LOBYTE(parse_dest); + numparsedloc = parse_dest + 1; + parse_dest+=2; + + if(maxwords == 0) + n_show_warn(E_OUTPUT, "small parse size", maxwords); + + parsed_words = tokenise(dictionarytable, text, length, + &parse_dest, maxwords, separatortable, numseparators, + write_unrecognized); + + LOBYTEwrite(numparsedloc, parsed_words); +} + + +void op_tokenise(void) +{ + if(numoperands < 3 || operand[2] == 0) + operand[2] = z_dictionary; + if(numoperands < 4) + operand[3] = 0; + z_tokenise((char *) z_memory + operand[0] + 2, LOBYTE(operand[0] + 1), + operand[1], operand[2], operand[3]==0); +} + diff --git a/interpreters/nitfol/tokenise.h b/interpreters/nitfol/tokenise.h new file mode 100644 index 0000000..fa1f824 --- /dev/null +++ b/interpreters/nitfol/tokenise.h @@ -0,0 +1,20 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i tokenise.c' */ +#ifndef CFH_TOKENISE_H +#define CFH_TOKENISE_H + +/* From `tokenise.c': */ + +#ifdef SMART_TOKENISER +extern struct Typocorrection * recent_corrections; +void forget_corrections (void); + +#endif +void z_tokenise (const char *text , int length , zword parse_dest , zword dictionarytable , BOOL write_unrecognized ); +void op_tokenise (void); + +#endif /* CFH_TOKENISE_H */ diff --git a/interpreters/nitfol/undo.c b/interpreters/nitfol/undo.c new file mode 100644 index 0000000..3f2cb22 --- /dev/null +++ b/interpreters/nitfol/undo.c @@ -0,0 +1,386 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + + +/* Early versions just used quetzal format for the undo slot, which let + me just call the same function, but made it impossible to add undo + capability to games which don't natively support it, and was too expensive + to provide infinite undo/redo. + + Now I use a linked list containing a memory delta and stack stored in + quetzal format. We take the delta between consecutive turns. This makes + things smaller as there should be fewer differences between turns than + from beginning to end of a story. + + Since there is very little difference between turns, I use quetzal + format for the delta with a twist. If there are runs of more than + 127 unchanged bytes, I store the length in a 15 bit number, ala UTF-8. + This saves over a hundred bytes each turn (depending on game size and + arrangement). + + Here is some data for advent (note that the usages are mostly for the + the previous turn, since the save_undo is called shortly after each input): + +Command: Overhead Dynamic Stack Total + Memory +enter 28 + 20 + 172 = 220 +take all 28 + 435 + 172 = 635 +x stream 28 + 266 + 172 = 466 +exit 28 + 132 + 172 = 332 +s 28 + 120 + 172 = 320 +verbose 28 + 114 + 172 = 314 +d 28 + 64 + 172 = 264 +s 28 + 90 + 172 = 290 +unlock grate with keys 28 + 127 + 172 = 327 +open grate. down. 28 + 202 + 172 = 402 +help 28 + 199 + 172 = 399 + + Overhead doesn't count alignment and malloc overhead. Assume another 20 + bytes there. + + At approx 380 bytes per turn, I can store over 2500 turns in a meg of + memory, enough for a longish game. + + Note that stack size (and contents mostly) stay the same between calls + since save_undo is almost always called from the same place. I should take + advantage of this somehow. (another hundred bytes to save, another 1000 + turns per meg...) + +*/ + + +static zbyte *prevstate = NULL; + +typedef struct move_difference move_difference; + +struct move_difference { + move_difference *next; + + zbyte *delta; /* Encoded like quetzal mixed with UTF-8 */ + glui32 deltalength; + + offset PC; + offset oldPC; + BOOL PC_in_instruction; + + zbyte *stackchunk; /* Quetzal encoded */ + glui32 stacklength; +}; + +static move_difference *movelist = NULL; +static int move_index; + + +void init_undo(void) +{ + kill_undo(); + + prevstate = (zbyte *) n_malloc(dynamic_size); + n_memcpy(prevstate, z_memory, dynamic_size); +} + +/* Frees old undo slots if possible in order to reduce memory consumption. + Will never free the most recent @save_undo */ +BOOL free_undo(void) +{ + move_difference *p, *g = NULL; + for(p = movelist; p; p=p->next) + if(p->next) + g = p; + if(g == NULL) + return FALSE; + + n_free(g->next->delta); + n_free(g->next->stackchunk); + n_free(g->next); + g->next = NULL; + return TRUE; +} + + +BOOL saveundo(BOOL in_instruction) +{ + move_difference newdiff; + strid_t stack; + stream_result_t poo; + + if(!allow_saveundo) + return TRUE; + + /* In games which provide @save_undo, we will have already issued a faked + saveundo before the first @save_undo hits, since there hadn't been any + @save_undo before the first read line. So when this happens, wipe the + fake saveundo in favor of the real one */ + if(in_instruction && movelist && !movelist->next + && !movelist->PC_in_instruction) + init_undo(); + + + if(!quetzal_diff(z_memory, prevstate, dynamic_size, &newdiff.delta, + &newdiff.deltalength, TRUE)) + return FALSE; + +#ifdef PARANOID + { + char *newmem = (char *) n_malloc(dynamic_size); + n_memcpy(newmem, prevstate, dynamic_size); + quetzal_undiff(newmem, dynamic_size, newdiff.delta, + newdiff.deltalength, TRUE); + if(n_memcmp(z_memory, newmem, dynamic_size)) { + n_show_error(E_SAVE, "save doesn't match itself", 0); + } + n_free(newmem); + } +#endif + + newdiff.PC = PC; + newdiff.oldPC = oldPC; + + newdiff.PC_in_instruction = in_instruction; + newdiff.stacklength = get_quetzal_stack_size(); + newdiff.stackchunk = (zbyte *) n_malloc(newdiff.stacklength); + stack = glk_stream_open_memory((char *) newdiff.stackchunk, + newdiff.stacklength, filemode_Write, 0); + if(!stack) { + n_free(newdiff.delta); + n_free(newdiff.stackchunk); + return FALSE; + } + if(!quetzal_stack_save(stack)) { + glk_stream_close(stack, NULL); + n_free(newdiff.delta); + n_free(newdiff.stackchunk); + return FALSE; + } + glk_stream_close(stack, &poo); + if(poo.writecount != newdiff.stacklength) { + n_show_error(E_SAVE, "incorrect stack size assessment", poo.writecount); + n_free(newdiff.delta); + n_free(newdiff.stackchunk); + return FALSE; + } + + while(move_index-- > 0) { + n_free(movelist->delta); + n_free(movelist->stackchunk); + LEremove(movelist); + } + LEadd(movelist, newdiff); + move_index++; + n_memcpy(prevstate, z_memory, dynamic_size); + + has_done_save_undo = TRUE; + return TRUE; +} + + +BOOL restoreundo(void) +{ + strid_t stack; + int i; + glui32 wid, hei; + move_difference *p = movelist; + + if(!p || move_index < 0) + return FALSE; + + for(i = 0; i < move_index; i++) { + p=p->next; + if(!p) + return FALSE; + } + move_index++; + + n_memcpy(z_memory, prevstate, dynamic_size); + + quetzal_undiff(prevstate, dynamic_size, p->delta, p->deltalength, TRUE); + + stack = glk_stream_open_memory((char *) p->stackchunk, p->stacklength, + filemode_Read, 0); + + quetzal_stack_restore(stack, p->stacklength); + glk_stream_close(stack, NULL); + + if(p->PC_in_instruction) { + PC = p->PC; + mop_store_result(2); + false_undo = FALSE; + } else { + PC = p->oldPC; + false_undo = TRUE; + } + has_done_save_undo = TRUE; + + z_find_size(&wid, &hei); + set_header(wid, hei); + return TRUE; +} + + +/* Just like restoreundo, but the opposite ;) + The idea is to go to the @save_undo location, but return 0 instead of 2 + so the game thinks it just successfully saved the game. For games which + don't contain @save_undo, jumps to right after the @read instruction. */ +BOOL restoreredo(void) +{ + strid_t stack; + int i; + glui32 wid, hei; + stream_result_t poo; + move_difference *p = movelist; + + if(!p || move_index <= 0) + return FALSE; + + move_index--; + for(i = 0; i < move_index; i++) { + p=p->next; + if(!p) + return FALSE; + } + + quetzal_undiff(prevstate, dynamic_size, p->delta, p->deltalength, TRUE); + + n_memcpy(z_memory, prevstate, dynamic_size); + + stack = glk_stream_open_memory((char *) p->stackchunk, p->stacklength, + filemode_Read, 0); + + quetzal_stack_restore(stack, p->stacklength); + glk_stream_close(stack, &poo); + + if(poo.readcount != p->stacklength) { + n_show_error(E_SAVE, "incorrect stack size assessment", poo.readcount); + return FALSE; + } + + if(p->PC_in_instruction) { + PC = p->PC; + mop_store_result(0); + false_undo = FALSE; + } else { + PC = p->PC; + false_undo = FALSE; + } + has_done_save_undo = TRUE; + + z_find_size(&wid, &hei); + set_header(wid, hei); + return TRUE; +} + + +#ifdef DEBUGGING + +/* For automapping, we want the saveundo/restoreundo to be as fast as possible + and we also don't want it clobbering the redo capabilities, so give it + separate fast_saveundo and fast_restoreundo. Doesn't need multiple undo + or redo +*/ + +struct fast_undoslot { + zbyte *z_mem; + glui32 z_memsize; + offset PC; + zbyte *stackchunk; /* Quetzal encoded */ + glui32 stackchunksize; + glui32 stacklength; +}; + + +static struct fast_undoslot automap_undoslot = { NULL, 0, 0, NULL, 0, 0 }; + +BOOL fast_saveundo(void) +{ + strid_t stack; + glui32 stack_size; + /* Avoids realloc() in hopes that'll make it a teensy bit faster */ + if(automap_undoslot.z_memsize < dynamic_size) { + n_free(automap_undoslot.z_mem); + automap_undoslot.z_mem = n_malloc(dynamic_size); + automap_undoslot.z_memsize = dynamic_size; + } + n_memcpy(automap_undoslot.z_mem, z_memory, dynamic_size); + + automap_undoslot.PC = oldPC; + + automap_undoslot.stacklength = stack_size = get_quetzal_stack_size(); + if(automap_undoslot.stackchunksize < stack_size) { + free(automap_undoslot.stackchunk); + automap_undoslot.stackchunk = (zbyte *) n_malloc(stack_size); + automap_undoslot.stackchunksize = stack_size; + } + + stack = glk_stream_open_memory((char *) automap_undoslot.stackchunk, + automap_undoslot.stacklength, + filemode_Write, 0); + if(!stack) + return FALSE; + if(!quetzal_stack_save(stack)) { + glk_stream_close(stack, NULL); + return FALSE; + } + glk_stream_close(stack, NULL); + return TRUE; +} + + +BOOL fast_restoreundo(void) +{ + strid_t stack; + n_memcpy(z_memory, automap_undoslot.z_mem, dynamic_size); + PC = automap_undoslot.PC; + + stack = glk_stream_open_memory((char *) automap_undoslot.stackchunk, + automap_undoslot.stacklength, + filemode_Read, 0); + + quetzal_stack_restore(stack, automap_undoslot.stacklength); + glk_stream_close(stack, NULL); + return TRUE; +} + +#endif + + +void kill_undo(void) +{ + n_free(prevstate); + prevstate = 0; + + while(movelist) { + n_free(movelist->delta); + n_free(movelist->stackchunk); + LEremove(movelist); + } + move_index = 0; + +#ifdef DEBUGGING + n_free(automap_undoslot.z_mem); + n_free(automap_undoslot.stackchunk); + automap_undoslot.z_mem = NULL; + automap_undoslot.z_memsize = 0; + automap_undoslot.PC = 0; + automap_undoslot.stackchunk = NULL; + automap_undoslot.stackchunksize = 0; + automap_undoslot.stacklength = 0; +#endif +} diff --git a/interpreters/nitfol/undo.h b/interpreters/nitfol/undo.h new file mode 100644 index 0000000..c0ccfeb --- /dev/null +++ b/interpreters/nitfol/undo.h @@ -0,0 +1,24 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i undo.c' */ +#ifndef CFH_UNDO_H +#define CFH_UNDO_H + +/* From `undo.c': */ +void init_undo (void); +BOOL free_undo (void); +BOOL saveundo (BOOL in_instruction ); +BOOL restoreundo (void); +BOOL restoreredo (void); + +#ifdef DEBUGGING +BOOL fast_saveundo (void); +BOOL fast_restoreundo (void); + +#endif +void kill_undo (void); + +#endif /* CFH_UNDO_H */ diff --git a/interpreters/nitfol/y2help.pl b/interpreters/nitfol/y2help.pl new file mode 100644 index 0000000..f5d0b53 --- /dev/null +++ b/interpreters/nitfol/y2help.pl @@ -0,0 +1,86 @@ +#!/usr/bin/perl -w +use strict; + +# Generate help (both C and texinfo) from inform.y +# +# This one doesn't even pretend to be nitfol-independant. + +my $helptext; +my $helpcommand; +my $helpargs; +my @helpentry; +my %helptable; + +# command => [ [ argtype1, argtype2, ... ], helptext [ helpname, ... ] ] + +while(<>) { + if(/^\/\* :: (.*) \*\//) { + $helptext = $1; + $_ = <>; + /\| (\S+)\s *(.*?(\/\*.*?\*\/)?.*?)\{/; + $helpcommand = $1; + $_ = $2; + s/\/\*(.*?)\*\//$1/; + s/\'(.)\'/$1/; + s/NUM/\@var\{num\}/g; + s/FILE/\@var\{file\}/g; + s/commaexp/\@var\{exp\}/g; + s/linespec/\@var\{linespec\}/g; + s/IF/if/g; # Ugly, but oh well... + s/TO/to/g; + $helpargs = $_; + if($helptable{$helpcommand}[0]) { + push @{ $helptable{$helpcommand}[0] }, $helpargs; + $helptable{$helpcommand}[1] = $helptable{$helpcommand}[1] . "\\n" . $helptext; + } else { + @{ $helptable{$helpcommand}[0] } = ( $helpargs ); + $helptable{$helpcommand}[1] = $helptext; + } + } elsif(/static name_token infix_commands/) { + while(<> =~ /\{\s*(\S+)\,\s*\"(.*?)\"\s*\}/) { + $helpcommand = $1; $helptext = $2; + push @{ $helptable{$helpcommand}[2] }, $helptext; + } + } +} + +open("MYTEXINFO", ">dbg_help.texi") || die "Unable to write to dbg_help.texi"; +select "MYTEXINFO"; + +foreach $helpcommand ( keys %helptable) { + my $tag = "\@item "; + foreach my $helparg (@{ $helptable{$helpcommand}[0] }) { + print $tag, @{$helptable{$helpcommand}[2]}[0], " $helparg\n"; + $tag = "\@itemx "; + } + $_ = $helptable{$helpcommand}[1]; + s/\\n/ /g; + print "$_\n\n"; +} + +close "MYTEXINFO"; + +open("MYCHELP", ">dbg_help.h") || die "Unable to write to dbg_help.c"; +select "MYCHELP"; + +print "static name_token command_help[] = {\n"; + +my $flag = 0; +foreach $helpcommand ( keys %helptable) { + if($flag) { + print ",\n"; + } + $flag = 1; + print " { $helpcommand, \"", texi2txt($helptable{$helpcommand}[1]), "\" }"; +} +print "\n};\n"; +close "MYCHELP"; + +sub texi2txt +{ + $_ = $_[0]; + s/\@code\{(.*?)\}/\'$1\'/g; + s/\@file\{(.*?)\}/\'$1\'/g; + s/\@\@/\@/g; + return $_; +} diff --git a/interpreters/nitfol/z_io.c b/interpreters/nitfol/z_io.c new file mode 100644 index 0000000..a06fb5a --- /dev/null +++ b/interpreters/nitfol/z_io.c @@ -0,0 +1,1132 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +#define UPPER_WINDOW 1 +#define LOWER_WINDOW 0 + +static zwinid lower_win, upper_win; +static zwinid current_window; + + +#define STREAM1 1 +#define STREAM2 2 +#define STREAM3 4 +#define STREAM4 8 + +static strid_t stream2, stream4; + +static int output_stream; +static zword stream3_table_starts[16]; +static zword stream3_table_locations[16]; +static int stream3_nesting_depth; + + +static int font = 1; + +static BOOL abort_output = FALSE; /* quickly stop outputting */ + + +BOOL is_transcripting(void) +{ + return (output_stream & STREAM2) != 0; +} + +void set_transcript(strid_t stream) +{ + if(stream) { + if(z_memory) { + zword flags2 = LOWORD(HD_FLAGS2) | b00000001; + LOWORDwrite(HD_FLAGS2, flags2); + } + stream2 = stream; + output_stream |= STREAM2; + if(lower_win) + z_set_transcript(lower_win, stream2); + } else { + if(z_memory) { + zword flags2 = LOWORD(HD_FLAGS2) & b11111110; + LOWORDwrite(HD_FLAGS2, flags2); + } + output_stream &= ~STREAM2; + if(lower_win) + z_set_transcript(lower_win, 0); + } +} + + +/* initialize the windowing environment */ +void init_windows(BOOL dofixed, glui32 maxwidth, glui32 maxheight) +{ + z_init_windows(dofixed, draw_upper_callback, upper_mouse_callback, + maxwidth, maxheight, &upper_win, &lower_win); + + current_window = lower_win; + output_stream = STREAM1 | (output_stream & STREAM2); + stream3_nesting_depth = 0; + font = 1; + + if(output_stream & STREAM2) { + set_transcript(stream2); + } else { + set_transcript(0); + } + + if(zversion == 6) { + v6_main_window_is(lower_win); + } +} + + +static int upper_roomname_length; + +static void counting_glk_put_char(int ch) +{ + upper_roomname_length++; + glk_put_char(ch); +} + +glui32 draw_upper_callback(winid_t win, glui32 width, glui32 height) +{ + glui32 curx = 0, cury = 0; + + glui32 numlines = 0; + + if(win == NULL || height == 0) { + if(zversion <= 3) + numlines++; + return numlines; + } + + if(zversion <= 3) { + zword location = get_var(16); + offset short_name_off = object_name(location); + + glk_window_move_cursor(win, 0, cury); + + if(location && short_name_off) { + glk_put_char(' '); curx++; + upper_roomname_length = 0; + decodezscii(short_name_off, counting_glk_put_char); + curx += upper_roomname_length; + } + + glk_window_move_cursor(win, width - 8, cury); + if((zversion <= 2) || ((LOBYTE(HD_FLAGS1) & 2) == 0)) { + if(width > curx + 26) { + glk_window_move_cursor(win, width - 24, cury); + w_glk_put_string("Score: "); + g_print_znumber(get_var(17)); + + glk_window_move_cursor(win, width - 12, cury); + w_glk_put_string("Moves: "); + g_print_znumber(get_var(18)); + } else { + g_print_znumber(get_var(17)); /* score */ + glk_put_char('/'); + g_print_znumber(get_var(18)); /* turns */ + } + } else { + const char *ampmstr[8] = { " AM", " PM" }; + int ampm = 0; + zword hours = get_var(17); + zword minutes = get_var(18); + while(hours >= 12) { + hours-=12; + ampm ^= 1; + } + if(hours == 0) + hours = 12; + if(hours < 10) + glk_put_char(' '); + g_print_number(hours); + glk_put_char(':'); + if(minutes < 10) + glk_put_char('0'); + g_print_number(minutes); + w_glk_put_string(ampmstr[ampm]); + } + numlines++; + cury++; + glk_window_move_cursor(win, 0, cury); + } + + return numlines; +} + +void output_string(const char *s) +{ + while(*s) + output_char(*s++); +} + +void output_char(int c) +{ + static int starlength = 0; + + if(output_stream & STREAM3) { /* Table output */ + zword num_chars = LOWORD(stream3_table_starts[stream3_nesting_depth-1]) +1; + if((c < 32 && c != 13) || (c >= 127 && c <= 159) || (c > 255)) + c = '?'; /* Section 7.5.3 */ + LOBYTEwrite(stream3_table_locations[stream3_nesting_depth-1], c); + stream3_table_locations[stream3_nesting_depth-1] += 1; + LOWORDwrite(stream3_table_starts[stream3_nesting_depth-1], num_chars); + } else { + if(output_stream & STREAM1) { /* Normal screen output */ + if(c >= 155 && c <= 251) { /* "extra characters" */ + zword game_unicode_table = header_extension_read(3); + if(game_unicode_table && LOBYTE(game_unicode_table) >= (zbyte) (c - 155)) { + zword address = game_unicode_table + 1 + (c - 155) * 2; + c = LOWORD(address); + } else { + const unsigned default_unicode_translation[] = { + 0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xbb, + 0xab, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9, + 0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3, + 0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0, + 0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4, + 0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5, + 0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5, + 0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0, + 0xa3, 0x153, 0x152, 0xa1, 0xbf + }; + c = default_unicode_translation[c - 155]; + } + } + if(c == '*') { + if(++starlength == 3) /* Three asterisks usually means win or death */ + if(automap_unexplore()) + abort_output = TRUE; + } else { + starlength = 0; + } + + if(font == 3) { + const char font3trans[] = + " <>/" "\\ --" "||||" "--\\/" /* 32-47 */ + "\\//\\/\\@ " " |" "|-- " /* 48-63 */ + " /" "\\/\\ " " " " " /* 64-79 */ + " " "####" " X+" "udb*" /* 80-95 */ + "?abc" "defg" "hijk" "lmno" /* 96-111 */ + "pqrs" "tuvw" "xyzU" "DB?"; /* 112-126 */ + if(c >= 32 && c <= 126) + c = font3trans[c - 32]; + } + + if(allow_output) + z_put_char(current_window, c); + } + } +} + +void n_print_number(unsigned n) +{ + int i; + char buffer[12]; + int length = n_to_decimal(buffer, n); + + for(i = length - 1; i >= 0; i--) + output_char(buffer[i]); +} + + +void g_print_number(unsigned n) +{ + int i; + char buffer[12]; + int length = n_to_decimal(buffer, n); + + for(i = length - 1; i >= 0; i--) + glk_put_char(buffer[i]); +} + +void g_print_snumber(int n) +{ + if(n < 0) { + glk_put_char('-'); + n = -n; + } + g_print_number(n); +} + +void g_print_znumber(zword n) +{ + if(is_neg(n)) { + glk_put_char('-'); + g_print_number(neg(n)); + } else { + g_print_number(n); + } +} + +void n_print_znumber(zword n) +{ + if(is_neg(n)) { + output_char('-'); + n_print_number(neg(n)); + } else { + n_print_number(n); + } +} + + +void stream4number(unsigned c) +{ + if(output_stream & STREAM4) { + glk_stream_set_current(stream4); + glk_put_char('['); + g_print_number(c); + glk_put_char(']'); + glk_put_char(10); + } +} + + +void op_buffer_mode(void) +{ + /* FIXME: Glk can't really do this. + * I could rely on the Plotkin Bug to do it, but that's ugly and would + * break 20 years from now when somebody fixes it. I could also print + * spaces between each letter, which isn't the intended effect + * + * For now, do nothing. Doubt this opcode is used often anyway... + */ +} + + +void op_check_unicode(void) +{ + unsigned result = 0; + if(operand[0] <= 255 && + (glk_gestalt(gestalt_CharOutput, (unsigned char) operand[0]) != + gestalt_CharOutput_CannotPrint)) + result |= 1; + if(operand[0] <= 255 && + (glk_gestalt(gestalt_LineInput, (unsigned char) operand[0]) != + FALSE)) + result |= 2; + mop_store_result(result); +} + + +void op_erase_line(void) +{ + if(!allow_output) + return; + + if(operand[0] == 1 && current_window == upper_win) { + z_erase_line(current_window); + } +} + + +void op_erase_window(void) +{ + if(!allow_output) + return; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "erase_window", operand[0]); + return; +#endif + + switch(operand[0]) { + case neg(1): + operand[0] = 0; op_split_window(); + current_window = lower_win; + case neg(2): + case UPPER_WINDOW: + z_clear_window(upper_win); + if(operand[0] == UPPER_WINDOW) break; /* Ok, this is evil, but it works. */ + case LOWER_WINDOW: + z_clear_window(lower_win); + break; + } +} + + +void op_get_cursor(void) +{ + zword x, y; + z_getxy(upper_win, &x, &y); + LOWORDwrite(operand[0], x); + LOWORDwrite(operand[0] + ZWORD_SIZE, y); +} + + +void op_new_line(void) +{ + output_char(13); +} + + +void op_output_stream(void) +{ + if(operand[0] == 0) + return; + if(is_neg(operand[0])) { + switch(neg(operand[0])) { + case 1: + if(!allow_output) + return; + output_stream &= ~STREAM1; + break; + + case 2: + if(!allow_output) + return; + set_transcript(0); + break; + + case 3: + if(stream3_nesting_depth) + stream3_nesting_depth--; + else + n_show_error(E_OUTPUT, "stream3 unnested too many times", 0); + if(!stream3_nesting_depth) + output_stream &= ~STREAM3; + break; + + case 4: + if(!allow_output) + return; + glk_stream_close(stream4, NULL); + stream4 = 0; + output_stream &= ~STREAM4; + break; + + default: + n_show_error(E_OUTPUT, "unknown stream deselected", neg(operand[0])); + } + } else { + switch(operand[0]) { + case 1: + if(!allow_output) + return; + output_stream |= STREAM1; + break; + + case 2: + if(!allow_output) + return; + if(!stream2) { + stream2 = n_file_prompt(fileusage_Transcript | fileusage_TextMode, + filemode_WriteAppend); + } + if(stream2) + set_transcript(stream2); + break; + + case 3: + if(stream3_nesting_depth >= 16) { + n_show_error(E_OUTPUT, "nesting stream 3 too deeply", + stream3_nesting_depth); + return; + } + LOWORDwrite(operand[1], 0); + stream3_table_starts[stream3_nesting_depth] = operand[1]; + stream3_table_locations[stream3_nesting_depth] = operand[1] + 2; + + output_stream |= STREAM3; + stream3_nesting_depth++; + break; + + case 4: + if(!allow_output) + return; + stream4 = n_file_prompt(fileusage_InputRecord | fileusage_TextMode, + filemode_WriteAppend); + if(stream4) + output_stream |= STREAM4; + break; + default: + n_show_error(E_OUTPUT, "unknown stream selected", operand[0]); + } + } +} + + +void op_print(void) +{ + int length; + abort_output = FALSE; + length = decodezscii(PC, output_char); + if(!abort_output) + PC += length; +} + + +void op_print_ret(void) +{ + int length; + abort_output = FALSE; + length = decodezscii(PC, output_char); + if(abort_output) + return; + PC += length; + output_char(13); + if(abort_output) + return; + mop_func_return(1); +} + + +void op_print_addr(void) +{ + decodezscii(operand[0], output_char); +} + + +void op_print_paddr(void) +{ + getstring(operand[0]); +} + + +void op_print_char(void) +{ + if(operand[0] > 1023) { + n_show_error(E_INSTR, "attempt to print character > 1023", operand[0]); + return; + } + output_char(operand[0]); +} + + +void op_print_num(void) +{ + n_print_znumber(operand[0]); +} + + +void op_print_table(void) +{ + unsigned x, y; + zword text = operand[0]; + zword width = operand[1]; + zword height = operand[2]; + zword skips = operand[3]; + + zword startx, starty; + unsigned win_width, win_height; + + z_getxy(current_window, &startx, &starty); + z_getsize(current_window, &win_width, &win_height); + + if(numoperands < 4) + skips = 0; + if(numoperands < 3) + height = 1; + + if(current_window == upper_win) { + if(startx + width - 1 > win_width) { + int diff; + n_show_warn(E_OUTPUT, "table too wide; trimming", width); + diff = startx + width - 1 - win_width; + width -= diff; + skips += diff; + } + if(starty + height - 1 > win_height) { + n_show_warn(E_OUTPUT, "table too tall; trimming", height); + height = win_height - starty + 1; + } + } + + for(y = 0; y < height; y++) { + if(current_window == upper_win && allow_output) + z_setxy(upper_win, startx, y+starty); + + for(x = 0; x < width; x++) { + output_char(LOBYTE(text)); + text++; + } + text += skips; + + if(current_window != upper_win && y+1 < height) + output_char(13); + } +} + + +void op_set_colour(void) +{ + if(!allow_output) + return; + + z_set_color(current_window, operand[0], operand[1]); +} + + +void op_set_cursor(void) +{ + unsigned width, height; + zword x = operand[1]; + zword y = operand[0]; + + if(!allow_output) + return; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "set_cursor y=", operand[0]); + n_show_debug(E_OUTPUT, "set_cursor x=", operand[1]); +#endif + + if(current_window != upper_win) { + return; + } + + z_getsize(current_window, &width, &height); + + if(y == 0 || y > height) { /* section 8.7.2.3 */ + n_show_error(E_OUTPUT, "illegal line for set_cursor", y); + if(y == 0 || y > 512) + return; + z_set_height(upper_win, y); /* Resize to allow broken games to work */ + } + if(x == 0 || x > width) { + n_show_error(E_OUTPUT, "illegal column for set_cursor", x); + return; + } + + z_setxy(current_window, x, y); +} + + +void op_set_text_style(void) +{ + if(!allow_output) + return; + + z_set_style(current_window, operand[0]); +} + + +void op_set_window(void) +{ + if(!allow_output) + return; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "set_window", operand[0]); + return; +#endif + + switch(operand[0]) { + case UPPER_WINDOW: + current_window = upper_win; + z_setxy(upper_win, 1, 1); + break; + case LOWER_WINDOW: + current_window = lower_win; + break; + default: + n_show_error(E_OUTPUT, "invalid window selected", operand[0]); + } +} + + +void op_split_window(void) +{ + if(!allow_output) + return; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "split_window", operand[0]); +#endif + + if(zversion == 6) + return; + + + if(operand[0] > 512) { + n_show_error(E_OUTPUT, "game is being ridiculous", operand[0]); + return; + } + + if(zversion == 3) + z_set_height(upper_win, 0); /* clear the whole upper window first */ + + z_set_height(upper_win, operand[0]); +} + + +static BOOL timer_callback(zword routine) +{ + zword dummylocals[16]; + in_timer = TRUE; + mop_call(routine, 0, dummylocals, -2); /* add a special stack frame */ + decode(); /* start interpreting the routine */ + in_timer = FALSE; + exit_decoder = FALSE; + return time_ret; +} + + +BOOL upper_mouse_callback(BOOL is_char_event, winid_t win, glui32 x, glui32 y) +{ + int i; + + if(!(LOBYTE(HD_FLAGS2) & b00100000)) + return FALSE; + + header_extension_write(1, x + 1); + header_extension_write(2, y + 1); + + stream4number(254); + stream4number(x + 1); + stream4number(y + 1); + + if(is_char_event) + return TRUE; + + for(i = z_terminators; LOBYTE(i) != 0; i++) + if(LOBYTE(i) == 255 || LOBYTE(i) == 254) + return TRUE; + + /* @read will not receive mouse input inputs if they're not + * terminating characters, but I'm not sure how to reasonably do + * that and it shouldn't matter to most things */ + + return FALSE; +} + + +typedef struct alias_entry alias_entry; + +struct alias_entry +{ + alias_entry *next; + char *from; + char *to; + BOOL in_use, is_recursive; +}; + +static alias_entry *alias_list = NULL; + + +void parse_new_alias(const char *aliascommand, BOOL is_recursive) +{ + char *from, *to; + char *stringcopy = n_strdup(aliascommand); + char *pcommand = stringcopy; + while(isspace(*pcommand)) + pcommand++; + from = pcommand; + while(isgraph(*pcommand)) + pcommand++; + if(*pcommand) { + *pcommand = 0; + pcommand++; + } + while(isspace(*pcommand)) + pcommand++; + to = pcommand; + + while(*to == ' ') + to++; + + if(*to == 0) /* Expand blank aliases to a single space */ + add_alias(from, " ", is_recursive); + else + add_alias(from, to, is_recursive); + free(stringcopy); +} + +void add_alias(const char *from, const char *to, BOOL is_recursive) +{ + alias_entry newalias; + remove_alias(from); + newalias.next = NULL; + newalias.from = n_strdup(from); + newalias.to = n_strdup(to); + newalias.in_use = FALSE; + newalias.is_recursive = is_recursive; + LEadd(alias_list, newalias); +} + + +BOOL remove_alias(const char *from) +{ + alias_entry *p, *t; + while(*from == ' ') + from++; + LEsearchremove(alias_list, p, t, n_strcmp(p->from, from) == 0, (n_free(p->from), n_free(p->to))); + return t != NULL; +} + + +static alias_entry *find_alias(const char *text, int length) +{ + alias_entry *p; + LEsearch(alias_list, p, n_strmatch(p->from, text, length)); + return p; +} + + +int search_for_aliases(char *text, int length, int maxlen) +{ + int word_start = 0; + int i; + if(!length) + return length; + for(i = 0; i <= length; i++) { + if(i == length || isspace(text[i]) || ispunct(text[i])) { + int word_length = i - word_start; + if(word_length) { + alias_entry *p = find_alias(text + word_start, word_length); + if(p && !(p->in_use)) { + int newlen = strlen(p->to); + if(length - word_length + newlen > maxlen) + newlen = maxlen - length + word_length; + n_memmove(text + word_start + newlen, + text + word_start + word_length, + maxlen - word_start - MAX(newlen, word_length)); + n_memcpy(text + word_start, p->to, newlen); + + if(p->is_recursive) { + p->in_use = TRUE; + newlen = search_for_aliases(text + word_start, newlen, maxlen - word_start); + p->in_use = FALSE; + } + + length += newlen - word_length; + i = word_start + newlen; + } + } + word_start = i+1; + } + } + return length; +} + + +int n_read(zword dest, unsigned maxlen, zword parse, unsigned initlen, + zword timer, zword routine, unsigned char *terminator) +{ + unsigned length; + unsigned i; + char *buffer = (char *) n_malloc(maxlen + 1); + +#ifdef SMART_TOKENISER + forget_corrections(); +#endif + + if(false_undo) + initlen = 0; + false_undo = FALSE; + + if(maxlen < 3) + n_show_warn(E_OUTPUT, "small text buffer", maxlen); + + if(dest > dynamic_size || dest < 64) { + n_show_error(E_OUTPUT, "input buffer in invalid location", dest); + return 0; + } + + if(dest + maxlen > dynamic_size) { + n_show_error(E_OUTPUT, "input buffer exceeds dynamic memory", dest + maxlen); + maxlen = dynamic_size - dest; + } + + if(parse >= dest && dest + maxlen > parse) { + n_show_warn(E_OUTPUT, "input buffer overlaps parse", dest + maxlen - parse); + maxlen = parse - dest; + } + + for(i = 0; i < maxlen; i++) + buffer[i] = LOBYTE(dest + i); + + length = z_read(current_window, buffer, maxlen, initlen, timer, timer_callback, routine, terminator); + + if(read_abort) { + n_free(buffer); + return 0; + } + + length = search_for_aliases(buffer, length, maxlen); + + for(i = 0; i < length; i++) { + buffer[i] = glk_char_to_lower(buffer[i]); + LOBYTEwrite(dest + i, buffer[i]); + } + + if(parse) + z_tokenise(buffer, length, parse, z_dictionary, TRUE); + + n_free(buffer); + return length; +} + + +void stream4line(const char *buffer, int length, char terminator) +{ + if(output_stream & STREAM4) { + w_glk_put_buffer_stream(stream4, buffer, length); + if(terminator != 10) + stream4number(terminator); + glk_put_char_stream(stream4, 10); + } +} + + +void op_sread(void) +{ + unsigned maxlen, length; + unsigned char term; + zword text = operand[0]; + + maxlen = LOBYTE(text) - 1; + if(numoperands < 3) + operand[2] = 0; + if(numoperands < 4) + operand[3] = 0; + + length = n_read(text + 1, maxlen, operand[1], 0, + operand[2], operand[3], &term); + if(!read_abort) { + LOBYTEwrite(text + 1 + length, 0); /* zero terminator */ + + if(allow_saveundo) { + if(!has_done_save_undo && auto_save_undo) + saveundo(FALSE); + has_done_save_undo = FALSE; + } + } +} + +void op_aread(void) +{ + int maxlen, length, initlen; + unsigned char term; + zword text = operand[0]; + + maxlen = LOBYTE(text); + initlen = LOBYTE(text + 1); + if(numoperands < 3) + operand[2] = 0; + if(numoperands < 4) + operand[3] = 0; + + length = n_read(text + 2, maxlen, operand[1], initlen, + operand[2], operand[3], &term); + if(!read_abort) { + LOBYTEwrite(text + 1, length); + mop_store_result(term); + + if(allow_saveundo) { + if(!has_done_save_undo && auto_save_undo) + saveundo(FALSE); + has_done_save_undo = FALSE; + } + } +} + +void op_read_char(void) +{ + zword validch = 0; + + if(in_timer) { + n_show_error(E_OUTPUT, "input attempted during time routine", 0); + mop_store_result(0); + return; + } + + if(operand[0] != 1) { + n_show_warn(E_OUTPUT, "read_char with non-one first operand", operand[0]); + mop_store_result(0); + return; + } + + if(numoperands < 2) + operand[1] = 0; + if(numoperands < 3) + operand[2] = 0; + + validch = z_read_char(current_window, operand[1], timer_callback, operand[2]); + + if(read_abort) + return; + + mop_store_result(validch); + + /* + if(!has_done_save_undo && auto_save_undo_char) { + saveundo(FALSE); + has_done_save_undo = FALSE; + } + */ + + stream4number(validch); +} + + +void op_show_status(void) +{ + if(!in_timer) + z_flush_fixed(upper_win); +} + +/* Returns a character, or returns 0 if it found a number, which it stores + in *num */ +unsigned char transcript_getchar(unsigned *num) +{ + glsi32 c; + *num = 0; + if(!input_stream1) + return 0; + + c = glk_get_char_stream(input_stream1); + + if(c == GLK_EOF) { + glk_stream_close(input_stream1, NULL); + input_stream1 = 0; + return 0; + } + + if(c == '[') { + while((c = glk_get_char_stream(input_stream1)) != GLK_EOF) { + if(c == ']') + break; + if(c >= '0' && c <= '9') { + *num = (*num * 10) + (c - '0'); + } + } + c = glk_get_char_stream(input_stream1); + if(c != 10) + n_show_error(E_OUTPUT, "input script not understood", c); + + return 0; + } + return c; +} + +/* Returns line terminator. Writes up to *len bytes to dest, writing in the + actual number of characters read in *len. */ +unsigned char transcript_getline(char *dest, glui32 *length) +{ + unsigned char term = 10; + unsigned char c; + unsigned num; + glui32 len; + if(!input_stream1) { + *length = 0; + return 0; + } + for(len = 0; len < *length; len++) { + c = transcript_getchar(&num); + if(!c) { + term = num; + break; + } + if(c == 10) + break; + + dest[len] = c; + } + *length = len; + return term; +} + + +void op_input_stream(void) +{ + /* *FIXME*: 10.2.4 says we shouldn't wait for [MORE]. Glk won't allow this */ + if(input_stream1) + glk_stream_close(input_stream1, NULL); + input_stream1 = 0; + + switch(operand[0]) { + case 0: + break; + case 1: + input_stream1 = n_file_prompt(fileusage_InputRecord | fileusage_TextMode, + filemode_Read); + break; + default: + n_show_error(E_OUTPUT, "unknown input stream selected", operand[0]); + } +} + + +void op_set_font(void) +{ + int lastfont; + + if(!allow_output) { + mop_store_result(0); + return; + } + + lastfont = font; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "set_font", operand[0]); + return; +#endif + + switch(operand[0]) { + case 1: font = 1; break; + case 4: font = 4; break; + case 3: if(enablefont3) font = 3; + default: mop_store_result(0); return; + } + set_fixed(font == 4); + + mop_store_result(lastfont); +} + + +void op_print_unicode(void) +{ + if(!allow_output) + return; + if(operand[0] >= 256 || (operand[0] > 127 && operand[0] < 160)) { + output_char('?'); + return; + } + if(output_stream & STREAM3) { + if(operand[0] >= 160) { + const unsigned char default_unicode_zscii_translation[] = { + 0x00, 0xde, 0x00, 0xdb, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xa3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xa2, 0x00, 0x00, 0x00, 0xdf, + 0xba, 0xaf, 0xc4, 0xd0, 0x9e, 0xca, 0xd4, 0xd6, + 0xbb, 0xb0, 0xc5, 0xa7, 0xbc, 0xb1, 0xc6, 0xa8, + 0xda, 0xd1, 0xbd, 0xb2, 0xc7, 0xd2, 0x9f, 0x00, + 0xcc, 0xbe, 0xb3, 0xc8, 0xa0, 0xb4, 0xd9, 0xa1, + 0xb5, 0xa9, 0xbf, 0xcd, 0x9b, 0xc9, 0xd3, 0xd5, + 0xb6, 0xaa, 0xc0, 0xa4, 0xb7, 0xab, 0xc1, 0xa5, + 0xd8, 0xce, 0xb8, 0xac, 0xc2, 0xcf, 0x9c, 0x00, + 0xcb, 0xb9, 0xad, 0xc3, 0x9d, 0xae, 0xd7, 0xa6 + }; + unsigned char c = default_unicode_zscii_translation[operand[0] - 160]; + output_char(c == 0 ? '?' : c); + } else if(operand[0] == 10) { + output_char(13); + } else { + output_char(operand[0]); + } + } else { + if(output_stream & STREAM1) { + z_put_char(current_window, operand[0]); + } + } +} diff --git a/interpreters/nitfol/z_io.c.orig b/interpreters/nitfol/z_io.c.orig new file mode 100644 index 0000000..631e7f0 --- /dev/null +++ b/interpreters/nitfol/z_io.c.orig @@ -0,0 +1,1099 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" + +#define UPPER_WINDOW 1 +#define LOWER_WINDOW 0 + +static zwinid lower_win, upper_win; +static zwinid current_window; + + +#define STREAM1 1 +#define STREAM2 2 +#define STREAM3 4 +#define STREAM4 8 + +static strid_t stream2, stream4; + +static int output_stream; +static zword stream3_table_starts[16]; +static zword stream3_table_locations[16]; +static int stream3_nesting_depth; + + +static int font = 1; + +static BOOL abort_output = FALSE; /* quickly stop outputting */ + + +BOOL is_transcripting(void) +{ + return (output_stream & STREAM2) != 0; +} + +void set_transcript(strid_t stream) +{ + if(stream) { + if(z_memory) { + zword flags2 = LOWORD(HD_FLAGS2) | b00000001; + LOWORDwrite(HD_FLAGS2, flags2); + } + stream2 = stream; + output_stream |= STREAM2; + if(lower_win) + z_set_transcript(lower_win, stream2); + } else { + if(z_memory) { + zword flags2 = LOWORD(HD_FLAGS2) & b11111110; + LOWORDwrite(HD_FLAGS2, flags2); + } + output_stream &= ~STREAM2; + if(lower_win) + z_set_transcript(lower_win, 0); + } +} + + +/* initialize the windowing environment */ +void init_windows(BOOL dofixed, glui32 maxwidth, glui32 maxheight) +{ + z_init_windows(dofixed, draw_upper_callback, upper_mouse_callback, + maxwidth, maxheight, &upper_win, &lower_win); + + current_window = lower_win; + output_stream = STREAM1 | (output_stream & STREAM2); + stream3_nesting_depth = 0; + font = 1; + + if(output_stream & STREAM2) { + set_transcript(stream2); + } else { + set_transcript(0); + } + + if(zversion == 6) { + v6_main_window_is(lower_win); + } +} + + +static int upper_roomname_length; + +static void counting_glk_put_char(int ch) +{ + upper_roomname_length++; + glk_put_char(ch); +} + +glui32 draw_upper_callback(winid_t win, glui32 width, glui32 height) +{ + glui32 curx = 0, cury = 0; + + glui32 numlines = 0; + + if(win == NULL || height == 0) { + if(zversion <= 3) + numlines++; + return numlines; + } + + if(zversion <= 3) { + zword location = get_var(16); + offset short_name_off = object_name(location); + + glk_window_move_cursor(win, 0, cury); + + if(location && short_name_off) { + glk_put_char(' '); curx++; + upper_roomname_length = 0; + decodezscii(short_name_off, counting_glk_put_char); + curx += upper_roomname_length; + } + + glk_window_move_cursor(win, width - 8, cury); + if((zversion <= 2) || ((LOBYTE(HD_FLAGS1) & 2) == 0)) { + if(width > curx + 26) { + glk_window_move_cursor(win, width - 24, cury); + w_glk_put_string("Score: "); + g_print_znumber(get_var(17)); + + glk_window_move_cursor(win, width - 12, cury); + w_glk_put_string("Moves: "); + g_print_znumber(get_var(18)); + } else { + g_print_znumber(get_var(17)); /* score */ + glk_put_char('/'); + g_print_znumber(get_var(18)); /* turns */ + } + } else { + const char *ampmstr[8] = { " AM", " PM" }; + int ampm = 0; + zword hours = get_var(17); + zword minutes = get_var(18); + while(hours >= 12) { + hours-=12; + ampm ^= 1; + } + if(hours == 0) + hours = 12; + if(hours < 10) + glk_put_char(' '); + g_print_number(hours); + glk_put_char(':'); + if(minutes < 10) + glk_put_char('0'); + g_print_number(minutes); + w_glk_put_string(ampmstr[ampm]); + } + numlines++; + cury++; + glk_window_move_cursor(win, 0, cury); + } + + return numlines; +} + +void output_string(const char *s) +{ + while(*s) + output_char(*s++); +} + +void output_char(int c) +{ + static int starlength = 0; + + if(output_stream & STREAM3) { /* Table output */ + zword num_chars = LOWORD(stream3_table_starts[stream3_nesting_depth-1]) +1; + if((c < 32 && c != 13) || (c >= 127 && c <= 159) || (c > 255)) + c = '?'; /* Section 7.5.3 */ + LOBYTEwrite(stream3_table_locations[stream3_nesting_depth-1], c); + stream3_table_locations[stream3_nesting_depth-1] += 1; + LOWORDwrite(stream3_table_starts[stream3_nesting_depth-1], num_chars); + } else { + if(output_stream & STREAM1) { /* Normal screen output */ + if(c >= 155 && c <= 251) { /* "extra characters" */ + zword game_unicode_table = header_extension_read(3); + if(game_unicode_table && LOBYTE(game_unicode_table) >= (zbyte) (c - 155)) { + zword address = game_unicode_table + 1 + (c - 155) * 2; + c = LOWORD(address); + } else { + const unsigned default_unicode_translation[] = { + 0xe4, 0xf6, 0xfc, 0xc4, 0xd6, 0xdc, 0xdf, 0xbb, + 0xab, 0xeb, 0xef, 0xff, 0xcb, 0xcf, 0xe1, 0xe9, + 0xed, 0xf3, 0xfa, 0xfd, 0xc1, 0xc9, 0xcd, 0xd3, + 0xda, 0xdd, 0xe0, 0xe8, 0xec, 0xf2, 0xf9, 0xc0, + 0xc8, 0xcc, 0xd2, 0xd9, 0xe2, 0xea, 0xee, 0xf4, + 0xfb, 0xc2, 0xca, 0xce, 0xd4, 0xdb, 0xe5, 0xc5, + 0xf8, 0xd8, 0xe3, 0xf1, 0xf5, 0xc3, 0xd1, 0xd5, + 0xe6, 0xc6, 0xe7, 0xc7, 0xfe, 0xf0, 0xde, 0xd0, + 0xa3, 0x153, 0x152, 0xa1, 0xbf + }; + c = default_unicode_translation[c - 155]; + } + } + if(c == '*') { + if(++starlength == 3) /* Three asterisks usually means win or death */ + if(automap_unexplore()) + abort_output = TRUE; + } else { + starlength = 0; + } + + if(font == 3) { + const char font3trans[] = + " <>/" "\\ --" "||||" "--\\/" /* 32-47 */ + "\\//\\/\\@ " " |" "|-- " /* 48-63 */ + " /" "\\/\\ " " " " " /* 64-79 */ + " " "####" " X+" "udb*" /* 80-95 */ + "?abc" "defg" "hijk" "lmno" /* 96-111 */ + "pqrs" "tuvw" "xyzU" "DB?"; /* 112-126 */ + if(c >= 32 && c <= 126) + c = font3trans[c - 32]; + } + + if(allow_output) + z_put_char(current_window, c); + } + } +} + +void n_print_number(unsigned n) +{ + int i; + char buffer[12]; + int length = n_to_decimal(buffer, n); + + for(i = length - 1; i >= 0; i--) + output_char(buffer[i]); +} + + +void g_print_number(unsigned n) +{ + int i; + char buffer[12]; + int length = n_to_decimal(buffer, n); + + for(i = length - 1; i >= 0; i--) + glk_put_char(buffer[i]); +} + +void g_print_snumber(int n) +{ + if(n < 0) { + glk_put_char('-'); + n = -n; + } + g_print_number(n); +} + +void g_print_znumber(zword n) +{ + if(is_neg(n)) { + glk_put_char('-'); + g_print_number(neg(n)); + } else { + g_print_number(n); + } +} + +void n_print_znumber(zword n) +{ + if(is_neg(n)) { + output_char('-'); + n_print_number(neg(n)); + } else { + n_print_number(n); + } +} + + +void stream4number(unsigned c) +{ + if(output_stream & STREAM4) { + glk_stream_set_current(stream4); + glk_put_char('['); + g_print_number(c); + glk_put_char(']'); + glk_put_char(10); + } +} + + +void op_buffer_mode(void) +{ + /* FIXME: Glk can't really do this. + * I could rely on the Plotkin Bug to do it, but that's ugly and would + * break 20 years from now when somebody fixes it. I could also print + * spaces between each letter, which isn't the intended effect + * + * For now, do nothing. Doubt this opcode is used often anyway... + */ +} + + +void op_check_unicode(void) +{ + unsigned result = 0; + if(operand[0] <= 255 && + (glk_gestalt(gestalt_CharOutput, (unsigned char) operand[0]) != + gestalt_CharOutput_CannotPrint)) + result |= 1; + if(operand[0] <= 255 && + (glk_gestalt(gestalt_LineInput, (unsigned char) operand[0]) != + FALSE)) + result |= 2; + mop_store_result(result); +} + + +void op_erase_line(void) +{ + if(!allow_output) + return; + + if(operand[0] == 1 && current_window == upper_win) { + z_erase_line(current_window); + } +} + + +void op_erase_window(void) +{ + if(!allow_output) + return; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "erase_window", operand[0]); + return; +#endif + + switch(operand[0]) { + case neg(1): + operand[0] = 0; op_split_window(); + current_window = lower_win; + case neg(2): + case UPPER_WINDOW: + z_clear_window(upper_win); + if(operand[0] == UPPER_WINDOW) break; /* Ok, this is evil, but it works. */ + case LOWER_WINDOW: + z_clear_window(lower_win); + break; + } +} + + +void op_get_cursor(void) +{ + zword x, y; + z_getxy(upper_win, &x, &y); + LOWORDwrite(operand[0], x); + LOWORDwrite(operand[0] + ZWORD_SIZE, y); +} + + +void op_new_line(void) +{ + output_char(13); +} + + +void op_output_stream(void) +{ + if(operand[0] == 0) + return; + if(is_neg(operand[0])) { + switch(neg(operand[0])) { + case 1: + if(!allow_output) + return; + output_stream &= ~STREAM1; + break; + + case 2: + if(!allow_output) + return; + set_transcript(0); + break; + + case 3: + if(stream3_nesting_depth) + stream3_nesting_depth--; + else + n_show_error(E_OUTPUT, "stream3 unnested too many times", 0); + if(!stream3_nesting_depth) + output_stream &= ~STREAM3; + break; + + case 4: + if(!allow_output) + return; + glk_stream_close(stream4, NULL); + stream4 = 0; + output_stream &= ~STREAM4; + break; + + default: + n_show_error(E_OUTPUT, "unknown stream deselected", neg(operand[0])); + } + } else { + switch(operand[0]) { + case 1: + if(!allow_output) + return; + output_stream |= STREAM1; + break; + + case 2: + if(!allow_output) + return; + if(!stream2) { + stream2 = n_file_prompt(fileusage_Transcript | fileusage_TextMode, + filemode_WriteAppend); + } + if(stream2) + set_transcript(stream2); + break; + + case 3: + if(stream3_nesting_depth >= 16) { + n_show_error(E_OUTPUT, "nesting stream 3 too deeply", + stream3_nesting_depth); + return; + } + LOWORDwrite(operand[1], 0); + stream3_table_starts[stream3_nesting_depth] = operand[1]; + stream3_table_locations[stream3_nesting_depth] = operand[1] + 2; + + output_stream |= STREAM3; + stream3_nesting_depth++; + break; + + case 4: + if(!allow_output) + return; + stream4 = n_file_prompt(fileusage_InputRecord | fileusage_TextMode, + filemode_WriteAppend); + if(stream4) + output_stream |= STREAM4; + break; + default: + n_show_error(E_OUTPUT, "unknown stream selected", operand[0]); + } + } +} + + +void op_print(void) +{ + int length; + abort_output = FALSE; + length = decodezscii(PC, output_char); + if(!abort_output) + PC += length; +} + + +void op_print_ret(void) +{ + int length; + abort_output = FALSE; + length = decodezscii(PC, output_char); + if(abort_output) + return; + PC += length; + output_char(13); + if(abort_output) + return; + mop_func_return(1); +} + + +void op_print_addr(void) +{ + decodezscii(operand[0], output_char); +} + + +void op_print_paddr(void) +{ + getstring(operand[0]); +} + + +void op_print_char(void) +{ + if(operand[0] > 1023) { + n_show_error(E_INSTR, "attempt to print character > 1023", operand[0]); + return; + } + output_char(operand[0]); +} + + +void op_print_num(void) +{ + n_print_znumber(operand[0]); +} + + +void op_print_table(void) +{ + unsigned x, y; + zword text = operand[0]; + zword width = operand[1]; + zword height = operand[2]; + zword skips = operand[3]; + + zword startx, starty; + unsigned win_width, win_height; + + z_getxy(current_window, &startx, &starty); + z_getsize(current_window, &win_width, &win_height); + + if(numoperands < 4) + skips = 0; + if(numoperands < 3) + height = 1; + + if(current_window == upper_win) { + if(startx + width - 1 > win_width) { + int diff; + n_show_warn(E_OUTPUT, "table too wide; trimming", width); + diff = startx + width - 1 - win_width; + width -= diff; + skips += diff; + } + if(starty + height - 1 > win_height) { + n_show_warn(E_OUTPUT, "table too tall; trimming", height); + height = win_height - starty + 1; + } + } + + for(y = 0; y < height; y++) { + if(current_window == upper_win && allow_output) + z_setxy(upper_win, startx, y+starty); + + for(x = 0; x < width; x++) { + output_char(LOBYTE(text)); + text++; + } + text += skips; + + if(current_window != upper_win && y+1 < height) + output_char(13); + } +} + + +void op_set_colour(void) +{ + if(!allow_output) + return; + + z_set_color(current_window, operand[0], operand[1]); +} + + +void op_set_cursor(void) +{ + unsigned width, height; + zword x = operand[1]; + zword y = operand[0]; + + if(!allow_output) + return; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "set_cursor y=", operand[0]); + n_show_debug(E_OUTPUT, "set_cursor x=", operand[1]); +#endif + + if(current_window != upper_win) { + return; + } + + z_getsize(current_window, &width, &height); + + if(y == 0 || y > height) { /* section 8.7.2.3 */ + n_show_error(E_OUTPUT, "illegal line for set_cursor", y); + if(y == 0 || y > 512) + return; + z_set_height(upper_win, y); /* Resize to allow broken games to work */ + } + if(x == 0 || x > width) { + n_show_error(E_OUTPUT, "illegal column for set_cursor", x); + return; + } + + z_setxy(current_window, x, y); +} + + +void op_set_text_style(void) +{ + if(!allow_output) + return; + + z_set_style(current_window, operand[0]); +} + + +void op_set_window(void) +{ + if(!allow_output) + return; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "set_window", operand[0]); + return; +#endif + + switch(operand[0]) { + case UPPER_WINDOW: + current_window = upper_win; + z_setxy(upper_win, 1, 1); + break; + case LOWER_WINDOW: + current_window = lower_win; + break; + default: + n_show_error(E_OUTPUT, "invalid window selected", operand[0]); + } +} + + +void op_split_window(void) +{ + if(!allow_output) + return; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "split_window", operand[0]); +#endif + + if(zversion == 6) + return; + + + if(operand[0] > 512) { + n_show_error(E_OUTPUT, "game is being ridiculous", operand[0]); + return; + } + + if(zversion == 3) + z_set_height(upper_win, 0); /* clear the whole upper window first */ + + z_set_height(upper_win, operand[0]); +} + + +static BOOL timer_callback(zword routine) +{ + zword dummylocals[16]; + in_timer = TRUE; + mop_call(routine, 0, dummylocals, -2); /* add a special stack frame */ + decode(); /* start interpreting the routine */ + in_timer = FALSE; + exit_decoder = FALSE; + return time_ret; +} + + +BOOL upper_mouse_callback(BOOL is_char_event, winid_t win, glui32 x, glui32 y) +{ + int i; + + if(!(LOBYTE(HD_FLAGS2) & b00100000)) + return FALSE; + + header_extension_write(1, x + 1); + header_extension_write(2, y + 1); + + stream4number(254); + stream4number(x + 1); + stream4number(y + 1); + + if(is_char_event) + return TRUE; + + for(i = z_terminators; LOBYTE(i) != 0; i++) + if(LOBYTE(i) == 255 || LOBYTE(i) == 254) + return TRUE; + + /* @read will not receive mouse input inputs if they're not + * terminating characters, but I'm not sure how to reasonably do + * that and it shouldn't matter to most things */ + + return FALSE; +} + + +typedef struct alias_entry alias_entry; + +struct alias_entry +{ + alias_entry *next; + char *from; + char *to; + BOOL in_use, is_recursive; +}; + +static alias_entry *alias_list = NULL; + + +void parse_new_alias(const char *aliascommand, BOOL is_recursive) +{ + char *from, *to; + char *stringcopy = n_strdup(aliascommand); + char *pcommand = stringcopy; + while(isspace(*pcommand)) + pcommand++; + from = pcommand; + while(isgraph(*pcommand)) + pcommand++; + if(*pcommand) { + *pcommand = 0; + pcommand++; + } + while(isspace(*pcommand)) + pcommand++; + to = pcommand; + + while(*to == ' ') + to++; + + if(*to == 0) /* Expand blank aliases to a single space */ + add_alias(from, " ", is_recursive); + else + add_alias(from, to, is_recursive); + free(stringcopy); +} + +void add_alias(const char *from, const char *to, BOOL is_recursive) +{ + alias_entry newalias; + remove_alias(from); + newalias.next = NULL; + newalias.from = n_strdup(from); + newalias.to = n_strdup(to); + newalias.in_use = FALSE; + newalias.is_recursive = is_recursive; + LEadd(alias_list, newalias); +} + + +BOOL remove_alias(const char *from) +{ + alias_entry *p, *t; + while(*from == ' ') + from++; + LEsearchremove(alias_list, p, t, n_strcmp(p->from, from) == 0, (n_free(p->from), n_free(p->to))); + return t != NULL; +} + + +static alias_entry *find_alias(const char *text, int length) +{ + alias_entry *p; + LEsearch(alias_list, p, n_strmatch(p->from, text, length)); + return p; +} + + +int search_for_aliases(char *text, int length, int maxlen) +{ + int word_start = 0; + int i; + if(!length) + return length; + for(i = 0; i <= length; i++) { + if(i == length || isspace(text[i]) || ispunct(text[i])) { + int word_length = i - word_start; + if(word_length) { + alias_entry *p = find_alias(text + word_start, word_length); + if(p && !(p->in_use)) { + int newlen = strlen(p->to); + if(length - word_length + newlen > maxlen) + newlen = maxlen - length + word_length; + n_memmove(text + word_start + newlen, + text + word_start + word_length, + maxlen - word_start - MAX(newlen, word_length)); + n_memcpy(text + word_start, p->to, newlen); + + if(p->is_recursive) { + p->in_use = TRUE; + newlen = search_for_aliases(text + word_start, newlen, maxlen - word_start); + p->in_use = FALSE; + } + + length += newlen - word_length; + i = word_start + newlen; + } + } + word_start = i+1; + } + } + return length; +} + + +int n_read(zword dest, unsigned maxlen, zword parse, unsigned initlen, + zword timer, zword routine, unsigned char *terminator) +{ + unsigned length; + unsigned i; + char *buffer = (char *) n_malloc(maxlen + 1); + +#ifdef SMART_TOKENISER + forget_corrections(); +#endif + + if(false_undo) + initlen = 0; + false_undo = FALSE; + + if(maxlen < 3) + n_show_warn(E_OUTPUT, "small text buffer", maxlen); + + if(dest > dynamic_size || dest < 64) { + n_show_error(E_OUTPUT, "input buffer in invalid location", dest); + return 0; + } + + if(dest + maxlen > dynamic_size) { + n_show_error(E_OUTPUT, "input buffer exceeds dynamic memory", dest + maxlen); + maxlen = dynamic_size - dest; + } + + if(parse >= dest && dest + maxlen > parse) { + n_show_warn(E_OUTPUT, "input buffer overlaps parse", dest + maxlen - parse); + maxlen = parse - dest; + } + + for(i = 0; i < maxlen; i++) + buffer[i] = LOBYTE(dest + i); + + length = z_read(current_window, buffer, maxlen, initlen, timer, timer_callback, routine, terminator); + + if(read_abort) { + n_free(buffer); + return 0; + } + + length = search_for_aliases(buffer, length, maxlen); + + for(i = 0; i < length; i++) { + buffer[i] = glk_char_to_lower(buffer[i]); + LOBYTEwrite(dest + i, buffer[i]); + } + + if(parse) + z_tokenise(buffer, length, parse, z_dictionary, TRUE); + + n_free(buffer); + return length; +} + + +void stream4line(const char *buffer, int length, char terminator) +{ + if(output_stream & STREAM4) { + w_glk_put_buffer_stream(stream4, buffer, length); + if(terminator != 10) + stream4number(terminator); + glk_put_char_stream(stream4, 10); + } +} + + +void op_sread(void) +{ + unsigned maxlen, length; + unsigned char term; + zword text = operand[0]; + + maxlen = LOBYTE(text) - 1; + if(numoperands < 3) + operand[2] = 0; + if(numoperands < 4) + operand[3] = 0; + + length = n_read(text + 1, maxlen, operand[1], 0, + operand[2], operand[3], &term); + if(!read_abort) { + LOBYTEwrite(text + 1 + length, 0); /* zero terminator */ + + if(allow_saveundo) { + if(!has_done_save_undo && auto_save_undo) + saveundo(FALSE); + has_done_save_undo = FALSE; + } + } +} + +void op_aread(void) +{ + int maxlen, length, initlen; + unsigned char term; + zword text = operand[0]; + + maxlen = LOBYTE(text); + initlen = LOBYTE(text + 1); + if(numoperands < 3) + operand[2] = 0; + if(numoperands < 4) + operand[3] = 0; + + length = n_read(text + 2, maxlen, operand[1], initlen, + operand[2], operand[3], &term); + if(!read_abort) { + LOBYTEwrite(text + 1, length); + mop_store_result(term); + + if(allow_saveundo) { + if(!has_done_save_undo && auto_save_undo) + saveundo(FALSE); + has_done_save_undo = FALSE; + } + } +} + +void op_read_char(void) +{ + zword validch = 0; + + if(in_timer) { + n_show_error(E_OUTPUT, "input attempted during time routine", 0); + mop_store_result(0); + return; + } + + if(operand[0] != 1) { + n_show_warn(E_OUTPUT, "read_char with non-one first operand", operand[0]); + mop_store_result(0); + return; + } + + if(numoperands < 2) + operand[1] = 0; + if(numoperands < 3) + operand[2] = 0; + + validch = z_read_char(current_window, operand[1], timer_callback, operand[2]); + + if(read_abort) + return; + + mop_store_result(validch); + + /* + if(!has_done_save_undo && auto_save_undo_char) { + saveundo(FALSE); + has_done_save_undo = FALSE; + } + */ + + stream4number(validch); +} + + +void op_show_status(void) +{ + if(!in_timer) + z_flush_fixed(upper_win); +} + +/* Returns a character, or returns 0 if it found a number, which it stores + in *num */ +unsigned char transcript_getchar(unsigned *num) +{ + glsi32 c; + *num = 0; + if(!input_stream1) + return 0; + + c = glk_get_char_stream(input_stream1); + + if(c == GLK_EOF) { + glk_stream_close(input_stream1, NULL); + input_stream1 = 0; + return 0; + } + + if(c == '[') { + while((c = glk_get_char_stream(input_stream1)) != GLK_EOF) { + if(c == ']') + break; + if(c >= '0' && c <= '9') { + *num = (*num * 10) + (c - '0'); + } + } + c = glk_get_char_stream(input_stream1); + if(c != 10) + n_show_error(E_OUTPUT, "input script not understood", c); + + return 0; + } + return c; +} + +/* Returns line terminator. Writes up to *len bytes to dest, writing in the + actual number of characters read in *len. */ +unsigned char transcript_getline(char *dest, glui32 *length) +{ + unsigned char term = 10; + unsigned char c; + unsigned num; + glui32 len; + if(!input_stream1) { + *length = 0; + return 0; + } + for(len = 0; len < *length; len++) { + c = transcript_getchar(&num); + if(!c) { + term = num; + break; + } + if(c == 10) + break; + + dest[len] = c; + } + *length = len; + return term; +} + + +void op_input_stream(void) +{ + /* *FIXME*: 10.2.4 says we shouldn't wait for [MORE]. Glk won't allow this */ + if(input_stream1) + glk_stream_close(input_stream1, NULL); + input_stream1 = 0; + + switch(operand[0]) { + case 0: + break; + case 1: + input_stream1 = n_file_prompt(fileusage_InputRecord | fileusage_TextMode, + filemode_Read); + break; + default: + n_show_error(E_OUTPUT, "unknown input stream selected", operand[0]); + } +} + + +void op_set_font(void) +{ + int lastfont; + + if(!allow_output) { + mop_store_result(0); + return; + } + + lastfont = font; + +#ifdef DEBUG_IO + n_show_debug(E_OUTPUT, "set_font", operand[0]); + return; +#endif + + switch(operand[0]) { + case 1: font = 1; break; + case 4: font = 4; break; + case 3: if(enablefont3) font = 3; + default: mop_store_result(0); return; + } + set_fixed(font == 4); + + mop_store_result(lastfont); +} + + +void op_print_unicode(void) +{ + output_char(operand[0]); +} diff --git a/interpreters/nitfol/z_io.h b/interpreters/nitfol/z_io.h new file mode 100644 index 0000000..f0789f2 --- /dev/null +++ b/interpreters/nitfol/z_io.h @@ -0,0 +1,59 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i z_io.c' */ +#ifndef CFH_Z_IO_H +#define CFH_Z_IO_H + +/* From `z_io.c': */ +BOOL is_transcripting (void); +void set_transcript (strid_t stream ); +void init_windows (BOOL dofixed , glui32 maxwidth , glui32 maxheight ); +glui32 draw_upper_callback (winid_t win , glui32 width , glui32 height ); +void output_string (const char *s ); +void output_char (int c ); +void n_print_number (unsigned n ); +void g_print_number (unsigned n ); +void g_print_snumber (int n ); +void g_print_znumber (zword n ); +void n_print_znumber (zword n ); +void stream4number (unsigned c ); +void op_buffer_mode (void); +void op_check_unicode (void); +void op_erase_line (void); +void op_erase_window (void); +void op_get_cursor (void); +void op_new_line (void); +void op_output_stream (void); +void op_print (void); +void op_print_ret (void); +void op_print_addr (void); +void op_print_paddr (void); +void op_print_char (void); +void op_print_num (void); +void op_print_table (void); +void op_set_colour (void); +void op_set_cursor (void); +void op_set_text_style (void); +void op_set_window (void); +void op_split_window (void); +BOOL upper_mouse_callback (BOOL is_char_event , winid_t win , glui32 x , glui32 y ); +void parse_new_alias (const char *aliascommand , BOOL is_recursive ); +void add_alias (const char *from , const char *to , BOOL is_recursive ); +BOOL remove_alias (const char *from ); +int search_for_aliases (char *text , int length , int maxlen ); +int n_read (zword dest , unsigned maxlen , zword parse , unsigned initlen , zword timer , zword routine , unsigned char *terminator ); +void stream4line (const char *buffer , int length , char terminator ); +void op_sread (void); +void op_aread (void); +void op_read_char (void); +void op_show_status (void); +unsigned char transcript_getchar (unsigned *num ); +unsigned char transcript_getline (char *dest , glui32 *length ); +void op_input_stream (void); +void op_set_font (void); +void op_print_unicode (void); + +#endif /* CFH_Z_IO_H */ diff --git a/interpreters/nitfol/zscii.c b/interpreters/nitfol/zscii.c new file mode 100644 index 0000000..f7c12d4 --- /dev/null +++ b/interpreters/nitfol/zscii.c @@ -0,0 +1,323 @@ +/* Nitfol - z-machine interpreter using Glk for output. + Copyright (C) 1999 Evin Robertson + + 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., 59 Temple Place, Suite 330, Boston, MA 02111, USA. + + The author can be reached at nitfol@deja.com +*/ +#include "nitfol.h" +#include +#include + +/* string.c - decode and encode strings */ + + +#define NUM_CACHE 4 +#define CACHE_SIZE 4096 + +/* +static offset recent[4]; +static unsigned uses[4]; + +static int is_cached; +*/ + +int getstring(zword packedaddress) +{ + return decodezscii(UNPACKS(packedaddress), output_char); +} + + +/* Returns character for given alphabet, letter pair */ +static unsigned char alphabetsoup(unsigned spoon, unsigned char letter) +{ + const char *alphabet; + + if(letter == 0) + return 32; /* space */ + + if(letter < 6 || letter > 31) + return 32; /* gurgle */ + + if(zversion == 1) { + alphabet = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + " 0123456789.,!?_#'\"/\\<-:()"; + if(letter == 1) + return 13; /* newline */ + } else { + alphabet = "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + " ^0123456789.,!?_#'\"/\\-:()"; + + if(spoon == 2 && letter == 7) + return 13; /* newline */ + + if(zversion >= 5) { /* v5 can use its own bowl of soup */ + zword t = LOWORD(HD_ALPHABET); + if(t != 0) + return LOBYTE(t + spoon*26 + (letter-6)); /* alphabet = z_memory+t */ + } + } + + return alphabet[spoon * 26 + (letter-6)]; +} + + +#define END -1 + + +/* Text is arranged in triplets - this function takes a reference to location, + * and to shift_amt which says how many bits are left at that location. Each + * letter in 5 bits, shift_amt is decremented by this each time. When a + * location runs out, we move to another location. + */ +static char untriplet(offset *location, int *shift_amt) +{ + unsigned triplet; + unsigned char result; + if(*shift_amt == END) { + if(!testing_string) + n_show_error(E_STRING, "attempt to read past end of string", *location); + string_bad = TRUE; + return 0; + } + triplet = HISTRWORD(*location); + result = (triplet >> *shift_amt) & b00011111; + + *shift_amt -= 5; /* next character is 5 bits to the right */ + + if(*shift_amt < 0) { /* Reached end of triplet; go on to next */ + *shift_amt = 10; + *location += 2; + + if(triplet & 0x8000) /* High bit set - reached the end of the string */ + *shift_amt = END; + } + return result; +} + + +/* Decodes a zscii string at address 'zscii', sending the decoded characters + into putcharfunc. Returns number of zscii characters it ate until it + reached the end of the string. */ +int decodezscii(offset zscii, void (*putcharfunc)(int)) +{ + const int alphaup[3] = { 1, 2, 0 }; + const int alphadn[3] = { 2, 0, 1 }; + + int shift_amt = 10; + int alphalock = 0; + int alphashift = 0; + int alphacurrent; + offset startzscii = zscii; + static int depth = 0; + depth++; + + if(depth > 2) { /* Nested abbreviations */ + if(!testing_string) { + int remdepth = depth; + depth = 0; + n_show_error(E_STRING, "nested abbreviations", zscii); + depth = remdepth; + } + string_bad = TRUE; + depth--; + return 0; + } + + do { + unsigned char z, x; + + if(zscii > total_size) { + if(!testing_string) + n_show_error(E_STRING, "attempt to print string beyond end of story", zscii); + string_bad = TRUE; + depth--; + return 0; + } + + z = untriplet(&zscii, &shift_amt); + + alphacurrent = alphashift; + alphashift = alphalock; + + if(z < 6) { + if(zversion <= 2) { + switch(z) { + case 0: putcharfunc(32); break; /* space */ + case 1: + if(zversion == 1) { + putcharfunc(13); /* newline */ + } else { /* abbreviation */ + x = untriplet(&zscii, &shift_amt); + decodezscii(((offset) HIWORD(z_synonymtable + x*ZWORD_SIZE)) * 2, + putcharfunc); + } + break; + case 2: alphashift = alphaup[alphashift]; break; + case 3: alphashift = alphadn[alphashift]; break; + case 4: alphalock = alphashift = alphaup[alphalock]; break; + case 5: alphalock = alphashift = alphadn[alphalock]; break; + } + } else { + switch(z) { + case 0: putcharfunc(32); break; /* space */ + case 1: case 2: case 3: /* abbreviations */ + x = untriplet(&zscii, &shift_amt); + decodezscii((offset) 2 * HIWORD(z_synonymtable + + (32*(z-1) + x) * ZWORD_SIZE), + putcharfunc); + + break; + case 4: alphashift = alphaup[alphashift]; break; + case 5: alphashift = alphadn[alphashift]; break; + } + } + } else { + + if(alphacurrent == 2 && z == 6) { + int multibyte; + if(shift_amt == END) + break; + + multibyte = untriplet(&zscii, &shift_amt) << 5; + + if(shift_amt == END) + break; + multibyte |= untriplet(&zscii, &shift_amt); + + putcharfunc(multibyte); + } else { + putcharfunc(alphabetsoup(alphacurrent, z)); + } + } + } while(shift_amt != END); + + depth--; + return zscii - startzscii; +} + + +static void tripletize(zbyte **location, unsigned *triplet, int *count, + char value, BOOL isend) +{ + if(*location == NULL) + return; /* stop doing stuff if we're already done. */ + + *triplet = ((*triplet) << 5) | value; + *count += 1; + + if(isend) { + while(*count < 3) { + *triplet = ((*triplet) << 5) | 5; /* 5 is the official pad char */ + *count += 1; + } + *triplet |= 0x8000; /* end bit */ + } + + if(*count == 3) { + (*location)[0] = *triplet >> 8; /* high byte first */ + (*location)[1] = *triplet & 255; /* then lower */ + *triplet = 0; + *count = 0; + *location += 2; + + if(isend) + *location = NULL; + } +} + + +static BOOL search_soup(zbyte c, int *rspoon, int *rletter) +{ + int spoon, letter; + for(spoon = 0; spoon < 3; spoon++) + for(letter = 0; letter < 32; letter++) + if(c == alphabetsoup(spoon, letter)) { + *rspoon = spoon; + *rletter = letter; + return TRUE; + } + return FALSE; +} + + +int encodezscii(zbyte *dest, int mindestlen, int maxdestlen, + const char *source, int sourcelen) +{ + int alphachanger[3]; + int i; + int destlen = 0; + int done = FALSE; + unsigned triplet = 0; int count = 0; + + if(zversion <= 2) { + alphachanger[1] = 2; /* Shift up */ + alphachanger[2] = 3; /* Shift down */ + } else { + alphachanger[1] = 4; /* Shift up */ + alphachanger[2] = 5; /* Shift down */ + } + mindestlen *= 3; maxdestlen *= 3; /* Change byte sizes to zscii sizes */ + mindestlen /= 2; maxdestlen /= 2; + + for(i = 0; i < sourcelen && !done && dest != NULL; i++) { + int spoon, letter; + if(search_soup(source[i], &spoon, &letter)) { + if(spoon != 0) { /* switch alphabet if necessary */ + destlen++; + tripletize(&dest, &triplet, &count, + alphachanger[spoon], destlen >= maxdestlen); + } + + destlen++; + done = ((destlen >= maxdestlen) || (i == sourcelen - 1)) && + (destlen >= mindestlen); + + tripletize(&dest, &triplet, &count, letter, done); + } else { /* The character wasn't found, so use multibyte encoding */ + destlen++; + tripletize(&dest, &triplet, &count, alphachanger[2],destlen>=maxdestlen); + destlen++; + tripletize(&dest, &triplet, &count, 6, destlen >= maxdestlen); + destlen++; + /* Upper 5 bits (really 3) */ + tripletize(&dest, &triplet, &count, source[i] >> 5, destlen>=maxdestlen); + + destlen++; + done = ((destlen >= maxdestlen) || (i == sourcelen - 1)) && + (destlen >= mindestlen); + /* Lower 5 bits */ + tripletize(&dest, &triplet, &count, source[i] & b00011111, done); + } + + } + + if(!done) { /* come back here */ + while(destlen < mindestlen - 1) { /* oh yeah you pad me out */ + tripletize(&dest, &triplet, &count, 5, FALSE);/* uh huh */ + destlen++; /* uh yup */ + } /* uh */ + tripletize(&dest, &triplet, &count, 5, TRUE); /* uh huh, done */ + } + return i; +} + +void op_encode_text(void) +{ + encodezscii(z_memory + operand[3], 6, 6, + (char *) z_memory + operand[0] + operand[2], operand[1]); +} diff --git a/interpreters/nitfol/zscii.h b/interpreters/nitfol/zscii.h new file mode 100644 index 0000000..f9a0471 --- /dev/null +++ b/interpreters/nitfol/zscii.h @@ -0,0 +1,16 @@ +/* This is a Cfunctions (version 0.24) generated header file. + Cfunctions is a free program for extracting headers from C files. + Get Cfunctions from `http://www.hayamasa.demon.co.uk/cfunctions'. */ + +/* This file was generated with: +`cfunctions -i zscii.c' */ +#ifndef CFH_ZSCII_H +#define CFH_ZSCII_H + +/* From `zscii.c': */ +int getstring (zword packedaddress ); +int decodezscii (offset zscii , void ( *putcharfunc ) ( int ) ); +int encodezscii (zbyte *dest , int mindestlen , int maxdestlen , const char *source , int sourcelen ); +void op_encode_text (void); + +#endif /* CFH_ZSCII_H */ diff --git a/src/Makefile.am b/src/Makefile.am index 6da19f9..29d42f7 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -33,7 +33,8 @@ libchimara_la_SOURCES = \ timer.c timer.h \ window.c window.h \ gi_blorb.c gi_blorb.h \ - resource.c resource.h + resource.c resource.h \ + glkstart.h libchimara_la_CPPFLAGS = \ -DG_LOG_DOMAIN=\"Chimara\" libchimara_la_CFLAGS = @CHIMARA_CFLAGS@ $(AM_CFLAGS) diff --git a/src/glkstart.h b/src/glkstart.h new file mode 100644 index 0000000..e6b9010 --- /dev/null +++ b/src/glkstart.h @@ -0,0 +1,57 @@ +/* glkstart.h: Unix-specific header file for GlkTerm, CheapGlk, and XGlk + (Unix implementations of the Glk API). + Designed by Andrew Plotkin + http://www.eblong.com/zarf/glk/index.html +*/ + +/* This header defines an interface that must be used by program linked + with the various Unix Glk libraries -- at least, the three I wrote. + (I encourage anyone writing a Unix Glk library to use this interface, + but it's not part of the Glk spec.) + + Because Glk is *almost* perfectly portable, this interface *almost* + doesn't have to exist. In practice, it's small. +*/ + +#ifndef GT_START_H +#define GT_START_H + +/* We define our own TRUE and FALSE and NULL, because ANSI + is a strange world. */ +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef NULL +#define NULL 0 +#endif + +#define glkunix_arg_End (0) +#define glkunix_arg_ValueFollows (1) +#define glkunix_arg_NoValue (2) +#define glkunix_arg_ValueCanFollow (3) +#define glkunix_arg_NumberValue (4) + +typedef struct glkunix_argumentlist_struct { + char *name; + int argtype; + char *desc; +} glkunix_argumentlist_t; + +typedef struct glkunix_startup_struct { + int argc; + char **argv; +} glkunix_startup_t; + +extern glkunix_argumentlist_t glkunix_arguments[]; + +extern int glkunix_startup_code(glkunix_startup_t *data); + +extern void glkunix_set_base_file(char *filename); +extern strid_t glkunix_stream_open_pathname(char *pathname, glui32 textmode, + glui32 rock); + +#endif /* GT_START_H */ + diff --git a/src/main.c b/src/main.c index f6c312e..9362559 100644 --- a/src/main.c +++ b/src/main.c @@ -118,7 +118,7 @@ main(int argc, char *argv[]) g_object_unref( G_OBJECT(builder) ); - if( !chimara_glk_run(CHIMARA_GLK(glk), ".libs/multiwin.so", &error) ) { + if( !chimara_glk_run(CHIMARA_GLK(glk), "../interpreters/nitfol/.libs/nitfol.so", &error) ) { error_dialog(GTK_WINDOW(window), error, "Error starting Glk library: "); return 1; }