resolver.t
#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