resolver.t

documentation
 #charset "us-ascii"
 
 /* 
  *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
  *   
  *   TADS 3 Library: Resolvers.
  *   
  *   This module defines the Resolver classes.  A Resolver is an abstract
  *   object that the parser uses to control the resolution of noun phrases
  *   to game objects.  Specialized Resolver subclasses allow noun phrases
  *   to be resolved differently according to their grammatical function in
  *   a command.  
  */
 
 #include "adv3.h"
 
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Basic object resolver.  An Action object creates an object resolver
  *   to mediate the process of resolving noun phrases to objects.
  *   
  *   A resolver encapsulates a set of object resolution rules.  In most
  *   cases, an action that takes only a direct object can be its own
  *   resolver, because it needs only one set of resolution rules; for this
  *   reason, this basic Resolver implementation is designed to work with
  *   the direct object.  Actions with multiple objects will need separate
  *   resolvers for each object, since they might want to use different
  *   rules for the different objects.  
  */
 class Resolver: object
     construct(action, issuingActor, targetActor)
     {
         /* remember my action and actor objects */
         action_ = action;
         issuer_ = issuingActor;
         actor_ = targetActor;
 
         /* cache the scope list */
         cacheScopeList();
     }
 
     /* 
      *   Are we a sub-phrase resolver?  This should return true if we're
      *   being used to resolve a sub-phrase of the main phrase. 
      */
     isSubResolver = nil
 
     /* 
      *   Reset the resolver - this can be called if we are to re-use the
      *   same resolver to resolve a list of noun phrases again.
      */
     resetResolver()
     {
         /* forget the equivalents we've resolved so far */
         equivs_ = nil;
     }
 
     /* get the action we're resolving */
     getAction() { return action_; }
 
     /* get the target actor */
     getTargetActor() { return actor_; }
 
     /*
      *   Match an object's name.  By default, we'll call the object's own
      *   matchName method with the given original and adjusted token
      *   lists.  Subclasses can override this to call different match
      *   methods (such as matchNameDisambig).  
      */
     matchName(obj, origTokens, adjustedTokens)
     {
         return obj.matchName(origTokens, adjustedTokens);
     }
     
     /*
      *   Cache the scope list for this object.  By default, we cache the
      *   standard scope list for our target actor.
      *   
      *   Note that if a subclass uses completely different rules for
      *   determining scope, it need actually store a scope_ list at all.
      *   The scope_ list is purely an implementation detail of the base
      *   Resolver class.  A subclass can use whatever internal
      *   implementation it wants, as long as it overrides objInScope() and
      *   getScopeList() to return consistent results.  
      */
     cacheScopeList()
     {
         /* cache our actor's default scope list */
         scope_ = actor_.scopeList();
     }
 
     /*
      *   Get the resolver for qualifier phrases.  By default, this simply
      *   returns myself, since the resolver for qualifiers is in most
      *   contexts the same as the main resolver.
      *   
      *   This can be overridden in contexts where the qualifier resolver
      *   is different from the main resolver.  In general, when a
      *   sub-resolver narrows the scope for resolving a phrase, such as an
      *   exclusion list or a disambiguation response, we will want to
      *   resolve qualifiers in the context of the main resolution scope
      *   rather than the narrowed scope.  
      */
     getQualifierResolver() { return self; }
 
     /*
      *   Get the resolver for possessive phrases.  By default, we return a
      *   standard possessive resolver.  This can be overridden in contexts
      *   wher ethe possesive resolution context is special.  
      */
     getPossessiveResolver() { return new PossessiveResolver(self); }
 
     /*
      *   Determine if an object is in scope for the purposes of object
      *   resolution.  By default, we'll simply check to see if the object
      *   is in our cached scope list.
      *   
      *   Some subclasses might want to override this to provide alternative
      *   behavior; for example, for a command whose scope is the set of all
      *   objects, it would be much more efficient to dispense with caching
      *   a scope list entirely and simply return true here.  Note that if a
      *   subclass overrides this using some mechanism other than a cached
      *   scope list, the subclass must also override getScopeList() to
      *   return results consistent with this method.  
      */
     objInScope(obj) { return scope_.indexOf(obj) != nil; }
 
     /*
      *   Get the full list of objects in scope.  By default, this simply
      *   returns our cached scope list.  If a subclass doesn't use a cached
      *   scope list, it must override objInScope() and getScopeList() to
      *   return consistent results. 
      */
     getScopeList() { return scope_; }
 
     /*
      *   Is this a "global" scope?  By default, the scope is local: it's
      *   limited to what the actor can see, hear, etc.  In some cases, the
      *   scope is broader, and extends beyond the senses; we call those
      *   cases global scope.
      *   
      *   This is an advisory status only.  The caller musn't take this to
      *   mean that everything is in scope; objInScope() and getScopeList()
      *   must still be used to make the exact determination of what objects
      *   are in scope.  However, some noun phrase productions might wish to
      *   know generally whether we're in a local or global sort of scope,
      *   so that they can adjust their zeal at reducing ambiguity.  In
      *   cases of global scope, we generally want to be more inclusive of
      *   possible matches than in local scopes, because we have much less
      *   of a basis to guess about what the player might mean.
      */
     isGlobalScope = nil
 
     /*
      *   Get the binding for a reflexive third-person pronoun (himself,
      *   herself, itself, themselves).  By default, the reflexive binding
      *   is the anaphoric binding from the action - that is, it refers
      *   back to the preceding noun phrase in a verb phrase with multiple
      *   noun slots (as in ASK BOB ABOUT HIMSELF: 'himself' refers back to
      *   'bob', the previous noun phrase).  
      */
     getReflexiveBinding(typ) { return getAction().getAnaphoricBinding(typ); }
 
     /*
      *   Resolve a pronoun antecedent, given a pronoun selector.  This
      *   returns a list of ResolveInfo objects, for use in object
      *   resolution.  'poss' is true if this is a possessive pronoun (his,
      *   her, its, etc), nil if it's an ordinary, non-possessive pronoun
      *   (him, her, it, etc).  
      */
     resolvePronounAntecedent(typ, np, results, poss)
     {
         local lst;
         local scopeLst;
 
         /* get the raw antecedent list */
         lst = getRawPronounAntecedent(typ);
 
         /* if there is no antecedent, return an empty list */
         if (lst != nil && lst != [])
         {
             local cur;
 
             /* if it's a single object, turn it into a list */
             if (dataType(lst) == TypeObject)
                 lst = [lst];
 
             /* add any extra objects for the pronoun binding */
             foreach (cur in lst)
                 lst = cur.expandPronounList(typ, lst);
 
             /* filter the list to keep only in-scope objects */
             scopeLst = new Vector(lst.length());
             foreach (cur in lst)
             {
                 local facets;
                 
                 /* get the object's facets */
                 facets = cur.getFacets();
 
                 /* 
                  *   If it has any, pick the best one that's in scope.  If
                  *   not, keep the object only if it's in scope. 
                  */
                 if (facets.length() != 0)
                 {
                     local best;
                     
                     /* 
                      *   This object has other facets, so we want to
                      *   consider the other in-scope facets in case any are
                      *   more suitable than the original one.  For example,
                      *   we might have just referred to a door, and then
                      *   traveled through the door to an adjoining room.
                      *   We now want the antecedent to be the side (facet)
                      *   of the door that's in the new location.  
                      */
 
                     /* get the in-scope subset of the facets */
                     facets = (facets + cur).subset({x: objInScope(x)});
 
                     /* keep the best facet from the list */
                     best = findBestFacet(actor_, facets);
 
                     /* 
                      *   If we found a winner, use it instead of the
                      *   original.  
                      */
                     if (best != nil)
                         cur = best;
                 }
 
                 /* if the object is in scope, include it in the results */
                 if (objInScope(cur))
                     scopeLst.append(cur);
             }
 
             /* create a list of ResolveInfo objects from the antecedents */
             lst = scopeLst.toList().mapAll({x: new ResolveInfo(x, 0)});
         }
 
         /* 
          *   If there's nothing matching in scope, try to find a default.
          *   Look to see if there's a unique default object matching the
          *   pronoun, and select it if so. 
          */
         if (lst == nil || lst == [])
             lst = getPronounDefault(typ, np);
         
         /* run the normal resolution list filtering on the list */
         lst = action_.finishResolveList(lst, whichObject, np, nil);
 
         /* return the result */
         return lst;
     }
 
     /*
      *   Get the "raw" pronoun antecedent list for a given pronoun
      *   selector.  This returns a list of objects matching the pronoun.
      *   The list is raw in that it is given as a list of game objects
      *   (not ResolveInfo objects), and it isn't filtered for scope.  
      */
     getRawPronounAntecedent(typ)
     {
         /* check for pronouns that are relative to the issuer or target */
         switch(typ)
         {
         case PronounMe:
             /*
              *   It's a first-person construction.  If the issuing actor
              *   is the player character, this refers to the player
              *   character only if the game refers to the player character
              *   in the second person (so, if the game calls the PC "you",
              *   the player calls the PC "me").  If the issuing actor
              *   isn't the player character, then a first-person pronoun
              *   refers to the command's issuer.  
              */
             if (issuer_.isPlayerChar
                 && issuer_.referralPerson != SecondPerson
                 && !gameMain.allowYouMeMixing)
             {
                 /* 
                  *   the issuer is the player, but the game doesn't call
                  *   the PC "you", so "me" has no meaning 
                  */
                 return [];
             }
             else
             {
                 /* "me" refers to the command's issuer */
                 return [issuer_];
             }
 
         case PronounYou:
             /*
              *   It's a second-person construction.  If the target actor is
              *   the player character, this refers to the player character
              *   only if the game refers to the player character in the
              *   first person (so, if the game calls the PC "me", then the
              *   player calls the PC "you").
              *   
              *   If the target actor isn't the player character, then a
              *   second-person pronoun refers to either the target actor or
              *   to the player character, depending on the referral person
              *   of the current command that's targeting the actor.  If the
              *   command is in the second person, then a second-person
              *   pronoun refers to the actor ("bob, hit you" means for Bob
              *   to hit himself).  If the command is in the third person,
              *   then a second-person pronoun is a bit weird, but probably
              *   refers to the player character ("tell bob to hit you"
              *   means for Bob to hit the PC).  
              */
             if (actor_.isPlayerChar
                 && actor_.referralPerson != FirstPerson
                 && !gameMain.allowYouMeMixing)
             {
                 /* 
                  *   the target is the player character, but the game
                  *   doesn't call the PC "me", so "you" has no meaning in
                  *   this command 
                  */
                 return [];
             }
             else if (actor_.commandReferralPerson == ThirdPerson)
             {
                 /* 
                  *   we're addressing the actor in the third person, so YOU
                  *   probably doesn't refer to the target actor; the only
                  *   other real possibility is that it refers to the player
                  *   character 
                  */
                 return [gPlayerChar];
             }
             else
             {
                 /* in other cases, "you" refers to the command's target */
                 return [actor_];
             }
 
         default:
             /* 
              *   it's not a relative pronoun, so ask the target actor for
              *   the antecedent based on recent commands 
              */
             return actor_.getPronounAntecedent(typ);
         }
     }
 
     /*
      *   Determine if "all" is allowed for the noun phrase we're resolving.
      *   By default, we'll just ask the action.  
      */
     allowAll()
     {
         /* ask the action to determine whether or not "all" is allowed */
         return action_.actionAllowsAll;
     }
 
     /*
      *   Get the "all" list - this is the list of objects that we should
      *   use when the object of the command is the special word "all".
      *   We'll ask the action to resolve 'all' for the direct object,
      *   since we are by default a direct object resolver.
      */
     getAll(np)
     {
         /* 
          *   ask the action to resolve 'all' for the direct object, and
          *   then filter the list and return the result 
          */
         return filterAll(action_.getAllDobj(actor_, getScopeList()),
                          DirectObject, np);
     }
 
     /*
      *   Filter an 'all' list to remove things that don't belong.  We
      *   always remove the actor executing the command, as well as any
      *   objects explicitly marked as hidden from 'all' lists.
      *   
      *   Returns a ResolveInfo list, with each entry marked with the
      *   MatchedAll flag.  
      */
     filterAll(lst, whichObj, np)
     {
         local result;
 
         /* set up a vector to hold the result */
         result = new Vector(lst.length());
 
         /* 
          *   run through the list and include elements that we don't want
          *   to exclude 
          */
         foreach (local cur in lst)
         {
             /* 
              *   if this item isn't the actor, and isn't marked for
              *   exclusion from 'all' lists in general, include it 
              */
             if (cur != actor_ && !cur.hideFromAll(getAction()))
                 result.append(cur);
         }
 
         /* 
          *   create a ResolveInfo for each object, with the 'MatchedAll'
          *   flag set for each object 
          */
         result.applyAll({x: new ResolveInfo(x, MatchedAll)});
 
         /* run through the list and apply each object's own filtering */
         result = getAction().finishResolveList(result, whichObject, np, nil);
 
         /* return the result as a list */
         return result.toList();
     }
 
     /*
      *   Get the list of potential default objects.  This is simply the
      *   basic 'all' list, not filtered for exclusion with hideFromAll.  
      */
     getAllDefaults()
     {
         local lst;
         
         /* ask the action to resolve 'all' for the direct object */
         lst = action_.getAllDobj(actor_, getScopeList());
 
         /* return the results as ResolveInfo objects */
         return lst.mapAll({x: new ResolveInfo(x, 0)});
     }
 
     /*
      *   Filter an ambiguous list of objects ('lst') resolving to a noun
      *   phrase.  If the objects in the list vary in the degree of
      *   suitability for the command, returns a list consisting only of the
      *   most suitable objects.  If the objects are all equally suitable -
      *   or equally unsuitable - the whole list should be returned
      *   unchanged.
      *   
      *   'requiredNum' is the number of objects required in the final list
      *   by the caller; if the result list is larger than this, the caller
      *   will consider the results ambiguous.
      *   
      *   'np' is the noun phrase production that we're resolving.  This is
      *   usually a subclass of NounPhraseProd.
      *   
      *   This routine does NOT perform any interactive disambiguation, but
      *   is merely a first attempt at reducing the number of matching
      *   objects by removing the obviously unsuitable ones.
      *   
      *   For example, for an "open" command, if the list consists of one
      *   object that's open and one object that's currently closed, the
      *   result list should include only the closed one, since it is
      *   obvious that the one that's already open does not need to be
      *   opened again.  On the other hand, if the list consists only of
      *   open objects, they should all be returned, since they're all
      *   equally unsuitable.
      *   
      *   It is not necessary to reduce the list to a single entry; it is
      *   adequate merely to reduce the ambiguity by removing any items that
      *   are clearly less suitable than the survivors.  
      */
     filterAmbiguousNounPhrase(lst, requiredNum, np)
     {
         return withGlobals(
             {:action_.filterAmbiguousDobj(lst, requiredNum, np)});
     }
 
     /*
      *   Filter a list of ambiguous matches ('lst') for a noun phrase, to
      *   reduce each set of equivalent items to a single such item, if
      *   desired.  If no equivalent reduction is desired for this type of
      *   resolver, this can simply return the original list.
      *   
      *   'np' is the noun phrase production that we're resolving.  This is
      *   usually a subclass of NounPhraseProd.  
      */
     filterAmbiguousEquivalents(lst, np)
     {
         /* if we have only one item, there's obviously nothing redundant */
         if (lst.length() == 1)
             return lst;
         
         /* scan the list, looking for equivalents */
         for (local i = 1, local len = lst.length() ; i <= len ; ++i)
         {
             /* 
              *   if this item is marked as equivalent, check for others
              *   like it 
              */
             if (lst[i].obj_.isEquivalent)
             {
                 /*
                  *   If this object is in our list of previously-used
                  *   equivalents, and we have more equivalents to this
                  *   object in our list, then omit this one, so that we
                  *   keep a different equivalent this time.  This way, if
                  *   we have a noun list such as "take coin and coin",
                  *   we'll return different equivalent items for each
                  *   equivalent noun phrase. 
                  */
                 if (equivs_ != nil
                     && equivs_.indexOf(lst[i].obj_) != nil
                     && lst.lastIndexWhich(
                        {x: x.obj_.isVocabEquivalent(lst[i].obj_)}) > i)
                 {
                     /* 
                      *   we've already returned this one, and we have
                      *   another equivalent later in the list that we can
                      *   use instead this time - remove this one from the
                      *   list 
                      */
                     lst = lst.removeElementAt(i);
 
                     /* adjust the our counters for the removal */
                     --len;
                     --i;
                 }
                 else
                 {
                     /*
                      *   We've decided to keep this element, either
                      *   because we haven't already returned it as a match
                      *   for this noun phrase, or because it's the last
                      *   one of its kind.  Add it to the list of
                      *   equivalents we've previously returned. 
                      */
                     if (equivs_ == nil)
                         equivs_ = new Vector(10);
                     equivs_.append(lst[i].obj_);
 
                     /* 
                      *   check each object at a higher index to see if
                      *   it's equivalent to this one 
                      */
                     for (local j = i + 1 ; j <= len ; ++j)
                     {
                         /* check this object */
                         if (lst[i].obj_.isVocabEquivalent(lst[j].obj_))
                         {
                             /* they match - remove the other one */
                             lst = lst.removeElementAt(j);
                             
                             /* reduce the list length accordingly */
                             --len;
                             
                             /* back up our scanning index as well */
                             --j;
                         }
                     }
                 }
             }
         }
 
         /* return the updated list */
         return lst;
     }
 
     /*
      *   Filter a plural phrase to reduce the set to the logical subset, if
      *   possible.  If there is no logical subset, simply return the
      *   original set.
      *   
      *   'np' is the noun phrase we're resolving; this is usually a
      *   subclass of PluralProd.  
      */
     filterPluralPhrase(lst, np)
     {
         return withGlobals({:action_.filterPluralDobj(lst, np)});
     }
 
     /*
      *   Select a resolution for an indefinite noun phrase ("a coin"),
      *   given a list of possible matches.  The matches will be given to
      *   us sorted from most likely to least likely, as done by
      *   filterAmbiguousNounPhrase().
      *   
      *   By default, we simply select the first 'n' items from the list
      *   (which are the most likely matches), because in most contexts, an
      *   indefinite noun phrase means that we should arbitrarily select
      *   any matching object.  This can be overridden for contexts in
      *   which indefinite noun phrases must be handled differently.  
      */
     selectIndefinite(results, lst, requiredNumber)
     {
         /* 
          *   arbitrarily choose the first 'requiredNumber' item(s) from
          *   the list 
          */
         return lst.sublist(1, requiredNumber);
     }
 
     /*
      *   Get the default object or objects for this phrase.  Returns a list
      *   of ResolveInfo objects if a default is available, or nil if no
      *   default is available.  This routine does not interact with the
      *   user; it should merely determine if the command implies a default
      *   strongly enough to assume it without asking the user.
      *   
      *   By default, we ask the action for a default direct object.
      *   Resolver subclasses should override this as appropriate for the
      *   specific objects they're used to resolve.  
      */
     getDefaultObject(np)
     {
         /* ask the action to provide a default direct object */
         return withGlobals({:action_.getDefaultDobj(np, self)});
     }
 
     /*
      *   Resolve a noun phrase involving unknown words, if possible.  If
      *   it is not possible to resolve such a phrase, return nil;
      *   otherwise, return a list of resolved objects.  This routine does
      *   not interact with the user - "oops" prompting is handled
      *   separately.
      *   
      *   'tokList' is the token list for the phrase, in the canonical
      *   format as returned from the tokenizer.  Each element of 'tokList'
      *   is a sublist representing one token.
      *   
      *   Note that this routine allows for specialized unknown word
      *   resolution separately from the more general matchName mechanism.
      *   The purpose of this method is to allow the specific type of
      *   resolver to deal with unknown words specially, rather than using
      *   the matchName mechanism.  This routine is called as a last
      *   resort, only after the matchName mechanism fails to find any
      *   matches.  
      */
     resolveUnknownNounPhrase(tokList)
     {
         /* by default, we can't resolve an unknown noun phrase */
         return nil;
     }
 
     /*
      *   Execute a callback function in the global context of our actor
      *   and action - we'll set gActor and gAction to our own stored actor
      *   and action values, then call the callback, then restore the old
      *   globals. 
      */
     withGlobals(func)
     {
         /* invoke the function with our action and actor in the globals */
         return withParserGlobals(issuer_, actor_, action_, func);
     }
 
     /* the role played by this object, if any */
     whichObject = DirectObject
 
     /*
      *   Get an indication of which object we're resolving, for message
      *   generation purposes.  By default, we'll indicate direct object;
      *   this should be overridden for resolvers of indirect and other
      *   types of objects.  
      */
     whichMessageObject = DirectObject
 
     /* 
      *   The cached scope list, if we have one.  Note that this is an
      *   internal implementation detail of the base class; subclasses can
      *   dispense with the cached scope list if they define their own
      *   objInScope() and getScopeList() overrides.
      */
     scope_ = []
 
     /* my action */
     action_ = nil
 
     /* the issuing actor */
     issuer_ = nil
 
     /* the target actor object */
     actor_ = nil
 
     /* 
      *   List of equivalent objects we've resolved so far.  We use this to
      *   try to return different equivalent objects when multiple noun
      *   phrases refer to the same set of equivalents. 
      */
     equivs_ = nil
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Proxy Resolver - this is used to create resolvers that refer methods
  *   not otherwise overridden back to an underlying resolver 
  */
 class ProxyResolver: object
     construct(origResolver)
     {
         /* remember my underlying resolver */
         self.origResolver = origResolver;
     }
 
     /* delegate methods we don't override to the underlying resolver */
     propNotDefined(prop, [args])
     {
         /* delegate the call to the original resolver */
         return origResolver.(prop)(args...);
     }
 
     /* base our possessive resolver on the proxy */
     getPossessiveResolver() { return new PossessiveResolver(self); }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Basic resolver for indirect objects 
  */
 class IobjResolver: Resolver
     /* 
      *   we resolve indirect objects for message generation purposes
      */
     whichObject = IndirectObject
     whichMessageObject = IndirectObject
 
     /* resolve 'all' for the indirect object */
     getAll(np)
     {
         /* 
          *   ask the action to resolve 'all' for the indirect object, and
          *   then filter the list and return the result 
          */
         return filterAll(action_.getAllIobj(actor_, getScopeList()),
                          IndirectObject, np);
     }
 
     /* get all possible default objects */
     getAllDefaults()
     {
         local lst;
         
         /* ask the action to resolve 'all' for the indirect object */
         lst = action_.getAllIobj(actor_, getScopeList());
 
         /* return the results as ResolveInfo objects */
         return lst.mapAll({x: new ResolveInfo(x, 0)});
     }
 
     /* filter an ambiguous noun phrase */
     filterAmbiguousNounPhrase(lst, requiredNum, np)
     {
         return withGlobals(
             {:action_.filterAmbiguousIobj(lst, requiredNum, np)});
     }
 
     /*
      *   Filter a plural phrase to reduce the set to the logical subset,
      *   if possible.  If there is no logical subset, simply return the
      *   original set.  
      */
     filterPluralPhrase(lst, np)
     {
         return withGlobals({:action_.filterPluralIobj(lst, np)});
     }
 
     /*
      *   Get the default object or objects for this phrase.  Since we
      *   resolve indirect objects, we'll ask the action for a default
      *   indirect object.  
      */
     getDefaultObject(np)
     {
         /* ask the action to provide a default indirect object */
         return withGlobals({:action_.getDefaultIobj(np, self)});
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Basic topic qualifier resolver.  This can be used to resolve qualifier
  *   phrases (such as possessives or locationals) within topic phrases.
  */
 class TopicQualifierResolver: Resolver
     getAll(np)
     {
         /* 'all' doesn't make sense as a qualifier; return an empty list */
         return [];
     }
 
     getAllDefaults()
     {
         /* we don't need defaults for a qualifier */
         return [];
     }
 
     filterAmbiguousNounPhrase(lst, requiredNum, np)
     {
         /* we have no basis for any filtering; return the list unchanged */
         return lst;
     }
 
     filterPluralPhrase(lst, np)
     {
         /* we have no basis for any filtering */
         return lst;
     }
 
     getDefaultObject(np)
     {
         /* have have no way to pick a default */
         return nil;
     }
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   Actor Resolver.  We use this to resolve the actor to whom a command
  *   is directed: the actor must be in scope for the player character.  
  */
 class ActorResolver: Resolver
     construct(issuingActor)
     {
         /* remember the issuing actor */
         actor_ = issuingActor;
 
         /* 
          *   Use our pseudo-action for "command actor" - this represents
          *   the intermediate step where the issuing actor is doing
          *   whatever physical activity is needed (such as talking) to give
          *   the command to the target actor.  This isn't a real action;
          *   it's just an implied intermediate step in the overall action.
          *   We need this mostly because there are assumptions elsewhere in
          *   the resolution process that there's a valid Action object
          *   available.  
          */
         action_ = CommandActorAction;
 
         /* ...and the action needs an actor */
         action_.actor_ = actor_;
 
         /* cache the scope list for the actor who issued the command */
         cacheScopeList();
     }
 
     /*
      *   Get the "all" list - this is the list of objects that we should
      *   use when the object of the command is the special word "all".  By
      *   default, we'll return everything in scope.  
      */
     getAll(np)
     {
         /* we can't address 'all' */
         throw new ParseFailureException(&cannotAddressMultiple);
     }
 
     /* get the default object list */
     getAllDefaults()
     {
         /* there are no default actors */
         return [];
     }
 
     /*
      *   Filter an ambiguous list of objects.  We will filter according to
      *   which objects are most logical as targets of commands.  
      */
     filterAmbiguousNounPhrase(lst, requiredNum, np)
     {
         local likelyCnt;
 
         /* give each object in the list a chance to filter the list */
         lst = getAction().finishResolveList(lst, ActorObject,
                                             np, requiredNum);
         
         /*
          *   Run through the list and see how many objects are likely
          *   command targets.  
          */
         likelyCnt = 0;
         foreach (local cur in lst)
         {
             /* if it's a likely command target, count it */
             if (cur.obj_.isLikelyCommandTarget)
                 ++likelyCnt;
         }
 
         /* 
          *   If some of the targets are likely and others aren't, and we
          *   have at least the required number of likely targets, keep
          *   only the likely ones.  If they're all likely or all unlikely,
          *   it doesn't help us because we still have no basis for
          *   choosing some over others; if removing unlikely ones would
          *   not give us enough to meet the minimum number required it
          *   also doesn't help, because we don't have a basis for
          *   selecting as many as are needed.  
          */
         if (likelyCnt != 0 && likelyCnt != lst.length()
             && likelyCnt >= requiredNum)
         {
             /* 
              *   we have a useful subset of likely ones - filter the list
              *   down to the likely subset 
              */
             lst = lst.subset({cur: cur.obj_.isLikelyCommandTarget});
         }
 
         /* return the result */
         return lst;
     }
 
     /*
      *   Filter a plural list 
      */
     filterPluralPhrase(lst, np)
     {
         /* 
          *   Use the same filtering that we use for ambiguous nouns.  This
          *   simply reduces the set to the likely command targets if any
          *   are likely command targets. 
          */
         return filterAmbiguousNounPhrase(lst, 1, np);
     }
 
     /* get a default object */
     getDefaultObject(np)
     {
         /* there is never a default for the target actor */
         return nil;
     }
 
     /* resolve a noun phrase involving unknown words */
     resolveUnknownNounPhrase(tokList)
     {
         /* we can't resolve an unknown noun phrase used as an actor target */
         return nil;
     }
 
     /* 
      *   Get a raw pronoun antecedent list.  Since we are resolving the
      *   target actor, pronouns are relative to the issuing actor.  
      */
     getRawPronounAntecedent(typ)
     {
         /* check for pronouns that are relative to the issuer */
         switch(typ)
         {
         case PronounMe:
             /*
              *   It's a first-person construction.  If the issuing actor
              *   is the player character, and the PC is in the second
              *   person, this refers to the player character (the game
              *   calls the PC "you", so the player calls the PC "me").  If
              *   the issuing actor is an NPC, this is unconditionally the
              *   PC.  
              */
             if (actor_.isPlayerChar && actor_.referralPerson != SecondPerson)
                 return [];
             else
                 return [actor_];
 
         case PronounYou:
             /*
              *   It's a second-person construction.  If the issuer is the
              *   player character, and the player character is in the
              *   first person, this refers to the player character (the
              *   game calls the PC "me", so the player calls the PC
              *   "you").  If the issuer isn't the player character, "you"
              *   has no meaning.  
              */
             if (!actor_.isPlayerChar || actor_.referralPerson != FirstPerson)
                 return [];
             else
                 return [actor_];
 
         default:
             /* 
              *   it's not a relative pronoun, so ask the issuing actor for
              *   the antecedent based on recent commands 
              */
             return actor_.getPronounAntecedent(typ);
         }
     }
 
     /* we resolve target actors */
     whichObject = ActorObject
     whichMessageObject = ActorObject
 ;
 
 /*
  *   A pseudo-action for "command actor."  This represents the act of one
  *   actor (usually the PC) giving a command to another, as in "BOB, GO
  *   NORTH".  This isn't a real action that the player can type; it's just
  *   an internal construct that we use to represent the partially resolved
  *   action, when we know that we're addressing another actor but we're
  *   still working on figuring out what we're saying.  
  */
 class CommandActorAction: Action
 ;
 
 /* ------------------------------------------------------------------------ */
 /*
  *   A possessive resolver is a proxy to a main resolver that considers an
  *   object in scope if (a) it's in scope in the base resolver, or (b) the
  *   object is known to the actor. 
  */
 class PossessiveResolver: ProxyResolver
     objInScope(obj)
     {
         /* 
          *   An object is in scope for the purposes of a possessive phrase
          *   if it's in scope in the base resolver, or it's known to the
          *   actor.  An object is only in scope for a possessive qualifier
          *   phrase if its canResolvePossessive property is true.  
          */
         return (obj.canResolvePossessive
                 && (origResolver.objInScope(obj)
                     || origResolver.getTargetActor().knowsAbout(obj)));
     }
 
     /* this is a sub-resolver */
     isSubResolver = true
 ;
 
TADS 3 Library Manual
Generated on 9/8/2006 from TADS version 3.0.11