| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library: Resolvers. |
| | 7 | * |
| | 8 | * This module defines the Resolver classes. A Resolver is an abstract |
| | 9 | * object that the parser uses to control the resolution of noun phrases |
| | 10 | * to game objects. Specialized Resolver subclasses allow noun phrases |
| | 11 | * to be resolved differently according to their grammatical function in |
| | 12 | * a command. |
| | 13 | */ |
| | 14 | |
| | 15 | #include "adv3.h" |
| | 16 | |
| | 17 | |
| | 18 | /* ------------------------------------------------------------------------ */ |
| | 19 | /* |
| | 20 | * Basic object resolver. An Action object creates an object resolver |
| | 21 | * to mediate the process of resolving noun phrases to objects. |
| | 22 | * |
| | 23 | * A resolver encapsulates a set of object resolution rules. In most |
| | 24 | * cases, an action that takes only a direct object can be its own |
| | 25 | * resolver, because it needs only one set of resolution rules; for this |
| | 26 | * reason, this basic Resolver implementation is designed to work with |
| | 27 | * the direct object. Actions with multiple objects will need separate |
| | 28 | * resolvers for each object, since they might want to use different |
| | 29 | * rules for the different objects. |
| | 30 | */ |
| | 31 | class Resolver: object |
| | 32 | construct(action, issuingActor, targetActor) |
| | 33 | { |
| | 34 | /* remember my action and actor objects */ |
| | 35 | action_ = action; |
| | 36 | issuer_ = issuingActor; |
| | 37 | actor_ = targetActor; |
| | 38 | |
| | 39 | /* cache the scope list */ |
| | 40 | cacheScopeList(); |
| | 41 | } |
| | 42 | |
| | 43 | /* |
| | 44 | * Are we a sub-phrase resolver? This should return true if we're |
| | 45 | * being used to resolve a sub-phrase of the main phrase. |
| | 46 | */ |
| | 47 | isSubResolver = nil |
| | 48 | |
| | 49 | /* |
| | 50 | * Reset the resolver - this can be called if we are to re-use the |
| | 51 | * same resolver to resolve a list of noun phrases again. |
| | 52 | */ |
| | 53 | resetResolver() |
| | 54 | { |
| | 55 | /* forget the equivalents we've resolved so far */ |
| | 56 | equivs_ = nil; |
| | 57 | } |
| | 58 | |
| | 59 | /* get the action we're resolving */ |
| | 60 | getAction() { return action_; } |
| | 61 | |
| | 62 | /* get the target actor */ |
| | 63 | getTargetActor() { return actor_; } |
| | 64 | |
| | 65 | /* |
| | 66 | * Match an object's name. By default, we'll call the object's own |
| | 67 | * matchName method with the given original and adjusted token |
| | 68 | * lists. Subclasses can override this to call different match |
| | 69 | * methods (such as matchNameDisambig). |
| | 70 | */ |
| | 71 | matchName(obj, origTokens, adjustedTokens) |
| | 72 | { |
| | 73 | return obj.matchName(origTokens, adjustedTokens); |
| | 74 | } |
| | 75 | |
| | 76 | /* |
| | 77 | * Get the resolver for qualifier phrases. By default, this simply |
| | 78 | * returns myself, since the resolver for qualifiers is in most |
| | 79 | * contexts the same as the main resolver. |
| | 80 | * |
| | 81 | * This can be overridden in contexts where the qualifier resolver |
| | 82 | * is different from the main resolver. In general, when a |
| | 83 | * sub-resolver narrows the scope for resolving a phrase, such as an |
| | 84 | * exclusion list or a disambiguation response, we will want to |
| | 85 | * resolve qualifiers in the context of the main resolution scope |
| | 86 | * rather than the narrowed scope. |
| | 87 | */ |
| | 88 | getQualifierResolver() { return self; } |
| | 89 | |
| | 90 | /* |
| | 91 | * Get the resolver for possessive phrases. By default, we return a |
| | 92 | * standard possessive resolver. This can be overridden in contexts |
| | 93 | * wher ethe possesive resolution context is special. |
| | 94 | */ |
| | 95 | getPossessiveResolver() { return new PossessiveResolver(self); } |
| | 96 | |
| | 97 | /* |
| | 98 | * Cache the scope list for this object. By default, we cache the |
| | 99 | * standard physical scope list for our target actor. |
| | 100 | * |
| | 101 | * Note that if a subclass uses completely different rules for |
| | 102 | * determining scope, it need not store a scope_ list at all. The |
| | 103 | * scope_ list is purely an implementation detail of the base |
| | 104 | * Resolver class. A subclass can use whatever internal |
| | 105 | * implementation it wants, as long as it overrides objInScope() and |
| | 106 | * getScopeList() to return consistent results. |
| | 107 | */ |
| | 108 | cacheScopeList() |
| | 109 | { |
| | 110 | /* cache our actor's default scope list */ |
| | 111 | scope_ = actor_.scopeList(); |
| | 112 | } |
| | 113 | |
| | 114 | /* |
| | 115 | * Determine if an object is in scope for the purposes of object |
| | 116 | * resolution. By default, we'll return true if the object is in our |
| | 117 | * cached scope list - this ensures that we produce results that are |
| | 118 | * consistent with getScopeList(). |
| | 119 | * |
| | 120 | * Some subclasses might want to override this method to decide on |
| | 121 | * scope without reference to a cached scope list, for efficiency |
| | 122 | * reasons. For example, if a command's scope is the set of all |
| | 123 | * objects, caching the full list would take a lot of memory; to save |
| | 124 | * the memory, you could override cacheScopeList() to do nothing at |
| | 125 | * all, and then override objInScope() to return true - this will |
| | 126 | * report that every object is in scope without bothering to store a |
| | 127 | * list of every object. |
| | 128 | * |
| | 129 | * Be aware that if you override objInScope(), you should ensure that |
| | 130 | * getScopeList() yields consistent results. In particular, |
| | 131 | * objInScope() should return true for every object in the list |
| | 132 | * returned by getScopeList() (although getScopeList() doesn't |
| | 133 | * necessarily have to return every object for which objInScope() is |
| | 134 | * true). |
| | 135 | */ |
| | 136 | objInScope(obj) { return scope_.indexOf(obj) != nil; } |
| | 137 | |
| | 138 | /* |
| | 139 | * Get the full list of objects in scope. By default, this simply |
| | 140 | * returns our cached scope list. |
| | 141 | * |
| | 142 | * For every object in the list that getScopeList() returns, |
| | 143 | * objInScope() must return true. However, getScopeList() need not |
| | 144 | * return *all* objects that are in scope as far as objInScope() is |
| | 145 | * concerned - it can, but a subset of in-scope objects is |
| | 146 | * sufficient. |
| | 147 | * |
| | 148 | * The default implementation returns the complete set of in-scope |
| | 149 | * objects by simply returning the cached scope list. This is the |
| | 150 | * same scope list that the default objInScope() checks, which |
| | 151 | * ensures that the two methods produce consistent results. |
| | 152 | * |
| | 153 | * The reason that it's okay for this method to return a subset of |
| | 154 | * in-scope objects is that the result is only used to resolve |
| | 155 | * "wildcard" phrases in input, and such phrases don't have to expand |
| | 156 | * to every possible object. Examples of wildcard phrases include |
| | 157 | * ALL, missing phrases that need default objects, and locational |
| | 158 | * phrases ("the vase on the table" - which isn't superficially a |
| | 159 | * wildcard, but implicitly contains one in the form of "considering |
| | 160 | * only everything on the table"). It's perfectly reasonable for the |
| | 161 | * parser to expand a wildcard based on what's actually in sight, in |
| | 162 | * mind, or whatever's appropriate. So, in cases where you define an |
| | 163 | * especially expansive objInScope() - for example, a universal scope |
| | 164 | * like the one TopicResolver uses - it's usually fine to use the |
| | 165 | * default definition of getScopeList(), which returns only the |
| | 166 | * objects that are in the smaller physical scope. |
| | 167 | */ |
| | 168 | getScopeList() { return scope_; } |
| | 169 | |
| | 170 | /* |
| | 171 | * Is this a "global" scope? By default, the scope is local: it's |
| | 172 | * limited to what the actor can see, hear, etc. In some cases, the |
| | 173 | * scope is broader, and extends beyond the senses; we call those |
| | 174 | * cases global scope. |
| | 175 | * |
| | 176 | * This is an advisory status only. The caller musn't take this to |
| | 177 | * mean that everything is in scope; objInScope() and getScopeList() |
| | 178 | * must still be used to make the exact determination of what objects |
| | 179 | * are in scope. However, some noun phrase productions might wish to |
| | 180 | * know generally whether we're in a local or global sort of scope, |
| | 181 | * so that they can adjust their zeal at reducing ambiguity. In |
| | 182 | * cases of global scope, we generally want to be more inclusive of |
| | 183 | * possible matches than in local scopes, because we have much less |
| | 184 | * of a basis to guess about what the player might mean. |
| | 185 | */ |
| | 186 | isGlobalScope = nil |
| | 187 | |
| | 188 | /* |
| | 189 | * Get the binding for a reflexive third-person pronoun (himself, |
| | 190 | * herself, itself, themselves). By default, the reflexive binding |
| | 191 | * is the anaphoric binding from the action - that is, it refers |
| | 192 | * back to the preceding noun phrase in a verb phrase with multiple |
| | 193 | * noun slots (as in ASK BOB ABOUT HIMSELF: 'himself' refers back to |
| | 194 | * 'bob', the previous noun phrase). |
| | 195 | */ |
| | 196 | getReflexiveBinding(typ) { return getAction().getAnaphoricBinding(typ); } |
| | 197 | |
| | 198 | /* |
| | 199 | * Resolve a pronoun antecedent, given a pronoun selector. This |
| | 200 | * returns a list of ResolveInfo objects, for use in object |
| | 201 | * resolution. 'poss' is true if this is a possessive pronoun (his, |
| | 202 | * her, its, etc), nil if it's an ordinary, non-possessive pronoun |
| | 203 | * (him, her, it, etc). |
| | 204 | */ |
| | 205 | resolvePronounAntecedent(typ, np, results, poss) |
| | 206 | { |
| | 207 | local lst; |
| | 208 | local scopeLst; |
| | 209 | |
| | 210 | /* check the Action for a special override for the pronoun */ |
| | 211 | lst = getAction().getPronounOverride(typ); |
| | 212 | |
| | 213 | /* if there's no override, get the standard raw antecedent list */ |
| | 214 | if (lst == nil) |
| | 215 | lst = getRawPronounAntecedent(typ); |
| | 216 | |
| | 217 | /* if there is no antecedent, return an empty list */ |
| | 218 | if (lst != nil && lst != []) |
| | 219 | { |
| | 220 | local cur; |
| | 221 | |
| | 222 | /* if it's a single object, turn it into a list */ |
| | 223 | if (dataType(lst) == TypeObject) |
| | 224 | lst = [lst]; |
| | 225 | |
| | 226 | /* add any extra objects for the pronoun binding */ |
| | 227 | foreach (cur in lst) |
| | 228 | lst = cur.expandPronounList(typ, lst); |
| | 229 | |
| | 230 | /* filter the list to keep only in-scope objects */ |
| | 231 | scopeLst = new Vector(lst.length()); |
| | 232 | foreach (cur in lst) |
| | 233 | { |
| | 234 | local facets; |
| | 235 | |
| | 236 | /* get the object's facets */ |
| | 237 | facets = cur.getFacets(); |
| | 238 | |
| | 239 | /* |
| | 240 | * If it has any, pick the best one that's in scope. If |
| | 241 | * not, keep the object only if it's in scope. |
| | 242 | */ |
| | 243 | if (facets.length() != 0) |
| | 244 | { |
| | 245 | local best; |
| | 246 | |
| | 247 | /* |
| | 248 | * This object has other facets, so we want to |
| | 249 | * consider the other in-scope facets in case any are |
| | 250 | * more suitable than the original one. For example, |
| | 251 | * we might have just referred to a door, and then |
| | 252 | * traveled through the door to an adjoining room. |
| | 253 | * We now want the antecedent to be the side (facet) |
| | 254 | * of the door that's in the new location. |
| | 255 | */ |
| | 256 | |
| | 257 | /* get the in-scope subset of the facets */ |
| | 258 | facets = (facets + cur).subset({x: objInScope(x)}); |
| | 259 | |
| | 260 | /* keep the best facet from the list */ |
| | 261 | best = findBestFacet(actor_, facets); |
| | 262 | |
| | 263 | /* |
| | 264 | * If we found a winner, use it instead of the |
| | 265 | * original. |
| | 266 | */ |
| | 267 | if (best != nil) |
| | 268 | cur = best; |
| | 269 | } |
| | 270 | |
| | 271 | /* if the object is in scope, include it in the results */ |
| | 272 | if (objInScope(cur)) |
| | 273 | scopeLst.append(cur); |
| | 274 | } |
| | 275 | |
| | 276 | /* create a list of ResolveInfo objects from the antecedents */ |
| | 277 | lst = scopeLst.toList().mapAll({x: new ResolveInfo(x, 0)}); |
| | 278 | } |
| | 279 | |
| | 280 | /* |
| | 281 | * If there's nothing matching in scope, try to find a default. |
| | 282 | * Look to see if there's a unique default object matching the |
| | 283 | * pronoun, and select it if so. |
| | 284 | */ |
| | 285 | if (lst == nil || lst == []) |
| | 286 | lst = getPronounDefault(typ, np); |
| | 287 | |
| | 288 | /* run the normal resolution list filtering on the list */ |
| | 289 | lst = action_.finishResolveList(lst, whichObject, np, nil); |
| | 290 | |
| | 291 | /* return the result */ |
| | 292 | return lst; |
| | 293 | } |
| | 294 | |
| | 295 | /* |
| | 296 | * Get the "raw" pronoun antecedent list for a given pronoun |
| | 297 | * selector. This returns a list of objects matching the pronoun. |
| | 298 | * The list is raw in that it is given as a list of game objects |
| | 299 | * (not ResolveInfo objects), and it isn't filtered for scope. |
| | 300 | */ |
| | 301 | getRawPronounAntecedent(typ) |
| | 302 | { |
| | 303 | /* check for pronouns that are relative to the issuer or target */ |
| | 304 | switch(typ) |
| | 305 | { |
| | 306 | case PronounMe: |
| | 307 | /* |
| | 308 | * It's a first-person construction. If the issuing actor is |
| | 309 | * the player character, and we don't treat you/me as |
| | 310 | * interchangeable, this refers to the player character only |
| | 311 | * if the game refers to the player character in the second |
| | 312 | * person (so, if the game calls the PC "you", the player |
| | 313 | * calls the PC "me"). If the issuing actor isn't the player |
| | 314 | * character, then a first-person pronoun refers to the |
| | 315 | * command's issuer. If we allow you/me mixing, then "me" |
| | 316 | * always means the PC in input, no matter how the game |
| | 317 | * refers to the PC in output. |
| | 318 | */ |
| | 319 | if (issuer_.isPlayerChar |
| | 320 | && issuer_.referralPerson != SecondPerson |
| | 321 | && !gameMain.allowYouMeMixing) |
| | 322 | { |
| | 323 | /* |
| | 324 | * the issuer is the player, but the game doesn't call |
| | 325 | * the PC "you", so "me" has no meaning |
| | 326 | */ |
| | 327 | return []; |
| | 328 | } |
| | 329 | else |
| | 330 | { |
| | 331 | /* "me" refers to the command's issuer */ |
| | 332 | return [issuer_]; |
| | 333 | } |
| | 334 | |
| | 335 | case PronounYou: |
| | 336 | /* |
| | 337 | * It's a second-person construction. If the target actor is |
| | 338 | * the player character, and we don't treat you/me as |
| | 339 | * interchangeable, this refers to the player character only |
| | 340 | * if the game refers to the player character in the first |
| | 341 | * person (so, if the game calls the PC "me", then the player |
| | 342 | * calls the PC "you"). If we allow you/me mixing, "you" is |
| | 343 | * always the PC in input, no matter how the game refers to |
| | 344 | * the PC in output. |
| | 345 | * |
| | 346 | * If the target actor isn't the player character, then a |
| | 347 | * second-person pronoun refers to either the target actor or |
| | 348 | * to the player character, depending on the referral person |
| | 349 | * of the current command that's targeting the actor. If the |
| | 350 | * command is in the second person, then a second-person |
| | 351 | * pronoun refers to the actor ("bob, hit you" means for Bob |
| | 352 | * to hit himself). If the command is in the third person, |
| | 353 | * then a second-person pronoun is a bit weird, but probably |
| | 354 | * refers to the player character ("tell bob to hit you" |
| | 355 | * means for Bob to hit the PC). |
| | 356 | */ |
| | 357 | if (actor_.isPlayerChar |
| | 358 | && actor_.referralPerson != FirstPerson |
| | 359 | && !gameMain.allowYouMeMixing) |
| | 360 | { |
| | 361 | /* |
| | 362 | * the target is the player character, but the game |
| | 363 | * doesn't call the PC "me", so "you" has no meaning in |
| | 364 | * this command |
| | 365 | */ |
| | 366 | return []; |
| | 367 | } |
| | 368 | else if (actor_.commandReferralPerson == ThirdPerson) |
| | 369 | { |
| | 370 | /* |
| | 371 | * we're addressing the actor in the third person, so YOU |
| | 372 | * probably doesn't refer to the target actor; the only |
| | 373 | * other real possibility is that it refers to the player |
| | 374 | * character |
| | 375 | */ |
| | 376 | return [gPlayerChar]; |
| | 377 | } |
| | 378 | else |
| | 379 | { |
| | 380 | /* in other cases, "you" refers to the command's target */ |
| | 381 | return [actor_]; |
| | 382 | } |
| | 383 | |
| | 384 | default: |
| | 385 | /* |
| | 386 | * it's not a relative pronoun, so ask the target actor for |
| | 387 | * the antecedent based on recent commands |
| | 388 | */ |
| | 389 | return actor_.getPronounAntecedent(typ); |
| | 390 | } |
| | 391 | } |
| | 392 | |
| | 393 | /* |
| | 394 | * Determine if "all" is allowed for the noun phrase we're resolving. |
| | 395 | * By default, we'll just ask the action. |
| | 396 | */ |
| | 397 | allowAll() |
| | 398 | { |
| | 399 | /* ask the action to determine whether or not "all" is allowed */ |
| | 400 | return action_.actionAllowsAll; |
| | 401 | } |
| | 402 | |
| | 403 | /* |
| | 404 | * Get the "all" list - this is the list of objects that we should |
| | 405 | * use when the object of the command is the special word "all". |
| | 406 | * We'll ask the action to resolve 'all' for the direct object, |
| | 407 | * since we are by default a direct object resolver. |
| | 408 | */ |
| | 409 | getAll(np) |
| | 410 | { |
| | 411 | /* |
| | 412 | * ask the action to resolve 'all' for the direct object, and |
| | 413 | * then filter the list and return the result |
| | 414 | */ |
| | 415 | return filterAll(action_.getAllDobj(actor_, getScopeList()), |
| | 416 | DirectObject, np); |
| | 417 | } |
| | 418 | |
| | 419 | /* |
| | 420 | * Filter an 'all' list to remove things that don't belong. We |
| | 421 | * always remove the actor executing the command, as well as any |
| | 422 | * objects explicitly marked as hidden from 'all' lists. |
| | 423 | * |
| | 424 | * Returns a ResolveInfo list, with each entry marked with the |
| | 425 | * MatchedAll flag. |
| | 426 | */ |
| | 427 | filterAll(lst, whichObj, np) |
| | 428 | { |
| | 429 | local result; |
| | 430 | |
| | 431 | /* set up a vector to hold the result */ |
| | 432 | result = new Vector(lst.length()); |
| | 433 | |
| | 434 | /* |
| | 435 | * run through the list and include elements that we don't want |
| | 436 | * to exclude |
| | 437 | */ |
| | 438 | foreach (local cur in lst) |
| | 439 | { |
| | 440 | /* |
| | 441 | * if this item isn't the actor, and isn't marked for |
| | 442 | * exclusion from 'all' lists in general, include it |
| | 443 | */ |
| | 444 | if (cur != actor_ && !cur.hideFromAll(getAction())) |
| | 445 | result.append(cur); |
| | 446 | } |
| | 447 | |
| | 448 | /* |
| | 449 | * create a ResolveInfo for each object, with the 'MatchedAll' |
| | 450 | * flag set for each object |
| | 451 | */ |
| | 452 | result.applyAll({x: new ResolveInfo(x, MatchedAll)}); |
| | 453 | |
| | 454 | /* run through the list and apply each object's own filtering */ |
| | 455 | result = getAction().finishResolveList(result, whichObject, np, nil); |
| | 456 | |
| | 457 | /* return the result as a list */ |
| | 458 | return result.toList(); |
| | 459 | } |
| | 460 | |
| | 461 | /* |
| | 462 | * Get the list of potential default objects. This is simply the |
| | 463 | * basic 'all' list, not filtered for exclusion with hideFromAll. |
| | 464 | */ |
| | 465 | getAllDefaults() |
| | 466 | { |
| | 467 | local lst; |
| | 468 | |
| | 469 | /* ask the action to resolve 'all' for the direct object */ |
| | 470 | lst = action_.getAllDobj(actor_, getScopeList()); |
| | 471 | |
| | 472 | /* return the results as ResolveInfo objects */ |
| | 473 | return lst.mapAll({x: new ResolveInfo(x, 0)}); |
| | 474 | } |
| | 475 | |
| | 476 | /* |
| | 477 | * Filter an ambiguous list of objects ('lst') resolving to a noun |
| | 478 | * phrase. If the objects in the list vary in the degree of |
| | 479 | * suitability for the command, returns a list consisting only of the |
| | 480 | * most suitable objects. If the objects are all equally suitable - |
| | 481 | * or equally unsuitable - the whole list should be returned |
| | 482 | * unchanged. |
| | 483 | * |
| | 484 | * 'requiredNum' is the number of objects required in the final list |
| | 485 | * by the caller; if the result list is larger than this, the caller |
| | 486 | * will consider the results ambiguous. |
| | 487 | * |
| | 488 | * 'np' is the noun phrase production that we're resolving. This is |
| | 489 | * usually a subclass of NounPhraseProd. |
| | 490 | * |
| | 491 | * This routine does NOT perform any interactive disambiguation, but |
| | 492 | * is merely a first attempt at reducing the number of matching |
| | 493 | * objects by removing the obviously unsuitable ones. |
| | 494 | * |
| | 495 | * For example, for an "open" command, if the list consists of one |
| | 496 | * object that's open and one object that's currently closed, the |
| | 497 | * result list should include only the closed one, since it is |
| | 498 | * obvious that the one that's already open does not need to be |
| | 499 | * opened again. On the other hand, if the list consists only of |
| | 500 | * open objects, they should all be returned, since they're all |
| | 501 | * equally unsuitable. |
| | 502 | * |
| | 503 | * It is not necessary to reduce the list to a single entry; it is |
| | 504 | * adequate merely to reduce the ambiguity by removing any items that |
| | 505 | * are clearly less suitable than the survivors. |
| | 506 | */ |
| | 507 | filterAmbiguousNounPhrase(lst, requiredNum, np) |
| | 508 | { |
| | 509 | return withGlobals( |
| | 510 | {:action_.filterAmbiguousDobj(lst, requiredNum, np)}); |
| | 511 | } |
| | 512 | |
| | 513 | /* |
| | 514 | * Filter an ambiguous noun phrase list using the strength of |
| | 515 | * possessive qualification, if any. If we have subsets at |
| | 516 | * different possessive strengths, choose the strongest subset that |
| | 517 | * has at least the required number of objects. |
| | 518 | */ |
| | 519 | filterPossRank(lst, num) |
| | 520 | { |
| | 521 | local sub1 = lst.subset({x: x.possRank_ >= 1}); |
| | 522 | local sub2 = lst.subset({x: x.possRank_ >= 2}); |
| | 523 | |
| | 524 | /* |
| | 525 | * sub2 is the subset with rank 2; if this meets our needs, |
| | 526 | * return it. If sub2 doesn't meet our needs, then check to see |
| | 527 | * if sub1 does; sub1 is the subset with rank 1 or higher. If |
| | 528 | * neither subset meets our needs, use the original list. |
| | 529 | */ |
| | 530 | if (sub2.length() >= num) |
| | 531 | return sub2; |
| | 532 | else if (sub1.length() >= num) |
| | 533 | return sub1; |
| | 534 | else |
| | 535 | return lst; |
| | 536 | } |
| | 537 | |
| | 538 | /* |
| | 539 | * Filter a list of ambiguous matches ('lst') for a noun phrase, to |
| | 540 | * reduce each set of equivalent items to a single such item, if |
| | 541 | * desired. If no equivalent reduction is desired for this type of |
| | 542 | * resolver, this can simply return the original list. |
| | 543 | * |
| | 544 | * 'np' is the noun phrase production that we're resolving. This is |
| | 545 | * usually a subclass of NounPhraseProd. |
| | 546 | */ |
| | 547 | filterAmbiguousEquivalents(lst, np) |
| | 548 | { |
| | 549 | /* if we have only one item, there's obviously nothing redundant */ |
| | 550 | if (lst.length() == 1) |
| | 551 | return lst; |
| | 552 | |
| | 553 | /* scan the list, looking for equivalents */ |
| | 554 | for (local i = 1, local len = lst.length() ; i <= len ; ++i) |
| | 555 | { |
| | 556 | /* |
| | 557 | * if this item is marked as equivalent, check for others |
| | 558 | * like it |
| | 559 | */ |
| | 560 | if (lst[i].obj_.isEquivalent) |
| | 561 | { |
| | 562 | /* |
| | 563 | * If this object is in our list of previously-used |
| | 564 | * equivalents, and we have more equivalents to this |
| | 565 | * object in our list, then omit this one, so that we |
| | 566 | * keep a different equivalent this time. This way, if |
| | 567 | * we have a noun list such as "take coin and coin", |
| | 568 | * we'll return different equivalent items for each |
| | 569 | * equivalent noun phrase. |
| | 570 | */ |
| | 571 | if (equivs_ != nil |
| | 572 | && equivs_.indexOf(lst[i].obj_) != nil |
| | 573 | && lst.lastIndexWhich( |
| | 574 | {x: x.obj_.isVocabEquivalent(lst[i].obj_)}) > i) |
| | 575 | { |
| | 576 | /* |
| | 577 | * we've already returned this one, and we have |
| | 578 | * another equivalent later in the list that we can |
| | 579 | * use instead this time - remove this one from the |
| | 580 | * list |
| | 581 | */ |
| | 582 | lst = lst.removeElementAt(i); |
| | 583 | |
| | 584 | /* adjust the our counters for the removal */ |
| | 585 | --len; |
| | 586 | --i; |
| | 587 | } |
| | 588 | else |
| | 589 | { |
| | 590 | /* |
| | 591 | * We've decided to keep this element, either |
| | 592 | * because we haven't already returned it as a match |
| | 593 | * for this noun phrase, or because it's the last |
| | 594 | * one of its kind. Add it to the list of |
| | 595 | * equivalents we've previously returned. |
| | 596 | */ |
| | 597 | if (equivs_ == nil) |
| | 598 | equivs_ = new Vector(10); |
| | 599 | equivs_.append(lst[i].obj_); |
| | 600 | |
| | 601 | /* |
| | 602 | * check each object at a higher index to see if |
| | 603 | * it's equivalent to this one |
| | 604 | */ |
| | 605 | for (local j = i + 1 ; j <= len ; ++j) |
| | 606 | { |
| | 607 | /* check this object */ |
| | 608 | if (lst[i].obj_.isVocabEquivalent(lst[j].obj_)) |
| | 609 | { |
| | 610 | /* they match - remove the other one */ |
| | 611 | lst = lst.removeElementAt(j); |
| | 612 | |
| | 613 | /* reduce the list length accordingly */ |
| | 614 | --len; |
| | 615 | |
| | 616 | /* back up our scanning index as well */ |
| | 617 | --j; |
| | 618 | } |
| | 619 | } |
| | 620 | } |
| | 621 | } |
| | 622 | } |
| | 623 | |
| | 624 | /* return the updated list */ |
| | 625 | return lst; |
| | 626 | } |
| | 627 | |
| | 628 | /* |
| | 629 | * Filter a plural phrase to reduce the set to the logical subset, if |
| | 630 | * possible. If there is no logical subset, simply return the |
| | 631 | * original set. |
| | 632 | * |
| | 633 | * 'np' is the noun phrase we're resolving; this is usually a |
| | 634 | * subclass of PluralProd. |
| | 635 | */ |
| | 636 | filterPluralPhrase(lst, np) |
| | 637 | { |
| | 638 | return withGlobals({:action_.filterPluralDobj(lst, np)}); |
| | 639 | } |
| | 640 | |
| | 641 | /* |
| | 642 | * Select a resolution for an indefinite noun phrase ("a coin"), |
| | 643 | * given a list of possible matches. The matches will be given to |
| | 644 | * us sorted from most likely to least likely, as done by |
| | 645 | * filterAmbiguousNounPhrase(). |
| | 646 | * |
| | 647 | * By default, we simply select the first 'n' items from the list |
| | 648 | * (which are the most likely matches), because in most contexts, an |
| | 649 | * indefinite noun phrase means that we should arbitrarily select |
| | 650 | * any matching object. This can be overridden for contexts in |
| | 651 | * which indefinite noun phrases must be handled differently. |
| | 652 | */ |
| | 653 | selectIndefinite(results, lst, requiredNumber) |
| | 654 | { |
| | 655 | /* |
| | 656 | * arbitrarily choose the first 'requiredNumber' item(s) from |
| | 657 | * the list |
| | 658 | */ |
| | 659 | return lst.sublist(1, requiredNumber); |
| | 660 | } |
| | 661 | |
| | 662 | /* |
| | 663 | * Get the default object or objects for this phrase. Returns a list |
| | 664 | * of ResolveInfo objects if a default is available, or nil if no |
| | 665 | * default is available. This routine does not interact with the |
| | 666 | * user; it should merely determine if the command implies a default |
| | 667 | * strongly enough to assume it without asking the user. |
| | 668 | * |
| | 669 | * By default, we ask the action for a default direct object. |
| | 670 | * Resolver subclasses should override this as appropriate for the |
| | 671 | * specific objects they're used to resolve. |
| | 672 | */ |
| | 673 | getDefaultObject(np) |
| | 674 | { |
| | 675 | /* ask the action to provide a default direct object */ |
| | 676 | return withGlobals({:action_.getDefaultDobj(np, self)}); |
| | 677 | } |
| | 678 | |
| | 679 | /* |
| | 680 | * Resolve a noun phrase involving unknown words, if possible. If |
| | 681 | * it is not possible to resolve such a phrase, return nil; |
| | 682 | * otherwise, return a list of resolved objects. This routine does |
| | 683 | * not interact with the user - "oops" prompting is handled |
| | 684 | * separately. |
| | 685 | * |
| | 686 | * 'tokList' is the token list for the phrase, in the canonical |
| | 687 | * format as returned from the tokenizer. Each element of 'tokList' |
| | 688 | * is a sublist representing one token. |
| | 689 | * |
| | 690 | * Note that this routine allows for specialized unknown word |
| | 691 | * resolution separately from the more general matchName mechanism. |
| | 692 | * The purpose of this method is to allow the specific type of |
| | 693 | * resolver to deal with unknown words specially, rather than using |
| | 694 | * the matchName mechanism. This routine is called as a last |
| | 695 | * resort, only after the matchName mechanism fails to find any |
| | 696 | * matches. |
| | 697 | */ |
| | 698 | resolveUnknownNounPhrase(tokList) |
| | 699 | { |
| | 700 | /* by default, we can't resolve an unknown noun phrase */ |
| | 701 | return nil; |
| | 702 | } |
| | 703 | |
| | 704 | /* |
| | 705 | * Execute a callback function in the global context of our actor |
| | 706 | * and action - we'll set gActor and gAction to our own stored actor |
| | 707 | * and action values, then call the callback, then restore the old |
| | 708 | * globals. |
| | 709 | */ |
| | 710 | withGlobals(func) |
| | 711 | { |
| | 712 | /* invoke the function with our action and actor in the globals */ |
| | 713 | return withParserGlobals(issuer_, actor_, action_, func); |
| | 714 | } |
| | 715 | |
| | 716 | /* the role played by this object, if any */ |
| | 717 | whichObject = DirectObject |
| | 718 | |
| | 719 | /* |
| | 720 | * Get an indication of which object we're resolving, for message |
| | 721 | * generation purposes. By default, we'll indicate direct object; |
| | 722 | * this should be overridden for resolvers of indirect and other |
| | 723 | * types of objects. |
| | 724 | */ |
| | 725 | whichMessageObject = DirectObject |
| | 726 | |
| | 727 | /* |
| | 728 | * The cached scope list, if we have one. Note that this is an |
| | 729 | * internal implementation detail of the base class; subclasses can |
| | 730 | * dispense with the cached scope list if they define their own |
| | 731 | * objInScope() and getScopeList() overrides. |
| | 732 | * |
| | 733 | * Note that any subclasses (including Actions) that make changes to |
| | 734 | * this list MUST ensure that the result only contains unique |
| | 735 | * entries. The library assumes in several places that there are no |
| | 736 | * duplicate entries in the list; subtle problems can occur if the |
| | 737 | * list contains any duplicates. |
| | 738 | */ |
| | 739 | scope_ = [] |
| | 740 | |
| | 741 | /* my action */ |
| | 742 | action_ = nil |
| | 743 | |
| | 744 | /* the issuing actor */ |
| | 745 | issuer_ = nil |
| | 746 | |
| | 747 | /* the target actor object */ |
| | 748 | actor_ = nil |
| | 749 | |
| | 750 | /* |
| | 751 | * List of equivalent objects we've resolved so far. We use this to |
| | 752 | * try to return different equivalent objects when multiple noun |
| | 753 | * phrases refer to the same set of equivalents. |
| | 754 | */ |
| | 755 | equivs_ = nil |
| | 756 | ; |
| | 757 | |
| | 758 | /* ------------------------------------------------------------------------ */ |
| | 759 | /* |
| | 760 | * Proxy Resolver - this is used to create resolvers that refer methods |
| | 761 | * not otherwise overridden back to an underlying resolver |
| | 762 | */ |
| | 763 | class ProxyResolver: object |
| | 764 | construct(origResolver) |
| | 765 | { |
| | 766 | /* remember my underlying resolver */ |
| | 767 | self.origResolver = origResolver; |
| | 768 | } |
| | 769 | |
| | 770 | /* delegate methods we don't override to the underlying resolver */ |
| | 771 | propNotDefined(prop, [args]) |
| | 772 | { |
| | 773 | /* delegate the call to the original resolver */ |
| | 774 | return origResolver.(prop)(args...); |
| | 775 | } |
| | 776 | |
| | 777 | /* base our possessive resolver on the proxy */ |
| | 778 | getPossessiveResolver() { return new PossessiveResolver(self); } |
| | 779 | ; |
| | 780 | |
| | 781 | /* ------------------------------------------------------------------------ */ |
| | 782 | /* |
| | 783 | * Basic resolver for indirect objects |
| | 784 | */ |
| | 785 | class IobjResolver: Resolver |
| | 786 | /* |
| | 787 | * we resolve indirect objects for message generation purposes |
| | 788 | */ |
| | 789 | whichObject = IndirectObject |
| | 790 | whichMessageObject = IndirectObject |
| | 791 | |
| | 792 | /* resolve 'all' for the indirect object */ |
| | 793 | getAll(np) |
| | 794 | { |
| | 795 | /* |
| | 796 | * ask the action to resolve 'all' for the indirect object, and |
| | 797 | * then filter the list and return the result |
| | 798 | */ |
| | 799 | return filterAll(action_.getAllIobj(actor_, getScopeList()), |
| | 800 | IndirectObject, np); |
| | 801 | } |
| | 802 | |
| | 803 | /* get all possible default objects */ |
| | 804 | getAllDefaults() |
| | 805 | { |
| | 806 | local lst; |
| | 807 | |
| | 808 | /* ask the action to resolve 'all' for the indirect object */ |
| | 809 | lst = action_.getAllIobj(actor_, getScopeList()); |
| | 810 | |
| | 811 | /* return the results as ResolveInfo objects */ |
| | 812 | return lst.mapAll({x: new ResolveInfo(x, 0)}); |
| | 813 | } |
| | 814 | |
| | 815 | /* filter an ambiguous noun phrase */ |
| | 816 | filterAmbiguousNounPhrase(lst, requiredNum, np) |
| | 817 | { |
| | 818 | return withGlobals( |
| | 819 | {:action_.filterAmbiguousIobj(lst, requiredNum, np)}); |
| | 820 | } |
| | 821 | |
| | 822 | /* |
| | 823 | * Filter a plural phrase to reduce the set to the logical subset, |
| | 824 | * if possible. If there is no logical subset, simply return the |
| | 825 | * original set. |
| | 826 | */ |
| | 827 | filterPluralPhrase(lst, np) |
| | 828 | { |
| | 829 | return withGlobals({:action_.filterPluralIobj(lst, np)}); |
| | 830 | } |
| | 831 | |
| | 832 | /* |
| | 833 | * Get the default object or objects for this phrase. Since we |
| | 834 | * resolve indirect objects, we'll ask the action for a default |
| | 835 | * indirect object. |
| | 836 | */ |
| | 837 | getDefaultObject(np) |
| | 838 | { |
| | 839 | /* ask the action to provide a default indirect object */ |
| | 840 | return withGlobals({:action_.getDefaultIobj(np, self)}); |
| | 841 | } |
| | 842 | ; |
| | 843 | |
| | 844 | /* ------------------------------------------------------------------------ */ |
| | 845 | /* |
| | 846 | * Basic topic qualifier resolver. This can be used to resolve qualifier |
| | 847 | * phrases (such as possessives or locationals) within topic phrases. |
| | 848 | */ |
| | 849 | class TopicQualifierResolver: Resolver |
| | 850 | getAll(np) |
| | 851 | { |
| | 852 | /* 'all' doesn't make sense as a qualifier; return an empty list */ |
| | 853 | return []; |
| | 854 | } |
| | 855 | |
| | 856 | getAllDefaults() |
| | 857 | { |
| | 858 | /* we don't need defaults for a qualifier */ |
| | 859 | return []; |
| | 860 | } |
| | 861 | |
| | 862 | filterAmbiguousNounPhrase(lst, requiredNum, np) |
| | 863 | { |
| | 864 | /* we have no basis for any filtering; return the list unchanged */ |
| | 865 | return lst; |
| | 866 | } |
| | 867 | |
| | 868 | filterPluralPhrase(lst, np) |
| | 869 | { |
| | 870 | /* we have no basis for any filtering */ |
| | 871 | return lst; |
| | 872 | } |
| | 873 | |
| | 874 | getDefaultObject(np) |
| | 875 | { |
| | 876 | /* have have no way to pick a default */ |
| | 877 | return nil; |
| | 878 | } |
| | 879 | ; |
| | 880 | |
| | 881 | /* ------------------------------------------------------------------------ */ |
| | 882 | /* |
| | 883 | * Actor Resolver. We use this to resolve the actor to whom a command |
| | 884 | * is directed: the actor must be in scope for the player character. |
| | 885 | */ |
| | 886 | class ActorResolver: Resolver |
| | 887 | construct(issuingActor) |
| | 888 | { |
| | 889 | /* remember the issuing actor */ |
| | 890 | actor_ = issuingActor; |
| | 891 | |
| | 892 | /* |
| | 893 | * Use our pseudo-action for "command actor" - this represents |
| | 894 | * the intermediate step where the issuing actor is doing |
| | 895 | * whatever physical activity is needed (such as talking) to give |
| | 896 | * the command to the target actor. This isn't a real action; |
| | 897 | * it's just an implied intermediate step in the overall action. |
| | 898 | * We need this mostly because there are assumptions elsewhere in |
| | 899 | * the resolution process that there's a valid Action object |
| | 900 | * available. |
| | 901 | */ |
| | 902 | action_ = CommandActorAction; |
| | 903 | |
| | 904 | /* ...and the action needs an actor */ |
| | 905 | action_.actor_ = actor_; |
| | 906 | |
| | 907 | /* cache the scope list for the actor who issued the command */ |
| | 908 | cacheScopeList(); |
| | 909 | } |
| | 910 | |
| | 911 | /* |
| | 912 | * Get the "all" list - this is the list of objects that we should |
| | 913 | * use when the object of the command is the special word "all". By |
| | 914 | * default, we'll return everything in scope. |
| | 915 | */ |
| | 916 | getAll(np) |
| | 917 | { |
| | 918 | /* we can't address 'all' */ |
| | 919 | throw new ParseFailureException(&cannotAddressMultiple); |
| | 920 | } |
| | 921 | |
| | 922 | /* get the default object list */ |
| | 923 | getAllDefaults() |
| | 924 | { |
| | 925 | /* there are no default actors */ |
| | 926 | return []; |
| | 927 | } |
| | 928 | |
| | 929 | /* |
| | 930 | * Filter an ambiguous list of objects. We will filter according to |
| | 931 | * which objects are most logical as targets of commands. |
| | 932 | */ |
| | 933 | filterAmbiguousNounPhrase(lst, requiredNum, np) |
| | 934 | { |
| | 935 | local likelyCnt; |
| | 936 | |
| | 937 | /* give each object in the list a chance to filter the list */ |
| | 938 | lst = getAction().finishResolveList(lst, ActorObject, |
| | 939 | np, requiredNum); |
| | 940 | |
| | 941 | /* |
| | 942 | * Run through the list and see how many objects are likely |
| | 943 | * command targets. |
| | 944 | */ |
| | 945 | likelyCnt = 0; |
| | 946 | foreach (local cur in lst) |
| | 947 | { |
| | 948 | /* if it's a likely command target, count it */ |
| | 949 | if (cur.obj_.isLikelyCommandTarget) |
| | 950 | ++likelyCnt; |
| | 951 | } |
| | 952 | |
| | 953 | /* |
| | 954 | * If some of the targets are likely and others aren't, and we |
| | 955 | * have at least the required number of likely targets, keep |
| | 956 | * only the likely ones. If they're all likely or all unlikely, |
| | 957 | * it doesn't help us because we still have no basis for |
| | 958 | * choosing some over others; if removing unlikely ones would |
| | 959 | * not give us enough to meet the minimum number required it |
| | 960 | * also doesn't help, because we don't have a basis for |
| | 961 | * selecting as many as are needed. |
| | 962 | */ |
| | 963 | if (likelyCnt != 0 && likelyCnt != lst.length() |
| | 964 | && likelyCnt >= requiredNum) |
| | 965 | { |
| | 966 | /* |
| | 967 | * we have a useful subset of likely ones - filter the list |
| | 968 | * down to the likely subset |
| | 969 | */ |
| | 970 | lst = lst.subset({cur: cur.obj_.isLikelyCommandTarget}); |
| | 971 | } |
| | 972 | |
| | 973 | /* return the result */ |
| | 974 | return lst; |
| | 975 | } |
| | 976 | |
| | 977 | /* |
| | 978 | * Filter a plural list |
| | 979 | */ |
| | 980 | filterPluralPhrase(lst, np) |
| | 981 | { |
| | 982 | /* |
| | 983 | * Use the same filtering that we use for ambiguous nouns. This |
| | 984 | * simply reduces the set to the likely command targets if any |
| | 985 | * are likely command targets. |
| | 986 | */ |
| | 987 | return filterAmbiguousNounPhrase(lst, 1, np); |
| | 988 | } |
| | 989 | |
| | 990 | /* get a default object */ |
| | 991 | getDefaultObject(np) |
| | 992 | { |
| | 993 | /* there is never a default for the target actor */ |
| | 994 | return nil; |
| | 995 | } |
| | 996 | |
| | 997 | /* resolve a noun phrase involving unknown words */ |
| | 998 | resolveUnknownNounPhrase(tokList) |
| | 999 | { |
| | 1000 | /* we can't resolve an unknown noun phrase used as an actor target */ |
| | 1001 | return nil; |
| | 1002 | } |
| | 1003 | |
| | 1004 | /* |
| | 1005 | * Get a raw pronoun antecedent list. Since we are resolving the |
| | 1006 | * target actor, pronouns are relative to the issuing actor. |
| | 1007 | */ |
| | 1008 | getRawPronounAntecedent(typ) |
| | 1009 | { |
| | 1010 | /* check for pronouns that are relative to the issuer */ |
| | 1011 | switch(typ) |
| | 1012 | { |
| | 1013 | case PronounMe: |
| | 1014 | /* |
| | 1015 | * It's a first-person construction. If the issuing actor |
| | 1016 | * is the player character, and the PC is in the second |
| | 1017 | * person, this refers to the player character (the game |
| | 1018 | * calls the PC "you", so the player calls the PC "me"). If |
| | 1019 | * the issuing actor is an NPC, this is unconditionally the |
| | 1020 | * PC. |
| | 1021 | */ |
| | 1022 | if (actor_.isPlayerChar && actor_.referralPerson != SecondPerson) |
| | 1023 | return []; |
| | 1024 | else |
| | 1025 | return [actor_]; |
| | 1026 | |
| | 1027 | case PronounYou: |
| | 1028 | /* |
| | 1029 | * It's a second-person construction. If the issuer is the |
| | 1030 | * player character, and the player character is in the |
| | 1031 | * first person, this refers to the player character (the |
| | 1032 | * game calls the PC "me", so the player calls the PC |
| | 1033 | * "you"). If the issuer isn't the player character, "you" |
| | 1034 | * has no meaning. |
| | 1035 | */ |
| | 1036 | if (!actor_.isPlayerChar || actor_.referralPerson != FirstPerson) |
| | 1037 | return []; |
| | 1038 | else |
| | 1039 | return [actor_]; |
| | 1040 | |
| | 1041 | default: |
| | 1042 | /* |
| | 1043 | * it's not a relative pronoun, so ask the issuing actor for |
| | 1044 | * the antecedent based on recent commands |
| | 1045 | */ |
| | 1046 | return actor_.getPronounAntecedent(typ); |
| | 1047 | } |
| | 1048 | } |
| | 1049 | |
| | 1050 | /* we resolve target actors */ |
| | 1051 | whichObject = ActorObject |
| | 1052 | whichMessageObject = ActorObject |
| | 1053 | ; |
| | 1054 | |
| | 1055 | /* |
| | 1056 | * A pseudo-action for "command actor." This represents the act of one |
| | 1057 | * actor (usually the PC) giving a command to another, as in "BOB, GO |
| | 1058 | * NORTH". This isn't a real action that the player can type; it's just |
| | 1059 | * an internal construct that we use to represent the partially resolved |
| | 1060 | * action, when we know that we're addressing another actor but we're |
| | 1061 | * still working on figuring out what we're saying. |
| | 1062 | */ |
| | 1063 | class CommandActorAction: Action |
| | 1064 | ; |
| | 1065 | |
| | 1066 | /* ------------------------------------------------------------------------ */ |
| | 1067 | /* |
| | 1068 | * A possessive resolver is a proxy to a main resolver that considers an |
| | 1069 | * object in scope if (a) it's in scope in the base resolver, or (b) the |
| | 1070 | * object is known to the actor. |
| | 1071 | */ |
| | 1072 | class PossessiveResolver: ProxyResolver |
| | 1073 | objInScope(obj) |
| | 1074 | { |
| | 1075 | /* |
| | 1076 | * An object is in scope for the purposes of a possessive phrase |
| | 1077 | * if it's in scope in the base resolver, or it's known to the |
| | 1078 | * actor. An object is only in scope for a possessive qualifier |
| | 1079 | * phrase if its canResolvePossessive property is true. |
| | 1080 | */ |
| | 1081 | return (obj.canResolvePossessive |
| | 1082 | && (origResolver.objInScope(obj) |
| | 1083 | || origResolver.getTargetActor().knowsAbout(obj))); |
| | 1084 | } |
| | 1085 | |
| | 1086 | /* this is a sub-resolver */ |
| | 1087 | isSubResolver = true |
| | 1088 | ; |
| | 1089 | |