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 mjr_@hotmail.com.

  - 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.