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);
    }
    
    /*
     *   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); }

    /*
     *   Cache the scope list for this object.  By default, we cache the
     *   standard physical scope list for our target actor.
     *   
     *   Note that if a subclass uses completely different rules for
     *   determining scope, it need not 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();
    }

    /*
     *   Determine if an object is in scope for the purposes of object
     *   resolution.  By default, we'll return true if the object is in our
     *   cached scope list - this ensures that we produce results that are
     *   consistent with getScopeList().
     *   
     *   Some subclasses might want to override this method to decide on
     *   scope without reference to a cached scope list, for efficiency
     *   reasons.  For example, if a command's scope is the set of all
     *   objects, caching the full list would take a lot of memory; to save
     *   the memory, you could override cacheScopeList() to do nothing at
     *   all, and then override objInScope() to return true - this will
     *   report that every object is in scope without bothering to store a
     *   list of every object.
     *   
     *   Be aware that if you override objInScope(), you should ensure that
     *   getScopeList() yields consistent results.  In particular,
     *   objInScope() should return true for every object in the list
     *   returned by getScopeList() (although getScopeList() doesn't
     *   necessarily have to return every object for which objInScope() is
     *   true).  
     */
    objInScope(obj) { return scope_.indexOf(obj) != nil; }

    /*
     *   Get the full list of objects in scope.  By default, this simply
     *   returns our cached scope list.
     *   
     *   For every object in the list that getScopeList() returns,
     *   objInScope() must return true.  However, getScopeList() need not
     *   return *all* objects that are in scope as far as objInScope() is
     *   concerned - it can, but a subset of in-scope objects is
     *   sufficient.
     *   
     *   The default implementation returns the complete set of in-scope
     *   objects by simply returning the cached scope list.  This is the
     *   same scope list that the default objInScope() checks, which
     *   ensures that the two methods produce consistent results.
     *   
     *   The reason that it's okay for this method to return a subset of
     *   in-scope objects is that the result is only used to resolve
     *   "wildcard" phrases in input, and such phrases don't have to expand
     *   to every possible object.  Examples of wildcard phrases include
     *   ALL, missing phrases that need default objects, and locational
     *   phrases ("the vase on the table" - which isn't superficially a
     *   wildcard, but implicitly contains one in the form of "considering
     *   only everything on the table").  It's perfectly reasonable for the
     *   parser to expand a wildcard based on what's actually in sight, in
     *   mind, or whatever's appropriate.  So, in cases where you define an
     *   especially expansive objInScope() - for example, a universal scope
     *   like the one TopicResolver uses - it's usually fine to use the
     *   default definition of getScopeList(), which returns only the
     *   objects that are in the smaller physical scope.  
     */
    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;

        /* check the Action for a special override for the pronoun */
        lst = getAction().getPronounOverride(typ);

        /* if there's no override, get the standard raw antecedent list */
        if (lst == nil)
            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, np)});
        }

        /* 
         *   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, and we don't treat you/me as
             *   interchangeable, 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 we allow you/me mixing, then "me"
             *   always means the PC in input, no matter how the game
             *   refers to the PC in output.
             */
            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, and we don't treat you/me as
             *   interchangeable, 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 we allow you/me mixing, "you" is
             *   always the PC in input, no matter how the game refers to
             *   the PC in output.
             *   
             *   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, np)});

        /* 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()
    {
        /* ask the action to resolve 'all' for the direct object */
        local lst = action_.getAllDobj(actor_, getScopeList());

        /* return the results as ResolveInfo objects */
        return lst.mapAll({x: new ResolveInfo(x, 0, nil)});
    }

    /*
     *   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 an ambiguous noun phrase list using the strength of
     *   possessive qualification, if any.  If we have subsets at
     *   different possessive strengths, choose the strongest subset that
     *   has at least the required number of objects. 
     */
    filterPossRank(lst, num)
    {
        local sub1 = lst.subset({x: x.possRank_ >= 1});
        local sub2 = lst.subset({x: x.possRank_ >= 2});

        /* 
         *   sub2 is the subset with rank 2; if this meets our needs,
         *   return it.  If sub2 doesn't meet our needs, then check to see
         *   if sub1 does; sub1 is the subset with rank 1 or higher.  If
         *   neither subset meets our needs, use the original list.  
         */
        if (sub2.length() >= num)
            return sub2;
        else if (sub1.length() >= num)
            return sub1;
        else
            return lst;
    }

    /*
     *   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.
     *   
     *   Note that any subclasses (including Actions) that make changes to
     *   this list MUST ensure that the result only contains unique
     *   entries.  The library assumes in several places that there are no
     *   duplicate entries in the list; subtle problems can occur if the
     *   list contains any duplicates.  
     */
    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()
    {
        /* ask the action to resolve 'all' for the indirect object */
        local lst = action_.getAllIobj(actor_, getScopeList());

        /* return the results as ResolveInfo objects */
        return lst.mapAll({x: new ResolveInfo(x, 0, nil)});
    }

    /* 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 5/16/2013 from TADS version 3.1.3