action.t

documentation
 #charset "us-ascii"
 
 /* 
  *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved.
  *   
  *   TADS 3 Library: Actions.
  *   
  *   This module defines the Action classes.  An Action is an abstract
  *   object representing a command to be performed.  
  */
 
 #include "adv3.h"
 #include "tok.h"
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Object associations lists.  We use this object to store some lookup
  *   tables that we build during preinitialization to relate object usages
  *   (DirectObject, IndirectObject) to certain properties. 
  */
 objectRelations: PreinitObject
     /* preinitialization - build the lookup tables */
     execute()
     {
         /* build the default pre-condition properties table */
         preCondDefaultProps[DirectObject] = &preCondDobjDefault;
         preCondDefaultProps[IndirectObject] = &preCondIobjDefault;
 
         /* build the catch-all pre-conditions properties table */
         preCondAllProps[DirectObject] = &preCondDobjAll;
         preCondAllProps[IndirectObject] = &preCondIobjAll;
 
         /* build the default verification properties table */
         verifyDefaultProps[DirectObject] = &verifyDobjDefault;
         verifyDefaultProps[IndirectObject] = &verifyIobjDefault;
 
         /* build the catch-all verification properties table */
         verifyAllProps[DirectObject] = &verifyDobjAll;
         verifyAllProps[IndirectObject] = &verifyIobjAll;
 
         /* build the default check properties table */
         checkDefaultProps[DirectObject] = &checkDobjDefault;
         checkDefaultProps[IndirectObject] = &checkIobjDefault;
 
         /* build the catch-all check properties table */
         checkAllProps[DirectObject] = &checkDobjAll;
         checkAllProps[IndirectObject] = &checkIobjAll;
 
         /* build the default action properties table */
         actionDefaultProps[DirectObject] = &actionDobjDefault;
         actionDefaultProps[IndirectObject] = &actionIobjDefault;
 
         /* build the catch-all check properties table */
         actionAllProps[DirectObject] = &actionDobjAll;
         actionAllProps[IndirectObject] = &actionIobjAll;
     }
 
     /* lookup table for default precondition properties */
     preCondDefaultProps = static new LookupTable()
 
     /* lookup table for catch-all precondition properties */
     preCondAllProps = static new LookupTable()
 
     /* lookup table for default verification properties */
     verifyDefaultProps = static new LookupTable()
 
     /* lookup table for catch-all verification properties */
     verifyAllProps = static new LookupTable()
 
     /* lookup table for default check properties */
     checkDefaultProps = static new LookupTable()
 
     /* lookup table for catch-all check properties */
     checkAllProps = static new LookupTable()
 
     /* lookup table for default action properties */
     actionDefaultProps = static new LookupTable()
 
     /* lookup table for catch-all action properties */
     actionAllProps = static new LookupTable()
 ;
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Invoke the given function with the given values for the parser global
  *   variables gActor and gAction.  
  */
 withParserGlobals(issuer, actor, action, func)
 {
     local oldIssuer;
     local oldActor;
     local oldAction;
     
     /* remember the old action and actor, so we can restore them later */
     oldIssuer = gIssuingActor;
     oldActor = gActor;
     oldAction = gAction;
     
     /* establish our actor and action as the global settings */
     gIssuingActor = issuer;
     gActor = actor;
     gAction = action;
     
     /* make sure we restore globals on the way out */
     try
     {
         /* invoke the callback and return the result */
         return (func)();
     }
     finally
     {
         /* restore the globals we changed */
         gActor = oldActor;
         gIssuingActor = oldIssuer;
         gAction = oldAction;
     }
 }
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Pre-condition Descriptor.  This object encapsulates a precondition
  *   object and the argument we want to pass to its condition check method.
  */
 class PreCondDesc: object
     construct(cond, arg)
     {
         /* remember the condition and the check argument */
         cond_ = cond;
         arg_ = arg;
     }
 
     /* check the precondition */
     checkPreCondition(allowImplicit)
     {
         /* call the precondition's check method with the argument we stored */
         return cond_.checkPreCondition(arg_, allowImplicit);
     }
 
     /* the precondition object */
     cond_ = nil
 
     /* the check argument */
     arg_ = nil
 
     /* our list sorting index */
     index_ = 0
 ;
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Basic Action class.  An Action is the language-independent definition
  *   of the abstract action of a command.  
  */
 class Action: BasicProd
     /*
      *   Are we the given kind of action?  By default, this simply returns
      *   true if we're of the given action class. 
      */
     actionOfKind(cls) { return ofKind(cls); }
 
     /*
      *   Reset the action in preparation for re-execution.  This should
      *   discard any scoped context from any past execution of the
      *   command, such as cached scope information.  
      */
     resetAction()
     {
         /* forget any past successful verification passes */
         verifiedOkay = [];
     }
 
     /* 
      *   Repeat the action, for an AGAIN command.
      */
     repeatAction(lastTargetActor, lastTargetActorPhrase,
                  lastIssuingActor, countsAsIssuerTurn)
     {
         /* execute the command */
         executeAction(lastTargetActor, lastTargetActorPhrase,
                       lastIssuingActor, countsAsIssuerTurn, self);
     }
 
     /*
      *   Cancel iteration of the action.  This can be called during the
      *   'check' or 'action' phases of executing this action.  It tells
      *   the action that we want to stop executing the action when we're
      *   finished with the current object.
      *   
      *   Note that this doesn't cause a jump out of the current code, so
      *   it's not like 'exit' or the other termination signals.  Instead,
      *   this simply tells the action to proceed normally for the
      *   remainder of the processing for the current object, and then act
      *   as though there were no more objects to iterate over, ending the
      *   command normally.  If you want to cut off the remainder of the
      *   execution cycle for the current object, you can use 'exit' (for
      *   example) immediately after calling this method.  
      */
     cancelIteration() { iterationCanceled = true; }
 
     /* internal flag: object iteration has been canceled */
     iterationCanceled = nil
 
     /*
      *   Create an instance of this action, for use by a recursive or
      *   programmatically-generated command.
      *   
      *   The generic actions defined in the library are always subclassed
      *   by language-specific library modules, because the language
      *   modules have to define the grammar rules for the verbs - we can't
      *   define the grammar rules generically because the verbs wouldn't
      *   be reusable for non-English translations if we did.  As a result,
      *   library code referring to one of the library verbs by name, say
      *   TakeAction, doesn't get a language-specific subclass of the verb,
      *   but just gets the language-independent base class.
      *   
      *   However, to make full use of an Action object in a recursive
      *   command, we do need a final language-specific subclass - without
      *   this, we won't be able to generate text describing the command,
      *   for example.  This method bridges this gap by finding a suitable
      *   language-specific subclass of the given action, then creating an
      *   instance of that subclass rather than an instance of the base
      *   class.
      *   
      *   By default, we'll take any subclass of this action that is itself
      *   a class.  However, if any subclass has the property
      *   defaultForRecursion set to true, we'll use that class
      *   specifically - this lets the language module designate a
      *   particular subclass to use as the default for recursive commands,
      *   which might be desirable in cases where the language module
      *   defines more than one subclass of an action.  
      */
     createActionInstance()
     {
         local found;
 
         /* 
          *   Iterate over our subclasses.  Initialize 'found' to this base
          *   class, so that if we fail to find any subclasses, we'll at
          *   least be able to create an instance of the generic base
          *   class.  
          */
         for (local cur = firstObj(self, ObjClasses), found = self ;
              cur != nil ; cur = nextObj(cur, self, ObjClasses))
         {
             /* 
              *   if this one is marked as a default for recursion, and the
              *   last one we found isn't, choose this one over the last
              *   one we found 
              */
             if (cur.defaultForRecursion && !found.defaultForRecursion)
                 found = cur;
             
             /* 
              *   If this one is a subclass of the last one we found, pick
              *   it instead of the last one.  We always want a final
              *   subclass here, never an intermediate class, so if we find
              *   a subclass of the one we've tentatively picked, we know
              *   that the tentative selection isn't final after all.
              */
             if (cur.ofKind(found))
                 found = cur;
         }
 
         /* return a new instance of what we found */
         return found.createInstance();
     }
 
     /*
      *   Create an instance of this action based on another action.  We'll
      *   copy the basic properties of the original action. 
      */
     createActionFrom(orig)
     {
         local action;
         
         /* create a new instance of this action */
         action = createActionInstance();
 
         /* copy the token list information to the new action */
         action.tokenList = orig.tokenList;
         action.firstTokenIndex = orig.firstTokenIndex;
         action.lastTokenIndex = orig.lastTokenIndex;
 
         /* the new action is implicit if the original was */
         if (orig.isImplicit)
             action.setImplicit(orig.implicitMsg);
 
         /* return the new action */
         return action;
     }
 
     /*
      *   Mark the command as implicit.  'msgProp' is the property (of
      *   gLibMessages) to use to announce the implicit command.  
      */
     setImplicit(msgProp)
     {
         /* mark ourselves as implicit */
         isImplicit = true;
 
         /* do not show default reports for implicit commands */
         showDefaultReports = nil;
 
         /* all implicit commands are nested */
         setNested();
 
         /* 
          *   do not include implicit commands in undo - since an implicit
          *   command is only a subpart of an explicit command, an implicit
          *   command is not undone individually but only as part of the
          *   enclosing explicit command 
          */
         includeInUndo = nil;
 
         /* remember the implicit command announcement message property */
         implicitMsg = msgProp;
     }
 
     /*
      *   Mark the command as nested, noting the parent action (which we
      *   take as the global current action). 
      */
     setNested()
     {
         /* remember the parent action */
         parentAction = gAction;
     }
 
     /* 
      *   Determine if I'm nested within the given action.  Returns true if
      *   the given action is my parent action, or if my parent action is
      *   nested within the given action. 
      */
     isNestedIn(action)
     {
         /* if my parent action is the given action, I'm nested in it */
         if (parentAction == action)
             return true;
 
         /* 
          *   if I have a parent action, and it's nested in the given
          *   action, then I'm nested in the given action because I'm
          *   nested in anything my parent is nested in 
          */
         if (parentAction != nil && parentAction.isNestedIn(action))
             return true;
 
         /* we're not nested in the given action */
         return nil;
     }
 
     /*
      *   Set the "original" action.  An action with an original action is
      *   effectively part of the original action for the purposes of its
      *   reported results.
      *   
      *   An action has an original action if it's a nested or replacement
      *   action for an action.  
      */
     setOriginalAction(action)
     {
         /* remember my original action */
         originalAction = action;
     }
 
     /*
      *   Get the "original" action.  If I'm a replacement or nested action,
      *   this returns the original main action of which I'm a part, for
      *   reporting pruposes.
      *   
      *   It's important to note that an implied action does NOT count as a
      *   nested or replacement action for the purposes of this method.
      *   That is, if a command A triggers an implied action B, which
      *   triggers a nested action C, and then after that command A itself
      *   triggers a nested action D, then
      *   
      *   A.getOriginalAction -> A
      *.  B.getOriginalAction -> B (B is implied, not nested)
      *.  C.getOriginalAction -> C (C is nested within B)
      *.  D.getOriginalAction -> A (D is nested within A)
      *   
      *   The purpose of the original action is to tell us, mainly for
      *   reporting purposes, what we're really trying to do with the
      *   action.  This allows reports to hide the internal details of how
      *   the action is carried out, and instead say what the action was
      *   meant to do from the player's perspective.  
      */
     getOriginalAction()
     {
         /* if I have no other original action, I'm the original action */
         if (originalAction == nil)
             return self;
 
         /* return my original action's original action */
         return originalAction.getOriginalAction();
     }
 
     /*
      *   Determine if this action is "part of" the given action.  I'm part
      *   of the given action if I am the given action, or the given action
      *   is my "original" action, or my original action is part of the
      *   given action.
      */
     isPartOf(action)
     {
         /* if I'm the same as the given action, I'm obviously part of it */
         if (action == self)
             return true;
 
         /* if my original action is part of the action, I'm part of it */
         if (originalAction != nil && originalAction.isPartOf(action))
             return true;
 
         /* I'm not part of the given action */
         return nil;
     }
 
     /*
      *   Mark the action as "remapped."  This indicates that the action
      *   was explicitly remapped to a different action during the remap()
      *   phase.  
      */
     setRemapped(orig) { remappedFrom = gAction; }
 
     /* determine if I'm remapped, and get the original action if so */
     isRemapped() { return remappedFrom != nil; }
     getRemappedFrom() { return remappedFrom; }
 
     /*
      *   Get the "simple synonym" remapping for one of our objects, if
      *   any.  'obj' is the resolved object to remap, and 'role' is the
      *   object role identifier (DirectObject, IndirectObject, etc).
      *   'remapProp' is the remapping property for the role; this is
      *   simply the result of our getRemapPropForRole(role), but we ask
      *   the caller to pass this in so that it can be pre-computed in
      *   cases where we'll called in a loop.
      *   
      *   A simple synonym remapping is a remapTo that applies the same
      *   verb to a new object in the same role.  For example, if we remap
      *   OPEN DESK to OPEN DRAWER, then the drawer is the simple synonym
      *   remapping for the desk in an OPEN command.  A remapping is
      *   considered a simple synonym remapping only if we're remapping to
      *   the same action, AND the new object is in the same action role as
      *   the original object was.
      *   
      *   If there's no simple synonym remapping, we'll return nil.  
      */
     getSimpleSynonymRemap(obj, role, remapProp)
     {
         local mapping;
         local remapIdx;
         
         /* if the object isn't remapped at all, there's no remapping */
         if ((mapping = obj.(remapProp)) == nil)
             return nil;
 
         /* if the mapping isn't to the same action, it's not a synonym */
         if (!ofKind(mapping[1]))
             return nil;
 
 
         /* 
          *   Find the specific (non-role) object in the remap vector -
          *   this is the entry that's actually an object rather than a
          *   role identifier.  (The remapping vector is required to have
          *   exactly one such entry.  Look from the right end of the list,
          *   since the first entry is always the new action, which is
          *   itself an object, but not the object we're looking for.)  
          */
         remapIdx = mapping.lastIndexWhich({x: dataType(x) == TypeObject});
         if (remapIdx == nil)
             return nil;
 
         /* 
          *   Determine if the object plays the same role in the new action
          *   as the original object did in the original action.  It does
          *   if it's at the index in the mapping vector of our object role
          *   in the action.  Note that the mapping vector has slot 1
          *   filled with the action, so its objects are actually one slot
          *   higher than they are in the action itself.
          *   
          *   If the new object isn't in the same role, then this isn't a
          *   simple synonym remapping.  
          */
         if (getRoleFromIndex(remapIdx - 1) != role)
             return nil;
 
         /* 
          *   We have the same action applied to a new object in the same
          *   role as the original object, so this is a simple synonym
          *   remapping.  Return the new object we're mapping to. 
          */
         return mapping[remapIdx];
     }
 
     /* 
      *   the defaultForRecursion flag must be explicitly set in subclasses
      *   when desired - by default we'll use any language-specific
      *   subclass of an Action for recursive commands 
      */
     defaultForRecursion = nil
 
     /* 
      *   Flag: the command is implicit.  An implicit command is one that
      *   is performed as an implied enabling step of another command - for
      *   example, if an actor wants to throw something, the actor must be
      *   holding the object, so will implicitly try to take the object.
      */
     isImplicit = nil
 
     /*
      *   The parent action.  If the command is performed programmatically
      *   in the course of executing another command, this is set to the
      *   enclosing action.
      *   
      *   Note that while all implicit commands are nested, not all nested
      *   commands are implicit.  A nested command may simply be a
      *   component of another command, or another command may be handled
      *   entirely by running a different command as a nested command.  In
      *   any case, a nested but non-implicit command does not appear to
      *   the player as a separate command; it is simply part of the
      *   original command.  
      */
     parentAction = nil
 
     /*
      *   The original action we were remapped from.  This is valid when
      *   the action was explicitly remapped during the remap() phase to a
      *   different action. 
      */
     remappedFrom = nil
 
     /* 
      *   the original action - we are effectively part of the original
      *   action for reporting purposes 
      */
     originalAction = nil
 
     /* the libMessage property, if any, to announce the implicit command */
     implicitMsg = nil
 
     /* 
      *   Flag: we are to show default reports for this action.  In most
      *   cases we will do so, but for some types of commands (such as
      *   implicit commands), we suppress default reports. 
      */
     showDefaultReports = true
 
     /*
      *   Get a message parameter object for the action.  Each action
      *   subclass defines this to return its objects according to its own
      *   classifications.  The default action has no objects, but
      *   recognizes 'actor' as the current command's actor.  
      */
     getMessageParam(objName)
     {
         switch(objName)
         {
         case 'pc':
             /* return the player character */
             return gPlayerChar;
             
         case 'actor':
             /* return the current actor */
             return gActor;
 
         default:
             /* 
              *   if we have an extra message parameters table, look up the
              *   parameter name in the table 
              */
             if (extraMessageParams != nil)
                 return extraMessageParams[objName];
 
             /* we don't recognize other names */
             return nil;
         }
     }
 
     /*
      *   Define an extra message-specific parameter.  Message processors
      *   can use this to add their own special parameters, so that they
      *   can refer to parameters that aren't involved directly in the
      *   command.  For example, a message for "take <dobj>" might want to
      *   refer to the object containing the direct object.
      */
     setMessageParam(objName, obj)
     {
         /* 
          *   if we don't yet have an extra message parameters table,
          *   create a small lookup table for it 
          */
         if (extraMessageParams == nil)
             extraMessageParams = new LookupTable(8, 8);
 
         /* add the parameter to the table, indexing by the parameter name */
         extraMessageParams[objName.toLower()] = obj;
     }
 
     /*
      *   For convenience, this method allows setting any number of
      *   name/value pairs for message parameters. 
      */
     setMessageParams([lst])
     {
         /* set each pair from the argument list */
         for (local i = 1, local len = lst.length() ; i+1 <= len ; i += 2)
             setMessageParam(lst[i], lst[i+1]);
     }
 
     /*
      *   Synthesize a global message parameter name for the given object.
      *   We'll store the association and return the synthesized name. 
      */
     synthMessageParam(obj)
     {
         local nm;
         
         /* synthesize a name */
         nm = 'synth' + toString(synthParamID++);
 
         /* store the association */
         setMessageParam(nm, obj);
 
         /* return the synthesized name */
         return nm;
     }
 
     /* synthesized message object parameter serial number */
     synthParamID = 1
 
     /*
      *   Extra message parameters.  If a message processor wants to add
      *   special message parameters of its own, we'll create a lookup
      *   table for the extra parameters.  Message processors might want to
      *   add their own special parameters to allow referring to objects
      *   other than the main objects of the command.  
      */
     extraMessageParams = nil
 
     /*
      *   Flag: this command is repeatable with 'again'.  Before executing
      *   a command, we'll save it for use by the 'again' command if this
      *   flag is true.
      */
     isRepeatable = true
 
     /*
      *   Flag: this command should be included in the undo records.  This
      *   is true for almost every command, but a few special commands
      *   (undo, save) are not subject to undo.  
      */
     includeInUndo = true
 
     /*
      *   Flag: this is a "conversational" command, as opposed to an order.
      *   When this type of command is addressed to another character, it
      *   doesn't ask the other character to do anything, but rather engages
      *   the other character in conversation.  Examples:
      *   
      *.  Bob, hello
      *.  Bob, goodbye
      *.  Bob, tell me about the planet
      *.  Bob, yes
      *.  Bob, no
      *   
      *   ("Tell me about..." is a little different from the others.  We
      *   treat it as conversational because it means the same thing as "ask
      *   Bob about...", which we consider conversational because it would
      *   be rendered in real life as a question.  In other words, the
      *   action involves the issuing actor stating the question, which
      *   means that issuing actor is the one doing the physical work of the
      *   action.  "Tell me about..." could be seen as an order, but it
      *   seems more appropriate to view it as simply an alternative way of
      *   phrasing a question.)
      *   
      *   The issuing actor is passed as a parameter because some actions
      *   are conversational only in some cases; "tell me about the planet"
      *   is conversational, but "tell Bill about the planet" isn't, since
      *   the latter doesn't ask Bob a question but orders Bob to talk to
      *   Bill.
      *   
      *   When the issuing actor and target actor are the same, this is
      *   irrelevant.  The purpose of this is to distinguish orders given to
      *   another character from conversational overtures directed to the
      *   other character, so if the command is coming from the player and
      *   bound for the player character, there's obviously no conversation
      *   going on.
      *   
      *   Note also that, contrary to what one might think at first glance,
      *   a command like ASK ABOUT is NOT conversational; it's a command to
      *   ask someone about something, and isn't itself a conversational
      *   overture.  The act of asking is itself a conversational overture,
      *   but the asking is the *result* of the command, not the command
      *   itself.  An action is only conversational if the action itself is
      *   a conversational overture.  So, "BOB, HELLO" is conversational;
      *   "BOB, ASK BILL ABOUT COMPUTER" is not, because it orders Bob to do
      *   something.  
      */
     isConversational(issuingActor) { return nil; }
 
     /*
      *   Get the actual verb phrase the player typed in to generate this
      *   Action, expressed in a canonical format.  The canonical format
      *   consists of the lower-case version of all of the actual text the
      *   player typed, but with each noun phrase replaced by a standard
      *   placeholder token describing the slot.  The placeholder tokens are
      *   '(dobj)' for the direct object, '(iobj)' for the indirect object,
      *   '(literal)' for a literal text phrase, and '(topic)' for a topic
      *   phrase.
      *   
      *   For example, if the player typed PUT BOOK AND PENCIL IN BOX, the
      *   canonical phrasing we return would be "put (dobj) in (iobj)".
      */
     getEnteredVerbPhrase()
     {
         local orig;
         local txt;
         
         /* 
          *   If there's an original action, let the original action do the
          *   work.  If we've been remapped, or if this is an implied
          *   action, we won't necessarily have been constructed from the
          *   actual player input, so we need to go back to the original
          *   action for this information. 
          */
         if (getOriginalAction() != self)
             return getOriginalAction.getEnteredVerbPhrase();
 
         /* start with the original token list for the predicate */
         orig = getPredicate().getOrigTokenList();
 
         /* add each token to the result text */
         for (txt = '', local i = 1, local len = orig.length() ;
              i <= len ; ++i)
         {
             local foundNp;
             
             /* add a space if this isn't the first element */
             if (i > 1)
                 txt += ' ';
 
             /*
              *   Check to see if we're entering one of the noun-phrase
              *   slots.  We are if we've reached the first token of one of
              *   the predicate noun phrases. 
              */
             foundNp = nil;
             foreach (local npProp in predicateNounPhrases)
             {
                 local match;
                 
                 /* check to see if we're at this noun phrase */
                 if ((match = self.(npProp)) != nil
                     && i == match.firstTokenIndex)
                 {
                     /* 
                      *   we're entering this noun phrase - add the generic
                      *   placeholder token for the noun phrase 
                      */
                     txt += (npProp == &dobjMatch ? '(dobj)' :
                             npProp == &iobjMatch ? '(iobj)' :
                             npProp == &topicMatch ? '(topic)' :
                             npProp == &literalMatch ? '(literal)' :
                             '(other)');
 
                     /* skip the entire run of tokens for the noun phrase */
                     i = match.lastTokenIndex;
 
                     /* note that we found a noun phrase */
                     foundNp = true;
 
                     /* stop looking for a noun phrase */
                     break;
                 }
             }
 
             /* 
              *   if we didn't find a noun phrase, this token is a literal
              *   part of the predicate grammar, so add it as-is 
              */
             if (!foundNp)
                 txt += getTokVal(orig[i]);
         }
 
         /* return the phrase, with everything converted to lower-case */
         return txt.toLower();
     }
 
     /*
      *   Get the grammar match tree object for the predicate that was used
      *   to enter this command.  By default, if we have an original action,
      *   we return the original action; otherwise we just return 'self'.
      *   
      *   Language libraries must override this to return the original match
      *   tree object if Actions are separate from predicate match trees.
      *   
      *   (The 'predicate' is simply the grammar match tree object for the
      *   entire verb phrase from the player's actual command entry text
      *   that matched this Action.  For example, in "BOB, TAKE BOX", the
      *   predicate is the match tree for the "TAKE BOX" part.  In "BOB,
      *   TAKE BOX AND GO NORTH", the predicate for the Take action is still
      *   the "TAKE BOX" part.  For "BOB, TAKE BOX AND BOOK AND GO NORTH",
      *   the predicate for the Take action is "TAKE BOX AND BOOK" - the
      *   full verb phrase includes any multiple-object lists in the
      *   original command.)  
      */
     getPredicate()
     {
         /* 
          *   By default, we just return the original Action - we assume
          *   that the language library defines Action subclasses as the
          *   actual match tree objects for predicate grammars.  Language
          *   modules must override this if they use separate object types
          *   for the predicate match tree objects.  
          */
         return getOriginalAction();
     }
 
     /*
      *   Get the noun-phrase information for my predicate grammar.  This
      *   returns a list of the match-tree properties for the noun-phrase
      *   sub-productions in our predicate grammar.  The properties
      *   generally include &dobjMatch, &iobjMatch, &literalMatch, and
      *   &topicMatch.  The order of the slots isn't important; they simply
      *   tell us which ones we should find in our predicate grammar match.
      *   
      *   The base Action is intransitive, so it doesn't have any
      *   noun-phrase slots, hence this is an empty list.  
      */
     predicateNounPhrases = []
 
     /*
      *   Get the object "role" identifier for the given index.  This
      *   returns the identifier (DirectObject, IndirectObject, etc.) for
      *   the object at the given slot index, as used in
      *   setResolvedObjects().  The first object is at index 1.  
      */
     getRoleFromIndex(idx) { return nil; }
 
     /* 
      *   Get the "other object" role - this is the complement of the given
      *   object role for a two-object action, and is used to determine the
      *   real role of the special OtherObject placeholder in a remapTo().
      *   This is only meaningful with actions of exactly two objects.  
      */
     getOtherObjectRole(role) { return nil; }
 
     /* get the resolved object in a given role */
     getObjectForRole(role) { return nil; }
 
     /* get the match tree for the noun phrase in the given role */
     getMatchForRole(role) { return nil; }
 
     /* get the 'verify' property for a given object role */
     getVerifyPropForRole(role)
     {
         return nil;
     }
 
     /* get the 'preCond' property for a given object role */
     getPreCondPropForRole(role)
     {
         return nil;
     }
 
     /* get the 'remap' property for a given object role */
     getRemapPropForRole(role)
     {
         return nil;
     }
 
     /* 
      *   Get the ResolveInfo for the given object in the current command.
      *   Since we don't have any objects at all, we'll simply return a new
      *   ResolveInfo wrapping the given object.  'cur' is the object we're
      *   looking for, and 'oldRole' is the role the object previously
      *   played in the action.  
      */
     getResolveInfo(obj, oldRole) { return new ResolveInfo(obj, 0); }
 
     /*
      *   Explicitly set the resolved objects.  This should be overridden
      *   in each subclass for the number of objects specific to the action
      *   (a simple transitive action takes one argument, an action with
      *   both a direct and indirect object takes two arguments, and so
      *   on).  The base action doesn't have any objects at all, so this
      *   takes no arguments.
      *   
      *   This method is used to set up an action to be performed
      *   programmatically, rather than based on parsed input.  Since
      *   there's no parsed input in such cases, the objects are specified
      *   directly by the programmatic caller.  
      */
     setResolvedObjects() { }
 
     /*
      *   Explicitly set the object match trees.  This sets the pre-resolved
      *   production match trees.  The arguments are given in the order of
      *   their roles in the action, using the same order that
      *   setResolvedObjects() uses.
      *   
      *   The arguments to this routine can either be match tree objects,
      *   which we'll plug into our grammar tree in the respective roles
      *   exactly as given; or they can be ResolveInfo objects giving the
      *   desired resolutions, in which case we'll build the appropriate
      *   kind of PreResolvedProd for each one.  The types can be freely
      *   mixed.  
      */
     setObjectMatches() { }
 
     /*
      *   Check that the resolved objects are all in scope.  Returns true if
      *   so, nil if not.
      *   
      *   This routine is meant for use only for actions built
      *   programmatically using setResolvedObjects().  In particular, we
      *   assume that we have only one object in each slot.  For normal
      *   parser-built actions, this check isn't necessary, because the
      *   parser only resolves objects that are in scope in the first place.
      */
     resolvedObjectsInScope()
     {
         /* we have no objects at all, so there is nothing out of scope */
         return true;
     }
 
     /*
      *   Get the list of bindings for an anaphoric pronoun - this is a
      *   pronoun that refers back to an earlier noun phrase in the same
      *   sentence, such as HIMSELF in ASK BOB ABOUT HIMSELF.  These
      *   obviously make no sense for verbs that take one (or zero)
      *   objects, since there's no earlier phrase to refer back to; these
      *   should return nil to indicate that an anaphoric pronoun is simply
      *   nonsensical in such a context.  Actions of two or more objects
      *   should return a list of objects for the preceding noun phrase.
      *   
      *   Actions of two or more objects should set this if possible to the
      *   resolved object list for the previous noun phrase, as a
      *   ResolveInfo list.
      *   
      *   The tricky part is that some actions will resolve noun phrases in
      *   a different order than they appear in the actual command grammar;
      *   similarly, it's also possible that some non-English languages use
      *   cataphoric pronouns (i.e., pronouns that refer to noun phrases
      *   that appear later in the sentence).  To allow for these cases,
      *   return an empty list here if a binding is grammatically possible
      *   but not yet available because of the resolution order, and note
      *   internally that the parser asked us for an anaphoric binding.
      *   Afterwards, the action's resolver method must go back and perform
      *   *another* resolve pass on the noun phrase production that
      *   requested the anaphor binding.
      *   
      *   'typ' is the PronounType specifier for the corresponding ordinary
      *   pronoun.  For 'himself', for example, typ will be PronounHim.  
      */
     getAnaphoricBinding(typ) { return nil; }
 
     /*
      *   Convert an object or list of objects to a ResolveInfo list 
      */
     makeResolveInfoList(val)
     {
         /* if we have a non-list collection, make it a list */
         if (dataType(val) == TypeObject && val.ofKind(Collection))
             val = val.toList();
 
         /* if it's nil or an empty list, return an empty list */
         if (val == nil || val == [])
             return [];
 
         /* see what we have */
         if (dataType(val) == TypeList)
         {
             /* it's a list - make a ResolveInfo for each item */
             return val.mapAll({x: new ResolveInfo(x, 0)});
         }
         else 
         {
             /* it's not a list - return a one-element ResolveInfo list */
             return [new ResolveInfo(val, 0)];
         }
     }
 
     /*
      *   Perform this action.  Throughout execution of the action, the
      *   global parser variables that specify the current actor and action
      *   are valid.  
      */
     doAction(issuingActor, targetActor, targetActorPhrase,
              countsAsIssuerTurn)
     {
         local oldActor;
         local oldIssuer;
         local oldAction;
         local oldResults;
 
         /* 
          *   save the current parser globals, for restoration when we
          *   finish this command - if this command is nested within
          *   another, this will let us ensure that everything is restored
          *   properly when we finish with this command 
          */
         oldActor = gActor;
         oldIssuer = gIssuingActor;
         oldAction = gAction;
         oldResults = gVerifyResults;
 
         /* 
          *   set the new globals (note that there are no verification
          *   results or command reports objects yet - these are valid only
          *   while we're running the corresponding command phases) 
          */
         gActor = targetActor;
         gIssuingActor = issuingActor;
         gAction = self;
         gVerifyResults = nil;
 
         /* 
          *   If the command is repeatable, save it for use by 'again'.
          *   Note that nested commands are never repeatable with 'again',
          *   since no one ever typed them in.
          *   
          *   "Again" is strictly for the player's use, so only save the
          *   command if this is a player turn.  This is a player command
          *   only if it is issued by the player character (which means it
          *   came from the player), and either it's directed to the player
          *   character or it counts as a turn for the player character.  
          */
         if (isRepeatable
             && parentAction == nil
             && (issuingActor.isPlayerChar()
                 && (targetActor.isPlayerChar() || countsAsIssuerTurn)))
             AgainAction.saveForAgain(issuingActor, targetActor,
                                      targetActorPhrase, self);
 
         /* make sure we restore globals on our way out */
         try
         {
             local pc;
             
             /* start a new command visually if this isn't a nested action */
             if (parentAction == nil)
                 gTranscript.addCommandSep();
 
             /* have the player character note initial conditions */
             pc = gPlayerChar;
             pc.noteConditionsBefore();
 
             /* run the before routine for the entire action */
             beforeActionMain();
 
             /* run the subclass-specific processing */
             doActionMain();
 
             /* run the after routine for the entire action */
             afterActionMain();
 
             /* 
              *   If this is a top-level action, show the command results.
              *   Don't show results for a nested action, since we want to
              *   wait and let the top-level action show the results after
              *   it has the full set of results.  
              */
             if (parentAction == nil)
             {
                 /* 
                  *   If the player character didn't change, ask the player
                  *   character to note any condition changes.  If the
                  *   player character did change to a new actor,
                  *   presumably the command will have displayed a specific
                  *   message, since this would be an unusual development
                  *   for which we can generate no generic message.  
                  */
                 if (gPlayerChar == pc)
                     pc.noteConditionsAfter();
             }
         }
         finally
         {
             /* restore the parser globals to how we found them */
             gActor = oldActor;
             gIssuingActor = oldIssuer;
             gAction = oldAction;
             gVerifyResults = oldResults;
         }
     }
 
     /*
      *   Perform processing before running the action.  This is called
      *   just once per action, even if the action will be iterated for a
      *   list of objects. 
      */
     beforeActionMain()
     {
     }
 
     /*
      *   Perform processing after running the entire action.  This is
      *   called just once per action, even if the action was iterated for
      *   a list of objects. 
      */
     afterActionMain()
     {
         /* call each registered after-action handler */
         if (afterActionMainList != nil)
         {
             foreach (local cur in afterActionMainList)
                 cur.afterActionMain();
         }
 
         /* 
          *   Mark ourselves as busy for the amount of time this action
          *   takes.  Don't count the time taken for implied actions,
          *   though, since these are meant to be zero-time sub-actions
          *   performed as part of the main action and thus don't have a
          *   separate time cost. 
          *   
          *   Note that we add our busy time in the main after-action
          *   processing because we only want to count our time cost once
          *   for the whole command, even if we're performing the command on
          *   multiple objects.  
          */
         if (!isImplicit)
         {
             local actor; 
             
             /* 
              *   If the command is conversational, the turn counts as an
              *   issuer turn; otherwise, it counts as a turn for the target
              *   actor.  Conversational commands are effectively carried
              *   out by the issuer, even though in form they're directed to
              *   another actor (as in "BOB, HELLO"), so we need to count
              *   the time they take as the issuer's time.  
              */
             actor = (isConversational(gIssuingActor)
                      ? gIssuingActor : gActor);
 
             /* add the busy time to the actor */ 
             actor.addBusyTime(self, actionTime);
         }
 
         /*
          *   If the command failed, and this is a top-level (not nested)
          *   action, check to see if the game wants to cancel remaining
          *   commands on the line.  
          */
         if (gTranscript.isFailure
             && parentAction == nil
             && gameMain.cancelCmdLineOnFailure)
         {
             /* 
              *   the command failed, and they want to cancel remaining
              *   commands on failure - throw a 'cancel command line'
              *   exception to cancel any remaining tokens 
              */
             throw new CancelCommandLineException();
         }
     }
 
     /*
      *   Register an object for afterActionMain invocation.  After we've
      *   finished with the entire action - including all iterated objects
      *   involved in the action - we'll invoke each registered object's
      *   afterActionMain() method.  This registration is only meaningful
      *   for the current action instance, and can only be set up before
      *   the action has been finished (i.e., before the current gAction
      *   invokes its own afterActionMain() method).
      *   
      *   Each object is only registered once.  If a caller attempts to
      *   register the same object repeatedly, we'll simply ignore the
      *   repeated requests.
      *   
      *   This is a convenient way to implement a collective follow-up to
      *   the parts of an iterated action.  Since repeated registrations
      *   are ignored, each handler for an iterated object (such as a
      *   "dobjFor" action() handler) can register its follow-up handler
      *   without worrying about redundant registration.  Then, when the
      *   overall action is completed for each iterated object involved,
      *   the follow-up handler will be invoked, and it can do any final
      *   work for the overall action.  For example, the follow-up handler
      *   could display a message summarizing the iterated parts of the
      *   action; or, it could even scan the transcript for particular
      *   messages and replace them with a summary.  
      */
     callAfterActionMain(obj)
     {
         /* if we don't have an after-action list yet, create one */
         if (afterActionMainList == nil)
             afterActionMainList = new Vector(8);
 
         /* if this item isn't already in the list, add it */
         if (afterActionMainList.indexOf(obj) == nil)
             afterActionMainList.append(obj);
     }
 
     /* list of methods to invoke after we've completed the action */
     afterActionMainList = nil
 
     /* 
      *   the amount of time on the game clock that the action consumes -
      *   by default, each action consumes one unit, but actions can
      *   override this to consume more or less game time 
      */
     actionTime = 1
 
     /*
      *   Zero the action time in this action and any parent actions.  This
      *   should be used when a nested replacement action is to completely
      *   take over the time-keeping responsibility for the entire turn; all
      *   containing actions in this case are to take zero time, since the
      *   nested action is the only part of the turn that will count for
      *   timing.  
      */
     zeroActionTime()
     {
         /* clear my action time */
         actionTime = 0;
 
         /* if we have a parent action, zero it and its parents */
         if (parentAction != nil)
             parentAction.zeroActionTime();
     }
 
     /*
      *   Execute the action for a single set of objects.  This runs the
      *   full execution sequence for the current set of objects.
      *   
      *   Subclasses generally won't override this method, but will instead
      *   override the methods that implement the individual steps in the
      *   execution sequence.
      */
     doActionOnce()
     {
         /*
          *   Perform the sequence of operations to execute the action.  If
          *   an ExitSignal is thrown during the sequence, skip to the
          *   end-of-turn processing.  
          */
         try
         {
             local result;
             local impReport;
 
             /*
              *   Before doing any actual execution, check the command for
              *   remapping.  If we end up doing any remapping, the
              *   remapping routine will simply replace the current command,
              *   so we the remapping call will terminate the current action
              *   with 'exit' and thus never return here.  
              */
             checkRemapping();
 
             /*
              *   If this is an implicit action, check for danger: we never
              *   try a command implicitly when the command is obviously
              *   dangerous.
              */
             if (isImplicit)
             {
                 /* 
                  *   verify the action for an implicit command, checking
                  *   for actions that aren't allowe implicitly - we never
                  *   try a command implicitly when the command is (or
                  *   should be) obviously dangerous or is simply
                  *   non-obvious 
                  */
                 result = verifyAction();
 
                 /* 
                  *   If the action can be performed, but can't be performed
                  *   implicitly, abort.  Note that we only silently ignore
                  *   the action if it is allowed normally but not
                  *   implicitly: if it's not even allowed normally, we'll
                  *   simply fail later with the normal failure message,
                  *   since there's no harm in trying.  
                  */
                 if (result != nil
                     && result.allowAction
                     && !result.allowImplicit)
                     abortImplicit;
             }
 
             /*
              *   If this is an implicit command, display a message
              *   indicating that we're performing the command.
              */
             impReport = maybeAnnounceImplicit();
 
             /*
              *   Make one or two passes through verifications and
              *   preconditions.  If any precondition performs an implicit
              *   command, we must run everything a second time to ensure
              *   that the implicit command or commands did not invalidate
              *   any earlier precondition or a verification.
              *   
              *   Run verifications before preconditions, because there
              *   would be no point in applying implicit commands from
              *   preconditions if the command verifies as illogical in the
              *   first place.  
              */
             for (local iter = 1 ; iter <= 2 ; ++iter)
             {
                 /* verify the action */
                 result = verifyAction();
                 
                 /* 
                  *   if verification doesn't allow the command to proceed,
                  *   show the reason and end the command 
                  */
                 if (result != nil && !result.allowAction)
                 {
                     /* show the result message */
                     result.showMessage();
                     
                     /* mark the command as a failure */
                     gTranscript.noteFailure();
 
                     /* 
                      *   if we have an implicit report, mark it as a mere
                      *   attempt, since the action can't be completed 
                      */
                     if (impReport != nil)
                         impReport.noteJustTrying();
                     
                     /* terminate the command */
                     exit;
                 }
 
                 /* 
                  *   Check preconditions of the action.  If we don't invoke
                  *   any implicit commands, we can stop here: nothing in
                  *   the game state will have changed, so there is no need
                  *   to re-verify or re-check preconditions.
                  *   
                  *   Only allow implicit actions on the first pass.  Do not
                  *   perform implicit actions on the second pass, because
                  *   if we did so we could get into an infinite loop of
                  *   conflicting preconditions, where each pass would
                  *   reverse the state from the last pass.  
                  */
                 if (!checkPreConditions(iter == 1))
                     break;
             }
 
             /* 
              *   Disable sense caching once we start the action phase -
              *   once we start making changes to game state, it's too much
              *   work to figure out when to invalidate the cache, so simply
              *   turn off caching entirely.
              *   
              *   Note that the sense cache will already be disabled if we
              *   executed any implied commands, because the first implied
              *   command will have disabled the cache as soon as it reached
              *   its execution phase, and no one will have turned caching
              *   back on.  It does no harm to disable it again here.  
              */
             libGlobal.disableSenseCache();
 
             /* run the before-action processing */
             beforeAction();
                 
             /* 
              *   notify the actor's containers that an action is about to
              *   take place within them 
              */
             gActor.forEachContainer(callRoomBeforeAction);
 
             /* call beforeAction for each object in the notify list */
             notifyBeforeAfter(&beforeAction);
                 
             /* 
              *   Invoke the action's execution method.  Catch any "exit
              *   action" exceptions - these indicate that the action is
              *   finished but that the rest of the command processing is to
              *   proceed as normal.  
              */
             try
             {
                 /* notify the actor of what we're about to do */
                 gActor.actorAction();
 
                 /* check the action */
                 checkAction();
                 
                 /* execute the action */
                 execAction();
             }
             catch (ExitActionSignal eaSig)
             {
                 /* 
                  *   an exit action signal was thrown - since we've now
                  *   skipped past any remaining action processing, simply
                  *   continue with the rest of the command processing as
                  *   normal 
                  */
             }
             
             /* call afterAction for each object in the notify list */
             notifyBeforeAfter(&afterAction);
             
             /* notify the actor's containers of the completed action */
             gActor.forEachContainer(callRoomAfterAction);
             
             /* run the after-action processing */
             afterAction();
         }
         catch (ExitSignal exc)
         {
             /* the execution sequence is finished - simply stop here */
         }
     }
 
     /*
      *   Reset the message generation context for a sense change.  This
      *   can be called when something substantial happens in the midst of
      *   a command, and we might need different message generation rules
      *   before and after the change.  For example, this is used when a
      *   non-player character moves from one location to another, because
      *   the NPC might want to generate leaving and arriving messages
      *   differently in the two locations.
      */
     recalcSenseContext()
     {
         /* tell the sense context capturer to recalculate the context */
         senseContext.recalcSenseContext();
     }
 
     /*
      *   Maybe announce the action as an implied action. 
      */
     maybeAnnounceImplicit()
     {
         /* 
          *   if we're a remapped action, we'll actually want to announce
          *   the original, not the remapping 
          */
         if (remappedFrom != nil)
             return remappedFrom.maybeAnnounceImplicit();
             
         /* 
          *   if we're implicit, and we have an implicit announcement
          *   message, announce the implicit action 
          */
         if (isImplicit && implicitMsg != nil)
             return gTranscript.announceImplicit(self, implicitMsg);
 
         /* we don't need to announce the implied action */
         return nil;
     }
 
     /*
      *   Announce the object of an action.  This should be used for each
      *   iteration of a command that takes objects to announce the objects
      *   on this iteration.
      *   
      *   We announce an object under several circumstances:
      *   
      *   - If we are iterating through multiple objects, we'll show the
      *   current object to show the player the individual step in the
      *   command being performed.
      *   
      *   - If 'all' was used to specify the object, we'll announce it even
      *   if only one object is involved, to make it clear to the player
      *   exactly what we chose as a match.
      *   
      *   - If we are executing the command on a single object, and the
      *   object was chosen through disambiguation of a set of ambiguous
      *   choices, and some of the discarded possibilities were logical but
      *   less so than the chosen object, we'll show the assumption we
      *   made.  In such cases, our assumption is not necessarily correct,
      *   so we'll tell the user about our choice explicitly by way of
      *   confirmation - this gives the user a better chance of noticing
      *   quickly if our assumption was incorrect.
      *   
      *   - If we supplied a default for a missing noun phrase in the
      *   player's command, we'll show what we chose.  Since the player
      *   didn't say what they meant, we'll make it plain that we're
      *   providing an assumption about what we thought they must have
      *   meant.
      *   
      *   'info' is the ResolveInfo object describing this resolved object,
      *   and 'numberInList' is the total number of objects we're iterating
      *   over for this object function (direct object, indirect object,
      *   etc).  'whichObj' is one of the object function constants
      *   (DirectObject, IndirectObject, etc) describing which object we're
      *   mentioning; the language-specific message generator might use
      *   this in conjunction with the action to include a preposition with
      *   the displayed phrase, for example, or choose an appropriate
      *   inflection.
      */
     announceActionObject(info, numberInList, whichObj)
     {
         /* 
          *   Show prefix announcements only if the player character is
          *   performing the action.  For NPC's, we don't use the prefix
          *   format, because it doesn't work as well for NPC result
          *   reports; instead, the NPC versions of the library messages
          *   tend to use sufficiently detailed reports that the prefix
          *   isn't required (for example, "Bob takes the iron key" rather
          *   than just "Taken").  
          */
         if (gActor.isPlayerChar)
         {
             /* check for the various announcements */
             if (maybeAnnounceMultiObject(info, numberInList, whichObj))
             {
                 /* we've done a multi announcement, so we're now done */
             }
             else if ((info.flags_ & UnclearDisambig) != 0)
             {
                 /* show the object, since we're not certain it's right */
                 gTranscript.announceAmbigActionObject(info.obj_, whichObj);
             }
             else if ((info.flags_ & DefaultObject) != 0
                      && !(info.flags_ & AnnouncedDefaultObject))
             {
                 /*   
                  *   Show the object, since we supplied it as a default.
                  *   At this stage in the command, we have resolved
                  *   everything.  
                  */
                 gTranscript.announceDefaultObject(
                     info.obj_, whichObj, self, true);
 
                 /* note that we've announced this object */
                 info.flags_ |= AnnouncedDefaultObject;
             }
         }
     }
 
     /* announce a multi-action object, if appropriate */
     maybeAnnounceMultiObject(info, numberInList, whichObj)
     {
         /* 
          *   announce if we have more than one object, or we're set to
          *   announce this object in any case 
          */
         if (numberInList > 1 || (info.flags_ & AlwaysAnnounce) != 0)
         {
             /* show the current object of a multi-object action */
             gTranscript.announceMultiActionObject(info.obj_, whichObj);
 
             /* tell the caller we made an announcement */
             return true;
         }
 
         /* tell the caller we didn't make an announcement */
         return nil;
     }
     
 
     /*
      *   Announce a defaulted object list, if appropriate.  We'll announce
      *   the object if we have a single object in the given resolution
      *   list, it was defaulted, and it hasn't yet been announced.
      */
     maybeAnnounceDefaultObject(lst, which, allResolved)
     {
         /* 
          *   if the list has exactly one element, and it's marked as a
          *   defaulted object, and it hasn't yet been announced, announce
          *   it 
          */
         if (lst != nil
             && lst.length() == 1
             && (lst[1].flags_ & DefaultObject) != 0
             && !(lst[1].flags_ & AnnouncedDefaultObject))
         {
             /* announce the object */
             gTranscript.announceDefaultObject(
                 lst[1].obj_, which, self, allResolved);
 
             /* 
              *   we've now announced the object; mark it as announced so
              *   we don't show the same announcement again 
              */
             lst[1].flags_ |= AnnouncedDefaultObject;
         }
     }
 
     /*
      *   "Pre-announce" a common object for a command that might involve
      *   iteration over other objects.  For example, in "put all in box",
      *   the box is common to all iterations of the command, so we would
      *   want to preannounce it, if it needs to be announced at all,
      *   before the iterations of the command.
      *   
      *   We'll announce the object only if it's marked as defaulted or
      *   unclearly disambiguated, and then only if the other list will be
      *   announcing its objects as multi-action objects.  However, we do
      *   not pre-announce anything for a remapped action, because we'll
      *   show the full action description for each individually announced
      *   object, so we don't need or want a separate announcement for the
      *   group.
      *   
      *   Returns true if we did any pre-announcing, nil otherwise.  If we
      *   return true, the caller should not re-announce this object during
      *   the iteration, since our pre-announcement is common to all
      *   iterations.  
      */
     preAnnounceActionObject(info, mainList, whichObj)
     {
         /* do not pre-announce anything for a remapped action */
         if (isRemapped())
             return nil;
 
         /* 
          *   determine if the main list will be announcing its objects -
          *   it will if it has more than one object, or if its one object
          *   is marked as "always announce" 
          */
         if (mainList.length() > 1
             || (mainList[1].flags_ & AlwaysAnnounce) != 0)
         {
             /* 
              *   we will be announcing the main list object or objects, so
              *   definitely pre-announce this object if appropriate 
              */
             announceActionObject(info, 1, whichObj);
 
             /* tell the caller we pre-announced the object */
             return true;
         }
 
         /* we didn't pre-announce the object */
         return nil;
     }
 
     /*
      *   Run our action-specific pre-processing.  By default, we do
      *   nothing here.  
      */
     beforeAction()
     {
     }
 
     /*
      *   Check the action.  This runs the 'check' phase, and must be
      *   overridden for each subclass.
      *   
      *   Intransitive actions don't generally have to do anything in the
      *   'check' phase, since they can simply do any necessary checks in
      *   the 'execute' phase that runs immediately after 'check'.  This
      *   phase is separated out from 'execute' mostly for the benefit of
      *   transitive actions, where the 'check' phase gives the involved
      *   objects a chance to object to the action.  
      */
     checkAction() { }
 
     /*
      *   Execute the action.  This must be overridden by each subclass.
      *   
      *   Intransitive actions must do all of their work in this routine.
      *   In most cases, transitive actions will delegate processing to one
      *   or more of the objects involved in the command - for example,
      *   most single-object commands will call a method in the direct
      *   object to carry out the command.  
      */
     execAction()
     {
         /* by default, just show the 'no can do' message */
         mainReport(&cannotDoThatMsg);
     }
 
     /*
      *   Run our action-specific post-processing.  By default, we do
      *   nothing here. 
      */
     afterAction()
     {
     }
 
     /*
      *   Verify the action.  Action subclasses with one or more objects
      *   should call object verification routines here.  Returns a
      *   VerifyResultList with the results, or nil if there are no
      *   verification results at all.  A nil return should be taken as
      *   success, not failure, because it means that we found no objection
      *   to the command.  
      */
     verifyAction()
     {
         /* 
          *   there are no objects in the default action, but we might have
          *   pre-condition verifiers 
          */
         return callVerifyPreCond(nil);
     }
 
     /*
      *   Initialize tentative resolutions for other noun phrases besides
      *   the one indicated. 
      */
     initTentative(issuingActor, targetActor, whichObj)
     {
         /* by default, we have no noun phrases to tentatively resolve */
     }
 
     /*
      *   Check for remapping the action.  This should check with each
      *   resolved object involved in the command to see if the object wants
      *   to remap the action to a new action; if it does, the object must
      *   replace the current action (using replaceAction or equivalent).
      *   Note that replacing the action must use 'exit' to terminate the
      *   original action, so this will never return if remapping actually
      *   does occur.  
      */
     checkRemapping()
     {
         /* by default, we have no objects, so we do nothing here */
     }
 
     /*
      *   Invoke a callback with a verify results list in gVerifyResults,
      *   using the existing results list or creating a new one if there is
      *   no existing one.  Returns the results list used. 
      */
     withVerifyResults(resultsSoFar, obj, func)
     {
         local oldResults;
 
         /* if we don't already have a result list, create one */
         if (resultsSoFar == nil)
             resultsSoFar = new VerifyResultList();
 
         /* remember the old global results list */
         oldResults = gVerifyResults;
 
         /* install the new one */
         gVerifyResults = resultsSoFar;
 
         /* make sure we restore the old result list on the way out */
         try
         {
             /* invoke the callback */
             (func)();
         }
         finally
         {
             /* restore the old result list */
             gVerifyResults = oldResults;
         }
 
         /* return the result list */
         return resultsSoFar;
     }
 
     /*
      *   Verify the non-object-related pre-conditions.  This runs
      *   verification on each of the pre-condition objects defined for the
      *   action.  
      */
     callVerifyPreCond(resultSoFar)
     {
         /* look at each of our preconditions */
         foreach (local cond in preCond)
         {
             /* 
              *   If this precondition defines a verifier, call it.  Check
              *   to see if we have a verifier defined first so that we can
              *   avoid creating a result list if we won't have any use for
              *   it. 
              */
             if (cond.propDefined(&verifyPreCondition))
             {
                 /* 
                  *   invoke the pre-condition verifier - this is an
                  *   action-level verifier, so there's no object involved 
                  */
                 resultSoFar = withVerifyResults(resultSoFar, nil,
                     {: cond.verifyPreCondition(nil) });
             }
         }
 
         /* return the latest result list */
         return resultSoFar;
     }
 
     /*
      *   Call a catch-all property on the given object.
      *   
      *   actionProp is the custom per-object/per-action property that we
      *   normally invoke to process the action.  For example, if we're
      *   processing verification for the direct object of Take, this would
      *   be &verifyDobjTake.
      *   
      *   defProp is the default property that corresponds to actionProp.
      *   This is the per-object/default-action property that we invoke
      *   when the object doesn't provide a "more specialized" version of
      *   actionProp - that is, if the object doesn't define or inherit
      *   actionProp at a point in its class hierarchy that is more
      *   specialized than the point at which it defines defProp, we'll
      *   call defProp.  If there is a more specialized definition of
      *   actionProp for the object, it effectively overrides the default
      *   handler, so we do not invoke the default handler.
      *   
      *   allProp is the catch-all property corresponding to actionProp.
      *   We invoke this property in all cases.
      *   
      *   Returns true if there is indeed a Default property that overrides
      *   the action, nil if not.  
      */
     callCatchAllProp(obj, actionProp, defProp, allProp)
     {
         /* always invoke the catch-all property first */
         obj.(allProp)();
 
         /* 
          *   invoke the default property only if the object doesn't have a
          *   "more specialized" version of actionProp 
          */
         if (!obj.propHidesProp(actionProp, defProp))
         {
             /* the Default overrides the action-specific method */
             obj.(defProp)();
 
             /* tell the caller the Default routine handled it */
             return true;
         }
         else
         {
             /* tell the caller to call the action-specific property */
             return nil;
         }
     }
 
     /*
      *   Call a verification routine.  This creates a results object and
      *   makes it active, then invokes the given verification routine on
      *   the given object.
      *   
      *   We call verification directly on the object, and we also call
      *   verification on the object's preconditions.
      *   
      *   If resultSoFar is non-nil, it is a VerifyResultList that has the
      *   results so far - this can be used for multi-object verifications
      *   to gather all of the verification results for all of the objects
      *   into a single result list.  If resultSoFar is nil, we'll create a
      *   new result list.  
      */
     callVerifyProp(obj, verProp, preCondProp, remapProp,
                    resultSoFar, whichObj)
     {
         local remapInfo;
 
         /* check for remapping */
         if ((remapInfo = obj.(remapProp)()) != nil)
             return remapVerify(whichObj, resultSoFar, remapInfo);
 
         /* initialize tentative resolutions for other noun phrases */
         initTentative(gIssuingActor, gActor, whichObj);
 
         /* 
          *   run the verifiers in the presence of a results list, and
          *   return the result list 
          */
         return withVerifyResults(resultSoFar, obj, new function()
         {
             local lst;
             
             /* 
              *   check the object for a default or catch-all verifier, and
              *   call it if present; if there isn't a default that
              *   overrides the action-specific verifier, continue to the
              *   action-specific verifer 
              */
             if (!callCatchAllProp(
                 obj, verProp, objectRelations.verifyDefaultProps[whichObj],
                 objectRelations.verifyAllProps[whichObj]))
             {
                 /* 
                  *   invoke the action-specific verifier method - this
                  *   will update the global verification results object
                  *   with the appropriate status for this action being
                  *   performed on this object 
                  */
                 obj.(verProp)();
             }
 
             /*
              *   Check the pre-conditions defined for this action on this
              *   object.  For each one that has a verifier, invoke the
              *   verifier.  
              */
             lst = getObjPreConditions(obj, preCondProp, whichObj);
             if (lst != nil)
                 foreach (local cur in lst)
                     cur.verifyPreCondition(obj);
         });
     }
 
     /*
      *   Get the precondition list for an object.  whichObj is the object
      *   role of the object whose preconditions we're retrieving; this is
      *   nil if we're looking for action-level preconditions.  
      */
     getObjPreConditions(obj, preCondProp, whichObj)
     {
         local allPreProp;
         local defPreProp;
         local pre;
 
         /* 
          *   if we're looking for action preconditions, there are no
          *   default or catch-all properties, so simply get the
          *   preconditions from the action
          */
         if (whichObj == nil)
             return obj.(preCondProp);
 
         /* get the default-action and catch-all precondition properties */
         defPreProp = objectRelations.preCondDefaultProps[whichObj];
         allPreProp = objectRelations.preCondAllProps[whichObj];
         
         /*
          *   Check for an "overriding" default-action handler.  If we have
          *   a default-action handler that hides the specific handler for
          *   this action, use the default handler's precondition list
          *   instead.  Otherwise, use the specific action preconditions.  
          */
         if (obj.propHidesProp(defPreProp, preCondProp))
             pre = obj.(defPreProp);
         else
             pre = obj.(preCondProp);
 
         /* if we have catch-all preconditions, add them to the list */
         if (obj.propDefined(allPreProp))
         {
             /* get the catch-all preconditions */
             local allPre = obj.(allPreProp);
 
             /* add them to the list so far (if any) */
             pre = (pre == nil ? allPre : pre + allPre);
         }
 
         /* return the precondition list */
         return pre;
     }
 
     /*
      *   Verify that some sort of handling for this action is defined on
      *   at least one of the given objects.  If we have no handlers at all
      *   definfed, we'll add an "illogical" status to the verification
      *   results to indicate that the action is not defined on this
      *   object.  This check provides a last resort for verbs with no
      *   handling at all defined on the involved objects, to ensure that
      *   the verb won't go completely unprocessed.
      *   
      *   Each entry in objList is an object involved in the action.  For
      *   each entry in objList, there must be *THREE* entries in propList:
      *   a verify property, a check property, and an action property.  If
      *   any of these three properties is defined on the corresponding
      *   object, we'll allow the command to proceed.  If we can find none
      *   of the given handler properties on any of our objects, we'll add
      *   an "illogical" verify result.  
      */
     verifyHandlersExist(objList, propList, result)
     {
         /* 
          *   check each object to see if any define their corresponding
          *   action property 
          */
         for (local i = 1, local j = 1, local len = objList.length() ;
              i <= len ; ++i, j += 3)
         {
             /* check each of the associated handler properties */
             for (local k = 0 ; k < 3 ; ++k)
             {
                 /* check this handler property */
                 if (objList[i].propDefined(propList[j + k]))
                 {
                     /* we've found a handler, so we can proceed */
                     return result;
                 }
             }
         }
         
         /* 
          *   None of the objects defines an appropriate action method, so
          *   this verifies as illogical.  If there's no result list so
          *   far, create one.  
          */
         if (result == nil)
             result = new VerifyResultList();
 
         /* add an "illogical" status to the results */
         result.addResult(new IllogicalVerifyResult(&cannotDoThatMsg));
 
         /* return the result */
         return result;
     }
     
     /*
      *   Call the beforeAction or afterAction method for each object in
      *   the notification list. 
      */
     notifyBeforeAfter(prop)
     {
         local lst;
 
         /* get a table of potential notification receivers */
         lst = getNotifyTable();
 
         /* go through the table and notify each object */
         lst.forEachAssoc({obj, val: obj.(prop)()});
     }
 
     /*
      *   Get the list of objects to notify before or after the action has
      *   been performed.  
      */
     getNotifyTable()
     {
         local tab;
         local curObjs;
         local actor;
 
         /* stash the current actor in a local for faster reference */
         actor = gActor;
         
         /* start with everything connected by containment to the actor */
         tab = actor.connectionTable();
 
         /* add the actor's explicitly registered notification list */
         foreach (local cur in actor.getActorNotifyList())
             tab[cur] = true;
 
         /* add the items explicitly registered in the actor's location(s) */
         actor.forEachContainer(
             {loc: loc.getRoomNotifyList().forEach(
                 {obj: tab[obj] = true})
             });
 
         /* get the list of objects directly involved in the command */
         curObjs = getCurrentObjects();
 
         /* 
          *   add any objects explicitly registered with the objects
          *   directly involved in the command 
          */
         foreach (local cur in curObjs)
         {
             /* add each item in the object's notify list */
             foreach (local ncur in cur.getObjectNotifyList())
                 tab[ncur] = true;
         }
 
         /*
          *   Remove from the list all of the actor's containers.  These
          *   will be notified via the more specific room notification
          *   methods, so we don't want to send them generic notifiers as
          *   well. 
          */
         tab.forEachAssoc(new function(obj, val)
         {
             if (actor.isIn(obj))
                 tab.removeElement(obj);
         });
 
         /* if we have any explicitly registered objects, add them */
         if (beforeAfterObjs != nil)
         {
             /* add each object in the list of explicitly registered objects */
             foreach (local cur in beforeAfterObjs)
                 tab[cur] = true;
         }
 
         /* return the final table */
         return tab;
     }
 
     /*
      *   Register an object for explicit inclusion in beforeAction and
      *   afterAction notifications.  This can be used to register objects
      *   that might not be connected by containment or otherwise
      *   notifiable by normal means.  If this is called after the
      *   beforeAction notification loop has already started, then the
      *   object will only be sent an afterAction notification.  
      */
     addBeforeAfterObj(obj)
     {
         /* if we don't yet have a before/after list, create one */
         if (beforeAfterObjs == nil)
             beforeAfterObjs = new Vector(16);
 
         /* add the object to the list */
         beforeAfterObjs.append(obj);
     }
 
     /* vector of objects requiring explicit before/after notification */
     beforeAfterObjs = nil
 
     /*
      *   Get the list of all of the objects (direct object, indirect
      *   object, and any additional objects for actions with three or more
      *   object roles) involved in the current execution.  This is valid
      *   only during a call to doActionOnce(), since that's the only time
      *   a particular set of objects are selected for the action.
      *   
      *   By default, an action has no objects roles at all, so we'll just
      *   return an empty list.  
      */
     getCurrentObjects()
     {
         return [];
     }
 
     /*
      *   Set the current objects.  This takes a list of the same form
      *   returned by getCurrentObjects(). 
      */
     setCurrentObjects(lst) { }
 
     /*
      *   Check any pre-conditions for the action.
      *   
      *   This should check all of the conditions that must be met for the
      *   action to proceed.  If any pre-condition can be met by running an
      *   implicit command first, that implicit command should be executed
      *   here.  If any pre-condition cannot be met, this routine should
      *   notify the actor and throw an ExitSignal.
      *   
      *   Returns true if any implicit commands are executed, nil if not.
      *   Implicit commands can only be attempted if allowImplicit is true;
      *   if this is nil, a precondition must simply fail (by displaying an
      *   appropriate failure report and using 'exit') without attempting
      *   an implicit command if its assertion does not hold.  
      */
     checkPreConditions(allowImplicit)
     {
         /* check each condition in our action preconditions */
         return callPreConditions(getPreCondDescList(), allowImplicit);
     }
 
     /*
      *   Get the precondition descriptor list for the action.  For the base
      *   intransitive action type, this simply returns the list of
      *   conditions for the action itself.  
      */
     getPreCondDescList()
     {
         /* get the action-level preconditions */
         return getObjPreCondDescList(self, &preCond, nil, nil);
     }
 
     /*
      *   Get a precondition descriptor list for the given object.  This
      *   returns a list of PreCondDesc objects that wrap the preconditions
      *   for the given object in the given role for this action.  
      */
     getObjPreCondDescList(obj, preCondProp, checkArg, whichObj)
     {
         local lst;
         
         /* get the list of preconditions for the given object in its role */
         lst = getObjPreConditions(obj, preCondProp, whichObj);
 
         /* if there are no preconditions, return an empty list */
         if (lst == nil)
             return [];
 
         /* 
          *   wrap the precondition objects in descriptors, so that we can
          *   keep track of the check argument that goes with these
          *   preconditions 
          */
         return lst.mapAll({x: new PreCondDesc(x, checkArg)});
     }
 
     /*
      *   Call a method on all of the precondition objects in the
      *   precondition list obtained from the given property of the given
      *   object. 
      */
     callPreConditions(lst, allowImplicit)
     {
         local ret;
         local i;
         
         /* presume we won't call any implicit commands */
         ret = nil;
 
         /* 
          *   note the original descriptor list order, so that we can retain
          *   the current ordering when the execution order doesn't require
          *   changes 
          */
         i = 0;
         lst.forEach({x: x.index_ = i++});
 
         /* sort the precondition list by execution order */
         lst = lst.sort(SortAsc, new function(a, b) {
             local delta;
             
             /* if the execution orders differ, sort by execution order */
             delta = a.cond_.preCondOrder - b.cond_.preCondOrder;
             if (delta != 0)
                 return delta;
 
             /* otherwise, retain the original list order */
             return a.index_ - b.index_;
         });
 
         /* catch any 'exit' signals within the preconditions */
         try
         {
             /* invoke the check method for each condition in the list */
             foreach (local cur in lst)
             {
                 /* 
                  *   call this precondition; if it runs an implicit
                  *   command, note that we have run an implicit command 
                  */
                 if (cur.checkPreCondition(allowImplicit))
                     ret = true;
             }
         }
         catch (ExitSignal es)
         {
             /* 
              *   any 'exit' that occurs within a precondition is a failure
              *   for the enclosing action 
              */
             gTranscript.noteFailure();
             
             /* re-throw the 'exit' to cancel the enclosing action */
             throw es;
         }
 
         /* return true if any implicit commands were executed */
         return ret;
     }
 
     /* 
      *   Our list of action-level pre-condition objects.  These are the
      *   conditions that apply to the overall action, not to the
      *   individual objects involved.  (Object-level pre-conditions are
      *   attached to the objects, not to the action.)  
      */
     preCond = []
 
     /*
      *   Finish the result list for a resolved noun phrase.  This is used
      *   just before disambiguation.  We'll give each object in the list a
      *   chance to filter the list with filterResolveList, and we'll note
      *   the noun phrase we matched in each resolved object.  
      */
     finishResolveList(lst, whichObj, np, requiredNum)
     {
         /* give each object a chance to filter the list */
         foreach (local cur in lst)
             lst = cur.obj_.filterResolveList(
                 lst, self, whichObj, np, requiredNum);
 
         /* stash the noun phrase in each object in the list */
         foreach (local cur in lst)
             cur.np_ = np;
 
         /* return the list */
         return lst;
     }
 
     /*
      *   Get a list of verification results for the given ResolveInfo
      *   objects, sorted from best to worst.  Each entry in the returned
      *   list will be a VerifyResultList object whose obj_ property is set
      *   to the ResolveInfo object for which it was generated.  
      */
     getSortedVerifyResults(lst, verProp, preCondProp, remapProp,
                            whichObj, np, requiredNum)
     {
         local results;
         local idx;
 
         /* if there's nothing in the list, we're done */
         if (lst == [])
             return lst;
 
         /*
          *   Before we run verification, give each object in the list a
          *   chance to do its own filtering on the entire list.  This form
          *   of filtering allows an object to act globally on the list, so
          *   that it can take special action according to the presence or
          *   absence of other objects, and can affect the presence of other
          *   objects.  
          */
         lst = finishResolveList(lst, whichObj, np, requiredNum);
 
         /* create a vector to hold the verification results */        
         results = new Vector(lst.length());
 
         /*
          *   Call the given verifier method on each object, noting each
          *   result.
          */
         idx = 0;
         foreach (local cur in lst)
         {
             local curResult;
 
             /* call the verifier method and note the current result */
             curResult = callVerifyProp(cur.obj_, verProp, preCondProp,
                                        remapProp, nil, whichObj);
 
             /* 
              *   save the ResolveInfo in the verify result list object, so
              *   that we can figure out later (after sorting the results)
              *   which original ResolveInfo this verification result
              *   applies to 
              */
             curResult.obj_ = cur;
 
             /* remember the original list order */
             curResult.origOrder = idx++;
 
             /* add this verify result to our result vector */
             results.append(curResult);
         }
 
         /* 
          *   Sort the results in descending order of logicalness, and
          *   return the sorted list.  When results are equivalently
          *   logical, keep the results in their existing order.  
          */
         return results.toList().sort(SortDesc, new function(x, y)
         {
             /* compare the logicalness */
             local c = x.compareTo(y);
 
             /* 
              *   if it's the same, keep in ascending pluralOrder - note
              *   that we must reverse the sense of this comparison, since
              *   we're sorting the overall list in descending order
              */
             if (c == 0)
                 c = (y.obj_.obj_.pluralOrder - x.obj_.obj_.pluralOrder);
 
             /* if they're otherwise the same, preserve the original order */
             if (c == 0)
                 c = (y.origOrder - x.origOrder);
 
             /* return the result */
             return c;
         });
     }
 
     /*
      *   Combine any remapped verify results in the given verify result
      *   list.  We will remove any result that was remapped to a different
      *   object if and only if the target of the remapping appears in the
      *   list and has the same results as the remapped original.  When
      *   objects are remapped in this fashion, they become effectively
      *   equivalent for the purposes of this command, so we don't have to
      *   distinguish between them for disambiguation or defaulting
      *   purposes.  
      */
     combineRemappedVerifyResults(lst, role)
     {
         /* scan each element in the list */
         for (local i = 1, local len = lst.length() ; i <= len ; ++i)
         {
             local cur = lst[i];
             
             /* if this element has been remapped, consider it further */
             if (cur.remapTarget_ != nil)
             {
                 local other;
                 
                 /* 
                  *   scan for another entry in the list that matches us
                  *   for remapping purposes 
                  */
                 other = lst.indexWhich(
                     {x: x != cur
                         && cur.matchForCombineRemapped(x, self, role)});
 
                 /* 
                  *   if we found another entry, delete the other entry,
                  *   since it is indistinguishable from this entry
                  */
                 if (other != nil)
                 {
                     /* remove this element from the list */
                     lst -= cur;
 
                     /* adjust our list counters for the deletion */
                     --i;
                     --len;
                 }
             }
         }
 
         /* return the updated list */
         return lst;
     }
 
     /*
      *   Filter an ambiguous object list using the given verification
      *   method.  We call the given verification method on each object,
      *   noting the result, then find the best (most logical) result in
      *   the list.  We reduce the set to the objects that all have the
      *   same best value - everything else in the list is less logical, so
      *   we discard it.  This gives us a set of objects that are all of
      *   equivalent likelihood and all of the best likelihood of all the
      *   objects.
      *   
      *   This is the typical way that we disambiguate a list of objects,
      *   but this is merely a service routine, so individual actions can
      *   choose to use this or other mechanisms as appropriate.  
      */
     filterAmbiguousWithVerify(lst, requiredNum, verProp,
                               preCondProp, remapProp, whichObj, np)
     {
         local results;
         local discards;
         local keepers;
         local bestResult;
         local uniqueKeepers;
 
         /* if there's nothing in the list, there's nothing to do */
         if (lst == [])
             return lst;
 
         /* first, filter out redundant facets */
         lst = filterFacets(lst);
         
         /* 
          *   Call the verifier method on each object, and sort the results
          *   from best to worst.  
          */
         results = getSortedVerifyResults(lst, verProp, preCondProp,
                                          remapProp, whichObj,
                                          np, requiredNum);
 
         /* note the best result value */
         bestResult = results[1];
 
         /* 
          *   ask the noun phrase to filter this list down to the ones it
          *   wants to keep 
          */
         keepers = np.getVerifyKeepers(results);
 
         /*
          *   Count the number of unique keepers - this is the number of
          *   items in the keepers list that don't have any equivalents in
          *   the keepers list.
          *   
          *   To calculate this number, start with the total number of
          *   items in the list, and reduce it by one for each item with an
          *   earlier equivalent in the list.  Note that we only ignore
          *   items with unique equivalents *earlier* in the list so that
          *   we keep exactly one of each equivalent - if we ignored every
          *   element with a unique equivalent elsewhere in the list, we'd
          *   ignore every equivalent item, so we'd only count the items
          *   with no equivalents at all.  
          */
         uniqueKeepers = keepers.length();
         for (local i = 1, local cnt = keepers.length() ; i <= cnt ; ++i)
         {
             local eqIdx;
             
             /* check for a unique equivalent earlier in the list */
             eqIdx = keepers.indexWhich(
                 {x: x.obj_.obj_.isVocabEquivalent(keepers[i].obj_.obj_)});
             if (eqIdx != nil && eqIdx < i)
             {
                 /* 
                  *   this one has an earlier equivalent, so don't include
                  *   it in the unique item count 
                  */
                 --uniqueKeepers;
             }
         }
 
         /*
          *   If we found more items to keep than were required by the
          *   caller, we were not able to reduce the set to an unambiguous
          *   subset.  In this case, keep *all* of the items that are
          *   logical. 
          */
         if (uniqueKeepers > requiredNum)
         {
             local allAllowed;
             
             /* filter so that we keep all of the logical results */
             allAllowed = results.subset({x: x.allowAction});
 
             /* if that list is non-empty, use it as the keepers */
             if (allAllowed.length() != 0)
                 keepers = allAllowed;
         }
 
         /* 
          *   Get the list of discards - this is the balance of the
          *   original result list after removing the best ones.  
          */
         discards = results - keepers;
 
         /* 
          *   We now have the set of objects we want, but the entries in
          *   the list are all VerifyResultList instances, and we just want
          *   the objects - pull out a list of just the ResolveInfo, and
          *   return that.  
          */
         keepers = keepers.mapAll({x: x.obj_});
 
         /*
          *   Check to see if we should set flags in the results.  If we
          *   eliminated any objects, flag the remaining objects as having
          *   been selected through disambiguation.  If the best of the
          *   discarded objects were logical, flag the survivors as
          *   "unclear," because we only selected them as better than the
          *   discards, and not because they were the only possible
          *   choices.  
          */
         if (discards != [])
         {
             local addedFlags;
             
             /* 
              *   We have reduced the set.  If the best of the discards was
              *   ranked as highly as the survivors at the coarsest level,
              *   flag the survivors as having been "unclearly"
              *   disambiguated; otherwise, mark them as "clearly"
              *   disambiguated.  
              */
             if (keepers.indexOf(bestResult.obj_) != nil
                 && (discards[1].getEffectiveResult().resultRank ==
                     bestResult.getEffectiveResult().resultRank))
             {
                 /* 
                  *   we had to make a choice that discarded possibilities
                  *   that were valid, though not as good as the one we
                  *   chose - mark the objects as being not perfectly clear 
                  */
                 addedFlags = UnclearDisambig;
 
                 /* 
                  *   if the keepers and the rejects are all basic
                  *   equivalents, don't bother flagging this as unclear,
                  *   since there's no point in mentioning that we chose
                  *   one basic equivalent over another, as they all have
                  *   the same name 
                  */
                 if (BasicResolveResults.filterWithDistinguisher(
                     keepers + discards.mapAll({x: x.obj_}),
                     basicDistinguisher).length() == 1)
                 {
                     /* they're all basic equivalents - mark as clear */
                     addedFlags = ClearDisambig;
                 }
             }
             else
             {
                 /* the choice is clear */
                 addedFlags = ClearDisambig;
             }
 
             /* add the flags to each survivor */
             foreach (local cur in keepers)
                 cur.flags_ |= addedFlags;
         }
 
         /* return the results */
         return keepers;
     }
 
     /*
      *   Filter a plural list with a verification method.  We'll reduce
      *   the list to the subset of objects that verify as logical, if
      *   there are any.  If there are no logical objects in the list,
      *   we'll simply return the entire original list.  
      */
     filterPluralWithVerify(lst, verProp, preCondProp, remapProp, whichObj, np)
     {
         local results;
 
         /* if there's nothing in the list, there's nothing to do */
         if (lst == [])
             return lst;
 
         /* first, filter out redundant facets */
         lst = filterFacets(lst);
         
         /* 
          *   Call the verifier method on each object, and sort the results
          *   from best to worst.  
          */
         results = getSortedVerifyResults(lst, verProp, preCondProp,
                                          remapProp, whichObj, np, nil);
 
         /*
          *   If the best (and thus first) result allows the action, filter
          *   the list to keep only the "keepers," as determined by the noun
          *   phrase.  Otherwise, there are no allowed results, so return
          *   the original list.  
          */
         if (results[1].allowAction)
             results = np.getVerifyKeepers(results);
 
         /* return the resolve results objects from the list */
         return results.mapAll({x: x.obj_});
     }
 
     /*
      *   Filter out redundant facets of the same object.  The various
      *   facets of an object are equivalent to the parser.  An object that
      *   has multiple facets is meant to appear to be one game world
      *   object from the perspective of a character - the multiple facet
      *   objects are an internal implementation detail. 
      */
     filterFacets(lst)
     {
         local result;
         local actor = gActor;
 
         /* 
          *   create a vector for the result list, presuming we'll keep all
          *   of the original list elements 
          */
         result = new Vector(lst.length(), lst);
 
         /* check for facets */
         foreach (local cur in lst)
         {
             local allFacets;
 
             /* if this item has any facets, check for them in our results */
             allFacets = cur.obj_.getFacets() + cur.obj_;
             if (allFacets.length() != 0)
             {
                 local inScopeFacets;
                 local best;
                 
                 /* make a new list of the facets that appear in the results */
                 inScopeFacets = allFacets.subset(
                     {f: result.indexWhich({r: r.obj_ == f}) != nil});
 
                 /* pick the best of those facets */
                 best = findBestFacet(actor, inScopeFacets);
                 
                 /* 
                  *   remove all of the facets besides the best one from the
                  *   result list - this will ensure that we have only the
                  *   single best facet in the final results 
                  */
                 foreach (local r in result)
                 {
                     /* 
                      *   if this result list item is in the facet list, and
                      *   it's not the best facet, delete it from the result
                      *   list 
                      */
                     if (r.obj_ != best
                         && inScopeFacets.indexOf(r.obj_) != nil)
                         result.removeElement(r);
                 }
 
             }
         }
 
         /* return the result list */
         return result.toList();
     }
 
     /*
      *   Get a default object using the given verification method.  We'll
      *   start with the 'all' list, then use the verification method to
      *   reduce the list to the most likely candidates.  If we find a
      *   unique most likely candidate, we'll return a ResolveInfo list
      *   with that result; otherwise, we'll return nothing, since there is
      *   no suitable default.  
      */
     getDefaultWithVerify(resolver, verProp, preCondProp, remapProp,
                          whichObj, np)
     {
         local lst;
         local results;
         local bestResult;
         
         /* 
          *   Start with the 'all' list for this noun phrase.  This is the
          *   set of every object that we consider obviously suitable for
          *   the command, so it's a good starting point to guess at a
          *   default object.
          */
         lst = resolver.getAllDefaults();
 
         /* eliminate objects that can't be defaults for this action */
         lst = lst.subset({x: !x.obj_.hideFromDefault(self)});
 
         /* 
          *   reduce equivalent items to a single instance of each
          *   equivalent - if we have several equivalent items that are
          *   equally good as defaults, we can pick just one 
          */
         lst = resolver.filterAmbiguousEquivalents(lst, np);
 
         /* if we have no entries in the list, there is no default */
         if (lst == [])
             return nil;
 
         /* 
          *   get the verification results for these objects, sorted from
          *   best to worst 
          */
         results = getSortedVerifyResults(lst, verProp, preCondProp,
                                          remapProp, whichObj, np, nil);
 
         /* eliminate redundant remapped objects */
         results = combineRemappedVerifyResults(results, whichObj);
 
         /* note the best result */
         bestResult = results[1];
 
         /* 
          *   if the best item is not allowed as an implied object, there
          *   is no default 
          */
         if (!bestResult.allowImplicit)
             return nil;
 
         /* 
          *   The best item must be uniquely logical in order to be a
          *   default - if more than one item is equally good, it makes no
          *   sense to assume anything about what the user meant.  So, if
          *   we have more than one item, and the second item is equally as
          *   logical as the first item, we cannot supply a default.  (The
          *   second item cannot be better than the first item, because of
          *   the sorting - at most, it can be equally good.)  
          */
         if (results.length() != 1 && bestResult.compareTo(results[2]) == 0)
             return nil;
 
         /* 
          *   We have a uniquely logical item, so we can assume the user
          *   must have been referring to this item.  Return the
          *   ResolveInfo for this item (which the verify result sorter
          *   stashed in the obj_ property of the verify result object).
          *   
          *   Before returning the list, clear the 'all' flag in the result
          *   (getAll() set that flag), and replace it with the 'default'
          *   flag, since the object is an implied default.  
          */
         bestResult.obj_.flags_ &= ~MatchedAll;
         bestResult.obj_.flags_ |= DefaultObject;
 
         /* return the result list */
         return [bestResult.obj_];
     }
 
     /* 
      *   List of objects that verified okay on a prior pass.  This is a
      *   scratch-pad for use by verifier routines, to keep track of work
      *   they've already done.  A few verifiers use this as a way to
      *   detect when an implicit action actually finished the entire job,
      *   which would in many cases result in a verify failure if not
      *   checked (because a command that effects conditions that already
      *   hold is normally illogical); by tracking that the verification
      *   previously succeeded, the verifier can know that the action
      *   should be allowed to proceed and do nothing.  
      */
     verifiedOkay = []
 ;
 
 /*
  *   Call the roomBeforeAction method on a given room's containing rooms,
  *   then on the room itself.  
  */
 callRoomBeforeAction(room)
 {
     /* first, call roomBeforeAction on the room's containers */
     room.forEachContainer(callRoomBeforeAction);
 
     /* call roomBeforeAction on this room */
     room.roomBeforeAction();
 }
 
 /*
  *   Call the roomAfterAction method on a given room, then on the room's
  *   containing rooms.  
  */
 callRoomAfterAction(room)
 {
     /* first, call roomAfterAction on this room */
     room.roomAfterAction();
 
     /* next, call roomAfterAction on the room's containers */
     room.forEachContainer(callRoomAfterAction);
 }
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Intransitive Action class - this is an action that takes no objects.
  *   In general, each subclass should implement its action handling in its
  *   execAction() method.  
  */
 class IAction: Action
     /* 
      *   resolve my noun phrases to objects 
      */
     resolveNouns(issuingActor, targetActor, results)
     {
         /* 
          *   We have no objects to resolve.  The only thing we have to do
          *   is note in the results our number of structural noun slots
          *   for the verb, which is zero, since we have no objects at all. 
          */
         results.noteNounSlots(0);
     }
 
     /*
      *   Execute the action.  
      */
     doActionMain()
     {
         /* 
          *   we have no objects to iterate, so simply run through the
          *   execution sequence once 
          */
         doActionOnce();
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Transitive Action class - this is an action that takes a direct
  *   object.
  *   
  *   For simplicity, this object is its own object resolver - we really
  *   don't need a separate resolver object because we have only one object
  *   list for this verb.  (In contrast, an action with both a direct and
  *   indirect object might need separate resolution rules for the two
  *   objects, and hence would need separate resolver objects for the two.)
  *   
  *   The advantage of implementing the Resolver behavior in this object,
  *   rather than using a separate object, is that it's less trouble to
  *   override object resolution rules - simply override the resolver
  *   methods in the subclass where you define the grammar rule for the
  *   action.  
  */
 class TAction: Action, Resolver
     construct()
     {
         /* inherit only the Action constructor */
         inherited Action.construct();
     }
 
     resetAction()
     {
         /* inherit default handling */
         inherited();
         
         /* discard our cached resolver */
         dobjResolver_ = nil;
     }
     
     /*
      *   Create an action for retrying an original action with changes. 
      */
     createForRetry(orig)
     {
         local action;
         
         /* create the new action based on the original action */
         action = createActionFrom(orig);
 
         /* 
          *   do not include the new command in undo, since it's merely
          *   part of the enclosing explicit command 
          */
         action.includeInUndo = nil;
 
         /* mark the action as nested */
         action.setNested();
 
         /*
          *   Even if the original action is implicit, don't announce this
          *   action as implicit, because it's good enough to have
          *   announced the original implicit action.  Now, this new
          *   command actually still is implicit if the original was - we
          *   simply don't want to announce it as such.  To suppress the
          *   extra announcement while still retaining the rest of the
          *   desired implicitness, simply set the implicitMsg property of
          *   the new action to nil; when there's no implicitMsg, there's
          *   no announcement. 
          */
         action.implicitMsg = nil;
 
         /* return the new action */
         return action;
     }
 
     /*
      *   Retry an intransitive action as a single-object action.  We'll
      *   obtain a indirect object using the normal means (first looking
      *   for a default, then prompting the player if we can't find a
      *   suitable default).  'orig' is the original zero-object action.
      *   
      *   This routine terminates with 'exit' if it doesn't throw some
      *   other error.  
      */
     retryWithMissingDobj(orig, asker)
     {
         /* resolve and execute the replacement action */
         resolveAndReplaceAction(createForMissingDobj(orig, asker));
     }
 
     /*
      *   Retry an action as a single-object action with an ambiguous
      *   direct object.  We'll ask which of the given possible objects is
      *   intended.  
      */
     retryWithAmbiguousDobj(orig, objs, asker, objPhrase)
     {
         local action;
         local resolver;
         
         /* create a missing-direct-object replacement action */
         action = createForMissingDobj(orig, asker);
 
         /* reduce the object list to the objects in scope */
         resolver = action.getDobjResolver(gIssuingActor, gActor, true);
         objs = objs.subset({x: resolver.objInScope(x)});
 
         /* plug in the ambiguous direct object list */
         action.dobjMatch = new PreResolvedAmbigProd(objs, asker, objPhrase);
         
         /* resolve and execute the replacement action */
         resolveAndReplaceAction(action);
     }
 
     /*
      *   Test to see if askForDobj() would find a default direct object.
      *   Returns true if there's a default, nil if not.  If this returns
      *   true, then askForDobj() will simply take the default and proceed;
      *   otherwise, it will have to actually ask the user for the missing
      *   information.  
      */
     testRetryDefaultDobj(orig)
     {
         local action;
         local def;
         
         /* create the new action for checking for a direct object */
         action = createForMissingDobj(orig, ResolveAsker);
 
         /* get the default dobj */
         def = action.getDefaultDobj(
             action.dobjMatch,
             action.getDobjResolver(gIssuingActor, gActor, nil));
 
         /* if there's exactly one result, then we have a default */
         return (def != nil && def.length() == 1);
     }
 
     /*
      *   Create an instance of this action for retrying a given
      *   single-object action with a missing direct object.  
      */
     createForMissingDobj(orig, asker)
     {
         local action;
 
         /* create the action for a retry */
         action = createForRetry(orig);
 
         /* use an empty noun phrase for the new action's direct object */
         action.dobjMatch = new EmptyNounPhraseProd();
 
         /* set our custom response production and ResolveAsker */
         action.dobjMatch.setPrompt(action.askDobjResponseProd, asker);
 
         /* initialize the new action with any pre-resolved parts */
         action.initForMissingDobj(orig);
 
         /* return the new action */
         return action;
     }
 
     /*
      *   Initialize this action in preparation for retrying with a missing
      *   direct object.  This routine must copy any phrases from the
      *   original action that have already been resolved.  This base
      *   TAction implementation obviously can't have anything pre-resolved
      *   in the original, since the original must simply be an IAction.
      *   Subclasses must override as appropriate for the kinds of base
      *   actions from which they can be retried.  
      */
     initForMissingDobj(orig) { }
 
     /*
      *   The root production to use to parse missing direct object
      *   responses.  By default, this is nounList, but individual actions
      *   can override this as appropriate.
      *   
      *   Note that language modules might want to override this to allow
      *   for special responses.  For example, in English, some verbs might
      *   want to override this with a specialized production that allows
      *   the appropriate preposition in the response.  
      */
     askDobjResponseProd = nounList
 
     /*
      *   Resolve objects.  This is called at the start of command
      *   execution to resolve noun phrases in the command to specific
      *   objects.  
      */
     resolveNouns(issuingActor, targetActor, results)
     {
         /* note that we have a single noun slot (the direct object) */
         results.noteNounSlots(1);
 
         /* 
          *   Ask the direct object noun phrase list to resolve itself, and
          *   store the resulting object list.  Since we're starting a
          *   resolution pass through our objects, reset the resolver if
          *   we're reusing it.  
          */
         dobjList_ = dobjMatch.resolveNouns(
             getDobjResolver(issuingActor, targetActor, true), results);
     }
 
     /* a transitive action has one noun phrase: the direct object */
     predicateNounPhrases = [&dobjMatch]
 
     /* get the role of an object */
     getRoleFromIndex(idx)
     {
         /* we only take a single object role - the direct object */
         return (idx == 1 ? DirectObject : inherited(idx));
     }
 
     /* get the resolved object in a given role */
     getObjectForRole(role)
     {
         /* return the direct object if requested */
         return (role == DirectObject ? getDobj() : inherited(role));
     }
 
     /* get the match tree for the noun phrase in the given role */
     getMatchForRole(role)
     {
         /* return the direct object match tree if requested */
         return (role == DirectObject ? dobjMatch : inherited(role));
     }
 
     /* get the 'verify' property for a given object role */
     getVerifyPropForRole(role)
     {
         return (role == DirectObject ? verDobjProp : inherited(role));
     }
 
     /* get the 'preCond' property for a given object role */
     getPreCondPropForRole(role)
     {
         return (role == DirectObject ? preCondDobjProp : inherited(role));
     }
 
     /* get the 'remap' property for a given object role */
     getRemapPropForRole(role)
     {
         return (role == DirectObject ? remapDobjProp : inherited(role));
     }
 
     /* get the ResolveInfo for the given object */
     getResolveInfo(obj, oldRole)
     {
         local info;
         
         /* scan our resolved direct object list for the given object */
         if (dobjList_ != nil)
             info = dobjList_.valWhich({x: x.obj_ == obj});
 
         /* if we didn't find one, create one from scratch */
         if (info == nil)
         {
             /* get the flags for the first object in the old role */
             local flags = (dobjList_.length() > 0 ? dobjList_[1].flags_ : 0);
 
             /* create a ResolveInfo to represent the object */
             info = new ResolveInfo(obj, flags);
         }
 
         /* return what we found (or created) */
         return info;
     }
 
     /* get the list of resolved direct objects */
     getResolvedDobjList()
     {
         /* 
          *   if we have a direct object list, return the objects from it;
          *   otherwise, return an empty list 
          */
         return (dobjList_ == nil ? nil : dobjList_.mapAll({x: x.obj_}));
     }
 
     /* manually set the resolved objects - we'll set the direct object */
     setResolvedObjects(dobj)
     {
         /* 
          *   set the direct object tree to a fake grammar tree that
          *   resolves to our single direct object, in case we're asked to
          *   resolve explicitly 
          */
         dobjMatch = new PreResolvedProd(dobj);
 
         /* 
          *   Set the resolved direct object list to the single object or
          *   to the list of objects, depending on what we received.
          */
         dobjList_ = makeResolveInfoList(dobj);
     }
 
     /* manually set the unresolved object noun phrase match trees */
     setObjectMatches(dobj)
     {
         /* if it's a ResolveInfo, wrap it in a pre-resolved production */
         if (dobj.ofKind(ResolveInfo))
             dobj = new PreResolvedProd(dobj);
 
         /* note the new direct object match tree */
         dobjMatch = dobj;
     }
 
     /* check that the resolved objects are in scope */
     resolvedObjectsInScope()
     {
         /* check the direct object */
         return getDobjResolver(gIssuingActor, gActor, true)
             .objInScope(dobjList_[1].obj_);
     }
 
     /*
      *   Get a message parameter object for the action.  We define 'dobj'
      *   as the direct object, in addition to any inherited targets.  
      */
     getMessageParam(objName)
     {
         switch(objName)
         {
         case 'dobj':
             /* return the current direct object */
             return dobjCur_;
 
         default:
             /* inherit default handling */
             return inherited(objName);
         }
     }
 
     /*
      *   Execute the action.  We'll run through the execution sequence
      *   once for each resolved direct object.  
      */
     doActionMain()
     {
         /* 
          *   Set the direct object list as the antecedent, using the
          *   language-specific pronoun setter.  Don't set pronouns for a
          *   nested command, because the player didn't necessarily refer to
          *   the objects in a nested command.  
          */
         if (parentAction == nil)
             gActor.setPronoun(dobjList_);
 
         /* we haven't yet canceled the iteration */
         iterationCanceled = nil;
 
         /* run through the sequence once for each direct object */
         for (local i = 1, local len = dobjList_.length() ;
              i <= len && !iterationCanceled ; ++i)
         {
             /* make this object our current direct object */
             dobjCur_ = dobjList_[i].obj_;
             dobjInfoCur_ = dobjList_[i];
 
             /* announce the object if appropriate */
             announceActionObject(dobjList_[i], len, whichMessageObject);
 
             /* run the execution sequence for the current direct object */
             doActionOnce();
 
             /* if we're top-level, count the iteration in the transcript */
             if (parentAction == nil)
                 gTranscript.newIter();
         }
     }
 
     /* get the precondition descriptor list for the action */
     getPreCondDescList()
     {
         /* 
          *   return the inherited preconditions plus the conditions that
          *   apply to the direct object 
          */
         return inherited()
             + getObjPreCondDescList(dobjCur_, preCondDobjProp, dobjCur_,
                                     DirectObject);
     }
         
     /*
      *   Get the list of active objects.  We have only a direct object, so
      *   we'll return a list with the current direct object. 
      */
     getCurrentObjects()
     {
         return [dobjCur_];
     }
 
     /* set the current objects */
     setCurrentObjects(lst)
     {
         dobjCur_ = lst[1];
         dobjInfoCur_ = nil;
     }
 
     /*
      *   Verify the action.
      */
     verifyAction()
     {
         local result;
         
         /* invoke any general (non-object) pre-condition verifiers */
         result = callVerifyPreCond(nil);
 
         /* 
          *   invoke the verifier routine ("verXxx") on the current direct
          *   object and return the result
          */
         result = callVerifyProp(dobjCur_, verDobjProp, preCondDobjProp,
                                 remapDobjProp, result, DirectObject);
 
         /* verify that the action routine ("doXxx") exists on the object */
         return verifyHandlersExist(
             [dobjCur_], [verDobjProp, actionDobjProp, checkDobjProp],
             result);
     }
 
     /* initialize tentative resolutions for other noun phrases */
     initTentative(issuingActor, targetActor, whichObj)
     {
         /* 
          *   we have only one noun phrase, so there's nothing else to
          *   tentatively resolve 
          */
     }
 
     /*
      *   Check for remapping 
      */
     checkRemapping()
     {
         local remapInfo;
         
         /* check for remapping in the direct object */
         if ((remapInfo = dobjCur_.(remapDobjProp)) != nil)
         {
             /* 
              *   we have a remapping, so apply it - note that this won't
              *   return, since this will completely replace the command
              *   and thus terminate the old command with 'exit' 
              */
             remapAction(nil, DirectObject, remapInfo);
         }
     }
 
     /*
      *   Check the command.
      *   
      *   For a single-object transitive action, this runs the catch-all
      *   'check' properties (the dobjFor(Default) and dobjFor(All) 'check'
      *   methods) on the direct object, then calls the individual 'check'
      *   routine for this specific action.  
      */
     checkAction()
     {
         try
         {
             /* call the catch-all 'check' properties */
             if (!callCatchAllProp(dobjCur_, checkDobjProp,
                                   &checkDobjDefault, &checkDobjAll))
             {
                 /* 
                  *   the action-specific check routine overrides any
                  *   Default catch-all, so call the direct object's check
                  *   routine (its "checkXxx") method 
                  */
                 dobjCur_.(checkDobjProp)();
             }
         }
         catch (ExitSignal es)
         {
             /* mark the action as a failure in the transcript */
             gTranscript.noteFailure();
 
             /* re-throw the signal */
             throw es;
         }
     }
 
     /*
      *   Execute the command. 
      */
     execAction()
     {
         /* call the catch-all 'action' properties */
         if (!callCatchAllProp(dobjCur_, actionDobjProp,
                               &actionDobjDefault, &actionDobjAll))
         {
             /* 
              *   the verb-specific 'action' handler overrides any Default
              *   action handler, so call the verb-specific 'action'
              *   routine in the direct object (the "doXxx" method) 
              */
             dobjCur_.(actionDobjProp)();
         }
     }
 
     /*
      *   The direct object preconditions, verifier, remapper, check, and
      *   action methods for this action.  Each concrete action must define
      *   these appropriately.  By convention, the methods are named like
      *   so:
      *   
      *.  preconditions: preCondDobjAction
      *.  verifier: verDobjAction
      *.  remap: remapDobjAction
      *.  check: checkDobjAction
      *.  action: actionDobjAction
      *   
      *   where the 'Action' suffix is replaced by the name of the action.
      *   The DefineTAction macro applies this convention, so in most cases
      *   library and game authors will never have to create all of those
      *   property names manually.  
      */
     verDobjProp = nil
     preCondDobjProp = nil
     remapDobjProp = nil
     checkDobjProp = nil
     actionDobjProp = nil
 
     /*
      *   Get my direct object resolver.  If I don't already have one,
      *   create one and cache it; if I've already cached one, return it.
      *   Note that we cache the resolver because it can sometimes take a
      *   bit of work to set one up (the scope list can in some cases be
      *   complicated to calculate).  We use the resolver only during the
      *   object resolution phase; since game state can't change during
      *   this phase, it's safe to keep a cached copy.  
      */
     getDobjResolver(issuingActor, targetActor, reset)
     {
         /* create a new resolver if we don't already have one cached */
         if (dobjResolver_ == nil)
             dobjResolver_ = createDobjResolver(issuingActor, targetActor);
 
         /* reset the resolver if desired */
         if (reset)
             dobjResolver_.resetResolver();
 
         /* return it */
         return dobjResolver_;
     }
 
     /*
      *   Create a resolver for the direct object.  By default, we are our
      *   own resolver.  Some actions might want to override this to create
      *   and return a specialized resolver instance if special resolution
      *   rules are needed.  
      */
     createDobjResolver(issuingActor, targetActor)
     {
         /* initialize myself as a resolver */
         initResolver(issuingActor, targetActor);
 
         /* return myself */
         return self;
     }
 
     /*
      *   Does this action allow "all" to be used in noun phrases?  By
      *   default, we allow it or not according to a gameMain property.
      *   
      *   Note that the inventory management verbs (TAKE, TAKE FROM, DROP,
      *   PUT IN, PUT ON) override this to allow "all" to be used, so
      *   disallowing "all" here (or via gameMain) won't disable "all" for
      *   those verbs.  
      */
     actionAllowsAll = (gameMain.allVerbsAllowAll)
 
     /*
      *   Resolve 'all' for the direct object, given a list of everything
      *   in scope.  By default, we'll simply return everything in scope;
      *   some actions might want to override this to return a more
      *   specific list of objects suitable for 'all'.  
      */
     getAllDobj(actor, scopeList)
     {
         return scopeList;
     }
 
     /* filter an ambiguous direct object noun phrase */
     filterAmbiguousDobj(lst, requiredNum, np)
     {
         /* filter using the direct object verifier method */
         return filterAmbiguousWithVerify(lst, requiredNum, verDobjProp,
                                          preCondDobjProp, remapDobjProp,
                                          DirectObject, np);
     }
 
     /* filter a plural phrase */
     filterPluralDobj(lst, np)
     {
         /* filter using the direct object verifier method */
         return filterPluralWithVerify(lst, verDobjProp, preCondDobjProp,
                                       remapDobjProp, DirectObject, np);
     }
 
     /* get the default direct object */
     getDefaultDobj(np, resolver)
     {
         /* get a default direct object using the verify method */
         return getDefaultWithVerify(resolver, verDobjProp, preCondDobjProp,
                                     remapDobjProp, DirectObject, np);
     }
 
     /* get the current direct object of the command */
     getDobj() { return dobjCur_; }
 
     /* get the full ResolveInfo associated with the current direct object */
     getDobjInfo() { return dobjInfoCur_; }
 
     /* the predicate must assign the direct object production tree here */
     dobjMatch = nil
 
     /* my resolved list of direct objects */
     dobjList_ = []
 
     /* 
      *   The resolved direct object on which we're currently executing the
      *   command.  To execute the command, we iterate through the direct
      *   object list, calling the execution sequence for each object in
      *   the list.  We set this to the current object in each iteration.  
      */
     dobjCur_ = nil
 
     /* the full ResolveInfo associated with dobjCur_ */
     dobjInfoCur_ = nil
 
     /* my cached direct object resolver */
     dobjResolver_ = nil
 
     /* -------------------------------------------------------------------- */
     /*
      *   Resolver interface implementation - for the moment, we don't need
      *   any special definitions here, since the basic Resolver
      *   implementation (which we inherit) is suitable for a single-object
      *   action.  
      */
 
     /* -------------------------------------------------------------------- */
     /*
      *   private Resolver implementation details 
      */
 
     /*
      *   Initialize me as a resolver.  
      */
     initResolver(issuingActor, targetActor)
     {
         /* remember the actors */
         issuer_ = issuingActor;
         actor_ = targetActor;
 
         /* I'm the action as well as the resolver */
         action_ = self;
 
         /* cache the actor's default scope list */
         cacheScopeList();
     }
 
     /* issuing actor */
     issuer_ = nil
 
     /* target actor */
     actor_ = nil
 
     /*
      *   By default, our direct object plays the direct object role in
      *   generated messages.  Subclasses can override this if the resolved
      *   object is to play a different role.  Note that this only affects
      *   generated messages; for parsing purposes, our object is always in
      *   the DirectObject role.  
      */
     whichMessageObject = DirectObject
 ;
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   "Tentative" noun resolver results gather.  This type of results
  *   gatherer is used to perform a tentative pre-resolution of an object
  *   of a multi-object action.
  *   
  *   Consider what happens when we resolve a two-object action, such as
  *   "put <dobj> in <iobj>".  Since we have two objects, we obviously must
  *   resolve one object or the other first; but this means that we must
  *   resolve one object with no knowledge of the resolution of the other
  *   object.  This often makes it very difficult to resolve that first
  *   object intelligently, because we'd really like to know something
  *   about the other object.  For example, if we first resolve the iobj of
  *   "put <dobj> in <iobj>", it would be nice to know which dobj we're
  *   talking about, since we could reduce the likelihood that the iobj is
  *   the dobj's present container.
  *   
  *   Tentative resolution addresses this need by giving us some
  *   information about a later-resolved object while resolving an
  *   earlier-resolved object, even though we obviously can't have fully
  *   resolved the later-resolved object.  In tentative resolution, we
  *   perform the resolution of the later-resolved object, completely in
  *   the dark about the earlier-resolved object(s), and come up with as
  *   much information as we can.  The important thing about this stage of
  *   resolution is that we don't ask any interactive questions and we
  *   don't count anything for ranking purposes - we simply do the best we
  *   can and note the results, leaving any ranking or interaction for the
  *   true resolution phase that we'll perform later.  
  */
 class TentativeResolveResults: ResolveResults
     construct(target, issuer) { setActors(target, issuer); }
 
     /* 
      *   ignore most resolution problems, since this is only a tentative
      *   resolution pass 
      */
     noMatch(action, txt) { }
     noVocabMatch(action, txt) { }
     noMatchForAll() { }
     noteEmptyBut() { }
     noMatchForAllBut() { }
     noMatchForListBut() { }
     noMatchForPronoun(typ, txt) { }
     reflexiveNotAllowed(typ, txt) { }
     wrongReflexive(typ, txt) { }
     noMatchForPossessive(owner, txt) { }
     noMatchForLocation(loc, txt) { }
     noteBadPrep() { }
     nothingInLocation(loc) { }
     unknownNounPhrase(match, resolver) { return []; }
     noteLiteral(txt) { }
     emptyNounPhrase(resolver) { return []; }
     zeroQuantity(txt) { }
     insufficientQuantity(txt, matchList, requiredNum) { }
     uniqueObjectRequired(txt, matchList) { }
     noteAdjEnding() { }
     noteIndefinite() { }
     noteMiscWordList(txt) { }
     notePronoun() { }
     noteMatches(matchList) { }
     incCommandCount() { }
     noteActorSpecified() { }
     noteNounSlots(cnt) { }
     noteWeakPhrasing(level) { }
     allowActionRemapping = nil
 
     /* 
      *   during the tentative phase, keep all equivalents - we don't want
      *   to make any arbitrary choices among equivalents during this
      *   phase, because doing so could improperly force a choice among
      *   otherwise ambiguous resolutions to the other phrase 
      */
     allowEquivalentFiltering = nil
 
     /* 
      *   for ambiguous results, don't attempt to narrow things down - just
      *   keep the entire list 
      */
     ambiguousNounPhrase(keeper, asker, txt,
                         matchList, fullMatchList, scopeList,
                         requiredNum, resolver)
     {
         return matchList;
     }
 
     /* 
      *   no interaction is allowed, so return nothing if we need to ask
      *   for a missing object 
      */
     askMissingObject(asker, resolver, responseProd)
     {
         /* note that we have a missing noun phrase */
         npMissing = true;
 
         /* return nothing */
         return nil;
     }
 
     /* 
      *   no interaction is allowed, so return no tokens if we need to ask
      *   for a literal 
      */
     askMissingLiteral(action, which) { return []; }
 
     /* no interaction is allowed during tentative resolution */
     canResolveInteractively() { return nil; }
 
     /* 
      *   flag: the noun phrase we're resolving is a missing noun phrase,
      *   which means that we'll ask for it to be filled in when we get
      *   around to resolving it for real 
      */
     npMissing = nil
 ;
 
 /*
  *   A dummy object that we use for the *tentative* resolution of a noun
  *   phrase when the noun phrase doesn't match anything.  This lets us
  *   distinguish cases where we have a noun phrase that has an error from a
  *   noun phrase that's simply missing.  
  */
 dummyTentativeObject: object
 ;
 
 dummyTentativeInfo: ResolveInfo
     obj_ = dummyTentativeObject
     flags_ = 0
 ;
 
 /* ------------------------------------------------------------------------ */
 /* 
  *   Transitive-with-indirect Action class - this is an action that takes
  *   both a direct and indirect object.  We subclass the basic one-object
  *   action to add the indirect object handling.  
  */
 class TIAction: TAction
     resetAction()
     {
         /* inherit defaulting handling */
         inherited();
 
         /* discard our cached iobj resolver */
         iobjResolver_ = nil;
     }
     
     /*
      *   Retry a single-object action as a two-object action.  We'll treat
      *   the original action's direct object list as our direct object
      *   list, and obtain an indirect object using the normal means (first
      *   looking for a default, then prompting the player if we can't find
      *   a suitable default).  'orig' is the original single-object action.
      *   
      *   This routine terminates with 'exit' if it doesn't throw some
      *   other error.  
      */
     retryWithMissingIobj(orig, asker)
     {
         /* resolve and execute the replacement action */
         resolveAndReplaceAction(createForMissingIobj(orig, asker));
     }
 
     /*
      *   Retry an action as a two-object action with an ambiguous indirect
      *   object.  We'll ask which of the given possible objects is
      *   intended.  
      */
     retryWithAmbiguousIobj(orig, objs, asker, objPhrase)
     {
         local action;
         local resolver;
         
         /* create a missing-indirect-object replacement action */
         action = createForMissingIobj(orig, asker);
 
         /* reduce the object list to the objects in scope */
         resolver = action.getIobjResolver(gIssuingActor, gActor, true);
         objs = objs.subset({x: resolver.objInScope(x)});
 
         /* plug in the ambiguous indirect object list */
         action.iobjMatch = new PreResolvedAmbigProd(objs, asker, objPhrase);
         
         /* resolve and execute the replacement action */
         resolveAndReplaceAction(action);
     }
 
     /*
      *   Test to see if askForIobj() would find a default indirect object.
      *   Returns true if there's a default, nil if not.  If this returns
      *   true, then askForIobj() will simply take the default and proceed;
      *   otherwise, it will have to actually ask the user for the missing
      *   information.  
      */
     testRetryDefaultIobj(orig)
     {
         local action;
         local def;
         
         /* create the new action for checking for an indirect object */
         action = createForMissingIobj(orig, ResolveAsker);
 
         /* get the default iobj */
         def = action.getDefaultIobj(
             action.iobjMatch,
             action.getIobjResolver(gIssuingActor, gActor, nil));
 
         /* if there's exactly one result, then we have a default */
         return (def != nil && def.length() == 1);
     }
 
     /*
      *   Create an instance of this action for retrying a given
      *   single-object action with a missing indirect object.  
      */
     createForMissingIobj(orig, asker)
     {
         local action;
         
         /* create the new action based on the original action */
         action = createForRetry(orig);
 
         /* use an empty noun phrase for the new action's indirect object */
         action.iobjMatch = new EmptyNounPhraseProd();
 
         /* set our custom response production and ResolveAsker */
         action.iobjMatch.setPrompt(action.askIobjResponseProd, asker);
 
         /* copy what we've resolved so far */
         action.initForMissingIobj(orig);
 
         /* return the action */
         return action;
     }
 
     /*
      *   Initialize the action for retrying with a missing direct object.
      *   
      *   If we're trying a TIAction, we can only be coming from a TAction
      *   (since that's the only kind of original action that can turn into
      *   a two-object, at least in the base library).  That means the
      *   original action already has a direct object.  Now, since we're
      *   asking for a MISSING direct object, the only possibility is that
      *   the original action's direct object is our INDIRECT object.  For
      *   example: SWEEP WITH BROOM is turning into SWEEP <what> WITH
      *   BROOM. 
      */
     initForMissingDobj(orig)
     {
         local origDobj = orig.getDobj();
         
         /* 
          *   Set the indirect object in the new action to the direct object
          *   from the original action.  If there's no individual direct
          *   object yet, we must be retrying the overall command, before we
          *   started iterating through the individual dobjs, so copy the
          *   entire dobj list from the original.  
          */
         iobjMatch = new PreResolvedProd(origDobj != nil
                                         ? origDobj : orig.dobjList_ );
     }
 
     /*
      *   Initialize the action for retrying with a missing indirect object.
      *   
      *   We can only be coming from a TAction, so the TAction will have a
      *   direct object already.  Simply copy that as our own direct
      *   object.  For example: UNLOCK DOOR is turning into UNLOCK DOOR
      *   WITH <what>.  
      */
     initForMissingIobj(orig)
     {
         local origDobj = orig.getDobj();
         
         /* 
          *   Copy the direct object from the original.  If there's no
          *   individual direct object yet, we must be retrying the overall
          *   command, before we started iterating through the individual
          *   dobjs, so copy the entire dobj list.  
          */
         dobjMatch = new PreResolvedProd(origDobj != nil
                                         ? origDobj : orig.dobjList_);
     }
 
     /*
      *   The root production to use to parse missing indirect object
      *   responses.  By default, this is singleNoun, but individual
      *   actions can override this as appropriate.
      *   
      *   Note that language modules might want to override this to allow
      *   for special responses.  For example, in English, most verbs will
      *   want to override this with a specialized production that allows
      *   the appropriate preposition in the response.  
      */
     askIobjResponseProd = singleNoun
 
     /*
      *   Resolution order - returns DirectObject or IndirectObject to
      *   indicate which noun phrase to resolve first in resolveNouns().
      *   By default, we'll resolve the indirect object first, but
      *   individual actions can override this to resolve in a non-default
      *   order.  
      */
     resolveFirst = IndirectObject
 
     /*
      *   Empty phrase resolution order.  This is similar to the standard
      *   resolution order (resolveFirst), but is used only when both the
      *   direct and indirect objects are empty phrases.
      *   
      *   When both phrases are empty, we will either use a default or
      *   prompt interactively for the missing phrase.  In most cases, it
      *   is desirable to prompt interactively for a missing direct object
      *   first, regardless of the usual resolution order.  
      */
     resolveFirstEmpty = DirectObject
     
     /*
      *   Determine which object to call first for action processing.  By
      *   default, we execute in the same order as we resolve, but this can
      *   be overridden if necessary.  
      */
     execFirst = (resolveFirst)
 
     /*
      *   resolve our noun phrases to objects 
      */
     resolveNouns(issuingActor, targetActor, results)
     {
         local first;
         local objMatch1, objMatch2;
         local objList1, objList2;
         local getResolver1, getResolver2;
         local objCur1;
         local remapProp;
         local reResolveFirst;
 
         /* 
          *   note in the results that we have two noun slots (the direct
          *   and indirect objects) 
          */
         results.noteNounSlots(2);
 
         /* we have no current known direct or indirect object yet */
         dobjCur_ = nil;
         iobjCur_ = nil;
 
         /* 
          *   presume we won't have to re-resolve the first noun phrase,
          *   and clear out our record of anaphor bindings 
          */
         reResolveFirst = nil;
         needAnaphoricBinding_ = nil;
         lastObjList_ = nil;
 
         /* 
          *   Determine which object we want to resolve first.  If both
          *   phrases are empty, use the special all-empty ordering;
          *   otherwise, use the standard ordering for this verb.  
          */
         if (dobjMatch.isEmptyPhrase && iobjMatch.isEmptyPhrase)
         {
             /* both phrases are empty - use the all-empty ordering */
             first = resolveFirstEmpty;
         }
         else
         {
             /* 
              *   we have at least one non-empty phrase, so use our
              *   standard ordering 
              */
             first = resolveFirst;
         }
 
         /*
          *   The resolution process is symmetrical for the two possible
          *   resolution orders (direct object first or indirect object
          *   first); all we need to do is to set up the parameters we'll
          *   need according to which order we're using.
          *   
          *   This parameterized approach makes the code further below a
          *   little mind-boggling, because it's using so much indirection.
          *   But the alternative is worse: the alternative is to
          *   essentially make two copies of the code below (one for the
          *   dobj-first case and one for the iobj-first case), which is
          *   prone to maintenance problems because of the need to keep the
          *   two copies synchronized when making any future changes.  
          */
         if (first == DirectObject)
         {
             objMatch1 = dobjMatch;
             objMatch2 = iobjMatch;
             objList1 = &dobjList_;
             objList2 = &iobjList_;
             getResolver1 = &getDobjResolver;
             getResolver2 = &getIobjResolver;
             objCur1 = &dobjCur_;
             remapProp = remapDobjProp;
         }
         else
         {
             objMatch1 = iobjMatch;
             objMatch2 = dobjMatch;
             objList1 = &iobjList_;
             objList2 = &dobjList_;
             getResolver1 = &getIobjResolver;
             getResolver2 = &getDobjResolver;
             objCur1 = &iobjCur_;
             remapProp = remapIobjProp;
         }
 
         /* 
          *   Get the unfiltered second-resolved object list - this will
          *   give the first-resolved object resolver access to some
          *   minimal information about the possible second-resolved object
          *   or objects.
          *   
          *   Note that we're not *really* resolving the second object here
          *   - it is the second-resolved object, after all.  What we're
          *   doing is figuring out the *potential* set of objects that
          *   could resolve to the second phrase, with minimal
          *   disambiguation, so that the first object resolver is not
          *   totally in the dark about the second object's potential
          *   resolution.  
          */
         initTentative(issuingActor, targetActor, first);
             
         /* resolve the first-resolved noun phrase */
         self.(objList1) = objMatch1.resolveNouns(
             self.(getResolver1)(issuingActor, targetActor, true), results);
 
         /* 
          *   If the first-resolved noun phrase asked for an anaphoric
          *   binding, we'll need to go back and re-resolve that noun
          *   phrase after we resolve our other noun phrase, since the
          *   first-resolved phrase refers to the second-resolved phrase. 
          */
         reResolveFirst = needAnaphoricBinding_;
 
         /* 
          *   if the second-resolved phrase uses an anaphoric pronoun, the
          *   anaphor can be taken as referring to the first-resolved
          *   phrase's results 
          */
         lastObjList_ = self.(objList1);
 
         /* 
          *   if the first-resolved phrase resolves to just one object, we
          *   can immediately set the current resolved object, so that we
          *   can use it while resolving the second-resolved object list 
          */
         if (self.(objList1).length() == 1)
         {
             /* set the current first-resolved object */
             self.(objCur1) = self.(objList1)[1].obj_;
 
             /* if remapping is allowed at this point, look for a remapping */
             if (results.allowActionRemapping)
             {
                 withParserGlobals(issuingActor, targetActor, self,
                                   new function()
                 {
                     local remapInfo;
                     
                     /* check for a remapping */
                     if ((remapInfo = self.(objCur1).(remapProp)) != nil)
                     {
                         /* 
                          *   we have a remapping, so apply it - note that
                          *   we're still in the process of resolving noun
                          *   phrases (since we've only resolved one of our
                          *   two noun phrases so far), so pass 'true' for
                          *   the inResolve parameter 
                          */
                         remapAction(true, first, remapInfo);
                     }
                 });
             }
         }
 
         /* resolve the second-resolved object */
         self.(objList2) = objMatch2.resolveNouns(
             self.(getResolver2)(issuingActor, targetActor, true), results);
 
         /* 
          *   if we have to re-resolve the first noun phrase due to an
          *   anaphoric pronoun, go back and do that now 
          */
         if (reResolveFirst)
         {
             /* 
              *   use the second-resolved noun phrase as the referent of
              *   the anaphoric pronoun(s) in the first-resolved phrase 
              */
             lastObjList_ = self.(objList2);
 
             /* re-resolve the first object list */
             self.(objList1) = objMatch1.resolveNouns(
                 self.(getResolver1)(issuingActor, targetActor, true),
                 results);
         }
     }
 
     /* we have a direct and indirect object */
     predicateNounPhrases = [&dobjMatch, &iobjMatch]
 
     /* get an object role */
     getRoleFromIndex(idx)
     {
         /* 
          *   the second object is always our indirect object; for other
          *   roles, defer to the inherited behavior 
          */
         return (idx == 2 ? IndirectObject : inherited(idx));
     }
 
     /* get the resolved object in a given role */
     getObjectForRole(role)
     {
         /* return the indirect object if requested; otherwise inherit */
         return (role == IndirectObject ? getIobj() : inherited(role));
     }
 
     /* get the ResolveInfo for the given resolved object */
     getResolveInfo(obj, oldRole)
     {
         local info;
         
         /* scan our resolved direct object list for the given object */
         if (dobjList_ != nil)
             info = dobjList_.valWhich({x: x.obj_ == obj});
 
         /* if we didn't find it there, try the indirect object list */
         if (info == nil && iobjList_ != nil)
             info = iobjList_.valWhich({x: x.obj_ == obj});
 
         /* if we didn't find one, create one from scratch */
         if (info == nil)
         {
             local lst;
             local flags;
 
             /* get the list for the old role */
             lst = (oldRole == DirectObject ? dobjList_ : iobjList_);
 
             /* get the flags from the first element of the old list */
             flags = (lst.length() > 0 ? lst[1].flags_ : 0);
 
             /* create a ResolveInfo to wrap the object */
             info = new ResolveInfo(obj, flags);
         }
 
         /* return what we found (or created) */
         return info;
     }
 
     /* get the OtherObject role for the given role */
     getOtherObjectRole(role)
     {
         /* the complementary roles are DirectObject and IndirectObject */
         return (role == IndirectObject ? DirectObject : IndirectObject);
     }
 
     /* get the match tree for the noun phrase in the given role */
     getMatchForRole(role)
     {
         /* return the indirect object match tree if requested; else inherit */
         return (role == IndirectObject ? iobjMatch : inherited(role));
     }
 
     /* get the 'verify' property for a given object role */
     getVerifyPropForRole(role)
     {
         return (role == IndirectObject ? verIobjProp : inherited(role));
     }
 
     /* get the 'preCond' property for a given object role */
     getPreCondPropForRole(role)
     {
         return (role == IndirectObject ? preCondIobjProp : inherited(role));
     }
 
     /* get the 'remap' property for a given object role */
     getRemapPropForRole(role)
     {
         return (role == IndirectObject ? remapIobjProp : inherited(role));
     }
 
     /* get the list of resolved indirect objects */
     getResolvedIobjList()
     {
         /* 
          *   if we have an indirect object list, return the objects from
          *   it; otherwise, return an empty list 
          */
         return (iobjList_ == nil ? nil : iobjList_.mapAll({x: x.obj_}));
     }
 
     /*
      *   Manually set the resolved objects.  We'll set our direct and
      *   indirect objects.  
      */
     setResolvedObjects(dobj, iobj)
     {
         /* inherit the base class handling to set the direct object */
         inherited(dobj);
 
         /* build a pre-resolved production for the indirect object */
         iobjMatch = new PreResolvedProd(iobj);
 
         /* set the resolved indirect object */
         iobjList_ = makeResolveInfoList(iobj);
     }
 
     /* manually set the unresolved object noun phrase match trees */
     setObjectMatches(dobj, iobj)
     {
         /* inherit default handling */
         inherited(dobj);
 
         /* if the iobj is a ResolveInfo, wrap it in a PreResolvedProd */
         if (iobj.ofKind(ResolveInfo))
             iobj = new PreResolvedProd(iobj);
         
         /* note the new indirect object match tree */
         iobjMatch = iobj;
     }
 
     /*
      *   Get the anaphoric binding for the noun phrase we're currently
      *   resolving. 
      */
     getAnaphoricBinding(typ)
     {
         /* if we've resolved a prior noun phrase, return it */
         if (lastObjList_ != nil)
             return lastObjList_;
 
         /* 
          *   we don't have a prior noun phrase - make a note that we have
          *   to come back and re-resolve the current noun phrase 
          */
         needAnaphoricBinding_ = true;
 
         /* 
          *   return an empty list to indicate that the anaphor is
          *   acceptable in this context but has no binding yet 
          */
         return [];
     }
 
     /*
      *   The last object list we resolved.  We keep track of this so that
      *   we can provide it as the anaphoric binding, if an anaphor binding
      *   is requested.  
      */
     lastObjList_ = nil
 
     /*
      *   Flag: we have been asked for an anaphoric binding, but we don't
      *   have a binding available.  We'll check this after resolving the
      *   first-resolved noun phrase so that we can go back and re-resolve
      *   it again after resolving the other noun phrase.  
      */
     needAnaphoricBinding_ = nil
 
     /* check that the resolved objects are in scope */
     resolvedObjectsInScope()
     {
         /* 
          *   check the indirect object, and inherit the base class handling
          *   to check the direct object 
          */
         return (inherited()
                 && (getIobjResolver(gIssuingActor, gActor, true)
                     .objInScope(iobjList_[1].obj_)));
     }
 
     /* 
      *   get our indirect object resolver, or create one if we haven't
      *   already cached one 
      */
     getIobjResolver(issuingActor, targetActor, reset)
     {
         /* if we don't already have one cached, create a new one */
         if (iobjResolver_ == nil)
             iobjResolver_ = createIobjResolver(issuingActor, targetActor);
 
         /* reset the resolver if desired */
         if (reset)
             iobjResolver_.resetResolver();
         
         /* return the cached resolver */
         return iobjResolver_;
     }
 
     /*
      *   Create our indirect object resolver.  By default, we'll use a
      *   basic indirect object resolver.
      */
     createIobjResolver(issuingActor, targetActor)
     {
         /* create and return a new basic indirect object resolver */
         return new IobjResolver(self, issuingActor, targetActor);
     }
     
     /* 
      *   Resolve 'all' for the indirect object.  By default, we'll return
      *   everything in the scope list.  
      */
     getAllIobj(actor, scopeList)
     {
         return scopeList;
     }
 
     /* filter an ambiguous indirect object noun phrase */
     filterAmbiguousIobj(lst, requiredNum, np)
     {
         /* filter using the indirect object verifier method */
         return filterAmbiguousWithVerify(lst, requiredNum, verIobjProp,
                                          preCondIobjProp, remapIobjProp,
                                          IndirectObject, np);
     }
 
     /* filter a plural phrase */
     filterPluralIobj(lst, np)
     {
         /* filter using the direct object verifier method */
         return filterPluralWithVerify(lst, verIobjProp, preCondIobjProp,
                                       remapIobjProp, IndirectObject, np);
     }
 
     /* get the default indirect object */
     getDefaultIobj(np, resolver)
     {
         /* get a default indirect object using the verify method */
         return getDefaultWithVerify(resolver, verIobjProp, preCondIobjProp,
                                     remapIobjProp, IndirectObject, np);
     }
 
     /*
      *   Execute the action.  We'll run through the execution sequence
      *   once for each resolved object in our direct or indirect object
      *   list, depending on which one is the list and which one is the
      *   singleton.  
      */
     doActionMain()
     {
         local lst;
         local preAnnouncedDobj;
         local preAnnouncedIobj;
         
         /* 
          *   Get the list of resolved objects for the multiple object.  If
          *   neither has multiple objects, it doesn't matter which is
          *   iterated, since we'll just do the command once anyway.  
          */
         lst = (iobjList_.length() > 1 ? iobjList_ : dobjList_);
 
         /* 
          *   Set the pronoun antecedents, using the game-specific pronoun
          *   setter.  Don't set an antecedent for a nested command.
          */
         if (parentAction == nil)
         {
            /* 
             *   Set both direct and indirect objects as potential
             *   antecedents.  Rather than trying to figure out right now
             *   which one we might want to refer to in the future, remember
             *   both - we'll decide which one is the logical antecedent
             *   when we find a pronoun to resolve in a future command.  
             */ 
            gActor.setPronounMulti(dobjList_, iobjList_); 
 
             /*
              *   If one or the other object phrase was specified in the
              *   input as a pronoun, keep the meaning of that pronoun the
              *   same, overriding whatever we just did.  Note that the
              *   order we use here doesn't matter: if a given pronoun
              *   appears in only one of the two lists, then the list where
              *   it's not set has no effect on the pronoun, hence it
              *   doesn't matter which comes first; if a pronoun appears in
              *   both lists, it will have the same value in both lists, so
              *   we'll just do the same thing twice, so, again, order
              *   doesn't matter.  
              */
             setPronounByInput(dobjList_);
             setPronounByInput(iobjList_);
         }
 
         /* 
          *   pre-announce the non-list object if appropriate - this will
          *   provide a common pre-announcement if we iterate through
          *   several announcements of the main list objects 
          */
         if (lst == dobjList_)
         {
             /* pre-announce the single indirect object if needed */
             preAnnouncedIobj = preAnnounceActionObject(
                 iobjList_[1], dobjList_, IndirectObject);
 
             /* we haven't announced the direct object yet */
             preAnnouncedDobj = nil;
         }
         else
         {
             /* pre-announce the single direct object if needed */
             preAnnouncedDobj = preAnnounceActionObject(
                 dobjList_[1], iobjList_, DirectObject);
 
             /* we haven't announced the indirect object yet */
             preAnnouncedIobj = nil;
         }
 
         /* we haven't yet canceled the iteration */
         iterationCanceled = nil;
 
         /* iterate over the resolved list for the multiple object */
         for (local i = 1, local len = lst.length() ;
              i <= len && !iterationCanceled ; ++i)
         {
             local dobjInfo;
             local iobjInfo;
 
             /* 
              *   make the current list item the direct or indirect object,
              *   as appropriate 
              */
             if (lst == dobjList_)
             {
                 /* the direct object is the multiple object */
                 dobjInfo = dobjInfoCur_ = lst[i];
                 iobjInfo = iobjInfoCur_ = iobjList_[1];
             }
             else
             {
                 /* the indirect object is the multiple object */
                 dobjInfo = dobjInfoCur_ = dobjList_[1];
                 iobjInfo = iobjInfoCur_ = lst[i];
             }
 
             /* get the current dobj and iobj from the resolve info */
             dobjCur_ = dobjInfo.obj_;
             iobjCur_ = iobjInfo.obj_;
 
             /* 
              *   if the action was remapped, and we need to announce
              *   anything, announce the entire action 
              */
             if (isRemapped())
             {
                 /*
                  *   We were remapped.  The entire phrasing of the new
                  *   action might have changed from what the player typed,
                  *   so it might be nonsensical to show the objects as we
                  *   usually would, as sentence fragments that are meant
                  *   to combine with what the player actually typed.  So,
                  *   instead of showing the usual sentence fragments, show
                  *   the entire phrasing of the command.
                  *   
                  *   Only show the announcement if we have a reason to: we
                  *   have unclear disambiguation in one of the objects, or
                  *   one of the objects is defaulted.
                  *   
                  *   If we don't want to announce the remapped action,
                  *   still consider showing a multi-object announcement,
                  *   if we would normally need to do so.  
                  */
                 if (needRemappedAnnouncement(dobjInfo)
                     || needRemappedAnnouncement(iobjInfo))
                 {
                     /* show the remapped announcement */
                     gTranscript.announceRemappedAction();
                 }
                 else
                 {
                     /* announce the multiple dobj if necessary */
                     if (!preAnnouncedDobj)
                         maybeAnnounceMultiObject(
                             dobjInfo, dobjList_.length(), DirectObject);
 
                     /* announce the multiple iobj if necessary */
                     if (!preAnnouncedIobj)
                         maybeAnnounceMultiObject(
                             iobjInfo, iobjList_.length(), IndirectObject);
                 }
             }
             else
             {
                 /* announce the direct object if appropriate */
                 if (!preAnnouncedDobj)
                     announceActionObject(dobjInfo, dobjList_.length(),
                                          DirectObject);
 
                 /* announce the indirect object if appropriate */
                 if (!preAnnouncedIobj)
                     announceActionObject(iobjInfo, iobjList_.length(),
                                          IndirectObject);
             }
 
             /* run the execution sequence for the current direct object */
             doActionOnce();
 
             /* if we're top-level, count the iteration in the transcript */
             if (parentAction == nil)
                 gTranscript.newIter();
         }
     }
 
     /*
      *   Set the pronoun according to the pronoun type actually used in
      *   the input.  For example, if we said PUT BOX ON IT, we want IT to
      *   continue referring to whatever IT referred to before this command
      *   - we specifically do NOT want IT to refer to the BOX in this
      *   case. 
      */
     setPronounByInput(lst)
     {
         local objs;
         
         /* get the subset of the list that was specified by pronoun */
         lst = lst.subset({x: x.pronounType_ != nil});
 
         /* 
          *   Get a list of the unique objects in the list.  This will
          *   ensure that we can distinguish THEM from IT AND IT AND IT: if
          *   we have the same pronoun appearing with multiple objects,
          *   we'll know that it's because the pronoun was actually plural
          *   (THEM) rather than a singular pronoun that was repeated (IT
          *   AND IT AND IT). 
          */
         objs = lst.mapAll({x: x.obj_}).getUnique();
 
         /* 
          *   Now retain one 'lst' element for each 'objs' element.  This
          *   is a bit tricky: we're mapping each element in 'objs' to pick
          *   out one element of 'lst' where the 'lst' element points to
          *   the 'objs' element.  
          */
         lst = objs.mapAll({o: lst.valWhich({l: l.obj_ == o})});
 
         /* 
          *   Now we can set the pronouns.  Go through the list, and set
          *   each different pronoun type that appears.  Set it to the
          *   subset of the list with that matches that pronoun type, and
          *   then remove that subset and keep iterating to pick up the
          *   remaining pronoun types.  
          */
         while (lst.length() != 0)
         {
             local cur;
             local typ;
 
             /* on this iteration, handle the first pronoun type in the list */
             typ = lst[1].pronounType_;
             
             /* pick out the subset with the current pronoun type */
             cur = lst.subset({x: x.pronounType_ == typ});
 
             /* set the current pronoun to the current list */
             gActor.setPronounByType(typ, cur);
 
             /* remove the subset we just handled from the remaining list */
             lst -= cur;
         }
     }
 
     /*
      *   Determine if we need to announce this action when the action was
      *   remapped, based on the resolution information for one of our
      *   objects.  We need to announce a remapped action when either
      *   object had unclear disambiguation or was defaulted. 
      */
     needRemappedAnnouncement(info)
     {
         /* 
          *   if it's a defaulted object that hasn't been announced, or it
          *   was unclearly disambiguated, we need an announcement 
          */
         return (((info.flags_ & DefaultObject) != 0
                  && (info.flags_ & AnnouncedDefaultObject) == 0)
                 || (info.flags_ & UnclearDisambig) != 0);
     }
 
     /*
      *   Verify the action.
      */
     verifyAction()
     {
         local result;
         
         /* invoke any general (non-object) pre-condition verifiers */
         result = callVerifyPreCond(nil);
 
         /* check the direct object */
         result = callVerifyProp(dobjCur_, verDobjProp, preCondDobjProp,
                                 remapDobjProp, result, DirectObject);
 
         /* 
          *   Check the indirect object, combining the results with the
          *   direct object results.  We combine the results for the two
          *   objects because we're simply looking for any reason that we
          *   can't perform the command.  
          */
         result = callVerifyProp(iobjCur_, verIobjProp, preCondIobjProp,
                                 remapIobjProp, result, IndirectObject);
 
         /* 
          *   check that the action method ("doXxx") is defined on at least
          *   one of the objects 
          */
         return verifyHandlersExist(
             [dobjCur_, iobjCur_],
             [verDobjProp, checkDobjProp, actionDobjProp,
             verIobjProp, checkIobjProp, actionIobjProp],
             result);
     }
 
     /* initialize tentative resolutions for other noun phrases */
     initTentative(issuingActor, targetActor, whichObj)
     {
         local tRes;
         local ti, td;
 
         /* 
          *   remember the old tentative direct and indirect objects, then
          *   set them to empty lists - this will ensure that we don't
          *   recursively try to get a tentative resolution for the object
          *   we're working on right now, which would cause infinite
          *   recursion 
          */
         td = tentativeDobj_;
         ti = tentativeIobj_;
 
         /* set them to empty lists to indicate they don't need resolving */
         tentativeDobj_ = [];
         tentativeIobj_ = [];
 
         /* make sure our built-in dobj resolver is initialized */
         issuer_ = issuingActor;
         actor_ = targetActor;
 
         /* make sure we set things back when we're done */
         try
         {
             /* initialize the other noun phrase */
             if (whichObj == DirectObject && ti == nil)
             {
                 /* tentatively resolve the indirect object */
                 tRes = new TentativeResolveResults(targetActor, issuingActor);
                 ti = iobjMatch.resolveNouns(
                     getIobjResolver(issuingActor, targetActor, true), tRes);
 
                 /* 
                  *   if the list is empty, and we didn't have a missing
                  *   noun phrase, use a dummy object as the tentative
                  *   resolution - this distinguishes erroneous noun phrases
                  *   from those that are simply missing 
                  */
                 if (ti == [] && !tRes.npMissing)
                     ti = [dummyTentativeInfo];
             }
             else if (whichObj == IndirectObject && td == nil)
             {
                 /* tentatively resolve the direct object */
                 tRes = new TentativeResolveResults(targetActor, issuingActor);
                 td = dobjMatch.resolveNouns(
                     getDobjResolver(issuingActor, targetActor, true), tRes);
 
                 /* use a dummy object if appropriate */
                 if (td == [] && !tRes.npMissing)
                     td = [dummyTentativeInfo];
             }
         }
         finally
         {
             /* set the original (or updated) tentative lists */
             tentativeDobj_ = td;
             tentativeIobj_ = ti;
         }
     }
 
     /*
      *   Check for remapping 
      */
     checkRemapping()
     {
         local remapInfo;
         local role;
 
         /* presume we'll find remapping for the first-resolved object */
         role = resolveFirst;
         
         /* check remapping for each object, in the resolution order */
         if (resolveFirst == DirectObject)
         {
             /* the direct object is resolved first, so try it first */
             if ((remapInfo = dobjCur_.(remapDobjProp)) == nil)
             {
                 remapInfo = iobjCur_.(remapIobjProp);
                 role = IndirectObject;
             }
         }
         else
         {
             /* the indirect object is resolved first, so remap it first */
             if ((remapInfo = iobjCur_.(remapIobjProp)) == nil)
             {
                 remapInfo = dobjCur_.(remapDobjProp);
                 role = DirectObject;
             }
         }
 
         /* if we found a remapping, apply it */
         if (remapInfo != nil)
             remapAction(nil, role, remapInfo);
     }
 
     /*
      *   Check the command.
      *   
      *   For a two-object action, this first calls the catch-all 'check'
      *   methods (the dobjFor(Default) and dobjFor(All) methods) on the two
      *   objects (indirect object first), then calls the 'check' methods
      *   for this specific action (direct object first).  
      */
     checkAction()
     {
         local defIo, defDo;
                 
         try
         {
             /* invoke the catch-all 'check' methods on each object */
             defIo = callCatchAllProp(iobjCur_, checkIobjProp,
                                      &checkIobjDefault, &checkIobjAll);
             defDo = callCatchAllProp(dobjCur_, checkDobjProp,
                                      &checkDobjDefault, &checkDobjAll);
 
             /* 
              *   invoke the 'check' method on each object, as long as it
              *   overrides any corresponding Default 'check' handler 
              */
             if (!defDo)
                 dobjCur_.(checkDobjProp)();
             if (!defIo)
                 iobjCur_.(checkIobjProp)();
         }
         catch (ExitSignal es)
         {
             /* mark the action as a failure in the transcript */
             gTranscript.noteFailure();
 
             /* re-throw the signal */
             throw es;
         }
     }
 
     /*
      *   Execute the command. 
      */
     execAction()
     {
         local defIo, defDo;
                 
         /* invoke the catch-all 'action' method on each object */
         defIo = callCatchAllProp(iobjCur_, actionIobjProp,
                                  &actionIobjDefault, &actionIobjAll);
         defDo = callCatchAllProp(dobjCur_, actionDobjProp,
                                  &actionDobjDefault, &actionDobjAll);
 
         /* 
          *   Invoke the action method on each object, starting with the
          *   non-list object.  Call these only if the corresponding
          *   default 'action' methods didn't override the verb-specific
          *   methods.  
          */
         if (execFirst == DirectObject)
         {
             if (!defDo)
                 dobjCur_.(actionDobjProp)();
             if (!defIo)
                 iobjCur_.(actionIobjProp)();
         }
         else
         {
             if (!defIo)
                 iobjCur_.(actionIobjProp)();
             if (!defDo)
                 dobjCur_.(actionDobjProp)();
         }
     }
 
     /* get the precondition descriptor list for the action */
     getPreCondDescList()
     {
         /* 
          *   return the inherited preconditions plus the conditions that
          *   apply to the indirect object 
          */
         return inherited()
             + getObjPreCondDescList(iobjCur_, preCondIobjProp, iobjCur_,
                                     IndirectObject);
     }
 
     /*
      *   Get a message parameter object for the action.  We define 'dobj'
      *   as the direct object and 'iobj' as the indirect object, in
      *   addition to any inherited targets.  
      */
     getMessageParam(objName)
     {
         switch(objName)
         {
         case 'iobj':
             /* return the current indirect object */
             return iobjCur_;
 
         default:
             /* inherit default handling */
             return inherited(objName);
         }
     }
 
     /* get the current indirect object being executed */
     getIobj() { return iobjCur_; }
     
     /* get the full ResolveInfo associated with the current indirect object */
     getIobjInfo() { return iobjInfoCur_; }
 
     /*
      *   Get the list of active objects.  We have a direct and indirect
      *   object.  
      */
     getCurrentObjects()
     {
         return [dobjCur_, iobjCur_];
     }
 
     /* set the current objects */
     setCurrentObjects(lst)
     {
         /* remember the current direct and indirect objects */
         dobjCur_ = lst[1];
         iobjCur_ = lst[2];
 
         /* we don't have ResolveInfo objects for this call */
         dobjInfoCur_ = iobjInfoCur_ = nil;
     }
 
     /*
      *   Copy one tentative object list to the other.  This is useful when
      *   an object's verifier for one TIAction wants to forward the call
      *   to the other object verifier for a different TIAction.  
      */
     copyTentativeObjs()
     {
         /* copy whichever tentative list is populated to the other slot */
         if (tentativeDobj_ != nil)
             tentativeDobj_ = tentativeIobj_;
         else
             tentativeIobj_ = tentativeDobj_;
     }
 
     /*
      *   Get the tentative direct/indirect object resolution lists.  A
      *   tentative list is available for the later-resolved object while
      *   resolving the earlier-resolved object. 
      */
     getTentativeDobj() { return tentativeDobj_; }
     getTentativeIobj() { return tentativeIobj_; }
 
     /* 
      *   the predicate grammar must assign the indirect object production
      *   tree to iobjMatch 
      */
     iobjMatch = nil
 
     /* the indirect object list */
     iobjList_ = []
 
     /* current indirect object being executed */
     iobjCur_ = nil
 
     /* the full ResolveInfo associated with iobjCur_ */
     iobjInfoCur_ = nil
 
     /* my cached indirect object resolver */
     iobjResolver_ = nil
 
     /*
      *   The tentative direct and indirect object lists.  A tentative list
      *   is available for the later-resolved object while resolving the
      *   earlier-resolved object.  
      */
     tentativeDobj_ = nil
     tentativeIobj_ = nil
 
     /*
      *   Verification and action properties for the indirect object.  By
      *   convention, the verification method for the indirect object of a
      *   two-object action is verIobjXxx; the check method is
      *   checkIobjXxx; and the action method is actionIobjXxx.  
      */
     verIobjProp = nil
     preCondIobjProp = nil
     checkIobjProp = nil
     actionIobjProp = nil
 
     /*
      *   Action-remap properties for the indirect object.  By convention,
      *   the remapper properties are named remapDobjAction and
      *   remapIobjAction, for the direct and indirect objects,
      *   respectively, where Action is replaced by the root name of the
      *   action.  
      */
     remapIobjProp = nil
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Common base class for actions involving literal phrases.  This is a
  *   mix-in class that can be combined with Action subclasses to create
  *   specific kinds of literal actions.  
  */
 class LiteralActionBase: object
     /* 
      *   Get a message parameter.  We define 'literal' as the text of the
      *   literal phrase, in addition to inherited targets.  
      */
     getMessageParam(objName)
     {
         switch(objName)
         {
         case 'literal':
             /* return the text of the literal phrase */
             return text_;
 
         default:
             /* inherit default handling */
             return inherited(objName);
         }
     }
 
     /* manually set the resolved objects */
     setResolvedObjects(txt)
     {
         /* remember the literal text */
         text_ = txt;
     }
 
     /* manually set the pre-resolved match trees */
     setObjectMatches(lit)
     {
         /* if it's not already a PreResolvedLiteralProd, wrap it */
         if (!lit.ofKind(PreResolvedLiteralProd))
             lit = new PreResolvedLiteralProd(lit);
         
         /* note the new literal match tree */
         literalMatch = lit;
     }
 
     /* get the current literal text */
     getLiteral() { return text_; }
 
     /* the text of the literal phrase */
     text_ = nil
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   An action with a literal phrase as its only object, such as "say <any
  *   text>".  We'll accept anything as the literal phrase - a number, a
  *   quoted string, or arbitrary words - and treat them all simply as text.
  *   
  *   The grammar rules that produce these actions must set literalMatch to
  *   the literal phrase's match tree.
  *   
  *   Because we don't have any actual resolved objects, we're based on
  *   IAction.  Subclasses that implement particular literal actions should
  *   override execAction() to carry out the action; this method can call
  *   the getLiteral() method of self to get a string giving the literal
  *   text.  
  */
 class LiteralAction: LiteralActionBase, IAction
     /*
      *   Resolve objects.  We don't actually have any objects to resolve,
      *   but we do have to get the text for the literal phrase.  
      */
     resolveNouns(issuingActor, targetActor, results)
     {
         /* the literal phrase counts as one noun slot */
         results.noteNounSlots(1);
         
         /* 
          *   "Resolve" our literal phrase.  The literal phrase doesn't
          *   resolve to an object list the way a regular noun phrase would,
          *   but rather just resolves to a text string giving the original
          *   literal contents of the phrase.  
          */
         literalMatch.resolveLiteral(results);
 
         /* retrieve the text of the phrase, exactly as the player typed it */
         text_ = literalMatch.getLiteralText(results, self, DirectObject);
     }
 
     /* we have a literal phrase as our only noun phrase */
     predicateNounPhrases = [&literalMatch]
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   An action with a direct object and a literal, such as "turn dial to
  *   <setting>" or "type <string> on keypad".  We'll accept anything as the
  *   literal phrase - a number, a quoted string, or arbitrary words - and
  *   treat them all simply as text.
  *   
  *   The grammar rules that produce these actions must set dobjMatch to the
  *   resolvable object of the command, and must set literalMatch to the
  *   literal phrase's match tree.  Note that we use dobjMatch as the
  *   resolvable object even if the object serves grammatically as the
  *   indirect object - this is a simplification, and the true grammatical
  *   purpose of the object isn't important since there's only one true
  *   object in the command.
  *   
  *   When referring to objects by role (such as in remapTo), callers should
  *   ALWAYS refer to the resolvable object as DirectObject, and the literal
  *   phrase as IndirectObject.
  *   
  *   Each subclass must set the property whichMessageLiteral to the
  *   grammatical role (DirectObject, IndirectObject) the literal phrase
  *   plays for message generation purposes.  This only affects messages; it
  *   doesn't affect anything else; in particular, regardless of the
  *   whichMessageLiteral setting, callers should always refer to the
  *   literal as IndirectObject when calling getObjectForRole() and the
  *   like, and should always call getDobj() to get the resolved version of
  *   the resolvable object phrase.  
  */
 class LiteralTAction: LiteralActionBase, TAction
     /*
      *   Resolve objects.  
      */
     resolveNouns(issuingActor, targetActor, results)
     {
         /* 
          *   the literal phrase counts as one noun slot, and the direct
          *   object as another 
          */
         results.noteNounSlots(2);
 
         /* 
          *   If the literal phrase serves as the direct object in generated
          *   messages, ask for its literal text first, so that we ask for
          *   it interactively first.  Otherwise, get the tentative literal
          *   text, in case it's useful to have in resolving the other
          *   object.  
          */
         if (whichMessageLiteral == DirectObject)
             text_ = literalMatch.getLiteralText(results, self, DirectObject);
         else
             text_ = literalMatch.getTentativeLiteralText();
 
         /* resolve the direct object */
         dobjList_ = dobjMatch.resolveNouns(
             getDobjResolver(issuingActor, targetActor, true), results);
 
         /* 
          *   "Resolve" the literal, ignoring the result - we call this so
          *   that the literal can do any scoring it wants to do during
          *   resolution; but since we're treating it literally, it
          *   obviously has no actual resolved value.  
          */
         literalMatch.resolveLiteral(results);
 
         /* the literal phrase resolves to the text only */
         text_ = literalMatch.getLiteralText(results, self,
                                             whichMessageLiteral);
     }
 
     /* we have a direct object and a literal phrase */
     predicateNounPhrases = [&dobjMatch, &literalMatch]
 
     /* get an object role */
     getRoleFromIndex(idx)
     {
         /* 
          *   index 2 is the literal object, which is always in the indirect
          *   object role; for others, inherit the default handling 
          */
         return (idx == 2 ? IndirectObject : inherited(idx));
     }
 
     /* get the OtherObject role for the given role */
     getOtherObjectRole(role)
     {
         /* the complementary roles are DirectObject and IndirectObject */
         return (role == DirectObject ? IndirectObject : DirectObject);
     }
 
     /* get the resolved object in a given role */
     getObjectForRole(role)
     {
         /* 
          *   the literal is in the IndirectObject role; inherit the default
          *   for others 
          */
         return (role == IndirectObject ? text_ : inherited(role));
     }
 
     /* get the match tree for the given role */
     getMatchForRole(role)
     {
         /* 
          *   the literal is in the IndirectObject role; inherit the default
          *   for anything else 
          */
         return (role == IndirectObject ? literalMatch : inherited(role));
     }
 
     /* manually set the resolved objects */
     setResolvedObjects(dobj, txt)
     {
         /* inherit default TAction handling for the direct object */
         inherited TAction(dobj);
 
         /* inherit the LiteralActionBase handling for the literal text */
         inherited LiteralActionBase(txt);
     }
 
     /* manually set the pre-resolved match trees */
     setObjectMatches(dobj, lit)
     {
         /* inherit default TAction handling for the direct object */
         inherited TAction(dobj);
 
         /* inherit the default LiteralActionBase handling for the literal */
         inherited LiteralActionBase(lit);
     }
 
     /* 
      *   Get a list of the current objects.  We include only the direct
      *   object here, since the literal text is not a resolved object but
      *   simply literal text. 
      */
     getCurrentObjects() { return [dobjCur_]; }
 
     /* set the current objects */
     setCurrentObjects(lst)
     {
         dobjCur_ = lst[1];
         dobjInfoCur_ = nil;
     }
 
     /*
      *   Retry a single-object action as an action taking both an object
      *   and a literal phrase.  We'll treat the original action's direct
      *   object list as our direct object list, and obtain a literal
      *   phrase interactively.
      *   
      *   This routine terminates with 'exit' if it doesn't throw some
      *   other error.  
      */
     retryWithMissingLiteral(orig)
     {
         local action;
         
         /* create the new action based on the original action */
         action = createForRetry(orig);
 
         /* use an empty literal phrase for the new action */
         action.literalMatch = new EmptyLiteralPhraseProd();
 
         /* initialize for the missing literal phrase */
         action.initForMissingLiteral(orig);
 
         /* resolve and execute the replacement action */
         resolveAndReplaceAction(action);
     }
 
     /* initialize with a missing direct object phrase */
     initForMissingDobj(orig)
     {
         /*
          *   Since we have a missing direct objet, we must be coming from
          *   a LiteralAction.  The LiteralAction will already have a
          *   literal phrase, so copy it.  For example: WRITE HELLO ->
          *   WRITE HELLO ON <what>.  
          */
         literalMatch = new PreResolvedLiteralProd(orig.getLiteral());
     }
 
     /* initialize for a missing literal phrase */
     initForMissingLiteral(orig)
     {
         local origDobj = orig.getDobj();
         
         /*
          *   Since we have a missing literal, we must be coming from a
          *   TAction.  The TAction will already have a direct object,
          *   which we'll want to keep as our own direct object.  For
          *   example: WRITE ON SLATE -> WRITE <what> ON SLATE.  
          */
         dobjMatch = new PreResolvedProd(origDobj != nil
                                         ? origDobj : orig.dobjList_);
     }
 
     /* object role played by the literal phrase */
     whichMessageLiteral = nil
 
 
     /* -------------------------------------------------------------------- */
     /*
      *   Direct Object Resolver implementation.  We serve as our own
      *   direct object resolver, so we define any special resolver
      *   behavior here.  
      */
 
     /* 
      *   the true grammatical role of the resolved object is always the
      *   direct object 
      */
     whichObject = DirectObject
 
     /* 
      *   What we call our direct object might actually be playing the
      *   grammatical role of the indirect object - in order to inherit
      *   easily from TAction, we call our resolved object our direct
      *   object, regardless of which grammatical role it actually plays.
      *   For the most part it doesn't matter which is which; but for the
      *   purposes of our resolver, we actually do care about its real role.
      *   So, override the resolver method whichMessageObject so that it
      *   returns whichever role is NOT served by the topic object.  
      */
     whichMessageObject =
         (whichMessageLiteral == DirectObject ? IndirectObject : DirectObject)
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Base class for actions that include a "topic" phrase.  This is a
  *   mix-in class that can be used in different types of topic actions.  In
  *   all cases, the topic phrase must be assigned to the 'topicMatch'
  *   property in grammar rules based on this class.  
  */
 class TopicActionBase: object
     /*
      *   Resolve the topic phrase.  This resolves the match tree in out
      *   'topicMatch' property, and stores the result in topicList_.  This
      *   is for use in resolveNouns().  
      */
     resolveTopic(issuingActor, targetActor, results)
     {
         /* resolve the topic match tree */
         topicList_ = topicMatch.resolveNouns(
             getTopicResolver(issuingActor, targetActor, true), results);
 
         /* 
          *   if the topic is other than a single ResolvedTopic object, run
          *   it through the topic resolver's ambiguous noun phrase filter
          *   to get it into that canonical form 
          */
         if (topicList_.length() > 1
             || (topicList_.length() == 1
                 && !topicList_[1].obj_.ofKind(ResolvedTopic)))
         {
             /* it's not in canonical form, so get it in canonical form now */
             topicList_ = getTopicResolver(issuingActor, targetActor, true)
                          .filterAmbiguousNounPhrase(
                              topicList_, 1, topicMatch);
         }
     }
 
     /*
      *   Set the resolved topic to the given object list.  This is for use
      *   in setResolvedObjects().  
      */
     setResolvedTopic(topic)
     {
         /* if the topic isn't given as a ResolvedTopic, wrap it in one */
         if (!topic.ofKind(ResolvedTopic))
             topic = ResolvedTopic.wrapObject(topic);
 
         /* use the match object from the ResolvedTopic */
         topicMatch = topic.topicProd;
 
         /* finally, make a ResolveInfo list out of the ResolvedTopic */
         topicList_ = makeResolveInfoList(topic);
     }
 
     /*
      *   Set the topic match tree.  This is for use in setObjectMatches().
      */
     setTopicMatch(topic)
     {
         /* if it's given as a ResolveInfo, wrap it in a PreResolvedProd */
         if (topic.ofKind(ResolveInfo))
             topic = new PreResolvedProd(topic);
 
         /* save the topic match */
         topicMatch = topic;
     }
 
     /*
      *   get the topic resolver 
      */
     getTopicResolver(issuingActor, targetActor, reset)
     {
         /* create one if we don't already have one */
         if (topicResolver_ == nil)
             topicResolver_ = createTopicResolver(issuingActor, targetActor);
 
         /* reset the resolver if desired */
         if (reset)
             topicResolver_.resetResolver();
 
         /* return the cached resolver */
         return topicResolver_;
     }
 
     /*
      *   Create the topic resolver.  
      */
     createTopicResolver(issuingActor, targetActor)
     {
         return new TopicResolver(self, issuingActor, targetActor,
                                  topicMatch, whichMessageTopic,
                                  getTopicQualifierResolver(
                                      issuingActor, targetActor, nil));
     }
 
     /*
      *   Get a message parameter by name.  We'll return the topic for the
      *   keyword 'topic', and inherit the default handling for anything
      *   else.  
      */
     getMessageParam(objName)
     {
         switch(objName)
         {
         case 'topic':
             /* return the topic */
             return getTopic();
 
         default:
             /* inherit default handling */
             return inherited(objName);
         }
     }
 
     /* get the current topic */
     getTopic()
     {
         /* 
          *   Because the topic list always contains one entry (a
          *   ResolvedTopic object encapsulating the topic information),
          *   our current topic is always simply the first and only element
          *   of the topic list. 
          */
         return topicList_[1].obj_;
     }
 
 
     /*
      *   Get the resolver for qualifiers we find in the topic phrase
      *   (qualifiers that might need resolution include things like
      *   possessive adjectives and locational phrases).  
      */
     getTopicQualifierResolver(issuingActor, targetActor, reset)
     {
         /* if we don't already have a qualifier resolver, create one */
         if (topicQualResolver_ == nil)
             topicQualResolver_ = createTopicQualifierResolver(
                 issuingActor, targetActor);
 
         /* reset it if desired */
         if (reset)
             topicQualResolver_.resetResolver();
 
         /* return it */
         return topicQualResolver_;
     }
 
     /* create a topic qualifier resolver */
     createTopicQualifierResolver(issuingActor, targetActor)
     {
         /* create and return a topic qualifier object resolver */
         return new TopicQualifierResolver(self, issuingActor, targetActor);
     }
 
     /* the topic qualifier resolver */
     topicQualResolver_ = nil
     
     /* the resolved topic object list */
     topicList_ = nil
 
     /* my cached topic resolver */
     topicResolver_ = nil
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   An action with a topic phrase as its only object, such as "think about
  *   <topic>".  
  *   
  *   The grammar rules that produce these actions must set topicMatch to
  *   the topic match tree.
  *   
  *   Because we don't have any actual resolved objects, we're based on
  *   IAction.  Subclasses that implement particular topic actions should
  *   override execAction() to carry out the action; this method can call
  *   the getTopic() method of self to get a string giving the topic.  
  */
 class TopicAction: TopicActionBase, IAction
     /*
      *   Resolve objects.  We don't actually have any normal objects to
      *   resolve, but we do have to get the resolved topic phrase.  
      */
     resolveNouns(issuingActor, targetActor, results)
     {
         /* the topic phrase counts as one noun slot */
         results.noteNounSlots(1);
 
         /* resolve the topic */
         resolveTopic(issuingActor, targetActor, results);
     }
 
     /* manually set the resolved objects */
     setResolvedObjects(topic)
     {
         /* set the resolved topic */
         setResolvedTopic(topic);
     }
 
     /* manually set the pre-resolved match trees */
     setObjectMatches(topic)
     {
         /* note the new topic match tree */
         setTopicMatch(topic);
     }
 
     /* we have a topic noun phrase */
     predicateNounPhrases = [&topicMatch]
 ;
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   An Action with a direct object and a topic, such as "ask <actor> about
  *   <topic>".  Topics differ from ordinary noun phrases in scope: rather
  *   than resolving to simulation objects based on location, we resolve
  *   these based on the actor's knowledge.
  *   
  *   The grammar rules that produce these actions must set dobjMatch to the
  *   resolvable object of the command (the <actor> in "ask <actor> about
  *   <topic>"), and must set topicMatch to the topic match tree object,
  *   which must be a TopicProd object.  Note that, in some cases, the
  *   phrasing might make the dobjMatch the indirect object, grammatically
  *   speaking: "type <topic> on <object>"; even in such cases, use
  *   dobjMatch for the resolvable object.
  *   
  *   When we resolve the topic, we will always resolve it to a single
  *   object of class ResolvedTopic.  This contains the literal tokens of
  *   the original command plus a list of simulation objects matching the
  *   topic name, ordered from best to worst.  This is different from the
  *   way most commands work, since we do not resolve the topic to a simple
  *   game world object.  We keep all of this extra information because we
  *   don't want to perform disambiguation in the normal fashion, but
  *   instead resolve as much as we can with what we're given, and then give
  *   the specialized action code as much information as we can to let the
  *   action code figure out how to respond to the topic.  
  */
 class TopicTAction: TopicActionBase, TAction
     /* 
      *   reset the action 
      */
     resetAction()
     {
         /* reset the inherited state */
         inherited();
 
         /* forget our topic resolver */
         topicResolver_ = nil;
     }
     
     /*
      *   resolve our noun phrases to objects 
      */
     resolveNouns(issuingActor, targetActor, results)
     {
         local dobjRes;
         
         /* 
          *   the topic phrase counts as one noun slot, and the direct
          *   object as another 
          */
         results.noteNounSlots(2);
 
         /* resolve the direct object, if we have one */
         if (dobjMatch != nil)
         {
             /* presume we won't find an unbound anaphor */
             needAnaphoricBinding_ = nil;
             
             /* 
              *   if the direct object phrase is an empty phrase, resolve
              *   the topic first 
              */
             if (dobjMatch.isEmptyPhrase)
                 resolveTopic(issuingActor, targetActor, results);
             
             /* get the direct object resolver */
             dobjRes = getDobjResolver(issuingActor, targetActor, true);
             
             /* resolve the direct object */
             dobjList_ = dobjMatch.resolveNouns(dobjRes, results);
 
             /* 
              *   if that turned up an anaphor (LOOK UP BOOK IN ITSELF),
              *   resolve the topic phrase as though it were the direct
              *   object 
              */
             if (needAnaphoricBinding_ && topicMatch != nil)
                 dobjList_ = topicMatch.resolveNouns(dobjRes, results);
         }
 
         /* resolve the topic */
         resolveTopic(issuingActor, targetActor, results);
     }
 
     /*
      *   Filter the resolved topic.  This is called by our
      *   TActionTopicResolver, which refers the resolution back to us.  
      */
     filterTopic(lst, np, resolver)
     {
         /* by default, simply put everything in an undifferentiated list */
         return new ResolvedTopic(lst, [], [], topicMatch);
     }
 
     /*
      *   Retry a single-object action as an action taking both an object
      *   and a topic phrase.  We'll treat the original action's direct
      *   object list as our direct object list, and we'll obtain a topic
      *   phrase interactively.
      *   
      *   This routine terminates with 'exit' if it doesn't throw some other
      *   error.  
      */
     retryWithMissingTopic(orig)
     {
         local action;
         
         /* create the new action based on the original action */
         action = createForRetry(orig);
 
         /* use an empty topic phrase for the new action */
         action.topicMatch = new EmptyTopicPhraseProd();
 
         /* initialize for the missing topic */
         action.initForMissingTopic(orig);
 
         /* resolve and execute the replacement action */
         resolveAndReplaceAction(action);
     }
 
     /* initialize a new action we're retrying for a missing direct object */
     initForMissingDobj(orig)
     {
         /*
          *   The original action must have been a TopicAction, so we
          *   already have a topic.  Simply copy the original topic to use
          *   as our own topic.  For example: ASK ABOUT BOOK -> ASK <whom>
          *   ABOUT BOOK.  
          */
         topicMatch = new PreResolvedProd(orig.getTopic());
     }
 
     /* initialize for retrying with a missing topic phrase */
     initForMissingTopic(orig)
     {
         local origDobj = orig.getDobj();
         
         /*
          *   The original action must have been a TAction, so we already
          *   have a direct object.  Simply copy the original direct object
          *   for use as our own.  For example: ASK BOB -> ASK BOB ABOUT
          *   <what>.  
          */
         dobjMatch = new PreResolvedProd(origDobj != nil
                                         ? origDobj : orig.dobjList_);
     }
 
     /* create our TAction topic resolver */
     createTopicResolver(issuingActor, targetActor)
     {
         return new TActionTopicResolver(self, issuingActor, targetActor,
                                         topicMatch, whichMessageTopic,
                                         getTopicQualifierResolver(
                                             issuingActor, targetActor, nil));
     }
 
     /* 
      *   In the topic phrase, we can use an anaphoric pronoun to refer
      *   back to the direct object.  Since we resolve the direct object
      *   phrase first, we can simply return the direct object list as the
      *   binding.  If the direct object isn't resolved yet, make a note to
      *   come back and re-bind the anaphor.  
      */
     getAnaphoricBinding(typ)
     {
         /* if we've already resolved the direct object phrase, return it */
         if (dobjList_ not in (nil, []))
             return dobjList_;
 
         /* no dobj yet - make a note that we need to re-bind the anaphor */
         needAnaphoricBinding_ = true;
 
         /* 
          *   return an empty list for now to indicate that the anaphor is
          *   acceptable but has no binding yet 
          */
         return [];
     }
 
     /*
      *   Flag: we have been asked for an anaphoric binding, but we don't
      *   have a binding available.  We'll check this after resolving the
      *   first-resolved noun phrase so that we can go back and re-resolve
      *   it again after resolving the other noun phrase.  
      */
     needAnaphoricBinding_ = nil
 
     /* we have a direct object and a topic phrase */
     predicateNounPhrases = [&dobjMatch, &topicMatch]
 
     /* get an object role */
     getRoleFromIndex(idx)
     {
         /* 
          *   index 2 is the topic object, which is always in the indirect
          *   object role; for others, inherit the default handling 
          */
         return (idx == 2 ? IndirectObject : inherited(idx));
     }
 
     /* get the OtherObject role for the given role */
     getOtherObjectRole(role)
     {
         /* the complementary roles are DirectObject and IndirectObject */
         return (role == DirectObject ? IndirectObject : DirectObject);
     }
 
     /* get the resolved object in a given role */
     getObjectForRole(role)
     {
         /* 
          *   the topic is in the IndirectObject role; inherit the default
          *   for others 
          */
         return (role == IndirectObject ? getTopic() : inherited(role));
     }
 
     /* get the match tree for the given role */
     getMatchForRole(role)
     {
         /* 
          *   the topic is in the IndirectObject role; inherit the default
          *   for anything else 
          */
         return (role == IndirectObject ? topicMatch : inherited(role));
     }
 
     /*
      *   Manually set the resolved objects.  We'll set our direct and
      *   indirect objects.  
      */
     setResolvedObjects(dobj, topic)
     {
         /* inherit default handling for the direct object */
         inherited(dobj);
 
         /* set the resolved topic */
         setResolvedTopic(topic);
     }
 
     /* manually set the pre-resolved match trees */
     setObjectMatches(dobj, topic)
     {
         /* inherit default handling for the direct object */
         inherited(dobj);
         
         /* note the new topic match tree */
         setTopicMatch(topic);
     }
 
     /*
      *   Get the list of active objects.  We return only our direct
      *   object, since our topic isn't actually a simulation object.  
      */
     getCurrentObjects()
     {
         return [dobjCur_];
     }
 
     /* set the current objects */
     setCurrentObjects(lst)
     {
         dobjCur_ = lst[1];
         dobjInfoCur_ = nil;
     }
 
     /* the resolved topic object list */
     topicList_ = nil
 
     /* my cached topic resolver */
     topicResolver_ = nil
 
     /* grammatical role played by topic phrase in generated messages */
     whichMessageTopic = nil
 
     /* -------------------------------------------------------------------- */
     /*
      *   Direct Object Resolver implementation.  We serve as our own
      *   direct object resolver, so we define any special resolver
      *   behavior here.  
      */
 
     /* the true role of the resolved object is always as the direct object */
     whichObject = DirectObject
 
     /* 
      *   What we call our direct object might actually be playing the
      *   grammatical role of the indirect object - in order to inherit
      *   easily from TAction, we call our resolved object our direct
      *   object, regardless of which grammatical role it actually plays.
      *   For the most part it doesn't matter which is which; but for the
      *   purposes of our resolver, we actually do care about its real role.
      *   So, override the resolver method whichMessageObject so that it
      *   returns whichever role is NOT served by the topic object.  
      */
     whichMessageObject = (whichMessageTopic == DirectObject
                           ? IndirectObject : DirectObject)
 ;
 
 /*
  *   "Conversation" TopicTAction.  Many TopicTAction verbs involve
  *   conversation with an actor, who's specified as the direct object: ASK
  *   <actor> ABOUT <topic>, TELL <actor> ABOUT <topic>, ASK <actor> FOR
  *   <topic>.  For these common cases, the most likely default direct
  *   object is the last interlocutor of the actor performing the command -
  *   that is, ASK ABOUT BOOK should by default be directed to whomever we
  *   were speaking to last.
  *   
  *   This subclass is suitable for such verbs.  When asked for a default
  *   direct object, we'll check for a current interlocutor, and use it as
  *   the default if available.  If no interlocutor is available, we'll
  *   inherit the standard default handling.  
  */
 class ConvTopicTAction: TopicTAction
     getDefaultDobj(np, resolver)
     {
         local obj;
         
         /* 
          *   check to see if the actor has a default interlocutor; if so,
          *   use it as the default actor to be addressed here, otherwise
          *   use the default handling 
          */
         obj = resolver.getTargetActor().getCurrentInterlocutor();
         if (obj != nil)
             return [new ResolveInfo(obj, 0)];
         else
             return inherited(np, resolver);
     }
 
     /* 
      *   Create the topic resolver.  Use a conversational topic resolver
      *   for this type of action. 
      */
     createTopicResolver(issuingActor, targetActor)
     {
         return new ConvTopicResolver(self, issuingActor, targetActor,
                                      topicMatch, whichMessageTopic,
                                      getTopicQualifierResolver(
                                          issuingActor, targetActor, nil));
     }
 ;
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   A conversational intransitive action.  This class is for actions such
  *   as Hello, Goodbye, Yes, No - conversational actions that don't involve
  *   any topics or other objects, but whose expression is entirely
  *   contained in the verb.  
  */
 class ConvIAction: IAction
     /* this action is conversational */
     isConversational(issuer) { return true; }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Resolved Topic object.  The topic of a TopicTAction always resolves to
  *   one of these objects.  
  */
 class ResolvedTopic: object
     construct(inScope, likely, others, prod)
     {
         /*
          *   Remember our lists of objects.  We keep three separate lists,
          *   so that the action knows how we've classified the objects
          *   matching our phrase.  We keep a list of objects that are in
          *   scope; a list of objects that aren't in scope but which the
          *   actor thinks are likely topics; and a list of all of the
          *   other matches.
          *   
          *   We keep only the simulation objects from the lists - we don't
          *   keep the full ResolveInfo data.  
          */
         inScopeList = inScope.mapAll({x: x.obj_});
         likelyList = likely.mapAll({x: x.obj_});
         otherList = others.mapAll({x: x.obj_});
 
         /* keep the production match tree */
         topicProd = prod;
     }
 
     /* 
      *   Static method: create a ResolvedTopic to represent an object
      *   that's already been resolved to a game object for the current
      *   action.  'role' is the object role to wrap (DirectObject,
      *   IndirectObject, etc).  
      */
     wrapActionObject(role)
     {
         local rt;
         
         /* create a ResolvedTopic with just the match tree */
         rt = new ResolvedTopic([],  [], [], gAction.getMatchForRole(role));
 
         /* 
          *   add the current resolved object in the role as the one
          *   in-scope match 
          */
         rt.inScopeList += gAction.getObjectForRole(role);
 
         /* return the ResolvedTopic wrapper */
         return rt;
     }
 
     /*
      *   Static method: create a ResolvedTopic to represent the given
      *   object.
      */
     wrapObject(obj)
     {
         local rt;
         
         /* create a ResolvedTopic with a PreResolvedProd for the object */
         rt = new ResolvedTopic([], [], [], new PreResolvedProd(obj));
 
         /* add the given object or objects as the in-scope match list */
         rt.inScopeList += obj;
 
         /* return the ResolvedTopic wrapper */
         return rt;
     }
 
     /*
      *   Get the best object match to the topic.  This is a default
      *   implementation that can be changed by game authors or library
      *   extensions to implement different topic-matching strategies.  This
      *   implementation simply picks an object arbitrarily from the
      *   "strongest" of the three lists we build: if there's anything in
      *   the inScopeList, we choose an object from that list; otherwise, if
      *   there's anything in the likelyList, we choose an object from that
      *   list; otherwise we choose an object from the otherList.
      */
     getBestMatch()
     {
         /* if there's an in-scope match, pick one */
         if (inScopeList.length() != 0)
             return inScopeList[1];
 
         /* nothing's in scope, so try to pick a likely match */
         if (likelyList.length() != 0)
             return likelyList[1];
 
         /* 
          *   there's not even anything likely, so pick an "other", if
          *   there's anything in that list 
          */
         if (otherList.length() != 0)
             return otherList[1];
 
         /* there are no matches at all - return nil */
         return nil;
     }
 
     /* get the original text of the topic phrase */
     getTopicText() { return topicProd.getOrigText(); }
 
     /*
      *   Are we allowed to match the topic text literally, for parsing
      *   purposes?  If this is true, it means that we can match the literal
      *   text the player entered against strings, regular expressions,
      *   etc.; for example, we can match a TopicMatchTopic's matchPattern
      *   regular expression.  If this is nil, it means that we can only
      *   interpret the meaning of the resolved topic by looking at the
      *   various topic match lists (inScopeList, likelyList, otherList).
      *   
      *   By default, we simply return true.  Note that the base library
      *   never has any reason of its own to disallow literal matching of
      *   topic text; this property is purely for the use of language
      *   modules, to handle language-specific input that parses at a high
      *   level as a topic phrase but which has some idiomatic or
      *   grammatical function that makes it in appropriate to try to
      *   extract the meaning of the resolved topic from the literal text of
      *   the topic phrase in isolation.  This case doesn't seem to arise in
      *   English, but does occur in other languages: Michel Nizette cites
      *   "parlez-en a Bob" as an example in French, where "en" is
      *   essentially a particle modifying the verb, not a full-fledged
      *   phrase that we can interpret separately as a topic.  
      */
     canMatchLiterally = true
 
     /* 
      *   get the original tokens of the topic phrase, in canonical
      *   tokenizer format 
      */
     getTopicTokens() { return topicProd.getOrigTokenList(); }
 
     /*
      *   get the original word strings of the topic phrase - this is
      *   simply a list of the original word strings (in their original
      *   case), without any of the extra information of the more
      *   complicated canonical tokenizer format 
      */
     getTopicWords()
     {
         /* return just the original text strings from the token list */
         return topicProd.getOrigTokenList().mapAll({x: getTokOrig(x)});
     }
 
     /*
      *   Our lists of resolved objects matching the topic phrase,
      *   separated by classification.  
      */
     inScopeList = nil
     likelyList = nil
     otherList = nil
 
     /*
      *   The production match tree object that matched the topic phrase in
      *   the command.  This can be used to obtain the original tokens of
      *   the command or the original text of the phrase.  
      */
     topicProd = nil
 ;
 
 /*
  *   A special topic for "nothing."  It's occasionally useful to be able
  *   to construct a TopicTAction with an empty topic phrase.  For the
  *   topic phrase to be well-formed, we need a valid ResolvedTopic object,
  *   even though it won't refer to anything. 
  */
 resolvedTopicNothing: ResolvedTopic
     inScopeList = []
     likelyList = []
     otherList = []
 
     getTopicText() { return ''; }
     getTopicTokens() { return []; }
     getTopicWords() { return []; }
 ;
 
 /*
  *   Topic Resolver
  */
 class TopicResolver: Resolver
     construct(action, issuingActor, targetActor, prod, which,
               qualifierResolver)
     {
         /* inherit the base class constructor */
         inherited(action, issuingActor, targetActor);
 
         /* the topic phrase doesn't have a true resolved role */
         whichObject = nil;
 
         /* remember the grammatical role of our object in the command */
         whichMessageObject = which;
 
         /* remember our topic match tree */
         topicProd = prod;
 
         /* remember the resolver for qualifier phrases */
         qualifierResolver_ = qualifierResolver;
     }
 
     resetResolver()
     {
         /* inherit the default handling */
         inherited();
 
         /* reset our qualifier resolver as well */
         qualifierResolver_.resetResolver();
     }
 
     /* our qualifier resolver */
     qualifierResolver_ = nil
 
     /* get our qualifier resolver */
     getQualifierResolver() { return qualifierResolver_; }
     getPossessiveResolver() { return qualifierResolver_; }
 
     /*
      *   Determine if the object is in scope.  We consider any vocabulary
      *   match to be in scope for the purposes of a topic phrase, since the
      *   subject matter of topics is mere references to things, not the
      *   things themselves; we can, for example, ASK ABOUT things that
      *   aren't physically present, or even about completely abstract
      *   ideas.  
      */
     objInScope(obj) { return true; }
 
     /* 
      *   our scope is global, because we don't limit the scope to the
      *   physical senses 
      */
     isGlobalScope = true
 
     /*
      *   Determine if an object is in physical scope.  We'll accept
      *   anything that's in physical scope, and we'll also accept any topic
      *   object that the actor knows about.
      *   
      *   Note that this isn't part of the basic Resolver interface.  It's
      *   instead provided as a service routine for our subclasses, so that
      *   they can easily determine the physical scope of an object if
      *   needed.  
      */
     objInPhysicalScope(obj)
     {
         /* 
          *   it's in physical scope, or if the actor knows about it as a
          *   topic, it's in scope 
          */
         return (scope_.indexOf(obj) != nil);
     }
 
     /*
      *   Filter an ambiguous noun list.
      *   
      *   It is almost always undesirable from a user interface perspective
      *   to ask for help disambiguating a topic phrase.  In the first
      *   place, since all topics tend to be in scope all the time, we
      *   might reveal too much about the inner model of the story if we
      *   were to enumerate all of the topic matches to a phrase.  In the
      *   second place, topics are used in conversational contexts, so it
      *   almost never make sense for the parser to ask for clarification -
      *   the other member of the conversation might ask, but not the
      *   parser.  So, we'll always filter the list to the required number,
      *   even if it means we choose arbitrarily.
      *   
      *   As a first cut, we prefer objects that are physically in scope to
      *   those not in scope: if the player is standing next to a control
      *   panel and types "ask bob about control panel," it makes little
      *   sense to consider any other control panels in the simulation.
      *   
      *   As a second cut, we'll ask the actor to filter the list.  Games
      *   that keep track of the actor's knowledge can use this to filter
      *   according to topics the actor is likely to know about.  
      */
     filterAmbiguousNounPhrase(lst, requiredNum, np)
     {
         local rt;
             
         /* ask the action to create the ResolvedTopic */
         rt = resolveTopic(lst, requiredNum, np);
 
         /* wrap the ResolvedTopic in the usual ResolveInfo list */
         return [new ResolveInfo(rt, 0)];
     }
         
     /*
      *   Resolve the topic phrase.  This returns a ResolvedTopic object
      *   encapsulating the resolution of the phrase.
      *   
      *   This default base class implementation simply creates a resolved
      *   topic list with the whole set of possible matches
      *   undifferentiated.  Subclasses for specialized actions might want
      *   to differentiate the items in the list, based on things like the
      *   actor's knowledge so far or what's in physical scope.  
      */
     resolveTopic(lst, requiredNum, np)
     {
         /* return a ResolvedTopic that lumps everything in the main list */
         return new ResolvedTopic(lst, [], [], topicProd);
     }
 
     /*
      *   Resolve an unknown phrase.  We allow unknown words to be used in
      *   topics; we simply return a ResolvedTopic that doesn't refer to
      *   any simulation objects at all.  
      */
     resolveUnknownNounPhrase(tokList)
     {
         local rt;
         
         /* 
          *   Create our ResolvedTopic object for the results.  We have
          *   words we don't know, so we're not referring to any objects,
          *   so our underlying simulation object list is empty.  
          */
         rt = new ResolvedTopic([], [], [], topicProd);
 
         /* return a resolved topic object with the empty list */
         return [new ResolveInfo(rt, 0)];
     }
 
     /* filter a plural */
     filterPluralPhrase(lst, np)
     {
         /*
          *   Handle this the same way we handle an ambiguous noun phrase,
          *   so that we yield only one object.  Topics are inherently
          *   singular; we'll allow asking about a grammatically plural
          *   term, but we'll turn it into a single topic result.  
          */
         return filterAmbiguousNounPhrase(lst, 1, np);
     }
 
     /* get a default object */
     getDefaultObject(np)
     {
         /* there is never a default for a topic */
         return nil;
     }
 
     /* it's fine not to match a topic phrase */
     noVocabMatch(action, txt) { }
     noMatch(action, txt) { }
 
     /* we don't allow ALL or provide defaults */
     getAll(np) { return []; }
     getAllDefaults() { return []; }
 
     /* the production match tree for the topic phrase we're resolving */
     topicProd = nil
 ;
 
 /*
  *   A topic resolver specialized for TopicTActions - actions involving a
  *   topic and a physical object, such as CONSULT ABOUT.  For these topics,
  *   we'll let the action handle the resolution.  
  */
 class TActionTopicResolver: TopicResolver
     resolveTopic(lst, requiredNum, np)
     {
         /* let the action handle it */
         return action_.filterTopic(lst, np, self);
     }
 ;
 
 /*
  *   A topic resolver specialized for conversational actions (ASK ABOUT,
  *   TELL ABOUT, etc).  When we resolve the topic, we'll differentiate the
  *   resolution to differentiate based on the knowledge of the actor who's
  *   performing the command.  
  */
 class ConvTopicResolver: TopicResolver
     /*
      *   Resolve the topic phrase.  We'll break up the vocabulary matches
      *   into three sublists: the objects that are either in physical scope
      *   or known to the actor performing the command; objects that the
      *   actor considers likely topics; and everything else.  
      */
     resolveTopic(lst, requiredNum, np)
     {
         local inScope;
         local actorPrefs;
 
         /* 
          *   First, get the subset of items that are in conversational
          *   scope - we'll consider this the best set of matches.  
          */
         inScope = lst.subset({x: objInConvScope(x.obj_)});
 
         /* 
          *   eliminate the in-scope items from the list, so we can
          *   consider only what remains 
          */
         lst -= inScope;
 
         /* 
          *   ask the actor to pick out the most likely set of topics from
          *   the ones that remain 
          */
         actorPrefs = lst.subset({x: actor_.isLikelyTopic(x.obj_)});
 
         /* eliminate those items from the list */
         lst -= actorPrefs;
 
         /* create our ResolvedTopic object and return it */
         return new ResolvedTopic(inScope, actorPrefs, lst, topicProd);
     }
 
     /*
      *   Determine if an object is in "conversational" scope - this returns
      *   true if the object is in physical scope or it's known to the actor
      *   performing the command. 
      */
     objInConvScope(obj)
     {
         /* 
          *   if it's in physical scope, or the actor knows about it, it's
          *   in conversation scope 
          */
         return (objInPhysicalScope(obj) || actor_.knowsTopic(obj));
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   System action.  These actions are for out-of-game meta-verbs (save,
  *   restore, undo).  These verbs take no objects, must be performed by
  *   the player (thus by the player character, not an NPC), and consume no
  *   game clock time.  
  */
 class SystemAction: IAction
     /* execute the action */
     execAction()
     {
         /* 
          *   Conceptually, system actions are performed by the player
          *   directly, not by any character in the game (not even the
          *   player character).  However, we don't distinguish in the
          *   command-entry user interface between a command the player is
          *   performing and a command to the player character, hence we
          *   can merely ensure that the command is not directed to a
          *   non-player character. 
          */
         if (!gActor.isPlayerChar)
         {
             gLibMessages.systemActionToNPC();
             exit;
         }
 
         /* 
          *   system actions sometimes need to prompt for interactive
          *   responses, so deactivate the report list - this will allow
          *   interactive prompts to be shown immediately, not treated as
          *   reports to be deferred until the command is finished 
          */
         gTranscript.showReports(true);
         gTranscript.clearReports();
 
         /* perform our specific action */
         execSystemAction();
 
         /* re-activate the transcript for the next command */
         gTranscript.activate();
     }
 
     /* each subclass must override this to perform its actual action */
     execSystemAction() { }
 
     /*
      *   Ask for an input file.  Freezes the real-time event clock for the
      *   duration of reading the event, and sets our property
      *   origElapsedTime to the elapsed time when we started processing
      *   the interaction.  
      */
     getInputFile(prompt, dialogType, fileType, flags)
     {
         local result;
         
         /* 
          *   note the game elapsed time before we start - we want to
          *   freeze the real-time clock while we're waiting for the user
          *   to respond, since this system verb exists outside of the
          *   usual time flow of the game 
          */
         origElapsedTime = realTimeManager.getElapsedTime();
         
         /* ask for a file */
         result = inputFile(prompt, dialogType, fileType, flags);
 
         /* 
          *   restore the game real-time counter to what it was before we
          *   started the interactive response 
          */
         realTimeManager.setElapsedTime(origElapsedTime);
 
         /* return the result from inputFile */
         return result;
     }
 
     /* system actions consume no game time */
     actionTime = 0
 
     /* real-time event clock at start of getInputFile() */
     origElapsedTime = nil
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   An exception class for remappings that we can't handle. 
  */
 class ActionRemappingTooComplexError: Exception
 ;
 
TADS 3 Library Manual
Generated on 9/8/2006 from TADS version 3.0.11