TADS Revision History - versions 2.2.3 through 2.4.0
====================================================

This file contains a list of changes that have been made to TADS from
version 2.1.0 through version 2.4.0.  Most of the changes are fixes
to bugs, so they don't change the documented behavior, but a few, as
explained below, add new functionality to TADS.  Releases are listed
with the most recent release first; each release incorporates all new
features and bug fixes of each prior release unless otherwise stated.

Starting with version 2.2.6, these release notes are divided into
two separate files: one for "generic" changes that apply to TADS on
all types of computers and operating systems, and one for changes
that apply only to specific platforms.  This file contains the
generic release notes.  Platform-specific release notes are in a
separate file:

  tadsv240.txt    - MS-DOS and Windows changes, 2.2.3 through 2.4.0


These release notes sometimes refer to the "character-mode" run-time.
This is meant to distinguish the traditional TADS run-time from the
HTML TADS run-time.  The traditional TADS run-time has a graphical
user interface on some systems, such as the Macintosh, so it's not
really a character-mode application on those systems; nonetheless,
we still refer to it here as the character-mode run-time simply to
make it clear that we're not talking about the HTML TADS version.

Note that changes from version 2.0 up to 2.2.2 are not included in
this file in order to keep its size under control.  Older release
notes are in separate files for each platform.  For DOS, revisions
from 2.0 through 2.1.0 are available in the file TADSV200.DOS, and
those from 2.1.1 through 2.2.2 are in TADSV222.DOS.  Similar files
are available for other platforms; please refer to your platform-
specific release notes for details.  Because these files are quite
large and are static (they are, after all, historical), they're not
included in the normal TADS distributions, but you can download them
from the Interactive Fiction Archive via the internet at
ftp://ftp.gmd.de/if-archive/programming/tads.


------------------------------------------------------------------------------
2.4.0 - May 16, 1999

 - A new built-in function, exitobj, provides a new method for skipping
   the remaining processing for a command at any point.  This new
   function is very similar to the "exit" statement, but differs in one
   respect: whereas the "exit" statement terminates all further
   processing for a command and skips directly to the fuses and daemons,
   "exitobj" skips the remaining processing only for the current object
   in a command and proceeds to the next object.

   This difference is significant when the player types in a command
   involving multiple objects.  For example, suppose that you define a
   roomAction method in the current room as follows:

     roomAction(actor, verb, dobj, prep, iobj) =
     {
       /*
        *  when the player touches anything not already in their inventory,
        *  make it vanish
        */
       if (dobj != nil && !dobj.isIn(actor))
       {
         "\^<<dobj.thedesc>> disappears in a flash of light!\n";
         dobj.moveInto(nil);
         exit;
       }
     }

   Consider the following transcript:

     >take ball
     The ball disappears in a flash of light!

     >take hammer and chisel
     The hammer disappears in a flash of light!

   The first response makes sense, but the second isn't exactly what you
   wanted.  The problem is that the "exit" statement tells the parser to
   skip processing of any objects other than the current one.

   To change this, you can simply change the "exit" statement in the code
   listing above to "exitobj".  The result will be more sensible:

     >take hammer and chisel
     The hammer disappears in a flash of light!
     The chisel disappears in a flash of light!

   "exitobj" is useful when you want to skip the remaining processing for
   a command for the current object, but you still want the command to be
   considered successful.  "exit" is more suited for situations where the
   outcome of the command is something less than total success, and you
   want to skip further processing of other objects involved in the
   command.  "exitobj" is particularly useful with the new execCommand()
   built-in function (see below), because it allows you to completely
   redirect the processing of a command, skipping all or part of the
   normal processing for the original command without telling the parser
   that the original command was unsuccessful.

   You an use exitobj anywhere you can use exit.

 - A new built-in function, execCommand, gives a game program direct
   access to the parser's command execution system.  This new function
   doesn't provide direct access to the string-parsing portion of the
   parser, but to the command execution portion, which takes the objects
   involved in the command and executes the command, performing object
   validation (validDo, validIo), room notification (roomAction),
   actor notification (actorAction), direct and indirect object checks
   (dobjCheck and iobjCheck), general object handling (dobjGen and
   iobjGen), object validation (verIoVerb and verDoVerb), and object
   action processing (ioVerb, doVerb, or verb.action, as appropriate).

   execCommand is called like this:

      errorCode := execCommand(actor, verb, dobj, prep, iobj, flags);

   The "actor" parameter is the object (usually of class Actor) of the
   character to perform the action; if the player character is to carry
   out the command, use Me as the "actor" parameter.  The "verb"
   parameter is the deepverb object of the command to execute.  "dobj"
   is the direct object of the command, and must be a single object
   (not a list); if you want to execute the same command on a series
   of direct objects, simply call execCommand in a loop.  Use nil for
   the "dobj" parameter if the command takes no direct object.  "prep"
   is the object (usually of class Prep) for the preposition that
   introduces the indirect object, or nil if there is no preposition.
   "iobj" is the indirect object of the command, or nil if the command
   takes no indirect object.

   The "flags" parameter lets you control how the parser processes the
   command.  The flags are "bit field" values, which means that you can
   combine any number of the constants below using the bitwise "or"
   operator, "|".  The constants below are defined in adv.t.

     EC_HIDE_SUCCESS - if this flag is included, the parser will hide
     any messages that the command generates when the command completes
     successfully.  The parser considers the command successful if the
     return code from execCommand is zero (see below).  If this flag is
     not included, and the command is successful, all messages will be
     displayed.  Note that this flag is independent of EC_HIDE_ERROR;
     whether or not this flag is included has no effect on messages if
     the command is not successful.

     EC_HIDE_ERROR - if this flag is included, the parser will hide
     any messages that the command generates when the command fails.
     The parser considers the command to have failed when the return
     code from execCommand is non-zero (see below).  If this flag is
     not included, and the command fails, all messages will be displayed.
     Note that this flag is independent of EC_HIDE_SUCCESS; whether or
     not this flag is included has no effect on messages if the command
     is successful.

     EC_SKIP_VALIDDO - if this flag is included, the parser skips
     validation of the direct object.  If this flag is not used, the
     parser uses the normal validation procedure for the direct object.
     You can use this flag when you want to bypass the normal validation
     process and execute the command even when the actor would not
     normally have access to the direct object.

     EC_SKIP_VALIDIO - if this flag is included, the parser skips
     indirect object validation.  If this flag isn't used, the parser
     uses the normal procedure for validating the indirect object.

   If you want to execute a command silently, so that the player doesn't
   see the messages the command would normally generate, you should
   specify both EC_HIDE_SUCCESS and EC_HIDE_ERROR:

     err := execCommand(actor, takeVerb, ball, nil, nil,
                        EC_HIDE_SUCCESS | EC_HIDE_ERROR);

   In some cases, you may want to show messages only in case of error.
   This is particularly useful when you're executing an implied command,
   such as opening a door in the course of moving to a new room, because
   you'll usually show a message of some kind noting the implied action.
   In this situation, you can use EC_HIDE_SUCCESS to suppress the normal
   confirmation message you'd receive on success, but still show any
   errors that occur:

     "(Opening the door)\n";
     err := execCommand(actor, openVerb, steelDoor, nil, nil,
                        EC_HIDE_SUCCESS);
     if (err = EC_SUCCESS)
     {
        // successfully opened the door, so proceed with the movement...
     }

   All of the parameters to execCommand after "verb" can be omitted,
   in which case dobj, iobj, and prep will default to nil, and flags
   will default to 0 (zero).  In addition, you can supply a 'flags'
   value but omit one or more of 'dobj', 'prep', and 'iobj', in
   which case the interpreter will use nil as the default value for
   the omitted object arguments.

   execCommand returns an error code indicating the parser's
   results.  A return value of zero indicates that the command was
   processed without any parser errors.  A return value of zero does
   not always mean that the command did what you wanted -- it merely
   indicates that all of the checks succeeded, including xobjCheck,
   xobjGen, roomAction, actorAction, verDoVerb, and verIoVerb, and
   that no "exit" or "abort" statement was executed.  In some cases,
   though, the action, doVerb, or ioVerb method will make further
   checks and generate an error; in these cases, execCommand will
   return zero even though the command failed.  You may therefore
   want to make an extra check to ensure that whatever state change
   you were attempting to accomplish actually occurred.

   This function can return the following values (the constants are
   defined in adv.t):

     EC_SUCCESS
       success - the doVerb or ioVerb method was successfully invoked,
       and no "exit" or "abort" statement was executed.

     EC_EXIT
       an "exit" statement was executed.  This usually means that a
       roomAction, actorAction, xobjCheck, or xobjGen method disallowed
       the command.

     EC_ABORT
       an "abort" statement was executed.

     EC_INVAL_SYNTAX
       sentence structure is invalid.  This indicates that the
       combination of verb, objects, and preposition does not form a
       valid command in the game.  The parser does not display any
       error message in this case; this indicates an error in your
       source code, since you're attempting to execute a verb
       pattern that your game doesn't define.

     EC_VERDO_FAILED
       verDoVerb failed.  The direct object's verification method
       displayed some text, indicating that verification failed.

     EC_VERIO_FAILED
       verIoVerb failed.  The indirect object's verification method
       displayed text.

     EC_NO_VERDO
       no verDoVerb method is defined for the direct object.  This
       is almost equivalent to EC_VERDO_FAILED, but indicates that a
       default parser message was displayed, because the object had
       no defined or inherited handler of its own.

     EC_NO_VERIO
       no verIoVerb method is defined for the indirect object.  The
       parser displays a default message.

     EC_INVAL_DOBJ
       direct object validation failed.  This indicates that that
       direct object isn't accessible for the verb; the parser will
       display the usual message (via the cantReach mechanism)
       before returning.

     EC_INVAL_IOBJ
       indirect object validation failed.  The indirect object is
       not accessible for the verb; the parser will display the
       usual message before returning.

   Note that the parser does not check to see if the actor is present
   in the same room with the current actor, or perform any other
   addressibility validation for the actor.  This allows you to use
   execCommand to script non-player character actions to be carried
   out autonomously, without regard to whether the player could have
   issued the same commands directly.

   The current actor, for the purposes of format string ("%xxx%")
   evaluation, is set to the "actor" parameter specified in the call.

   execCommand does not invoke any daemons or fuses.  The recursive
   command is considered part of the current turn.

   You should not use execCommand in verification methods (verIoVerb or
   verDoVerb), because execCommand invokes complete commands, which may
   make changes in game state.  Note that changes in game state will
   occur regardless of EC_HIDE_SUCCESS or EC_HIDE_ERROR - these flags
   merely hide the messages the command produces, but don't prevent
   the command from carrying out its other actions.

   One good use for execCommand is to treat different phrasings of a
   command the same way.  For example, suppose you had a can of spray
   paint in your game.  You might want to allow players to paint things
   with the spray paint using commands like "spray <paint> on <target>"
   and "spray <target> with <paint>."  To make these commands equivalent,
   you traditionally would have coded the verIoSprayOn, verDoSprayOn,
   ioSprayOn, and doSprayOn routines appropriately, then essentially
   duplicated the code in the SprayWith equivalents.  Alternatively, you
   could have set up the SprayWith routines to call the SprayOn routines
   (or vice versa); this kind of indirection is tricky, though, because
   of the TADS parser's assymetrical handling of direct and indirect
   object verification -- note that the direct and indirect objects
   reverse roles between the two phrasings.

   This kind of redirection is easy using execCommand, though.  First,
   choose a "canonical" phrasing -- this is the phrasing where you'll
   always implement your handlers for the command.  Let's choose
   "spray <paint> on <target>" as the canonical phrasing.  We now set
   up command handlers for our canonical phrasing just as we would for
   any other command: we'd write verIoSprayOn and ioSprayOn methods for
   objects that we want to allow as targets for spraying, and we'd write
   verDoSprayOn methods for objects that can do the spraying.  For
   example, in the sprayPaintCan object, we'd write a verDoSprayOn
   handler to allow spraying the paint on things:

     sprayPaintCan: item
        // put your sdesc, ldesc, vocabulary, etc. here
        verDoSprayOn(actor, iobj) = { }
     ;

   We'd proceed with the normal implementation of "spray <paint> on
   <target>" until that worked correctly.  Once the canonical phrasing
   worked, we'd set up the redirection.  Rather than setting up a
   complicated series of method-by-method redirections, we can simply
   allow any "spray <target> with <paint>" command to proceed all the
   way to the ioSprayWith handler, then redirect the entire command at
   that point.  Since we want to redirect the command for every pair of
   objects, we can put all of the handlers in the basic "thing" object:

     modify thing
       /* allow ANY "spray <target> with <object>" command */
       verIoSprayWith(actor) = { }
       verDoSprayWith(actor, iobj) = { }

       /* re-cast "spray <self> with <iobj>" as "spray <iobj> on <self> */
       ioSprayWith(actor, dobj) =
       {
         execCommand(actor, sprayVerb, self, onPrep, dobj);
       }
     ;

   That's all that we need to do -- because execCommand will run through
   the entire parsing sequence for the new phrasing, we don't need to
   worry about doing any verification for the non-canonical phrasing.
   Note that we must put the execCommand call in ioSprayWith, and not
   in one of the verXoSprayWith methods -- if we put the call in one of
   the verification methods, we could execute the recursive command
   multiple times in silent calls during disambiguation.  Note also that
   we can override the equivalence of "spray <x> on <y>" and "spray <y>
   with <x>" on an object-by-object basis, if we wanted, by overriding
   the SprayWith methods in the objects where we wanted different
   behavior; while the "spray" commands may never need such special
   handling, other equivalencies might benefit: "put <x> in <y>" and
   "fill <y> with <x>," for example, might only be equivalent for
   liquids and containers of liquids.

   Another use for execCommand is to perform "implied" commands -- these
   are commands that the game carries out automatically, without the
   player specifically typing them in, because they're obviously needed
   in the course of what the player actually did type.

   As an example, suppose you want the player to be wearing sunglasses
   every time they enter a particular room.  You could simply check to
   see if the player is wearing the sunglasses, and forbid travel into
   the room if not:

     >north
     You can't enter the fusion chamber without your sunglasses on.

     >wear sunglasses
     You're now wearing the sunglasses.

     >north

   This would work, but it's tedious for the player, in that the game
   tells the player exactly what to type, but still makes the player
   type it.  Some people would still prefer to believe (despite evidence
   to the contrary) that computers are our servants and not our masters,
   and tend to balk at this type of laziness on the part of the game.

   Even more tedious, though, was writing the code traditionally necessary
   to make this operation automatic.  The problem is that you'd have had
   to write code to make all of the same checks that the parser would
   normally make to find out if wearing the sunglasses is possible, and
   also make sure that any side effects are invoked.

   execCommand makes this kind of operation easy, by allowing you to
   use exactly the same code that the parser would invoke in order to
   carry out an explicit command from the player.  In effect, this
   lets you automatically run obviously implied commands, rather than
   telling the player to run them manually.  Here's how we might use
   this for the sunglasses:

     outsideChamber: room
        // normal sdesc/ldesc stuff
        north =
        {
           /* if the sunglasses aren't being worn, try putting them on */
           if (sunglasses.isIn(parserGetObj(PO_ACTOR)) && !sunglasses.isworn)
           {
              /*
               *  The sunglasses are here but not worn - put them on.
               *  Tell the player what we're doing, then execute a "wear"
               *  command recursively.  Note that we use EC_HIDE_SUCCESS
               *  in the execCommand call to suppress the normal success
               *  confirmation message - we only want a message if the
               *  player can't wear the sunglasses for some reason.
               */
              "(First wearing the sunglasses)\n";
              if (execCommand(parserGetObj(PO_ACTOR), wearVerb, sunglasses,
                  nil, nil, EC_HIDE_SUCCESS) != 0)
              {
                 /*
                  *  that failed - since execCommand showed error messages,
                  *  we have already explained what went wrong, so simply
                  *  return nil to disallow the travel
                  */
                 return nil;
              }
           }

           /* if they're not wearing eye protection, don't allow entry */
           if (!(sunglasses.isIn(parserGetObj(PO_ACTOR))
                 && sunglasses.isworn))
           {
              /* explain the problem */
              "%You% venture%s% a few steps, but the light in the chamber
              is so intense that %you're% forced to retreat.  %You%'ll
              need some sort of eye protection to enter. ";

              /* don't allow travel */
              return nil;
           }

           /* the sunglasses are deployed - we're good to go */
           return fusionChamber;
        }
     ;

   Note that this example uses the new parserGetObj() built-in function
   (described below), rather than parserGetMe(), to determine which actor
   is performing the travel.  In addition, the messages all use format
   strings (such as "%You%").  These two elements ensure that the travel
   method can be used for travel by the player, but also can be used for
   travel by a non-player character; this is especially important if you
   plan to use execCommand() to script NPC actions, since the current
   actor during recursive commands sent to an NPC would reflect the
   NPC object, rather than the player character ("Me") object.

 - A new action method gives the game program a chance to perform special
   handling on a command after the objects involved have been resolved,
   but before any of the usual parser processing begins.  This new hook
   is similar to preparse() and preparseCmd(), but takes as parameters
   the objects involved in the command rather than the original text.

   The new hook is a method named verbAction.  This new method is similar
   to the roomAction and actorAction methods, but gives the deepverb
   object a chance to act.  The parser calls this method like this:

     verb.verbAction(actor, dobj, prep, iobj)

   'actor' is the object representing the actor to whom the command is
   addressed; 'dobj' is the direct object, or nil if there is no direct
   object; 'prep' is the object for the preposition introducing the
   indirect object; and 'iobj' is the indirect object, or nil if there
   is no indirect object.

   This method returns no value.  If this method wants to cancel
   execution of the command, it should use the "exit" statement (to
   continue execution of any remaining commands on the command line,
   but terminate processing of the current command) or the "abort"
   statement (to terminate processing of the current command line
   entirely).  If can also use "exitobj" to terminate processing of
   the current object only, and continue execution with the next
   object of a multi-object command.

   The parser calls verbAction just before calling actorAction.

   The verbAction method can be used in conjunction with the new
   execCommand built-in function to substitute one sentence pattern
   for another, without the usual complexity of mapping several
   individual verification and action methods.  For example, if you
   wanted to process all "fill x with y" commands as "put y in x"
   commands, you could put this in your fillVerb definition:

     fillVerb: deepverb
        verb = 'fill'
        sdesc = "fill"
        ioAction(withPrep) = 'FillWith'
        verbAction(actor, dobj, prep, iobj) =
        {
           /* check for fill-with syntax */
           if (prep = withPrep)
           {
              /* handle "fill x with y" exactly like "put y in x" */
              execCommand(actor, putVerb, iobj, inPrep, dobj);

              /* we're done with the command - do not process it further */
              exitobj;
           }
        }
     ;

 - A new parser hook lets the game take control whenever the parser
   encounters an unknown verb, or unknown sentence syntax.  The new
   hook is a function called parseUnknownVerb, which is defined like
   this:

     parseUnknownVerb: function(actor, wordlist, typelist, errnum)

   'actor' is the current actor object.  The 'wordlist' parameter is a
   list with the strings of the words in the command, in the same format
   as the list that is passed to preparseCmd.  The 'errnum' parameter is
   the parser error number for the condition that caused the call to
   parseUnknownVerb; this is the same error number that is passed to
   parseError (and related functions).

   The 'typelist' argument is a list of the types of the words in the
   'wordlist' parameter.  Each element of 'typelist' gives the word type
   of the corresponding element of 'wordlist' (so typelist[3] gives the
   type of the word in wordlist[3], for example).  Each type is a number,
   which can contain any number of the values below combined with the
   bitwise OR operator ("|").  To test for a particular type, use an
   expression like this:  ((typelist[3] & PRSTYP_NOUN) != 0).  The
   type values, defined in adv.t, are:

     PRSTYP_ARTICLE - the word is defined as an article
     PRSTYP_ADJ     - adjective
     PRSTYP_NOUN    - noun
     PRSTYP_PLURAL  - plural
     PRSTYP_PREP    - preposition
     PRSTYP_VERB    - verb
     PRSTYP_SPEC    - special word (".", "of", "and", etc.)
     PRSTYP_UNKNOWN - the word is not in the dictionary

   This function can return, true, nil, or a number, or it can use
   "abort" to abort the command.

   Returning true indicates that the function has successfully handled
   the entire command itself; the parser does not display any error
   messages, it executes fuses and daemons as normal, and it proceeds to
   continue parsing any remaining text on the command line (after a
   period or "then").

   Returning a number (greater than zero) indicates success, just as true
   does, but also indicates that the function parsed only the words
   before the returned index, and that the remaining words (starting with
   the word at the index value returned) are to be considered an
   additional command.  The parser will run fuses and daemons as normal,
   and then will resume its normal parsing, starting with the word at the
   index given by the return value.  You can use this if you find "and"
   following a noun phrase that you parse, or if for any other reason you
   find that another sentence follows and should be parsed separately.
   For example, if you succesfully parse the first three words of the
   list, you should return the value 4 to indicate that you want the
   parser to apply the default parsing starting with the fourth word in
   the list.

   Returning nil indicates that the function has failed to handle the
   command, and wants the parser to display the default error message.
   The parser will display the message in the normal fashion, using
   parseErrorParam or parseError as appropriate, and will abort the
   command.  No fuses or daemons will be executed, and any remaining
   text on the command line will be discarded.

   If this function uses "abort" to end the command, the parser will
   not execute any fuses or daemons, and it will ignore any remaining
   text on the command line.  The difference between returning nil and
   executing an "abort" statement is that the parser will display the
   default message when this function returns nil, but will not display
   anything if the function uses "abort".

   The parseUnknownVerb function is currently called with the following
   error codes:

     17  There's no verb in that sentence!
     18  I don't understand that sentence.
     19  There are words after your command I couldn't use.
     20  I don't know how to use the word "%s" like that.
     21  There appear to be extra words after your command.
     23  internal error: verb has no action, doAction, or ioAction
     24  I don't recognize that sentence.

   Error code 17 indicates that the first word in the sentence is not
   defined as a verb (which may mean that the word is entirely uknown, or
   that it's defined as another part of speech but not as a verb).  18
   means that a noun phrase was not formed properly, or that the
   combination of the verb and verb preposition ("pick up," for example)
   is not defined.  19 means that a preposition occurred at the end of a
   sentence, but it could not be combined with the verb.  20 indicates
   that the word separating the indirect object is not defined as a
   preposition.  21 means that another word follows what the parser
   thinks should be the last word in the sentence (for example, the
   sentence ends with two prepositions).  23 means that the deepverb
   object has no defined templates (action, doAction, or ioAction).  24
   indicates that too many objects were used with the sentence: a direct
   object is present, but the deepverb doesn't have a doAction, or an
   indirect object is present, but the deepverb doesn't have an ioAction.

   The purpose of this function is to let you defined your own parsing
   for commands outside of the bounds of the built-in parser.  Although
   this function is similar to preparseCmd, it differs in that
   parseUnknownVerb runs only when the parser can't handle a command
   directly, which means that parseUnknownVerb doesn't have to decide
   whether or not to pass the command to the parser.  In addition,
   parseUnknownVerb integrates into the turn-handling mechanism, in
   that it can control fuse and daemon execution as well as the handling
   of remaining text on the command line.

 - A new parser hook lets the game program parse noun phrases and perform
   the initial resolution to possible matching objects.  The parser now
   calls a game-defined function named parseNounPhrase, which is defined
   like this:

     parseNounPhrase: function(wordlist, typelist, current_index,
                               complain_on_no_match, is_actor_check)

   The parameter 'wordlist' is a list of strings, where each string is a
   token in the player's command.  This is the same type of list that
   preparseCmd receives.

   'typelist' is a list of word types for the tokens in the list.  The
   types are bit flag values, so each element may have multiple types
   combined with OR.  To test to see if a particular type flag is set,
   use the bitwise AND operator, "&"; for example, to test the second
   element to determine if it's a noun, use this expresion:

     ((typelist[2] & PRSTYP_NOUN) != 0)

   The type flag values, defined in adv.t, are:

     PRSTYP_ARTICLE - the word is defined as an article
     PRSTYP_ADJ     - adjective
     PRSTYP_NOUN    - noun
     PRSTYP_PLURAL  - plural
     PRSTYP_PREP    - preposition
     PRSTYP_VERB    - verb
     PRSTYP_SPEC    - special word (".", "of", "and", etc.)
     PRSTYP_UNKNOWN - the word is not in the dictionary

   'current_index' is the index in the word list of the start of the
   noun phrase.  This function can look at the previous words in the
   list if desired, but the parser has already determined that words
   before 'current_index' are part of the verb or of another part of
   the command.  This function should start parsing at 'current_index'.

   'complain_on_no_match' is a boolean value (true or nil) indicating
   whether the function should display an error message if the noun
   phrase has no matching objects.  If this parameter is true, you should
   display an appropriate message on this type of error; otherwise, you
   should not display any message in this case.  You should always
   display an error if the noun phrase is not syntactically correct; the
   'complain_on_no_match' parameter applies only to error messages for
   syntactically correct noun phrases that don't refer to any objects.

   'is_actor_check' is a boolean value indicating whether the function
   is being called to check for an actor noun phrase.  When this is
   true, the function should not allow syntax that is obviously
   inappropriate for an actor, such as the word "all," or a string or
   number; in such cases, the function should simply return an empty
   object list to indicate that no valid actor is present.

   This function can do one of four things: it can parse the noun phrase,
   and return a list of matching objects; it can determine that no noun
   phrase is present; it can indicate that a noun phrase is present but
   contains a syntax error; or it can let the parser perform the default
   noun phrase parsing.

   If this function parses a noun phrase successfully, it should return a
   list.  The first element of the list must be a number, which is the
   index of the next token in the word list after the noun phrase.  For
   example, if 'current_index' is 3 when the function is called, and the
   noun phrase consists of one word, the first element of the returned
   list should be 4.  This tells the parser where it should resume
   parsing.  The remaining elements of the list are pairs of elements;
   the first of each pair is a game object matching the noun phrase, and
   the second is a number giving flags for the object.  At this point,
   it's not necessary to determine whether or not the objects are
   accessible, reachable, visible, or anything else; the parser will
   disambiguate the list later, when it knows more about the sentence
   structure.  For now, the routine can simply return a list of all of
   the objects that match the vocabulary words.

   Multiple flag values can be combined with the bitwise OR operator,
   "|".  The flags, defined in adv.t, are:

     PRSFLG_ALL - the entry is for the word "all" or equivalent
     PRSFLG_EXCEPT - the entry is excluded from the "all" list
     PRSFLG_IT - the entry matched the pronoun "it"
     PRSFLG_THEM - the entry matched the pronoun "them"
     PRSFLG_HIM - the entry matched the pronoun "him"
     PRSFLG_HER - the entry matched the pronoun "her"
     PRSFLG_NUM - the entry is a number
     PRSFLG_STR - the entry is a string
     PRSFLG_PLURAL - the entry matched a plural usage
     PRSFLG_COUNT - the entry has a numeric count as the first word
     PRSFLG_ANY - the entry was qualified with "any"
     PRSFLG_UNKNOWN - the entry contains an unknown word
     PRSFLG_ENDADJ - the entry ends with an adjective
     PRSFLG_TRUNC - the entry uses a truncated word

   Some examples might be helpful, since the return value is complicated.

   For "all" and for the pronouns (it, him, her, them), you should return
   a list containing nil as the object, and the appropriate flag value.
   For example, if the noun phrase is simply the word "everything", you
   would return this (assuming that the index of the word "everything"
   was 2):

     [3 nil PRSFLG_ALL]

   Similarly, if the noun phrase was simply "her", you would return:

     [3 nil PRSFLG_HER]

   The construction "all except" is also special.  You would return a
   list whose first entries are nil and PRSFLG_ALL, just as though you
   were parsing a simple "all" phrase, but then you'd add entries for all
   of the items in the "except" list, with each additional entry's flag
   including PRSFLG_EXCEPT.  For example, if the player typed "take all
   except book and candle", you might return something like this:

     [3 nil PRSFLG_ALL book PRSFLG_EXCEPT candle PRSFLG_EXCEPT]

   Strings and numbers work the same way: return nil for the object,
   and set the appropriate flag.  For example, if the player typed
   "type 'hello' on keypad", you'd return this:

     [3 nil PRSFLG_STR]

   If you encounter an unknown word in the noun phrase, and you want to
   let the parser resolve the unknown word using its normal mechanisms,
   you should return a nil object with the PRSFLG_UNKNOWN flag set:

     [4 nil PRSFLG_UNKNOWN]

   If you simply want to return a list of objects that match the noun
   phrase, it's easy:

     [4 book 0 candle 0]

   The parser also lets you omit the flags entirely, if you don't need to
   include any flags with an object.  If an element that follows an
   object is another object (or nil), the parser will assume that the
   flag value for the preceding object is zero.  So, for the example
   above, this list is equivalent:

     [4 book candle]

   If the function parses a noun phrase successfully, but can find no
   objects matching the words in the noun phrase (in other words, the
   words form a valid noun phrase syntactically, but don't actually refer
   to any object in the game), it should return a list that contains only
   the index of the next word after the noun phrase.

   If the function determines that a noun phrase appears to be present,
   but is not syntactically correct, you should display an error message
   and return PNP_ERROR.  You should not display an error if it doesn't
   look like a noun phrase is present at all; instead, you should simply
   return a list consisting of the original 'current_index' value to
   indicate that you didn't parse any words at all.  You should only
   display an error and return PNP_ERROR if you determine that a noun
   phrase is actually present, but is syntactically incorrect.

   If your function determines that it doesn't want to parse the noun
   phrase after all, it should simply return PNP_USE_DEFAULT.  This tells
   the parser to proceed with the default parsing for the noun phrase.

   The default noun phrase parser (built in to the TADS interpreter) does
   not attempt to resolve or disambiguate objects at this stage.
   Instead, it simply creates a list of all of the objects that match
   every word in the noun phrase.  The reason that the parser doesn't try
   to resolve the objects at this stage is that the parser doesn't have
   enough information when this routine is called.  So, the parser merely
   determines the syntactic structure of the noun phrase, and ensures
   that at least one object in the game can match all of the words;
   later, after the parser has fully analyzed the sentence structure and
   knows the verb, prepositions, and number of objects, the parser
   resolves and disambiguates the noun phrase.  If you write an
   implementation of this function, keep this design in mind.

   In most cases, you will not want to write a function that completely
   replaces the built-in noun phrase parser.  Instead, you'll probably
   want to check for special cases.  When you see a special case, you
   should perform your parsing and return an appropriate object list;
   when you don't see a special case, you should simply return
   PNP_USE_DEFAULT to use the default parsing routine.

 - A new parser hook allows the game program to control the disambiguation
   process, wherein noun phrases are resolved to specific game objects.
   The parser calls two new methods: dismabigDobj, to disambiguate direct
   objects; and disambigIobj, for indirect objects.  These new methods
   are defined on a deepverb object.

   The parser calls disambigDobj as follows:

     verb.disambigDobj(actor, prep, iobj, verprop,
                       wordlist, objlist, flaglist,
                       numWanted, isAmbig, silent);

   The parser calls this routine, if defined, most times it resolves a
   noun phrase to a concrete list of objects, whether or not the phrase
   is ambiguous.  This allows you to perform special resolution on
   specific noun phrases or for specific verbs, even when the parser
   wouldn't normally think the phrases require disambiguation.

   The 'actor' parameter is the actor involved in the command.

   'prep' is the preposition object associated with the word that
   introduces the indirect object, if present; if there is no
   preposition, 'prep' will be nil.

   'iobj' is the indirect object, if available; if there is no indirect
   object, or the indirect object has not yet been resolved when
   disambigDobj is invoked, 'iobj' will be nil.

   'verprop' is the property address of the verification method
   (verDoVerb) defined for the direct object for the verb; this parameter
   is included because a single verb object could define several
   verification methods that very by preposition ("put x in y" usually
   has a different verification method than "put x on y").

   'wordlist' is a list of strings giving the tokens of the player's
   command that make up the noun phrase.  This is the same type of list
   that preparseCmd receives.

   'objlist' is a list of the objects that the parser found with
   dictionary entries matching all of the words in the word list.

   'flaglist' is a list of numbers giving flags for the corresponding
   'objlist' entries; for example, flaglist[3] gives the flags associated
   with objlist[3].

   The flag values for 'flaglist' are defined in adv.t.  Thee flags are
   bit-field values, so multiple flags can be combined with OR into a
   single value.  To test if a particular flag is set, use the bitwise
   AND operator, "&"; for example, to test the second element to see if
   the PLURAL flag is set, use this expression:

     ((flaglist[2] & DISAMBIG_PLURAL) != 0)

   The flags are:

     PRSFLG_COUNT - the object matched with a numbered count.  For
     example, if the noun phrase is "3 gold coins," objlist will contain
     one or more objects matching the plural phrase "gold coins," and the
     VOCS_COUNT flag will be set for each object to indicate that a count
     is present.  In these cases, the first element of wordlist should
     always be the string with the user's number (the string '3' in the
     example).

     PRSFLG_PLURAL - the object matched a plural usage.

     PRSFLG_ANY - the noun phrase started with "any"

     PRSFLG_ENDADJ - the object matched with an adjective at the end of
     the noun phrase.  For example, suppose the noun phrase is "letter",
     and the game defines parchmentLetter with noun = 'letter', and
     defines letterOpener with adjective = 'letter'.  In this case,
     objlist would contain both parchmentLetter and letterOpener, and the
     flaglist entry corresponding to letterOpener would have the
     PRSFLG_ENDADJ flag set.  This flag allows the disambiguator to select
     in favor of the noun interpretation in case of ambiguity, to avoid
     having to ask a stupid disambiguation question ("which letter do you
     mean, the parchment letter, or the letter opener?"  should clearly
     not be asked).

     PRSFLG_TRUNC - the object matched with one or more truncated words.
     This will be set when a word in the player's noun phrase matches the
     leading substring of a dictionary word, and is at least six characters
     long, but doesn't match the entire dictionary word.  For example,
     "flashlig" matches "flashlight" because "flashlig" is at least six
     characters long and matches "flashlight" in its first eight characters
     (i.e., the length of "flashlig"), but the parser will flag the word
     with PRSFLG_TRUNC to indicate that it wasn't an exact match.

   'numWanted' is the number of objects that the parser wants in the
   resolved list.  For a definite singular noun phrase ("take the box"),
   this will be 1.  For a plural noun phrase, this will be the number
   of objects in the proposed resolution list (in the 'objlist' parameter).
   When the player specifies a count ("take 3 boxes"), this will be the
   give number.  For an "any" phrase ("take any box"), this will be 1.
   You don't have to obey the 'numWanted' parameter, but this information
   may be helpful in some cases.

   'isAmbig' is true if the parser thinks the noun phrase is ambiguous,
   nil if not.  For example, if the player specified a singular definite
   noun phrase, but the parser found two matching objects, 'isAmbig' will
   be true.  You can always deduce whether or not the list is ambiguous
   by examining the 'numWanted' value and all of the object flags, but
   doing so is complicated, so the parser provides 'isAmbig' for your
   convenience.  If your disambigDobj or disambigIobj is interested only
   in resolving ambiguity, as opposed to performing special noun phrase
   resolution, you can simply return DISAMBIG_CONTINUE immediately if
   'isAmbig' is nil.

   'silent' specifies whether the disambiguation is in interactive or
   non-interactive mode.  If 'silent' is true, it means that you should
   not display any messages or ask the player to help resolve any
   ambiguity.  If 'silent' is nil, you can display messages or prompt
   for more information if you wish.

   The disambigIobj method is essentially the same:

     verb.disambigIobj(actor, prep, dobj, verprop,
                       wordlist, objlist, flaglist,
                       numWanted, isAmbig, silent);

   The only difference from disambigDobj is that disambigIobj receives
   the direct object in the 'dobj' parameter.  Note that the direct
   object is not normally available during indirect object
   disambiguation, so 'dobj' will usually be nil.  The only time the
   direct object will be available will be for verb templates with the
   [disambigDobjFirst] flag.  For verbs without this flag, the indirect
   object is resolved before the direct object, hence the direct object
   is not yet known.

   The parser's built-in disambiguation routine calls these methods after
   it has done everything it can, short of asking the player, to resolve
   and disambiguate a noun list.  The parser will apply the normal
   validation checks (validDo, validDoList, etc.) to the objects,
   eliminating any that don't pass; and it will apply the silent
   verification checks (verDoVerb, verIoVerb) as well.  The parser will
   next call disambigDobj or disambigIobj, if the verb object defines it.

   These methods can return a status code from the list below, or they
   can return a list (see below).  The status codes (defined in adv.t)
   are:

     DISAMBIG_CONTINUE - continue through the remainder of the
     disambiguation process as normal.

     DISAMBIG_ERROR - abort the current command entirely.  This can be
     used when the method encounters an error and displays an error
     message to the player.  The parser will simply terminate the current
     command.  Note that the parser does not display an error of its own,
     so the method must display an appropriate message before returning
     this status code.

     DISAMBIG_PROMPTED - continue through the process as normal, but do
     not show a prompt for an interactive response ("which foo do you
     mean..."), because the disambigXobj function already displayed an
     appropriate prompt of its own.  This allows the disambigXobj
     function to display a customized prompt (using more information
     than the traditional parseDisambig function has available), but
     still use the parser's default response reader and parser.

     DISAMBIG_PARSE_RESP - this indicates that your code has asked the
     player a question and read a response (via the input() built-in
     function, for example), but that you want to use the default
     response parser in the normal disambiguation mechanism to parse
     the response.  Do not return this as a raw status code; instead,
     return a list containing this value as the first element, and the
     string to be parsed as the second element.

     DISAMBIG_DONE - consider the object list fully resolved.  This skips
     any additional checking the disambiguator would normally perform and
     uses the current list as-is.  This should generally never be
     returned directly, but is used when returning a list of objects.

   The method can also return a list, which contains objects that replace
   the original input list (in the 'objlist' parameter).  The first
   element of the returned list is a status code, from the list above.
   Subsequent elements are the objects to use as the result of
   disambiguation.

   You can optionally specify a flag value for each object value.  To
   specify a flag for an object, simply place the flag value after the
   object in the list.

     return [DISAMBIG_DONE redBox PRSFLG_PLURAL blueBox PRSFLG_PLURAL];

   Each flag value pertains to the object immediately preceding it.

   If you omit a flag value, zero is assumed.  So, the following two
   return lists are equivalent:

     [DISAMBIG_CONTINUE redBox 0 blueBox 0]
     [DISAMBIG_CONTINUE redBox blueBox]

   You can optionally omit the status code from the list.  If the first
   element of the list is an object, the parser uses DISAMBIG_CONTINUE as
   the default status code, so it takes the list you returned and
   proceeds to the next step in the disambiguation process.

   Note that the parser doesn't call disambigDobj or disambigIobj for
   certain cases: resolving pronouns (it, her, him, them); strings;
   numbers that don't map to vocabulary words, but simply resolve to
   numObj; and "all" phrases.  However, the parser does call these
   routines to resolve the items in an "except" phrase.

 - A new built-in function, parserGetObj, lets the game program learn
   the objects involved in the current command.  This function takes a
   single argument, which indicates which object you want, using the
   following constants (defined in adv.t):

     PO_ACTOR - the current command's actor
     PO_VERB - the deepverb object for the command's verb
     PO_DOBJ - the direct object
     PO_PREP - the preposition object introducing the indirect object
     PO_IOBJ - the indirect object

   The return value is an object, or nil if there is no such object
   for the current command.  For example, a command with no indirect
   object will return nil for PO_PREP and PO_IOBJ.

   Here's an example of using parserGetObj to get the direct object
   of the current command:

     local obj := parserGetObj(PO_DOBJ);

   parserGetObj returns valid information at any time from (and including)
   actorAction to (and including) the doVerb or ioVerb routines.  You
   can't use parserGetObj() outside of these bounds; if you do, it will
   simply return nil.  The reason for the limited lifetime of this
   function is that the parser simply doesn't know the final values
   for the command objects before actorAction, since it is still busy
   resolving the words in the command to objects until it's about to
   call actorAction.  Note some particular times when you can't call
   parserGetObj: in the "init" and "preinit" functions; during object
   disambiguation (thus during some verDoVerb and verIoVerb calls);
   during roomCheck; during preparse() and preparseCmd(); and during
   fuse and daemon execution.

   Note one important exception to the limited lifetime: the actor
   can be retrieved at any time after the preparse() function returns.
   The parser determines the actor very early in the process of
   interpreting the command, so the actor is available throughout
   the parsing sequence.

   parserGetObj returns information on the current command.  When a
   recursive command is in progress (using execCommand), parserGetObj
   returns information on the recursive command; once execCommand
   finishes and returns to the code that called it, parserGetObj will
   once again return information on the enclosing command.

 - A new built-in function lets the game invoke the parer's tokenizer to
   obtain a token list for a string of text.  You can call this new
   function like so:

     tokenList := parserTokenize(commandString);

   The 'commandString' parameter is any string of text.  The tokenizer
   will scan the string and break it up into tokens, and return a list of
   token strings.  The token strings follow the same rules as the token
   list passed to the preparseCmd() function.

   If the command string contains any invalid characters (such as
   punctuation marks that the tokenizer doesn't accept), it will return
   nil, but it won't display any error messages.

 - A new built-in function lets you obtain a list of token types given a
   list of tokens.  You can use this function to get the types of the
   tokens in a list returned by parserTokenize(), for example.  Call the
   new function like this:

     typeList := parserGetTokTypes(tokenList);

   The result is a list of numbers.  Each element of the result list
   gives the type of the corresponding element of the token list
   (typeList[3] contains the type of the token in tokenList[3], for
   example).

   The types in the result list are combinations of the following
   values, defined in adv.t:

     PRSTYP_ARTICLE  - article (a, an, the)
     PRSTYP_ADJ      - adjective
     PRSTYP_NOUN     - noun
     PRSTYP_PREP     - preposition
     PRSTYP_VERB     - verb
     PRSTYP_SPEC     - special word
     PRSTYP_PLURAL   - plural
     PRSTYP_UNKNOWN  - unknown word

   These type codes are bit-field values, so they can be combined with
   the bitwise OR operator ("|").  For example, a token that appears in
   the dictionary as both a noun and an adjective will have a token type
   value of (PRSTYP_ADJ | PRSTYP_NOUN).

   Because more than one PRSTYP_xxx value can be combined into a type
   code, you must use the bitwise AND operator ("&") to check a type code
   for a specific PRSTYP_xxx value.  For example, if you want to check a
   token to see if has "noun" among its types, you'd write this:

     ((typeList[3] & PRSTYP_NOUN) != 0)

 - A new built-in function lets you perform a parser dictionary look-up
   to obtain the list of objects that define a set of vocabulary words.
   This function can be used to perform your own noun-phrase parsing.
   Call the new function like this:

     objList := parserDictLookup(tokenList, typeList);

   The 'tokenList' parameter is a list of the token strings you want to
   look up in the dictionary; this list uses the same format as the list
   returned by parserTokenize(), so you can use the result of
   parserTokenize() as input to parserDictLookup().

   The 'typeList' parameter is a list of token types.  Each entry in
   'typeList' gives the token type of the corresponding entry in
   'tokenList'.  This list uses the same PRSTYP_xxx codes returned by
   parserGetTokTypes(), but each entry in the type list should have only
   a single PRSTYP_xxx code (a type code in this list should not be a
   combination of more than one PRSTYP_xxx code).

   Because the 'typeList' entries must contain individual PRSTYP_xxx type
   codes, rather than combinations of type codes, you should generally
   not pass the result of parserGetTokTypes() directly to to
   parserDictLookup().  Instead, you need to determine how you want to
   interpret the words in the token list by choosing a single token type
   for each entry.  How you determine each single type is up to you.  If
   you're parsing a noun phrase, for example, you might decide that all
   words in the noun phrase except the last must be adjectives, and the
   last must be a noun.  The assignment of token types will depend on the
   type of parsing you're doing, and the syntax rules that you decide to
   implement for the type of input you're parsing.

   The return value of parserDictLookup() is a list of all of the game
   objects that match all of the vocabulary words, with the given types.
   If there are no objects that match all of the words, the result is an
   empty list.

   Verbs that use combining prepositions (such as "pick up" or "go
   north") use a special form of the token string.  To look up a
   combining, two-word verb, use a token string that contains both words,
   separated by a space.  parserTokenize() will never return such a
   string, because it will always break up the tokens according to word
   separators, so you must re-combine such tokens yourself.  For example,
   to look up the deepverb object matching "pick up" as a verb, you could
   write this:

     objList := parserDictLookup(['pick up'], [PRSTYP_VERB]);

   Note that parserDictLookup() simply looks up words in the dictionary.
   This function doesn't perform any disambiguation, access checking,
   visibility checking, or any other validation on the objects.

 - A new built-in function allows the game to invoke the parser's
   internal function to parse a noun list.  This can be used in
   conjunction with the parseUnknownVerb to allow the function to
   interpret part of the word list as a noun list.

   Your game program can call the function like this:

     ret := parseNounList(wordlist, typelist, startingIndex,
                          complainOnNoMatch, multi, checkActor);

   'wordlist' is a list of the strings making up the command.  'typelist'
   is a list of word types; each entry in 'typelist' is a number giving
   the type of the corresponding word in 'wordlist'.  The values in
   'wordlist' have the same meaning as the 'typelist' parameter to the
   parseUnknownVerb function:

     PRSTYP_ARTICLE - the word is defined as an article
     PRSTYP_ADJ     - adjective
     PRSTYP_NOUN    - noun
     PRSTYP_PLURAL  - plural
     PRSTYP_PREP    - preposition
     PRSTYP_VERB    - verb
     PRSTYP_SPEC    - special word (".", "of", "and", etc.)
     PRSTYP_UNKNOWN - the word is not in the dictionary

   'startingIndex' is the index in 'wordlist' and 'typelist' of the first
   word to parse; the function will ignore all of the words before this
   index.  This allows you to parse a portion of a word list in your own
   code, and start parsing a noun phrase that follows the portion you
   parsed.

   Set 'complainOnNoMatch' to true to make the function display an error
   message if it parses a syntactically valid noun phrase, but there are
   no objects in the game that match the noun phrase; set this to nil if
   you want to suppress this message.  Note that the function will
   display any syntax error messages regardless of this setting.  If you
   want to suppress all messages, you can use outhide() or outcapture()
   to hide any error messages displayed.

   'multi' specifies whether you want the function to parse multiple noun
   phrases (separated by "and", for example) or just a single noun
   phrase.  If 'multi' is true, the function will parse any number of
   noun phrases; if 'multi' is nil, the function will only parse a single
   phrase, stopping if it reaches "and" or equivalent.

   'checkActor' specifies if you want to perform an actor check.  If this
   is true, the function will reject "all", quoted strings, and phrases
   involving "both" or "any"; it will only parse a single noun phrase
   (regardless of the setting of 'multi'); and it will not display an
   error if the noun phrase cannot be matched.  The parser uses this mode
   internally to check the beginning of a command to determine if the
   command is directed to an actor, and this is probably the only context
   in which 'checkActor' should ever need to be true.  In most cases, you
   should set 'checkActor' to nil.  Note that you should not use true
   just because noun phrase may happen to contain an actor or is expected
   to contain an actor; you should only use true when you want the
   special error-handling behavior.  Note also that using true for
   'checkActor' does not cause the parser to reject noun phrases that
   refer to non-actor objects; this flag simply controls the
   error-handling behavior and does not affect what objects can be
   matched.

   If the parser encounters a syntax error, the function returns nil.
   This indicates that the function displayed an error message
   (regardless of the value of 'complainOnNoMatch'), and that the words
   do not form a syntactically-correct noun phrase.

   If the parser finds a syntactically valid noun phrase, but finds no
   objects that match the noun phrase, it returns a list containing a
   single number.  The number is the index of the next word in 'wordlist'
   following the noun phrase.  For example, suppose we have this word
   list:

     ['take' 'red' 'ball' 'with' 'hook']

   Suppose that we start parsing at index 2 ('red'), and that 'red' and
   'ball' are in the dictionary as adjective and noun, respectively.
   The parser will parse the noun phrase "red ball", consuming two
   words from the word list.  Now, suppose that there are no objects
   in the game matching both vocabulary words (i.e., there's no red
   ball in the game).  The parser will indicate that a syntactically
   valid noun phrase is present, but that no objects match the noun
   phrase, by returning this:

     [4]

   The number is the index of the next word after the noun phrase (in
   this example, 'with').

   If the parser finds a syntactically valid noun phrase, and finds one
   or more matching objects, it returns a list giving the matching
   objects.  The first element of the list, as above, is the index in the
   word array of the next word after the noun phrase.  Each additional
   element is a sublist.

   Each sublist gives information on one noun phrase.  If 'multi' is nil,
   there can be at most one sublist.  If 'multi' is true, there will be
   one sublist per noun phrase (each noun phrase is separated from the
   previous one by "and" or equivalent).  The first element of the
   sublist is the index in the word array of the first word of the noun
   phrase, and the second element is the index of the last word of the
   noun phrase; the noun phrase is formed by the words in the array from
   the first index to the last index, inclusive, so the last index will
   always be greater than or equal to the first index.  After these two
   elements, the sublist contains pairs of entries: a matching object,
   and flags associated with the matching object.  Each matching object
   is a game object that matches all of the vocabulary words in the noun
   phrase.

   The flags value associated with each matching object is a combination
   of any number of the PRSFLG_xxx values described with the
   parseNounPhrase function.  These flag values can be combined with the
   bitwise OR operator ("|"), so to test for a particular flag value, use
   the bitwise AND operator: ((flag & PRSFLG_EXCEPT) != 0).

   Since the return list is rather complicated, some examples might be
   helpful.

   Suppose that we start with this word list:

     ['take' 'knife' ',' 'cardboard' 'box']

   Suppose also that we use 2 as the starting index (because we want to
   start at the word 'knife'), and that 'knife', 'cardboard' and 'box'
   are defined words in the game.

   Now, suppose we have the following game objects defined:

     rustyKnife: item noun='knife' adjective='rusty';
     sharpKnife: item noun='knife' adjective='sharp';
     dagger: item noun='dagger' 'knife';
     box: item noun='box' adjective='cardboard';

   Given all of this, the return list would look like this:

     [6 [2 2 rustyKnife 0 sharpKnife 0] [4 5 box 0]]

   The first element indicates that the next word after the noun list is
   element 6; since the list has only five elements, this simply means
   that the noun list runs all the way to the end of the word list.

   The next two elements are the sublists, one per noun phrase:

     [2 2 rustyKnife 0 sharpKnife 0]
     [4 5 box 0]

   The first sublist specifies a noun phrase that runs from word 2 to
   word 2, inclusive, hence 'knife'.  The remaining pairs of elements in
   the list tell us that the matching objects are rustyKnife (with flags
   of 0) and sharpKnife (also with flags of 0).

   The second sublist specifies a noun phrase that runs from word 4 to
   word 5, inclusive, hence 'cardboard box'.  The matching object for
   this phrase is box (with flags 0).

   To interpret this return value, consider this code:

     if (ret = nil)
     {
       /* the noun phrase had a syntax error; give up */
       return;  // or whatever we want to do in case of error
     }

     "Next word index = <<ret[1]>>\b";

     if (length(ret) = 1)
     {
       /* valid noun phrase, but no matching objects */
       "I don't see that here.";
       return;
     }

     /* handle each sublist individually */
     for (i := 2 ; i <= length(ret) ; ++i)
     {
       local sub;
       local firstWord, lastWord;
       local j;

       /* get the current sublist */
       sub := ret[i];

       /* get the first and last word indices for this noun phrase */
       firstWord := sub[1];
       lastWord := sub[2];

       /* display the word list (or whatever - this is just an example) */
       "\bNoun phrase #<<i>> is: '";
       for (j := firstWord ; j <= lastWord ; ++j)
       {
         say(wordlist[j]);
         if (j != lastWord)
           say(' ');
       }
       "'\n";

       /* scan the objects in the list - each object takes two elements */
       for (j := 3 ; j <= length(sub) ; j += 2)
       {
         /* display this object and its flags */
         "matching object = <<sub[j].sdesc>>, flags = <<sub[j+1]>>\n";
       }
     }

   Note that in many cases you won't care about interpreting this list
   directly; instead, you'll simply want to pass the list to the
   parserDisambig() built-in function for resolution and disambiguation.
   The return list is in the same format required for input to that
   function.

   This function directly invokes the parser's noun list parser, which is
   exactly the same code the parser uses while parsing a player's command
   line.  The noun list parser will in turn invoke your parseNounPhrase()
   function, if you've defined such a function in your game.  So, you
   should be careful not to set up an infinite recursion by calling this
   function from your parseNounPhrase() function.

 - A new built-in function lets you access the parser's object resolution
   and disambiguation subsystem programmatically.  You can use the object
   resolver in conjunction with parseNounList() or with your own
   noun-list parser to implement your own command parsing system.

   In the standard TADS parser, object resolution occurs after the parser
   has finished parsing the syntax structure of a sentence, and thus
   knows the verb, all of the noun phrases, and connecting prepositions.
   Once all of this information is known, the parser can intelligently
   determine the objects to which each noun phrase refers.  As a result
   of this design, the object resolver requires parameters that specify
   the other aspects of the sentence structure.

   The object resolution function is called like this:

      resultList := parserResolveObjects(actor, verb, prep, otherobj,
                                         usageType, verprop,
                                         tokenList, objList, silent);

   The 'actor' parameter is the actor object for the command for which
   the objects are to be resolved.  The 'verb' parameter is the deepverb
   object involved in the command.  The 'prep' parameter is the
   preposition object that introduces the indirect object; if there's no
   indirect object or no preposition, 'prep' should be nil.

   'usageType' specifies the type of object that you're resolving.  You
   should use one of these constants, defined in adv.t, for this
   parameter:

     PRO_RESOLVE_DOBJ  - direct object

     PRO_RESOLVE_IOBJ  - indirect object

     PRO_RESOLVE_ACTOR - actor: use this if you're resolving an object
     for use as an actor to whom the player is directing the command.

   'verprop' is the verification method address; this is the address of
   the verDoVerb for your verb.  This must be specified in addition to
   the deepverb object, because a single deepverb can be associated with
   multiple verification/action methods (for example, "put x on y" uses a
   different set of methods from "put x in y", but both are associated
   with putVerb).  For example, for the direct object of "put x in y",
   you'd specify &verDoPutIn.

   If you're validating an actor (not a direct or indirect object that
   happens to be an actor, but rather an actor that the player is
   addressing and who is to carry out a command), the parser normally
   uses &verDoTake for 'verprop', rather than the actual verb being
   executed, because the point is to verify that the player can access
   the actor, not that the player can perform the command on the actor.
   "Taking" the actor has reasonably suitable accessibility rules for
   talking to an actor.  You could conceivably define your own verb
   simply for the purposes of talking to an actor, and then use that verb
   and its appropriate verification method instead of takeVerb and
   verDoTake.

   'tokenList' is the list of tokens which was parsed to build the input
   object list.  If you obtained the object list from parseNounList(),
   you should simply use the same token list that you used with
   parseNounList().  The importance of 'tokenList' is that the token list
   indices in the object list refer to words in the token list.

   'objList' is the input object list.  The resolver starts with this
   list to produce the resolved list.  'objList' is in exactly the same
   format as the list returned by parseNounList(), so you can use the
   result of parseNounList() as the 'objList' parameter.  If you use your
   own noun list parser instead, you must prepare a list that uses the
   same format as the parseNounList() result.

   'silent' specifies whether the resolver is interactive or not.  If
   'silent' is true, the resolver will not display any messages to the
   player, and will not ask the player to resolve the list in case of
   ambiguity; instead, the resolver will simply return an error code.  If
   'silent' is nil, the resolver will display a message if an error
   occurs, and will ask the user to resolve ambiguity using the
   traditional interactive process ("Which foo do you mean...").

   The return value of this function is always a list.  The first element
   of this list is always a number giving a status code.  The status
   codes are the same values and have the same meanings as the codes
   passed to parseError() and parseErrorParam().

   The status code PRS_SUCCESS (this constant and the PRSERR_xxx
   constants mentioned below are defined in adv.t) indicates that the
   resolution was successful.  In this case, the remainder of the list
   simply contains the resolved objects:

     [PRS_SUCCESS goldCoin shoeBox]

   PRSERR_AMBIGUOUS indicates that the result list is ambiguous.  This
   code will only be returned if 'silent' is true, because in other cases
   the resolver will not return until the player resolves any ambiguity
   interactively, or an error occurs.  When this status code is returned,
   the remainder of the list contains the partially-resolved objects; the
   resolver will have narrowed down the list as much as possible by
   including only objects that are accessible to the actor for the
   purposes of the verb, but the list will still require further
   disambiguation to obtain the final set of objects.

     [PRSERR_AMBIGUOUS goldCoin silverCoin shoeBox cardboardBox]

   PRSERR_DISAMBIG_RETRY indicates that the player entered a new command
   in response to a disambiguation query.  This can only happen when
   'silent' is nil, because the parser won't ask the player any questions
   at all when 'silent' is true.  When this status code is returned, the
   list contains only one additional element, which is a string with the
   player's new command.  If you want to execute the new command, you can
   use parserReplaceCommand() to abandon the current command and execute
   the new command instead.

     [PRSERR_DISAMBIG_RETRY 'go north']

   Any other status code indicates an error which caused the resolver to
   fail.  The list will contain no other elements in these cases.

   Note that this function calls the identical internal parser code that
   the player command parser normally uses to process a command.  The
   object resolver in some cases calls the disambigDobj and disambigIobj
   methods defined in the deepverb object.  As a result, you should be
   careful not to call this function from disambigDobj or disambigIobj
   methods, since doing so could result in infinite recursion.

   Here's an example that uses several of the new parser functions,
   including parserResolveObjects().  This function reads a string from
   the keyboard, tokenizes it, gets the token types, parses the token
   list as a noun list, and then resolves the noun list to an object.

     askForObject: function
     {
       local str;
       local toklist, typelist;
       local objlist;

       /* get an object */
       "Type an object name: ";
       str :=  input();

       /* tokenize it */
       toklist := parserTokenize(str);
       if (toklist = nil)
       {
         "The object name is invalid!";
         return nil;
       }

       /* get the token types */
       typelist := parserGetTokTypes(toklist);

       /* parse a single noun phrase */
       objlist := parseNounList(toklist, typelist, 1, true, nil, nil);
       if (objlist = nil)
         return nil;
       if (length(objlist) = 1)
       {
         "You see no such thing. ";
         return nil;
       }
       if (objlist[1] <= length(toklist))
       {
         "There seem to be words after the object name that I can't use. ";
         return nil;
       }

       /* resolve and disambiguate */
       objlist := parserResolveObjects(Me, takeVerb, nil, nil,
                                       PRO_RESOLVE_DOBJ, &verDoTake,
                                       toklist, objlist, nil);
       if (objlist[1] = PRS_SUCCESS)
       {
         /* success! return the objects, which follow the status code */
         return cdr(objlist);
       }
       else if (objlist[1] = PRSERR_DISAMBIG_RETRY)
       {
         /* run the new command, which is in the second element */
         parserReplaceCommand(objlist[2]);
       }
       else
       {
         /* we were in non-silent mode, so the resolver displayed an error */
         return nil;
       }
     }

 - A new built-in function, parserReplaceCommand(), allows you to abort
   the current command and start executing a new command using a given
   text string.  Call the function like this:

     parserReplaceCommand(commandString);

   This function doesn't return -- it effectively executes an "abort"
   statement to terminate the current command.  The given command string
   is entered into the parser's internal buffer, and the system parses
   and executes the command as though the player had typed the command
   directly.

 - A new built-in function, setOutputFilter, allows the game program to
   intercept and optionally change all display output just before it's
   formatted for display.  setOutputFilter() takes one argument, which
   is the address of a user-defined function; this tells the output
   formatter to call this function each time a string is displayed.
   You can also call the function with a value of nil, which cancels
   the current output filter function, restoring unfiltered output.

   The filter function is defined like this:

     myFilter: function(str)

   You can use any name in place of "myFilter".  The parameter "str"
   is the string that the output formatter is about to display.  Your
   filter function can return nil, in which case the original string
   is displayed unchanged; or it can return a (single-quoted) string
   value, which the formatter will display instead of the original
   string.

   Here's an example that converts all displayed output to upper-case
   when the player types the verb "uppercase on," and restored output to
   normal when the player types "uppercase off."

     ucFilter: function(str)
     {
        return upper(str);
     }

     uppercaseonVerb: deepverb
        verb = 'uppercase on'
        action(actor) =
        {
           "Upper-case mode is now ON. ";
           setOutputFilter(ucFilter);
        }
     ;

     uppercaseoffVerb: deepverb
        verb = 'uppercase off'
        action(actor) =
        {
           "Upper-case mode is now OFF. ";
           setOutputFilter(nil);
        }
     ;

   The output filter function is called before the output formatter does
   any processing on the text to display.  After the filter returns, the
   formatter translates "%fmt%" sequences, applies caps() and nocaps()
   changes, translates "\t" and similar sequences, performs word-wrapping
   on the line, adjusts spacing for punctuation, and, when in HTML mode,
   interprets HTML mark-ups.  As a result, you can perform your own
   translations on any of these sequences; in addition, you can use such
   sequences in the returned string, and the formatter will interpret them
   normally.

   Note that the output formatter will make a separate call to your filter
   function to display the result of translating each "%fmt%" sequences in
   the text to be displayed.  These separate calls will occur after your
   filter function returns.  This allows you to perform the same operations
   on the translated "%fmt%" sequences that you perform on any other text.

 - Fixed a bug in the objwords() built-in function.  In the past, this
   function at times incorrectly returned a non-empty list of words for
   a command that didn't have the corresponding object at all.  This has
   been corrected; objwords() will now return an empty list when the
   command does not have the requested object.

 - The parser now re-validates objects in a multi-object command after
   executing the command on the first object.  This re-validation occurs
   before the verbAction method is called for each object, and is done
   only for the second and subsequent object in a multi-object command.

   Re-validation is necessary because an action performed on the first
   object in a multi-object command could change conditions in the game
   such that the second object becomes invalid for the command, even
   though it was valid during the object resolution phase.  Consider
   this example:

      >x large box
      The large box is open.  It contains a small box.

      >x small box.
      The small box is open.

      >close large box, small box
      large box: Closed.
      small box: You don't see that here.

   In the past, the parser only validated the objects in a multi-object
   command during the resolution phase, which happens before executing
   the command on any of the objects.  Since the small box is accessible
   before the large box is closed, the parser would have allowed the
   player to close the small box after closing the large box in the last
   command above.  By re-validating the second and subsequent objects,
   the parser now correctly detects when objects become inaccessible in
   the course of executing a command on multiple objects.

   If an object fails re-validation, the parser checks to see if the
   object is visible.  If it is, the parser uses the cantReach method
   as usual to display the error.  If the object is not visible, the
   parser displays error 38, "You don't see that here any more."

 - The parser error codes (the numbers passed to parseError and
   parseErrorParam, and returned by parserResolveObjects) now have
   constants defined in adv.t.  These constants have names like
   PRSERR_xxx.  Refer to adv.t for the complete list.

   You may prefer to use the constants in your code whenever possible for
   clarity.  The numeric values will be stable in future releases, so
   it's perfectly safe to use the numbers directly, but using the
   symbolic constants will make the purpose of your code clearer to
   others (and to you, if you come back to some code after not having
   looked at it for a few weeks).

 - Several new parser error codes have been added.

   The new codes 40, 41, 42, 43, and 44 never result in a
   parser-generated error message, thus these codes have no default
   message.  These codes can, however, be returned by
   parserResolveObjects(), so that callers can distinguish among
   different types of error conditions if necessary.

   Code 38, default message "You don't see that here any more."  This
   message is used during object re-validation when an object that was
   valid at the start of the command is no longer valid.

   Code 39, default message "You don't see that here."  This new message
   is used when object validation (via validDo or validIo) fails in the
   course of executing a command recursively with execCommand().

   Code 40, no default message.  This error code is used when the parser
   is unable to create a new generic numeric object when calling a
   newNumbered method.

   Code 41, no default message.  This error occurs when disambigDobj or
   disambigIobj returns an invalid status code.

   Code 42, no default message.  This error occurs when the parser gets
   an empty line of text in response to a disambiguation question.

   Code 43, no default message.  This error occurs when the parser gets
   what looks like an entirely new command in response to a
   disambiguation question.

   Code 44, no default message.  This occurs when the parser finds that
   it still has an ambiguous list of objects after applying validation
   and verification tests, but the disambiguator is being called in
   "silent" mode and hence is not allowed to ask the player for help.
   When this occurs, the parser simply returns this error to indicate
   that the list cannot be disambiguated.

 - Parser message 16 has been changed slightly.  The parser generates
   message 16 when the player's answer to a noun disambiguation question
   ("which <object> do you mean...") doesn't refer to any of the possible
   ambiguous objects.  For example, suppose the parser asks "which book
   do you mean, the red book, or the blue book?," and the player answers
   "silver."  In the past, message 16 simply said "I don't see that
   here."  Some players found this slightly confusing, especially since
   the answer may have referred to some other object that wasn't a book
   but was indeed present.  The new message is intended to be somewhat
   more explicit: "You don't see any %s %s here," where the first "%s"
   is replaced with the new noun phrase the player typed, and the second
   "%s" is replaced with the original noun phrase.

      >take book
      Which book do you mean, the red book, or the blue book?

      >the silver one
      You don't see any silver book here.

   Note that you can use the parseErrorParam() user-defined function if
   you want to intercept this message have have access to the string
   parameters.

 - In adv.t, the "doorway" class has a new method, setIslocked.
   This new method behaves analogously to setIsopen: when you call
   setIslocked on a door, the method automatically updates both sides
   of a two-sided door to the same locking status.  All of the code
   in "doorway" that updates the islocked property now uses setIslocked
   rather than changing the property directly.

 - The default "sav" extension on newly-created saved game files is now
   in lower-case letters on operating systems that support mixed-case
   filenames.

 - A long-standing parser bug involving vocabulary words defined as both
   adjectives and nouns (for different objects) has been fixed.  In the
   past, if a particular word was defined as a noun for one object, and
   as an adjective for another object, the word could not ever be used
   alone to refer to the second object.  For example, suppose the game
   defined one object, "letter," and another, "letter opener"; the word
   "letter" is defined as a noun for the first object, and as an adjective
   for the second object.  If the player typed "get letter," the parser
   formerly assumed that the word was to be used as a noun; even if the
   "letter" object wasn't present and the "letter opener" object was,
   the parser would never understand the sentence to mean "get letter
   opener."  This was inconsistent with other objects, since in other
   cases an adjective alone could be used to refer to an object when the
   meaning was not ambiguous.  This has now been fixed; the parser now
   will interpret the adjective alone to refer to an associated object
   when no other object defining the word as a noun is accessible.
   Note that this does not create a new ambiguity: if the player types
   "get letter," and both the letter and the letter opener objects
   are present, the parser will still assume that "letter" is being
   used as a noun.  The parser will only attempt the adjective-only
   interpretation as a last resort, when no matching objects using the
   word as a noun are accessible.

 - Fixed a parser bug involving disambiguation of objects with redundant
   nouns or adjectives.  When the player types a word, and the word is at
   least six characters long, the parser will match the word to any entry
   in the game's dictionary of which the player's word is a leading
   substring.  For example, "flashlig" matches "flashlight", because
   "flashlig" is eight characters long (which is greater than or equal
   to the required six), and matches the first eight characters of
   "flashlight".  Similarly, "headlight" will match "headlights",
   because "headlight" is nine characters long, and matches "headlights"
   in the first nine characters.  This short-hand feature is meant as a
   convenience for the player, but occasionally caused ambiguity problems
   in past versions of TADS.  In particular, when a single object had
   a vocabulary word that was a leading substring of another vocabulary
   word of the same object (for example, if an object defined as nouns
   both 'headlight' and 'headlights'), the object could in some cases
   show up twice in a list of ambiguous objects ("which headlight do
   you mean...").  The parser now always eliminates redundant entries
   in lists of ambiguous objects during object disambiguation.

 - Another parser bug involving truncated words has been fixed.  Suppose
   the game defines one object with a noun 'prince', and another object
   with a noun 'princess'.  In the past, these two objects both matched
   the word 'prince' in a player's command, so if both objects were
   present, the parser considered them ambiguous.  The parser now
   considers an exact match to be better than a truncated match, so the
   word 'prince' in a player's command will now match the 'prince' object
   more strongly than it will the 'princess' object.  If only the
   'princess' object is accessible, 'prince' will still match the
   'princess' object, but if both are present, the parser will simply
   assume the player is referring to the 'prince' object.

 - Fixed a parser bug that caused two error messages (without any spacing
   between them) when the first word in a command was unknown: the
   parser responded with both "There's no verb in that sentence!" and
   "I don't know the word 'xxx'".  The parser now displays only the
   first ("no verb") message.

 - Fixed a parser bug that caused fuses and deamons to be skipped when
   an "exit" statement was executed in the midst of executing a command
   involving multiple direct objects.  If the "exit" was executed during
   the processing of any but the last object in the direct object list,
   the parser incorrectly skipped fuses and daemons.  This has been
   corrected; fuses and daemons will now run after an "exit" is executed,
   regardless of how many direct objects are involved in the command.

 - In adv.t, in the definition of movableActor, the definitions of the
   format strings fmtYou, fmtYour, fmtYoure, fmtYoum, and fmtYouve now
   vary according to the gender defined for the object with the isHim
   and isHer properties.  If isHim evaluates to true, the format strings
   now use masculine pronouns; otherwise, if isHer evaluates to true,
   the format strings use feminine pronouns; otherwise, they use neuter
   pronouns.

   Note that, in the past, these format strings always used masculine
   pronouns; if you defined any Actor or movableActor objects with
   neither isHim nor isHer set to true, messages that in the past used
   masculine pronouns will now use neuter pronouns.  You should be
   careful to ensure that you're defining the appropriate gender property
   (isHim = true or isHer = true) for your Actor and movableActor
   objects, so that messages using the format strings display the correct
   pronouns.

 - The text of system error messages 1021 ("index value too low") and
   1022 ("index value too high") have been modified slightly so that
   they don't use ">" or "<" characters.  (When running in HTML mode,
   the "<" and ">" characters in these messages caused problems, because
   the renderer tried to parse them as HTML tags.)

 - In the past, when the player's command contained a quoted string,
   the value that the parser passed to preparseCmd() did not contain
   a usable form of the string.  This has been corrected; the value
   that the parser passes to preparseCmd() will now always be the
   original string text, enclosed in double quote marks.  You can
   therefore detect a string token in the preparseCmd() list by
   checking to see if the first character (obtained with substr())
   is a double quote mark, '"':

       if (substr(lst[i], 1, 1) = '"')
          /* this token is a quoted string */ ;

 - In adv.t, the implementation of clothingItem has been modified
   slightly, to correct some bugs and make some improvements.

     - An actor can now attempt to wear objects that aren't being
       carried; the game will now automatically attempt to take the item
       (via a recursive "take" command using execCommand) before wearing
       it if the actor isn't carrying it.

     - The game will also automatically take an object if it's within
       another object in the actor's inventory; this ensures that an
       object is always at the top level of an actor's inventory while
       being worn.

     - When doffing a clothingItem, the game first checks to ensure that
       the actor's maximum "bulk" carrying capacity is not exceeded; this
       check must be made because an item being worn doesn't encumber an
       actor with bulk since it's not being carried by hand.  If the
       maximum bulk would be exceeded, the game will not let the actor
       doff the item.

     - The verDoWear method now checks to make sure that another actor
       isn't wearing the item; in the past, if the player attempted to
       wear an item already being worn by another actor, the game replied
       with the nonsensical message "you're already wearing that."


------------------------------------------------------------------------------
2.3.0  02/01/1999

 - A new systemInfo() feature code lets the game determine whether
   the interpreter is in HTML mode or plain text mode.  The new code
   is __SYSINFO_HTML_MODE; systemInfo(__SYSINFO_HTML_MODE) returns
   true if the interpreter is currently interpreting HTML markups,
   nil if not.  Note that this new code has nothing to do with whether
   the interpreter is a full multimedia system (such as HTML TADS) or
   a text-only system (such as the DOS "TR" interpreters); this new
   code instead indicates only whether or not a "\H+" sequence is
   currently in effect.

 - Several new systemInfo() feature codes have been added to provide
   information on MPEG audio support:

     __SYSINFO_MPEG_AUDIO - this returns 1 if MPEG 2.0 audio support
         of any kind is present, 0 if not.  It is possible that some
         systems may support some types of MPEG audio, but not all
         three layers.  This feature code indicates whether MPEG
         audio of any kind is supported; the specific layer codes
         below can be used to check for each individual layer.

     __SYSINFO_MPEG_AUDIO_1 - 1 if MPEG 2.0 layer I is supported

     __SYSINFO_MPEG_AUDIO_2 - 1 if MPEG 2.0 layer II is supported

     __SYSINFO_MPEG_AUDIO_3 - 1 if MPEG 2.0 layer III is supported

 - To accomodate the new TADS-Input font feature in HTML TADS, std.t
   now has a definition of the commandPrompt and commandAfterRead
   functions that automatically switch to the TADS-Input font.  This
   is important only for HTML-enabled games.  If your game uses HTML
   features, you should #define USE_HTML_PROMPT before including std.t
   in order to use these new functions.  (If you're providing your own
   definitions of these functions, you should consider adding the font
   settings made in the new std.t versions.)

 - New feature: TADS now has a built-in regular expression matching
   facility.  Regular expressions provide a powerful and simple way
   to search for a complex pattern within a text string.  This feature
   is particularly useful for writing preparse() and preparseCmd()
   functions, since it allows you to perform complex pattern matching
   and replacing with very little code.

   The new function reSearch() searches for the first occurrence of a
   regular expression pattern within a string.  It returns nil if the
   pattern is not found.  If the pattern is found, the function returns
   a list, the first element of which is a number giving the character
   position within the string of the start of the match (the first
   character is at position 1), the second element giving the number
   of characters in the match, and the third element a string giving
   the actual text of the match.

      ret := reSearch(pattern, string_to_search);

   The pattern is specified using regular expression syntax similar to
   that used by "grep" and other similar utilities.  Here are the basic
   building blocks of the regular expression syntax:

      |     Alternation: matches the expression on the left or the
            expression on the right.  This operator affects as many
            characters as it can, out to the nearest parentheses.
      ( )   Groups an expression.
      +     Indicates that the immediately preceding character or
            parenthesized expression repeats one or more times.
      *     Indicates that the immediately preceding character or
            parenthesized expression repeats zero or more times.
      ?     Indicates that the immediately preceding character or
            parenthesized expression can occur zero or one time.
      .     (a period) Wildcard: matches any single character.
      ^     Matches the beginning of the string.
      $     Matches the end of the string.
      %     Quotes the following character, removing the special
            meaning of these characters: | . ( ) * ? + ^ $ % [
            Also introduces the special sequences listed later.
      [ ]   Indicates a character list or range expression.  Matches
            any one of the listed characters.  A range can be specified
            by following a character with '-' and another character;
            this matches all of the characters between and including
            these two characters.  For example, [a-z] matches any
            one lower-case letter, and [0-9] matches any one digit.
            Ranges and single characters can be combined; for example,
            [a-zA-Z] matches any letter, upper- or lower-case.  To
            include the character ']' in a list, make it the first
            character after the opening bracket; to include '-', make
            it the next character after that.  For example, []] matches
            just ']', [-] matches just '-', and []-] matches '-' and ']'.
      [^ ]  Exclusionary character list or range.  This matches any
            character *except* the ones listed.  For example, [^0-9]
            matches anything single character except a digit.
      %1    This matches the same text that matched the first parenthesized
            expression.  For example, consider the pattern '(a*).*%1'.
            The string 'aaabbbaaa' will match, because the first three
            characters match the parenthesized 'a*' expression, which
            causes '%1' to match the last three characters; the middle
            three characters are matched by the '.*' expression.
      %2    Matches the text matching the second parenthesized expression.
            And so on through...
      %9    Matches the text matching the ninth parenthesized expression.
      %<    Matches at the beginning of a word.  Words are considered to
            be contiguous groups of letters and numbers.
      %>    Matches at the end of a word.  For example, '%<and%>' matches
            the "and" in 'ball and box' and 'and then', but not in
            'rubber band' or 'the android'.  Note that %< and %> do not
            actually contribute any characters to the match - they simply
            ensure that they fall on a word boundary.  So, searching for
            '%<and%>' in 'ball and box' matches the string 'and' -- the
            spaces are not included in the match.
      %w    Matches any word character (a letter or a digit).
      %W    Matches any non-word character (anything but a letter or digit).
      %b    Matches at any word boundary (beginning or end of a word).
      %B    Matches except at a word boundary.

   Any character other than those listed above simply matches the exact
   same character.  For example, 'a' matches 'a'.

   Here are some examples of simple regular expressions, to help clarify
   the meanings of the basic building blocks:

      abc|def    either 'abc' or 'def'
      (abc)      'abc'
      abc+       'abc', 'abcc', 'abccc', etc.
      abc*       'ab', 'abc', 'abcc', 'abccc', etc.
      abc?       'ab' or 'abc'
      .          any single character
      ^abc       'abc', but only at the start of the string
      abc$       'abc', but only at the end of the string
      %^abc      literally '^abc'
      [abcx-z]   'a', 'b', 'c', 'x', 'y', or 'z'
      []-]       ']' or '-'
      [^abcx-z]  any character except 'a', 'b', 'c', 'x', 'y', or 'z'
      [^]-q]     any character except ']', '-', or 'q'

   Here are some more complicated examples:

      (%([0-9][0-9][0-9]%) *)?[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]

   This matches a North American-style telephone number, either with
   or without an area code in parentheses.  If an area code is
   present, it can optionally be separated by spaces from the rest
   of the number: '(415)555-1212', '555-1212', '(415) 555-1212'.

      [-+]?([0-9]+%.?|([0-9]*)%.[0-9]+)([eE][-+]?[0-9]+)?

   This matches a floating-point number in the notation used by C and
   some other programming languages: either a string of digits optionally
   ending with a decimal point, or zero or more digits followed by a
   decimal point followed by one or more digits; optionally followed
   by an exponent specified with the letter "E" (upper- or lower-case),
   an optional sign ('+' or '-'), and one or more digits; all of this
   can be preceded by an optional sign.  This matches: '3e9', '.5e+10',
   '+100', '-100.', '100.0', '-5e-9', '-23.e+50'.

      ^ *tell%>(.*)%<to%>(.*)

   This matches the word "tell" at the beginning of the string,
   preceded only by zero or more spaces, followed by any text, followed
   by the word "to", followed by any more text.  This matches
   'tell bob to go north' and 'tell teeterwaller to give me the mask'.

   Here's a code example:

      ret := reSearch('d.*h', 'abcdefghi');
      if (ret = nil)
         "No match.";
      else
         "Start = <<ret[1]>>, length = <<ret[2]>>, text = \"<<ret[3]>>\". ";

   When run, this code will display the following:

      Start = 4, length = 5, text = "defgh".

 - Another new built-in function, reGetGroup(), lets you retrieve the
   matching text for parenthesized groups within regular expressions.
   reGetGroup() returns information about the last call to reSearch().
   The function takes one argument, which is a number giving the group
   number to retrieve: the first parenthesized expression is group 1,
   the second is group 2, and so on.  (When groups are nested, the
   position of the open parenthesis is used to determine the group
   numbering.  The leftmost open parenthesis is numbered as group 1.)

   reGetGroup() returns nil if there is no such group in the most
   recent regular expression or if the last call to reSearch() did not
   match the expression.  Otherwise, reGetGroup() returns a list with
   three elements identifying the group.  The first element is a
   number giving the character position within the original search
   string of the start of the text that matched the group.  The second
   element is the length of the text that matched the group.  The third
   element is the actual text that matched the group.

   Here's a code example:

      ret := reSearch('d(.*)h', 'abcdefghi');
      if (ret != nil)
      {
         grp := reGetGroup(1);
         if (grp != nil)
           "Start = <<grp[1]>>, len = <<grp[2]>>, text = \"<<grp[3]>>\". ";
      }

   This will display the following:

      Start = 5, len = 3, text = "efg".

   You can use regular expression grouping to carry out complicated
   transformations on strings with relatively little code.  Since you
   determine exactly where in a string a particular group in a regular
   expression occurs, you can take the group text out of the string
   and put it back into the string in a different order or with other
   changes.

   For example, suppose you want to write a preparse() function that
   finds sentences of the form "tell <actor> to <command>" and converts
   them to the normal TADS actor command format, "<actor>, <command>".
   You can use regular expression grouping to find this pattern of text
   and build the new command from pieces of the original:

      ret := reSearch('^ *tell%> *(.*)%<to%> *(.*)', cmd);
      if (ret != nil)
         cmd := reGetGroup(1)[3] + ', ' + reGetGroup(2)[3];

   Or, suppose you have a telephone in your game, and you want to let
   the player dial numbers on the phone using normal North American
   telephone number notation, including an area code.  The TADS parser
   won't normally let you do this, since it would try to parse the
   number as several words.  You could solve this problem using preparse:
   after the player enters a command, find anything that looks like a
   telephone number, and enclose it in quotation marks; this will make
   the parser treat the phone number as a quoted string, so you can
   write your "dial" verb so that it uses strObj as the direct object.
   Here's how you could write the preparse routine:

      ret := reSearch('(%([0-9][0-9][0-9]%) *)?'
                      + '[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]', cmd);
      if (ret != nil)
         cmd := substr(cmd, 1, ret[1] - 1) + ' "' + ret[3] + '" '
                + substr(cmd, ret[1] + ret[2], length(cmd));

 - New feature: a new function extends the capabilities of the existing
   input functions, input() and inputkey(), to give you more control
   over event processing.  The new function, inputevent(), can read
   multiple types of events, and can also apply a timeout to limit the
   how long it waits for an event to occur.

   The inputevent() function takes zero or one argument.  With no
   arguments, inputevent() simply waits until an event occurs.  With
   one argument, which must be a number, inputevent() waits until an
   event occurs, or until the number of milliseconds specified by the
   argument has elapsed without an event occurring, in which case the
   function "times out" and returns without any event having occurred.

   Note that the timeout value, if given, may not always be obeyed
   to the exact millisecond.  Different types of computers have
   different system clock resolutions; in addition, multi-user and
   multi-tasking systems often have unpredictable latencies for event
   processing.  As a result, if you specify a timeout value, the actual
   time that elapses before the function times out and returns may be
   slightly longer than the specified timeout value.  Any additional
   latency should be no more than a few hundred milliseconds in most
   cases, so this shouldn't be noticeable for most purposes.

   The function returns a list value describing the event that occurred.
   The first element of the list is a number that specifies the type of
   the event.  The rest of the list varies according to the event type.
   Constants for the event codes are defined in adv.t.  The possible
   event codes are:

   INPUT_EVENT_KEY - the user pressed a key.  The second element of
   the list returned by inputevent() in this case is a string containing
   the key that the user pressed.  The string is the same that would be
   returned by inputkey() for the same keystroke.

   INPUT_EVENT_HREF - the user clicked on an <A HREF=xxx> link.  This
   event is only returned by an HTML TADS interpreter, never by a
   character-mode TADS interpreter.  The second element of the return
   list is a string containing the text of the HREF that the user
   clicked.

   INPUT_EVENT_TIMEOUT - no event occurred before the specified timeout
   elapsed.  The return list contains no additional elements.

   INPUT_EVENT_EOF - this indicates that the TADS interpreter is
   terminating or an error occurred reading an event.

   INPUT_EVENT_NOTIMEOUT - this is not actually an event, but an error
   indicating that the current system does not support the timeout
   feature of inputevent().  If this occurs, you can still use
   inputevent(), but you cannot specify a timeout.  The DOS TADS
   interpreters (TR, TRX, TR32) all support timeouts, as does HTML TADS
   for Windows; interpreters on most systems should be able to support
   this feature, but a few systems may not be able to.

 - New feature: the inputkey() function now returns a portable
   representation for certain extended keys.  In the past, keys outside
   of the normal ASCII character set were almost impossible to use with
   inputkey(), because TADS returned a useless "raw" format for extended
   keys, such as cursor navigation keys and function keys.  TADS now
   uses a portable string format to represent many common keys.

   Each extended key is represented as a string containing a key name
   enclosed in square brackets.  The key name are:

      [bksp]         - backspace (destructive backspace/delete left)
      [up]           - up arrow (cursor navigation key)
      [down]         - down arrow
      [right]        - right arrow
      [left]         - left arrow
      [end]          - "end" key
      [home]         - "home" key
      [del]          - delete character (the "del" key)
      [page up]      - "page up" key
      [page down]    - "page down" key
      [f1]           - function key F1
      [f2]           - F2
      [f3]           - F3
      [f4]           - F4
      [f5]           - F5
      [f6]           - F6
      [f7]           - F7
      [f8]           - F8
      [f9]           - F9
      [f10]          - F10

   In addition, "control" keys (i.e., keys entered by holding down the
   "control" or "ctrl" key on the keyboard and pressing an alphabetic
   key) are returned as "[ctrl-X]", where "X" is the lower-case letter
   key; and "alt" keys are returned as "[alt-X]".  Finally, the "Return"
   and "Enter" keys are returned as "\n", and the tab key is returned
   as "\t".

   Even though these key names are portable, be aware that not every
   computer has all of these keys, so you can't count on the player
   actually being able to enter them.  The only keys that you can always
   count on being present are the regular ASCII keys, Enter/Return, Tab,
   and Backspace.  So, if you're using these extended keys, you should
   always be sure to provide an alternative for each extended key using
   an ordinary key.  For example, if you want to implement a menu system
   that uses the up and down arrow keys to navigate through a set of
   choices, you could use "N" (for "next") and "P" (for "previous") as
   synonyms for [down] and [up], respectively.

   The arrow keys ([up], [down], [left], and [right]) are probably the
   most portable of the extended keys, since most computers and terminals
   have some sort of arrow keys.  The function keys ([f1] through [f10])
   are also available on many systems, although some systems use some or
   all of the function keys for special purposes; for example, Windows
   uses the F10 key to begin navigating the menu bar, so your game will
   never receive the [f10] extended key when running on Windows.  The
   ALT and CONTROL keys are also very non-portable.

   Here's an example of using the arrow keys.

     numberVerb: deepverb
         verb = 'number'
         action(actor) =
         {
             local num;
             local done;
             local changed;

             "Press the Up or Down arrow keys, or the + or - keys,
             to change the number.  Press Enter when finished.\b";

             num := 5;
             changed := true;
             for (done = nil ; !done ; )
             {
                 if (changed)
                 {
                     "\nCurrent value = <<num>>";
                     changed := nil;
                 }

                 switch(inputkey())
                 {
                 case '\n':
                     done := true;
                     break;

                 case '+':
                 case '[up]':
                     ++num;
                     changed := true;
                     break;

                 case '-':
                 case '[down]':
                     --num;
                     changed := true;
                     break;
                 }
             }

             "\bThe final value was <<num>>. ";
         }
     ;

 - New feature: the gettime() built-in function can now return additional
   system real-time clock information.  The function now optionally takes
   an argument specifying what type of information to return.  Constants
   for the argument values are defined in adv.t:

   GETTIME_DATE_AND_TIME - this returns the traditional date and time
   information that gettime() returned in the past.  This is the same
   information that the function returns if called with no arguments
   (thus ensuring that existing code that calls gettime() will continue
   to work unchanged).

   GETTIME_TICKS - this returns the number of milliseconds since an
   arbitrary zero point, which is usually some system event, such as
   starting the current session of the TADS interpreter, or turning
   on the computer.  The actual zero point is arbitrary, but it will
   remain fixed for a particular session, so you can use this form
   of gettime() to compute relative times between events over a short
   period of time.  For example, if you're reading events with the
   new inputevent() function, you can use this time value to set a
   limit on how long you read events.  For example:

      local max_time, cur_time, evt;

      /* process events for no more than 5 seconds (5000 milliseconds) */
      max_time := gettime(GETTIME_TICKS) + 5000;
      for (;;)
      {
         /* check to see if we've reached our time limit */
         cur_time := gettime(GETTIME_TICKS);
         if (cur_time >= max_time)
            break;

         /* get events, but time out if we exceed our time limit */
         evt := inputevent(max_time - cur_time);

         /* process the event */
         switch(evt[1])
            // and so on
       }

 - New feature: a new built-in function lets you pause the game for a
   specified interval.  You can use this function in the middle of
   displaying a long text passage, for example, to create a dramatic
   pause effect, without making the user press a key to continue.
   The new function is called timeDelay(), and takes a single argument
   giving the number of milliseconds to pause the game.  For example,
   to pause the game for five seconds, use this:

      timeDelay(5000);

 - The player command parser now allows a preposition to be used when
   answering a request for an indirect object.  For example:

    >unlock door
    What do you want to unlock it with?

    >with key

 - The "oops" command (which lets the player correct a misspelled word in
   the previous command) now accepts multiple words to substitute for a
   misspelled word.  This lets you correct an omitted space in a command:

     >take redbox
     I don't know the word "redbox".

     >oops red box
     Taken.

 - New feature: the askfile() built-in function now takes two additional,
   optional parameters that let you specify what type of prompt to show
   and what type of file to request.  These new arguments are hints to
   the system-specific code that displays the "open file" dialog; by
   specifying this new information, you help the system code show the
   correct type of dialog.

   The new askfile() syntax looks like this:

     filename := askfile(prompt_text, prompt_type_code, file_type_code);

   The prompt_type_code tells the open-file dialog whether you're
   opening an existing file or saving a file.  On some systems (Windows
   and Macintosh included), the user interface uses one type of dialog
   for opening an existing file, and a different type of dialog for
   saving a file; you can use this parameter to select the appropriate
   dialog type on systems that make this distinction.  This parameter
   can have one of the following values, defined in adv.t:

     ASKFILE_PROMPT_OPEN - open an existing file for reading
     ASKFILE_PROMPT_SAVE - open a file for saving information

   On some systems, the open-file dialog will filter the files it
   displays so that the player only sees files of the particular type
   being requested.  The file_type_code parameter lets you specify
   the type of file you're interested in, so that the dialog can use
   the appropriate filtering on systems that support this.  The
   file_type_code can be one of the following values, defined in adv.t:

     FILE_TYPE_GAME - a game data file (.gam)
     FILE_TYPE_SAVE - a saved game (.sav)
     FILE_TYPE_LOG - a transcript (log) file
     FILE_TYPE_DATA - general data file (used for fopen())
     FILE_TYPE_CMD - command input file
     FILE_TYPE_TEXT - text file
     FILE_TYPE_BIN - binary data file
     FILE_TYPE_UNKNOWN - unknown file type

   adv.t and std.t have been updated to specify these new arguments in
   all of their calls to askfile().

   If you leave out the new arguments in a call to askfile(), the
   function will behave as it did in the past.  This means that your
   prompt string must contain the word "save" or "write" in order to
   show a "save file" dialog rather than an "open file" dialog on
   those systems that differentiate between these dialog types.

 - Fixed a bug with the parserSetMe() function: on restart(), the system
   did not correctly restore the current "Me" object to the original
   object.  This works properly now.

 - Fixed a player command parser bug that caused problems with numbers
   in commands under certain circumstances.  In particular, if an object
   happened to use a number as an adjective, and the player typed a
   command involving the same number, but to refer to the number itself
   rather than to the object with the numeric adjective, the parser
   incorrectly assumed that the player meant to use the object rather
   than the number.  Consider this example:

    >i
    You are carrying Chapter 3.

    >turn dial to 3

   Previously, the parser interpreted the "3" as referring to the
   "chapter 3" object.  This problem has been corrected; now, the
   parser will still try the "chapter 3" object first, but when the
   parser finds that the command fails the verification pass (because
   verIoTurn or verDoTurn display an error), and the original word
   used in the command was simply a number, the parser will assume
   that the player means to refer to the number itself rather than
   any object that has an adjective matching the number.

 - Fixed a bug in adv.t that caused misleading messages when the player
   was in a chairItem and tried to refer to something in the enclosing
   room when the chairItem's reachable list did not include items in
   the enclosing room.  In this case, adv.t displayed the message "I
   don't see that here," which was clearly incorrect in that the objects
   in a chairItem's enclosing room are generally visible even when not
   reachable.

   To correct this, the room object in adv.t now calls a new method
   from its cantReach() method when the actor is not directly in the
   room.  The new method is cantReachRoom(); room.cantReach() calls
   actor.location.cantReachRoom(self) to display an appropriate
   message.  In other words, if the actor is in a chair within a room,
   adv.t will call the chair's cantReachRoom() method with the chair's
   enclosing room as the argument.

   adv.t defines a default cantReachRoom() method for the classes thing,
   room, and chairItem.  These default methods all generate similar
   messages saying roughly "You can't reach that from here."

   Note that the new cantReachRoom() method provides new versatility for
   situations where objects are visible from a location but not reachable.
   For example, if you want to create two rooms separated by a glass
   partition, so that objects in one room are visible from the other
   room but not reachable, you can override isVisible and getVisibleList
   for the rooms to make the contents of the other room visible from
   each room, and then add a cantReachRoom(otherRoom) method like this:

      cantReachRoom(otherRoom) =
      {
         if (otherRoom = eastSideOfGlassRoom)
            "That's on the other side of the glass partition. ";
         else
            inherited.cantReachRoom(otherRoom);
      }

 - Fixed a bug in the player command parser that caused the parser to
   repeatedly say "I don't know the word <word>" when an invalid word
   was entered in a command *and* the preparseCmd function modified
   the command by returning a list value.  In this situation, the
   parser would not stop saying "I don't know the word <word>" until
   the player entered an "oops" command to fix the misspelled word.
   This no longer occurs.

 - The parser responded incorrectly to unknown words entered in
   response to a disambiguation question ("which box do you mean, the
   red box, or the blue box?").  If the player entered an unknown word
   in response to such a question, the parser responded by asking the
   same question over again, without mentioning that the word was
   unknown.  The parser now correctly mentions the unknown word.

   Note that this change removes a certain amount of control from the
   game's processing of unknown words through parseUnknownXobj, since
   these methods are not called when unknown words are entered in
   response to disambiguation questions.

 - Fixed a parser bug that caused errors for certain valid commands
   involving an indirect object that used an adjective that also was
   used as a preposition.  For example, if the game had a "south wall"
   object, the parser did not correct interpret the command "put box
   on south wall" (the response was "I don't understand that sentence").
   This has been corrected.

 - Numerous changes in adv.t make the default messages more consistent
   at using parameterized pronouns and verbs to refer to objects.  These
   changes generally make the default messages agree more automatically
   in number and gender with the objects described.  The parameterized
   messages are mostly keyed on the isThem property; if you have an
   object that's described by a plural noun phrase ("the pants" or
   "some marbles," for example), setting isThem = true in the object
   will help adv.t generate the correct default messgaes when referring
   to the object ("you can't put the marbles in themselves," for example).
   Many thanks to Stephen Granade for his extensive work to fix these.

 - adv.t is now somewhat more consistent in its handling of reachability
   from nested rooms.  In the past, chairItem objects and other nestedroom
   objects had a strange difference: from a normal nestedroom, you could
   reach anything in the nestedroom's "reachable" list, plus anything in
   any open containers in the list, to any nesting level; in contrast,
   from a chairItem, you could only reach the objects directly in the
   chairItem's "reachable" list.

   For compatibility with past versions, this behavior hasn't changed.
   However, it's now simple to make the behavior the same for both types
   of rooms.  If you want chairItem objects to behave the same way that
   all other nestedroom objects behave, simply modify chairItem and set
   its "canReachContents" property to true:

     modify chairItem
       canReachContents = true
     ;

   This is probably the most logical behavior, so authors of new games
   may want to consider including this change.  However, the default
   remains for chairItem objects to limit reachability to the items
   directly in the "reachable" list with no access to their contents,
   so that any existing games that depended (intentionally or otherwise)
   on the old behavior are not broken by an incompatible change.

 - Fixed a bug that caused a spurious error (TADS-601, "error writing
   to game file") when attempting to create a pre-compiled header file,
   when a #define symbol was defined to an empty string.  This problem
   has been corrected.

 - The compiler went into an infinite loop reporting an error
   (TADS-353, "'local' is only allowed at the beginning of a block")
   when a misplaced "local" statement appeared.  This is fixed; the
   compiler now simply ignores the entire "local" statement and
   continues attempting to parse the code.

 - The random number generator caused a divide-by-zero error (which
   could abruptly terminate the interpreter on some platforms) when
   the game called rand(0) at any time after calling randomize().
   This has been corrected; rand(0) now simply returns 0 without
   causing a crash.

 - In adv.t, the clothingItem code was written so that, if the player
   attempted to wear something that another actor was wearing, the
   game responded with "You're already wearing that," which clearly
   isn't correct.  The game now says "You don't have that," which makes
   more sense.

------------------------------------------------------------------------------
2.2.6  09/30/98  bug fixes

 - The release notes are now separated into a generic section and a
   platform-specific section.  This file (TADSVER.TXT) describes the
   changes that apply to all types of computers and operating systems.
   A separate file describes the changes specific to each platform;
   for example, DOSVER.TXT describes the changes that apply only to
   the MS-DOS and Windows versions of TADS.

 - adv.t now has a "knock" verb (knock, knock on, and knock at are
   all synonyms).  The "doorway" class provides has a default doKnock
   method that simply displays "there is no answer" in response to
   the player knocking on the door.

 - adv.t now has a "go through" verb.  The "doorway" class provides a
   default handler for "go through" that has the same effect as the
   player traveling in the direction of the door.

 - In adv.t, the "doorway" class now has a default handler for the
   "enter" verb that has the same effect as the player traveling in
   the direction of the door.

 - In adv.t, the default message for eating a "thing" now correctly
   incorporates the ordinality of the object, and the verb agrees
   with the subject (so "the tire doesn't appear appetizing" whereas
   "the keys don't appear appetizing").

 - In the adv.t "doorway" class, the setIsopen method now calls
   setIsopen on the object representing the other side of the door,
   rather than updating its isopen property directly.  In addition, the
   code that automatically opens the door when the player attempts to
   travel through the door calls setIsopen rather than updating the
   isopen properties of the door object and the other side object
   directly.  These change make it easier to code a door that has
   special behavior when opened or closed, since all changes to the
   isopen property made by code in the doorway class now occur through
   the setIsopen method, so all of the special behavior code can be
   placed in an overridden setIsopen method.

 - The player command parser did not correctly respond to disambiguation
   questions (such as "which box do you mean, the green box, or the
   red box?").  In some cases, when the player ignored the question and
   simply typed a new command, the parser completely ignored the command
   and show a new command prompt with no other comment; alternatively,
   the parser responded to the new command as though it were an object,
   showing a message such as "I don't see any open door here."  In yet
   other situations, when the player attempted to answer the question
   with a more detailed object description, the parser would complain
   that it didn't see such an object even though the additional player
   input was correct.  All of these problems have been corrected; the
   parser should handle player responses to disambiguation questions
   correctly now.

 - A bug in the player command parser caused an infinite loop under
   certain conditions.  The loop occurred if one of an object's nouns
   was a word that was also used as a verb, and the player typed a
   command of the form "<verb> <noun> of <noun>", where <verb> is
   the word used as both a noun and a verb.  For example, the command
   "discard ace of spades" caused the loop if "discard" was defined as
   a verb and was also used as a noun for some object.  This has been
   corrected.

 - The compiler did not correctly handle the error "else without if".
   Rather than ignoring the offending "else" keyword, the compiler got
   stuck in an infinite loop generating the same error message over and
   over.  This has been corrected; the compiler now only generates the
   error message once per occurrence of the error in the source code.

 - The compiler did not correctly handle the -case- option.  Although
   games compiled correctly, they did correctly handle verbs with objects
   at run-time.  This has been corrected.

 - The compiler did not correctly interpret expressions of this form:

     (x ? "first string" : "second string << 5 >> end of second string")

   In particular, when a string containing an embedded expression was
   used as the third operand of a ternary conditional operator (the "?:"
   operator), the compiler interpreted the embedded expression and
   everything that followed as an entirely separate expression, which
   was incorrect.  The compiler now handles these expressions correctly.

 - The compiler did not correctly interpret a statement that consisted
   entirely of a parenthesized expression when used as the "true" branch
   of an "if" statement when an "else" clause was present:

     if (expr)
       (1+2+3);
     else
       ...

   The compiler incorrectly generated an "else without if" error in this
   situation, because it incorrectly ignored the semicolon that followed
   the statement.  This has been corrected; the semicolon is now parsed
   properly.  Note that if your game inadvertantly exploited this error
   by omitting semicolons in such cases, you will have to change your
   source code; because of the obscurity of this case we consider it
   extremely unlikely that anyone will be affected, but if you are, you
   will receive "expected semicolon" messages at the erroneous lines of
   code.

 - A compiler bug caused crashes when using __LINE__ under certain
   circumstances; this bug has been fixed.

 - The interpreter now displays a separate error message (TADS-617) when
   it attempts to load an external resource file (.RS0, etc) with an
   invalid header.  The interpreter previously displayed the message for
   an invalid game file, which was not helpful in tracking down the
   problem.  The error message for TADS-617 displays the name of the
   resource file causing the error, to help pinpoint the problem.


------------------------------------------------------------------------------
2.2.5  08/24/98  enhancements and bug fixes

 - New parser feature: The parser now calls a new pair of methods on
   the direct and indirect object in the course of processing each
   command.  The new methods are called dobjCheck and iobjCheck, and
   are called with these parameters:

      iobjCheck(actor, verb, dobj, prep)
      dobjCheck(actor, verb, iobj, prep)

   iobjCheck is called on the indirect object, if any, just before
   iobjGen is or would be called.  (iobjCheck is always called, whether
   or not iobjGen is called.)  Similarly, dobjCheck is called on the
   direct object just before dobjGen is or would be called.  Note that
   the dobj, iobj, and prep parameters may be nil if the corresponding
   objects are not present in the command.

   These new methods are very similar to dobjGen and iobjGen, but have
   one difference: the new methods are ALWAYS called.  This differs from
   dobjGen and iobjGen, which are called only when the object does not
   define the corresponding verb handler method (do<Verb> or io<Verb>)
   for the verb being used.

   The new dobjCheck and iobjCheck methods are called immediately
   BEFORE the corresponding dobjGen and iobjGen methods are called.
   So, the new calling sequence looks like this:

         actor.actorAction
         actor.location.roomAction
  *NEW*  iobj.iobjCheck     (if there's an indirect object)
         iobj.iobjGen       (if the indirect object doesn't define io<Verb>)
  *NEW*  dobj.dobjCheck     (if there's a direct object)
         dobj.dobjGen       (if the direct object doesn't define do<Verb>)

   and then on to the normal verIo<Verb> and/or verDo<Verb>, as
   appropriate.

   So, why have both xobjGen and xobjCheck?  The reason is that each
   is useful in its own way.

   The xobjGen routines are meant as *default* handlers.  They're
   catch-all methods that handle any command that isn't specially
   handled by the object.  Because any normal verb handler (one of the
   io<Verb> or do<Verb> routines) "overrides" the xobjGen routines, it's
   not necessary to include a special check in the xobjGen method for
   the verbs that the object handles in a non-default fashion.

   The xobjCheck routines, on the other hand, are mandatory checks that
   are applied to all verbs used with an object, whether or not the verb
   is otherwise handled by the object.  This makes these routines useful
   in cases when the object's behavior can change, because you can perform
   tests based on the object's state in a single place for all verbs,
   rather than having to apply the same test in every verb handler.

 - A bug in past versions handled doSynonym and ioSynonym incorrectly
   when they were used in classes.  Consider the following example:

     class draggableItem: fixeditem
         verDoPull(actor) = { }
         doPull(actor) =
         {
             "You manage to drag <<self.thedesc>> along the ground
              for a few feet. ";

             // etc...
         }
         doSynonym('Pull') = 'Push' 'Move'
     ;

     desk: draggableItem
         // noun, location, sdesc, etc...
         doPull(actor) =
         {
             "The desk scrapes along the floor for a few feet,
             but everything falls off! ";
             // etc...
         }
      ;

   Now, if the player typed PULL DESK, the result would be as you'd
   expect: "the desk scrapes along the floor for a few feet, but
   everything falls off!"

   However, if the player typed PUSH DESK, you'd get the unexpected
   reply "You manage to drag the desk along the ground for a few feet."

   The problem was that the system was invoking the synonym routine for
   the definition in the class defining the synonym, rather than properly
   using the overriding method in the actual object used in the command.

   This problem has been corrected.

 - A player command parser bug allowed using AGAIN to repeat a command
   directed to an actor after the actor had left the room (or otherwise
   become inaccessible).  The parser did not check to ensure that the
   actor was still present for the repeated command.  This has been
   corrected; the parser now checks that the actor is still accessible
   when using AGAIN.

 - A bug in the player command parser, introduced with the parseUnknownXobj
   feature added in version 2.2.4, caused the parser to fail to display
   proper default prompts when the player entered a command with an
   unknown word that also required an additional object (for example,
   if the player typed "put foo", where "foo" was not a valid vocabulary
   word).  This has been corrected; the parser now simply reports the
   unknown word in these cases.

 - A bug in the compiler caused a subtle problem when 'modify' was
   used to modify a class where the original class had vocabulary
   defined, but the 'modify' did not add any new vocabulary.  In such
   cases, the vocabulary defined in the original class was lost, so
   any objects derived from the class did not have the original class
   vocabulary words.  This problem has been corrected.

   Several people encountered this problem when using 'modify' with
   the 'basicMe' class defined in adv.t.  In these cases, the bug
   prevented the 'Me' object from inheriting the nouns from the
   original 'basicMe' in adv.t, so at run-time a command such as
   "look at me" would not work properly.  (The work-around, which
   was to add a noun='' definition to the modified object, is no
   longer necessary now that the bug has been fixed, but should not
   cause any problems.)

 - When restoring a saved game directly from the command line when
   starting the run-time, the run-time incorrectly invoked the init()
   function before invoking the initRestore() function.  Since the
   initRestore() function is meant to be called *instead of* the init()
   function in this situation, this was incorrect behavior.  This
   problem has been fixed; the run-time now invoked *either* init()
   or initRestore(), but not both, depending on whether the game is
   started normally or with a restored game on the command line.  This
   problem affected both the character-mode and HTML run-time versions.

 - A bug in the character-mode run-time caused HTML markups to be
   misinterpreted if closely preceded by a percent sign that wasn't
   being used with a format string (such as "%You%").  In such cases,
   the character-mode run-time sometimes displayed any tags that
   closely followed the percent sign (within about forty characters
   of output), rather than interpreting them and removing them from
   the display as it normally would.  This has been corrected.  Note
   that this problem affected only the character-mode run-time when
   operating with an HTML-enabled game.

 - The run-time did not correctly handle backslashes in text captured
   by the outcapture() built-in function; extra backslashes were
   unnecessarily (and incorrectly) added to escape backslashes.  This
   has been corrected.

 - The run-time was incompatible with games compiled with version of
   the compiler before 2.2.0.  This incompatibility normally resulted
   in the game ignoring most commands.  The problem has been corrected,
   so old games should work correctly again.

 - The character-mode run-time now recognizes the <TITLE> tag when the
   game is operating in HTML ("\H+") mode.  The character-mode version
   simply suppresses all text displayed between the <TITLE> and
   corresponding </TITLE> tags.

 - The character-mode run-time now allows hexadecimal values to be
   specified in numeric entity markups ("&#nnn").  This change is for
   compatibility with the HTML TADS run-time, which allows such markups.

 - The character-mode run-time did not always break lines correctly in
   text that switched in and out of HTML mode (using \H+ and \H-).  This
   has been corrected.  Note that this problem only affected the
   character-mode run-time (TR32), not the HTML run-time.

 - The dialItem object in adv.t now has a minsetting property, which
   specifies the minimum setting for the dial; the default value is 1.


------------------------------------------------------------------------------
2.2.4  07/20/98  enhancements and fixes

 - The TADS language has a new construct that allows you to write a method
   so that it inherits explicitly from a particular superclass.  This new
   syntax adds to the existing 'inherited' syntax, which allowed you to
   inherit from the superclass according to rules that TADS uses to resolve
   the superclass that a particular method overrides.

   The new syntax lets you specify the name of the superclass after the
   'inherited' keyword, but is otherwise similar to the normal 'inherited'
   syntax:

       inherited fixeditem.doTake(actor);

   This specifies that you want the method to inherit the doTake
   implementation from the fixeditem superclass, regardless of whether
   TADS might normally have chosen another superclass as the overridden
   method.  This is useful for situations involving multiple inheritance
   where you want more control over which of the base classes of an object
   should provide a particular behavior for the subclass.

 - adv.t now includes all of the bugs fixes that Stephen Granade assembled
   in his BUGS.T file.  If you've been using BUGS.T, you should be able to
   get the same effect by compiling with adv.t alone now.  I'd like to thank
   Stephen for creating this excellent improvement to the original adv.t.

 - The parser now provides a way to change the player character dynamically
   during game play.

   In the past, the parser used the object named "Me" to represent the
   player character; there was no way to change this.  This made it
   difficult to write a game with different personas for the player
   character.

   The parser still uses "Me" as the initial player character, but you
   can now switch the player character to a different object at any
   time using the new built-in function parserSetMe(newMe).  The argument
   to parserSetMe() is the new object to use to represent the player
   character.

   Another new built-in function, parserGetMe(), allows you to get the
   parser's current player character object.  This function takes no
   arguments.

   Note that adv.t and std.t no longer refer to the "Me" object directly
   in code related to the player character's status (such as inventory
   or room location descriptions).  Instead, adv.t and std.t use the new
   parserGetMe() function to get the player character object.

   If you use parserSetMe() in your game, you should be careful not to
   refer to the "Me" object directly in contexts where you really want
   the current player character object; use parserGetMe() instead to get
   the correct object from the parser.  Note that existing games should
   not be affected by this change; if you don't call parserSetMe(), then
   parserGetMe() will always return the "Me" object, so you can safely
   use a fixed "Me" object.

   IMPORTANT NOTE: this feature may be somewhat incomplete, in that
   additional adv.t support may be needed for some games to take full
   advantage of this feature.  If you try using this feature in your game
   and you experience any problems or have suggestions on adv.t changes
   that you would help you use this feature, please contact TADS's author
   by email at [email protected].

 - The compiler is now more tolerant of non-standard newline conventions
   used in source files.  MS-DOS, Macintosh, and Unix each have different,
   incompatible conventions for how text files represent line endings
   (and it's likely that there are a few other operating systems with
   even more different conventions).  When copying a file from one
   type of computer to another, the newline conventions aren't always
   correctly translated.  In the past, the TADS compiler was sometimes
   unable to process a file that did not use the correct newline
   conventions for the machine running the compiler.  The compiler can
   now accept most combinations of line ending conventions on any
   platform that runs the compiler.

 - The player can now specify a saved game to restore directly from the
   run-time command line.  Use the new -r argument to specify the name
   of the saved game to be restored:

      tr -r mygame.sav

   Note that the full filename must be provided; no default suffix is
   applied to the -r argument.

   TADS now stores the name of the .GAM file in each saved game file,
   and uses this information with the -r option.  If you start the
   run-time, and you specify a saved game file to restore using -r but
   do not specify a game file, the TADS run-time attempts to load the
   game file named in the saved game file.  You can always specify the
   name of the game file explicitly, in which case TADS will ignore the
   game file name stored in the save file:

      tr -r mygame.sav deep.gam

   When you use the -r option, TADS loads the game, and then immediately
   restores the save file.

   Game authors: note that you can customize the way this new feature
   works by using the initRestore function; see below for details.

 - The system now calls a new optional game-defined function named
   initRestore() if the player specifies a game to restore on the
   run-time command line.  initRestore() is called with a single-quoted
   string argument giving the name of the saved game file to be restored.

   If initRestore() is defined, and the player restores a game using
   the -r option, the system will *not* call your game's init() function.
   The reason that TADS skips init() in this case is that the player
   will normally want to skip your game's introductory text when jumping
   directly to a saved position at the start of the game.  (Note, however,
   that your preinit() function will always run as usual.)

   If initRestore() is not defined in your game, TADS will simply call
   the init() function as usual, and will then restore the game itself.
   This provides compatibility with older games, although it may result
   in a confusing initial display for the player, because TADS will not
   be able to show the current location immediately after restoring the
   game.  If possible, you should define initRestore() in your game.

   std.t provides a default implementation of initRestore() that simply
   restores the game, and then shows a full description of the current
   location.  If you use std.t in your game, you should be aware that
   your init() function will not be called when the player restores a
   game explicitly from the run-time command line, and make any
   necessary adjustments to your init() routine and the initRestore()
   routine defined in std.t.

   If you have code in your init() function that sets up any variables,
   or if you enter HTML mode in your init() function, you should be sure
   to call the same code from your initRestore() function, because init()
   will not be called in this case.  The best way to structure your
   initialization code is to break out the common initialization code
   into a separate function, and call this function from your normal
   init() routine as well as your initRestore() routine:

      initCommon: function
      {
          /* set up HTML mode */
          "\H+";
          "<body bgcolor=purple text=fuchsia>";
      }

      init: function
      {
          /* perform common initialization */
          initCommon();

          /* display opening text messages */
          "A long time ago in a cavern far, far away...";
      }

      initRestore: function(fname)
      {
          /* perform common initialization */
          initCommon();

          /* restore the game */
          mainRestore(fname);
      }

 - To facilitate the new initRestore function, adv.t now isolates the
   game restoration code that was previously in the restoreVerb object
   in the new mainRestore() function.  You can call this function from
   your own initRestore() routine, if you want the normal game restore
   functionality.  mainRestore() takes a single argument, which is a
   single-quoted string giving the name of the save file to restore.

   mainRestore() restores the saved game, then updates the status line
   and displays a full description of the current location.

 - Just as the system calls the game hook function commandPrompt prior
   to reading a command, the system now calls a new game hook function,
   commandAfterRead, immediately after reading a player command.  The
   new function looks like this:

      commandAfterRead: function(code)

   where 'code' is the same prompt type code that is passed to the
   corresponding call to commandPrompt.  This new function is intended
   to make it easier to customize certain aspects of your game's user
   interface.  In particular, if you're using special formatting for
   the player command in the HTML run-time (for example, you want to
   use a special typeface or font color for player commands), you can
   use commandAfterRead to turn off the special formatting you turned
   on in commandPrompt.  Each call to commandPrompt is matched by an
   equivalent call to commandAfterRead, so you can always count on
   being able to undo any formatting changes you make in commandPrompt
   by placing the matching formatting commands in commandAfterRead.

 - A new parser hook provides you with more flexibility in determining
   how unknown words in a player command are handled.  The new parser
   hook operates during noun phrase resolution (this is the process
   by which the parser attempts to determine what object should be used
   for each noun phrase entered by the player in a command).

   In the past, the parser detected unknown words (i.e., words not
   defined as vocabulary properties, such as 'noun' or 'adjective',
   somewhere in the game program) during the initial dictionary lookup
   step of command processing.  Upon encountering an unknown word at
   this step, the parser simply reported the error ("I don't know the
   word <word>") and aborted the command.

   The parser now marks unknown words found during the dictionary lookup
   step, but doesn't generate an error at this step.  Instead, it
   tentatively considers such words to be parts of noun phrases, and
   continues parsing.  The parser then attempts to determine the verb
   and general structure of the command as normal; if this fails, the
   parser goes back and reports the unknown word as it used to.  However,
   if the parser is able to find a valid sentence structure, it continues
   to the next step, which is noun phrase resolution.

   Note that the parser considers unknown words to have a completely
   neutral part of speech, which means that unknown words are merged with
   any known nouns and adjectives to which they are adjacent to form the
   noun phrase.  Since the parser doesn't know the word, it can't decide
   whether to treat it as a noun or adjective, so it simply considers it
   to be neutral and allows it to combine with whatever other words are
   present.

   Noun phrase resolution proceeds as normal until the parser once again
   encounters one of the unknown words.  So, the parser will call the
   verification (verDoXxx) and validation (validDo, validDoList)
   routines as usual for any noun phrases containing recognized words,
   in the usual order (for most verbs, this means that the indirect
   object is resolved first, then the direct objects).

   Upon encountering an unknown word, however, the parser checks to see
   if your game program defines the new parser hook.  The new hook is a
   method on the deepverb object called parseUnknownDobj (to resolve
   direct objects) or parseUnknownIobj (to resolve indirect objects).
   The methods are called with these parameters:

      parseUnknownDobj(actor, prep, iobj, wordlist)
      parseUnknownIobj(actor, prep, dobj, wordlist)

   In both cases, 'actor' is the actor object to which the command is
   directed; 'prep' is the preposition that introduces the indirect
   object, or nil if there is no preposition; and 'wordlist' is a list
   of single-quoted strings containing all of the words -- both known
   and unknown -- making up the noun phrase that contains the unknown
   word or words.

   The 'iobj' argument in parseUnknownDobj is the indirect object, if
   present and known; similarly, the 'dobj' argument in parseUnknownIobj
   is the direct object, if present and known.  The 'iobj' or 'dobj'
   argument will be nil if there was no other object in the command, or
   if that object has not been resolved by the time this method is called.
   When the indirect object is resolved first, as it is with most verbs,
   parseUnknownIobj will always receive nil for the direct object, since
   the direct object cannot be resolved until the indirect object is
   resolved, which is what parseUnknownIobj is doing.

   These routines can return the following values:

      nil - This indicates that the routine did not successfully resolve
      the object, and the system should use the default handling.  The
      parser reports the unknown word error as usual.

      true - This indicates that the routine has fully processed the
      command for this noun phrase, and no further parser action is
      necessary.  The parser simply continues processing any other
      objects in the command, but treats this noun phrase as having
      no corresponding objects in the command and does no further
      processing on it.

      object - The routines can return an object value, which the parser
      treats as the resolution of the noun phrase.  The parser proceeds
      to apply all normal processing to the object as though the parser
      had found an appropriate set of dictionary words for the noun
      phrase matching the returned object.  So, all of the normal
      verification, validation, and action routines are called for the
      object.

      list - The routines can return a list containing object values.
      The parser uses *all* of the objects in the list as the resolution
      of the noun phrase, and applies all of the normal processing to
      each object in the list.  The effect is the same as if the user
      had entered the objects in the command separated by commas.

   If your game doesn't define this new method for the verb, the parser
   simply uses the default handling, and reports the unknown word error.

   Here's a simple example that changes the ASK ABOUT command so that
   we always let the actor respond, even if a word that the player is
   asking about isn't defined anywhere in the game.  To accomplish this,
   we add a parseUnknownIobj to askVerb.  This routine will return a
   special object, unknownAskIobj, that we define solely as a placeholder
   for ASK ABOUT with an unknown word.  We'll set unknownAskIobj's
   wordlist property to the list of unknown words, and the object uses
   the list of words to construct its sdesc.

     #pragma C-

     /*
      *   Special object that we use as the indirect object of any
      *   ASK ABOUT command that refers to unknown words
      */
     unknownAskIobj: thing
         wordlist = []
         sdesc =
         {
             local i;

             for (i := 1 ; i <= length(wordlist) ; ++i)
             {
                 if (i != 1)
                     " ";
                 say(self.wordlist[i]);
             }
         }
     ;

     /*
      *   For "ask about," use special handling for unknown words so
      *   that the actor can respond directly to the unknown words.
      */
     modify askVerb
         parseUnknownIobj(actor, prep, dobj, words) =
         {
             /* if we're asking about something, have the actor respond */
             if (prep = aboutPrep)
             {
                 /* use our special ASK ABOUT object for the unknown words */
                 unknownAskIobj.wordlist := words;
                 return unknownAskIobj;
             }
             else
             {
                 /*
                  *   it's not ASK ABOUT, return nil to use the
                  *   default system handling
                  */
                 return nil;
             }
         }
     ;


 - The compiler can now capture all of the strings used in a game to a
   text file.  This can be useful for spell-checking your game, since
   it gives you a listing of all of the text in the game, separated
   from your source code.  To capture strings during compilation, use
   the -Fs option to specify the output file:

      tc -Fs mygame.lis mygame.t

   This compiles mygame.t, producing mygame.gam as usual, and writes
   all of the strings in the source code to the file mygame.lis.

 - The character-mode run-time now provides improved, but still limited,
   HTML support.  As in version 2.2.3, after you display an "\H+"
   sequence, the character-mode run-time becomes sensitive to HTML tag
   and character markup sequences.  However, whereas version 2.2.3
   ignored all tags and ampersand sequences, the new version now obeys
   certain markups.  In particular, the character-mode run-time will now
   process the following tags:

     <br> ends the current line; additional <br>'s display blank lines.
        The HEIGHT attribute is accepted, and produces results that
        are consistent with the graphical handling.
     <p> displays a blank line.
     </p> displays a blank line
     <b> and <em> start boldface mode.
     </b> and </em> end boldface mode.
     <tab> indents to the next tab stop (exactly like "\t")
     <img> and <sound> accept the ALT attribute, and display the text
        of the ALT attribute value in place of the image or sound.
        No other decoration is added; the ALT value is simply included
        in the text display as though it had appeared as ordinary text.
     <hr> starts a new line, displays a line of dashes, and starts
        another new line

   The character-mode run-time ignores all other tags.  As before,
   unrecognized tags are simply removed from the text entirely.

   In addition, the ampersand character-code markups are now supported.
   Since the DOS character set does not contain all of the characters in
   the HTML character set (ISO Latin 1), some ampersand markups are
   displayed as blanks, and others are displayed as approximations.
   The following characters are displayed correctly:

     &endash;
     &emdash;
     &iexcl;
     &cent;
     &pound;
     &yen;
     &brvbar;
     &ordf;
     &laquo;
     &not;
     &deg;
     &plusmn;
     &sup2;
     &micro;
     &middot;
     &ordm;
     &raquo;
     &frac14;
     &frac12;
     &iquest;
     &Auml;
     &Aring;
     &AElig;
     &Ccedil;
     &Eacute;
     &Ntilde;
     &Ouml;
     &Uuml;
     &agrave;
     &aacute;
     &acirc;
     &auml;
     &aring;
     &aelig;
     &ccedil;
     &egrave;
     &eacute;
     &ecirc;
     &euml;
     &igrave;
     &iacute;
     &icirc;
     &iuml;
     &ntilde;
     &ograve;
     &oacute;
     &ocirc;
     &ouml;
     &divide;
     &ugrave;
     &uacute;
     &ucirc;
     &uuml;
     &yuml;

   The following are displayed using approximations:

     &bdquo; is displayed as a normal double-quote
     &lsaquo; is displayed as '<'
     &lsquo; is displayed as a normal single-quote
     &rsquo; is displayed as a normal single-quote
     &ldquo; is displayed as a normal double-quote
     &rdquo; is displayed as a normal double-quote
     &rsaquo; is displayed as '>'
     &shy; is displayed as '-'
     &acute; is displayed as a normal single-quote
     &cedil; is displayed as a comma
     &times; is displayed as an 'x'
     &sbquo; is displayed as a normal single-quote
     &szlig; is displayed as a "beta"
     &trade; is displayed as "(tm)"
     &copy; is displayed as "(c)"

   The following are displayed using the corresponding unaccented
   character, since the accented version of the character is not in
   the DOS character set:

     &Yuml;
     &Agrave;
     &Aacute;
     &Acirc;
     &Atilde;
     &Egrave;
     &Ecirc;
     &Euml;
     &Igrave;
     &Iacute;
     &Icirc;
     &Iuml;
     &Ograve;
     &Oacute;
     &Ocirc;
     &Otilde;
     &Oslash;
     &Ugrave;
     &Uacute;
     &Ucirc;
     &Yacute;
     &atilde;
     &otilde;
     &oslash;
     &yacute;

   The following are not supported at all, since the DOS character set
   has no equivalents for these characters.  Each of these markups is
   rendered as a single space.

     &dagger;
     &permil;
     &OElig;
     &oelig;
     &curren;
     &sect;
     &uml;
     &reg;
     &macr;
     &sup3;
     &para;
     &sup1;
     &frac34;
     &ETH;
     &THORN;
     &eth;
     &thorn;

 - The new built-in function morePrompt() allows your game to explicitly
   display the system MORE prompt.  You can use this for such special
   effects as a dramatic pause prior to a chapter change.  The new
   function takes no arguments and returns no value.

 - The built-in file manipulation functions now allow you to read and
   write text-mode files.  Files written in text mode can be used by
   other applications as  ordinary text files.

   To use text-mode files, you must specify the new 't' file mode
   suffix in fopen (for symmetry, a new 'b' mode suffix, for binary
   mode files, is also allowed, but for compatibility with past
   versions, binary mode is the default if no mode suffix is
   specified).  You can use the 't' suffix with 'r' (read) and 'w'
   (write) modes; 't' is not currently allowed with 'r+' or 'w+'
   modes.

   When a file is opened in text mode, fwrite() can only be used with
   string values.  Strings passed to fwrite() can contain the escape
   characters '\t', '\n', and '\\'; other escapes are not allowed.
   '\t' is translated to a tab, '\n' is translated to a newline (using
   the appropriate local conventions for the current system), and '\\'
   is translated to a single backslash.  fwrite() does NOT add any
   newlines to the text you provide, so you must explicitly include
   any newlines you want to write to the file.

   Because TADS obeys local newline conventions, fwrite() always
   produces the correct sequence of characters for the current machine
   when you include '\n' in a string, so you don't have to worry about
   how newlines are handled on each platform.

   fread() always reads a line of text from the file.  If the end of
   the file is not reached, the line returned will end with a '\n'
   sequence (as with fwrite(), fread() translates newlines according
   to local conventions, and always returns the TADS '\n' sequence to
   represent a newline in the file).  If fread() encounters the end
   of the file in the middle of a line, it will return the text up
   to the end of the file, with no trailing newline.  The subsequent
   call will return nil to indicate that the end of the file has been
   reached.

 - Because TADS now has several ways of reading and writing files,
   some game players may be uncomfortable about the possibility that
   a malicious game author could harm their systems by writing a game
   that, for example, modifies their AUTOEXEC.BAT files.  To address
   any concerns that players may have, TADS now provides a "file safety
   level" setting.  This is an optional setting that allows the player
   to control the amount of access that a game has to files on the
   system at run-time.

   The file safety level is set through a command line option (note
   that HTML TADS also provides this setting through the "Preferences"
   dialog).  Use the new -s option to specify one of the possible
   safety levels:

      -s0    (default) minimum safety - read and write in any directory
      -s1    read in any directory, write in current directory
      -s2    read-only access in any directory
      -s3    read-only access in current directory only
      -s4    maximum safety - no file I/O allowed

   If the game attempts a file operation that is not allowed by the
   current safety level, the fopen() function returns nil to indicate
   that the file open failed.

   These options affect only explicit file I/O operations performed by
   the game.  Operations handled by the system, such as saving and
   restoring games and logging a transcript to a file, are not affected
   by the file safety level setting.

 - The TADS resource manager (TADSRSC) now supports building external
   resource files for HTML TADS games.  Refer to the resource file
   documentation (RES.HTM) that accompanies the HTML TADS distribution
   for details.

 - The new TADS built-in function systemInfo() allows your game to
   determine programmatically whether the TADS run-time that is
   currently executing the game has certain capabilities.  This
   function is called like this:

      result = systemInfo(__SYSINFO_xxx);

   where __SYSINFO_xxx is one of the pre-defined constants (defined
   automatically by the compiler) listed below.  The result tells
   you about the particular run-time that is executing your game,
   so you can customize the game, if you wish, for certain system
   capabilities.  For example, you might want to change some text
   in your game slightly depending on whether sound effects can be
   played.

   Before calling systemInfo() with any of the other codes, you
   *must* check to see if systemInfo() itself is supported.  Versions
   of the run-time prior to 2.2.4 do not support this function, so
   the return codes are meaningless.  Fortunately, you can determine
   if systemInfo() is itself supported using the following code
   fragment:

       if (systemInfo(__SYSINFO_SYSINFO) = true)
       {
           /*
            *  systemInfo IS supported by this run-time - other
            *  systemInfo codes will return meaningful results
            */
       }
       else
       {
           /*
            *  systemInfo is NOT supported by this run-time
            */
       }

   Only one version of HTML TADS (version 2.2.3) was ever released
   without systemInfo support, and this was the first public beta
   release.  So, it should be fairly safe to assume that any system
   that doesn't support systemInfo() doesn't support any of the HTML
   TADS features.

   The __SYSINFO_xxx codes are:

     __SYSINFO_VERSION - returns a string with the run-time version
       number.  This will be a string such as '2.2.4'.

     __SYSINFO_HTML - returns 1 if HTML markup is supported, 0 if not.
       0 indicates that this is a standard text-mode run-time system.
       If this returns 0, then JPEG, PNG, WAV, MIDI, WAV/MIDI overlap,
       WAV overlap, and the images, sounds, music, and links preference
       items can all be assumed to be unsupported, since these features
       are only provided by HTML TADS.

       Note that __SYSINFO_HTML returns 0 (HTML not supported) for the
       character-mode run-time, even for the newer versions of the
       run-time that do provide some limited HTML support, because this
       information code is intended to indicate whether the full HTML
       feature set is supported.  The character-mode version only supports
       a limited subset of HTML features, so it indicates that HTML is
       not supported.

     __SYSINFO_OS_NAME - returns a string with the name of the operating
       system on which the run-time is currently executing.  (The name
       is the same as the string that the compiler uses to pre-define
       the __TADS_SYSTEM_NAME preprocessor symbol, but this lets you
       determine what system is executing at run-time, rather than the
       system that was used to compile the game.)

     __SYSINFO_JPEG - returns 1 if JPEG images are supported, 0 if not.

     __SYSINFO_PNG - returns 1 if PNG images are supported, 0 if not.

     __SYSINFO_WAV - returns 1 if WAV sounds are supported, 0 if not.

     __SYSINFO_MIDI - returns 1 if MIDI music is supported, 0 if not.

     __SYSINFO_MIDI_WAV_OVL - returns 1 if MIDI and WAV sounds can be
       played back simultaneously (overlapped), 0 if not.  If this
       returns 0, it means that WAV playback will suspend MIDI playback.

     __SYSINFO_WAV_OVL - returns 1 if multiple WAV sounds can be played
       back simultaneously (overlapped), 0 if not.  If this returns 0,
       it means that any WAV played back in a foreground layer will
       suspend a WAV being played in any background layer.

     __SYSINFO_PREF_IMAGES - returns 1 if the user preferences are set
       to allow images to be displayed, 0 if not.  Note that, even if
       this preference is set so that images are not displayed, the
       preferences for JPEG and PNG images will still return 1 for each
       of those image types that are supported by the platform.  The
       image format codes (__SYSINFO_PNF and __SYSINFO_JPEG) indicate
       whether the image formats are supported at all, whereas this
       preference code indicates whether images are currently allowed
       for display.

     __SYSINFO_PREF_SOUNDS - returns 1 if the user preferences are set
       to allow digitized sound effects (WAV files) to play back, 0 if
       not.

     __SYSINFO_PREF_MUSIC - returns 1 if the user preferences are set
       to allow music (MIDI files) to play back, 0 if not.

     __SYSINFO_PREF_LINKS - returns 0 if the user preferences are set
       so that links are not highlighted at all (in which case they'll
       be displayed as ordinary text; they won't be highlighted as links
       and won't be active for the mouse); returns 1 if links are
       highlighted and active; and returns 2 if links are set to a "hot
       key" mode, in which case links aren't highlighted except when
       the user is holding down a special key to explicitly illuminate
       the links.

 - TADS has a new mechanism for better supporting non-US character
   sets in a more portable fashion.  The TADS Compiler, Run-Time, and
   Debugger (including the HTML TADS versions) now provide an option
   that lets you specify a character set translation to use for your
   game.  The character set translation allows your game to use a
   standardized character set, such as ISO Latin 1, but still run on
   any system by providing a mapping from your game's internal character
   set to the native system character set for each player's system.
   Refer to CHARMAP.HTM for information on how to use this new
   feature.

 - The parser now accepts a comma immediately after "oops" or "o".

 - The parser now allows strings in player commands to be entered with
   single quotes as well as double quotes.  So, the following commands
   are now interchangeable:

      type "hello" on keyboard
      type 'hello' on keyboard

   Note that this change means you can't create a vocabulary word that
   starts with a single quote, such as "'til", although you can still use
   a single quote within a vocabulary word, as in "bob's".  The parser
   treats a single quote within a word as part of the word, but it now
   treats a single quote at the start of a word as a quotation mark
   intended to mark a string.

 - In adv.t, 'q' is now a synonym for 'quit' for player commands.

 - The #define preprocessor directive did not work correctly in past
   versions when the "-case-" option (for case-insensitive compilation)
   was used.  In particular, preprocessor symbols defined with capital
   letters were not matched.  This has been corrected so that #define
   works properly when "-case-" is used.

 - The #define preprocessor directive sometimes did not work correctly
   with long source lines (over about 128 characters).  This has been
   corrected; #define now works correctly with long input lines, which
   are often necessary for lengthy macro expansions.

 - The TADS error code 1026, "wrong number of arguments to user function,"
   now includes the name of the function or object.method that was the
   target of the invalid function call.  This can be helpful in situations
   where the TADS parser calls the function directly, since there was
   previously no simple way of determining which function was being called
   in these cases.  Note that you must compile your code with debugging
   enabled, and run under the TADS Debugger, to see the extra information
   in the error message, since only the Debugger can load a game's symbol
   table, and can only do so if the game was compiled for debugging.

 - A bug that caused debugger crashes under certain obscure circumstances
   has been fixed.  In the past, if a game used the "replace" keyword to
   replace a method in an object, tracing into other methods in the same
   original object (the base object, before applying the modifications
   using the "modify" construct) could sometimes crash the debugger.
   This has been corrected.

 - The run-time is now more consistent about converting special escape
   sequences in strings obtained externally, such as from the input()
   function or from quoted strings in a player command.  In particular,
   when these strings contain characters such as backslashes or newlines,
   the run-time now consistently converts these characters into a
   backslash sequence (a backslash turns into '\\', a newline turns
   into '\n', and a tab turns into '\t').

 - adv.t and std.t are now considerably more consistent in terms of
   indenting and punctuation style.

------------------------------------------------------------------------------
2.2.3  03/25/98  corrections and enhancements, HTML

 - This new version of the TADS compiler and run-time supports
   HTML TADS, the new HTML-enabled version of the TADS run-time.
   You should use this new version of the compiler to generate
   games for the HTML TADS run-time.

 - Fixed a parser bug that caused various problems when the player
   issued a command to an actor, but used a pronoun to refer to
   the actor: "him, go east."  (This always failed, but the type
   of problem depended on the platform; in most cases, a TADS
   error such as "error loading object on demand" resulted.)

 - Fixed up the indenting in adv.t.  At some point adv.t was
   detabified with an incorrect tab size setting, which randomized
   the indenting.  The file has now been fully re-indented with
   spaces; since it has no tabs in it any more, it should look
   the same on any editor, regardless of the local tab size
   setting.

 - Added 'n', 's', and the other one-letter direction words to
   dirPrep's preposition vocabulary list in adv.t.  This corrects
   the problem that a command such as "push box n" was not accepted,
   even though "push box north" was.

 - A new parser error handling function has been added.  The new
   function, parseErrorParam(), is similar to parseError(), but
   provides additional arguments that provide the actual values of
   the "%" parameters in the error message:

       parseErrorParam: function(errNum, errMsg, ...)

   The errNum and errMsg parameters are the error code number and
   default error message text string, just as with parseError().
   The additional arguments give the values of the "%" parameters;
   for example, for message 2, "I don't know the word '%s'", the
   first extra parameter will be a string with the word that the
   parser didn't recognize.

   If your game defines a parseErrorParam() function, the parser
   will call this function rather than parseError(); parseError()
   is essentially obsoleted by the new function.  However, if your
   game does not contain a parseErrorParam() function, the parser
   will call parseError() as before.

 - Fixed a parser bug: if an object had the same name as a verb
   (for example, the defined an object with a noun of 'lock'), and
   the player attempted to issue a command to an actor starting
   with the re-used verb, the parser incorrectly issued the error
   "There's no verb in that sentence!"

------------------------------------------------------------------------------

Please consult TADSV222.DOS for information on releases from 2.1.1
through 2.2.2, and refer to TADSV200.DOS for information on releases
prior to 2.1.1.  See the note at the top of this file for details on
where to find these older release notes files.