| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library: parser |
| | 7 | * |
| | 8 | * This modules defines the language-independent parts of the command |
| | 9 | * parser. |
| | 10 | * |
| | 11 | * Portions based on xiny.t, copyright 2002 by Steve Breslin and |
| | 12 | * incorporated by permission. |
| | 13 | */ |
| | 14 | |
| | 15 | #include "adv3.h" |
| | 16 | #include "tok.h" |
| | 17 | #include <dict.h> |
| | 18 | #include <gramprod.h> |
| | 19 | #include <strcomp.h> |
| | 20 | |
| | 21 | |
| | 22 | /* ------------------------------------------------------------------------ */ |
| | 23 | /* |
| | 24 | * property we add to ResolveInfo to store the remaining items from an |
| | 25 | * ambiguous list |
| | 26 | */ |
| | 27 | property extraObjects; |
| | 28 | |
| | 29 | |
| | 30 | /* ------------------------------------------------------------------------ */ |
| | 31 | /* |
| | 32 | * ResolveResults - an instance of this class is passed to the |
| | 33 | * resolveNouns() routine to receive the results of the resolution. |
| | 34 | * |
| | 35 | * This class's main purpose is to virtualize the handling of error or |
| | 36 | * warning conditions during the resolution process. The ResolveResults |
| | 37 | * object is created by the initiator of the resolution, so it allows |
| | 38 | * the initiator to determine how errors are to be handled without |
| | 39 | * having to pass flags down through the match tree. |
| | 40 | */ |
| | 41 | class ResolveResults: object |
| | 42 | /* |
| | 43 | * Instances must provide the following methods: |
| | 44 | * |
| | 45 | * noVocabMatch(action, txt) - there are no objects in scope matching |
| | 46 | * the given noun phrase vocabulary. This is "worse" than noMatch(), |
| | 47 | * in the sense that this indicates that the unqualified noun phrase |
| | 48 | * simply doesn't refer to any objects in scope, whereas noMatch() |
| | 49 | * means that some qualification applied to the vocabulary ruled out |
| | 50 | * any matches. 'txt' is the original text of the noun phrase. |
| | 51 | * |
| | 52 | * noMatch(action, txt) - there are no objects in scope matching the |
| | 53 | * noun phrase. This is used in cases where we eliminate all |
| | 54 | * possible matches because of qualifications or constraints, so it's |
| | 55 | * possible that the noun phrase vocabulary, taken by itself, does |
| | 56 | * match some object; it's just that when the larger noun phrase |
| | 57 | * context is considered, there's nothing that matches. |
| | 58 | * |
| | 59 | * noMatchForAll() - there's nothing matching "all" |
| | 60 | * |
| | 61 | * noMatchForAllBut() - there's nothing matching "all except..." |
| | 62 | * (there might be something matching all, but there's nothing left |
| | 63 | * when the exception list is applied) |
| | 64 | * |
| | 65 | * noMatchForListBut() - there's nothing matching "<list> except..." |
| | 66 | * |
| | 67 | * noteEmptyBut() - a "but" (or "except") list matches nothing. We |
| | 68 | * don't consider this an error, but we rate an interpretation with a |
| | 69 | * non-empty "but" list and complete exclusion of the "all" or "any" |
| | 70 | * phrase whose objects are excluded by the "but" higher than one |
| | 71 | * with an empty "but" list and a non-empty all/any list - we do this |
| | 72 | * because it probably means that we came up with an incorrect |
| | 73 | * interpretation of the "but" phrase in the empty "but" list case |
| | 74 | * and failed to exclude things we should have excluded. |
| | 75 | * |
| | 76 | * noMatchForPronoun(typ, txt) - there's nothing matching a pronoun. |
| | 77 | * 'typ' is one of the PronounXxx constants, and 'txt' is the text of |
| | 78 | * the word used in the command. |
| | 79 | * |
| | 80 | * allNotAllowed() - the command contained the word ALL (or a |
| | 81 | * synonym), but the verb doesn't allow ALL to be used in its noun |
| | 82 | * phrases. |
| | 83 | * |
| | 84 | * reflexiveNotAllowed(typ, txt) - the reflexive pronoun isn't |
| | 85 | * allowed in this context. This usually means that the pronoun was |
| | 86 | * used with an intransitive or single-object action. 'typ' is one |
| | 87 | * of the PronounXxx constants, and 'txt' is the text of the word |
| | 88 | * used. |
| | 89 | * |
| | 90 | * wrongReflexive(typ, txt) - the reflexive pronoun doesn't agree |
| | 91 | * with its referent in gender, number, or some other way. |
| | 92 | * |
| | 93 | * noMatchForPossessive(owner, txt) - there's nothing matching the |
| | 94 | * phrase 'txt' owned by the resolved possessor object 'owner'. Note |
| | 95 | * that 'owner' is a list, since we can have plural possessive |
| | 96 | * qualifier phrases. |
| | 97 | * |
| | 98 | * noMatchForLocation(loc, txt) - there's nothing matching 'txt' in |
| | 99 | * the location object 'loc'. This is used when a noun phrase is |
| | 100 | * explicitly qualified by location ("the book on the table"). |
| | 101 | * |
| | 102 | * noteBadPrep() - we have a noun phrase or other phrase |
| | 103 | * incorporating an invalid prepositional phrase structure. This is |
| | 104 | * called from "badness" rules that are set up to match phrases with |
| | 105 | * embedded prepositions, as a last resort when no valid |
| | 106 | * interpretation can be found. |
| | 107 | * |
| | 108 | * nothingInLocation(loc) - there's nothing in the given location |
| | 109 | * object. This is used when we try to select the one object or all |
| | 110 | * of the objects in a given container, but the container doesn't |
| | 111 | * actually have any contents. |
| | 112 | * |
| | 113 | * ambiguousNounPhrase(keeper, asker, txt, matchLst, fullMatchList, |
| | 114 | * scopeList, requiredNum, resolver) - an ambiguous noun phrase was |
| | 115 | * entered: the noun phrase matches multiple objects that are all |
| | 116 | * equally qualified, but we only want the given exact number of |
| | 117 | * matches. 'asker' is a ResolveAsker object that we'll use to |
| | 118 | * generate any prompts; if no customization is required, simply pass |
| | 119 | * the base ResolveAsker. 'txt' is the original text of the noun |
| | 120 | * list in the command, which the standard prompt messages can use to |
| | 121 | * generate their questions. 'matchLst' is a list of the qualified |
| | 122 | * objects matching the phrase, with only one object included for |
| | 123 | * each set of equivalents in the original full list; 'fullMatchList' |
| | 124 | * is the full list of matches, including each copy of equivalents; |
| | 125 | * 'scopeList' is the list of everything in scope that matched the |
| | 126 | * original phrase, including illogical items. If it is desirable to |
| | 127 | * interact with the user at this point, prompt the user to resolve |
| | 128 | * the list, and return a new list with the results. If no prompting |
| | 129 | * is desired, the original list can be returned. If it is not |
| | 130 | * possible to determine the final set of objects, and a final set of |
| | 131 | * objects is required (this is up to the subclass to determine), a |
| | 132 | * parser exception should be thrown to stop further processing of |
| | 133 | * the command. 'keeper' is an AmbigResponseKeeper object, which is |
| | 134 | * usually simply the production object itself; each time we parse an |
| | 135 | * interactive response (if we are interactive at all), we'll call |
| | 136 | * addAmbigResponse() on the calling production object to add it to |
| | 137 | * the saved list of responses, and we'll call getAmbigResponses() to |
| | 138 | * find previous answers to the same question, in case of |
| | 139 | * re-resolving the phrase with 'again' or the like. |
| | 140 | * |
| | 141 | * unknownNounPhrase(match, resolver) - a noun phrase that doesn't |
| | 142 | * match any known noun phrase syntax was entered. 'match' is the |
| | 143 | * production match tree object for the unknown phrase. Returns a |
| | 144 | * list of the resolved objects for the noun phrase, if possible. If |
| | 145 | * it is not possible to resolve the phrase, and a resolution is |
| | 146 | * required (this is up to the subclass to determine), a parser |
| | 147 | * exception should be thrown. |
| | 148 | * |
| | 149 | * getImpliedObject(np, resolver) - a noun phrase was left out |
| | 150 | * entirely. 'np' is the noun phrase production standing in for the |
| | 151 | * missing noun phrase; this is usually an EmptyNounPhraseProd or a |
| | 152 | * subclass. If an object is implicit in the command, or a |
| | 153 | * reasonable default can be assumed, return the implicit or default |
| | 154 | * object or objects. If not, the routine can return nil or can |
| | 155 | * throw an error. The result is a ResolveInfo list. |
| | 156 | * |
| | 157 | * askMissingObject(asker, resolver, responseProd) - a noun phrase |
| | 158 | * was left out entirely, and no suitable default can be found |
| | 159 | * (getImpliedObject has already been called, and that returned nil). |
| | 160 | * If it is possible to ask the player interactively to fill in the |
| | 161 | * missing object, ask the player. If it isn't possible to resolve |
| | 162 | * an object, an error can be thrown, or an empty list can be |
| | 163 | * returned. 'asker' is a ResolveAsker object, which can be used to |
| | 164 | * customize the prompt (if any) that we show; pass the base |
| | 165 | * ResolveAsker if no customization is needed. 'responseProd' is the |
| | 166 | * production to use to parse the response. The return value is the |
| | 167 | * root match tree object of the player's interactive response, with |
| | 168 | * its 'resolvedObjects' property set to the ResolveInfo list from |
| | 169 | * resolving the player's response. (The routine returns the match |
| | 170 | * tree for the player's response so that, if we must run resolution |
| | 171 | * again on another pass, we can re-resolve the same response without |
| | 172 | * asking the player the same question again.) |
| | 173 | * |
| | 174 | * noteLiteral(txt) - note the text of a literal phrase. When |
| | 175 | * selecting among alternative interpretations of a phrase, we'll |
| | 176 | * favor shorter literals, since treating fewer tokens as literals |
| | 177 | * means that we're actually interpreting more tokens. |
| | 178 | * |
| | 179 | * askMissingLiteral(action, which) - a literal phrase was left out |
| | 180 | * entirely. If possible, prompt interactively for a player response |
| | 181 | * and return the result. If it's not possible to ask for a |
| | 182 | * response, an error can be thrown, or nil can be returned. The |
| | 183 | * return value is simply the text string the player enters. |
| | 184 | * |
| | 185 | * emptyNounPhrase(resolver) - a noun phrase involving only |
| | 186 | * qualifiers was entered ('take the'). In most cases, an exception |
| | 187 | * should be thrown. If the empty phrase can be resolved to an |
| | 188 | * object or list of objects, the resolved list should be returned. |
| | 189 | * |
| | 190 | * zeroQuantity(txt) - a noun phrase referred to a zero quantity of |
| | 191 | * something ("take zero books"). |
| | 192 | * |
| | 193 | * insufficientQuantity(txt, matchList, requiredNumber) - a noun |
| | 194 | * phrase is quantified with a number exceeding the number of objects |
| | 195 | * available: "take five books" when only two books are in scope. |
| | 196 | * |
| | 197 | * uniqueObjectRequired(txt, matchList) - a noun phrase yields more |
| | 198 | * than one object, but only one is allowed. For example, we'll call |
| | 199 | * this if the user attempts to use more than one result for a |
| | 200 | * single-noun phrase (such as by answering a disambiguation question |
| | 201 | * with 'all'). |
| | 202 | * |
| | 203 | * singleObjectRequired(txt) - a noun phrase list was used where a |
| | 204 | * single noun phrase is required. |
| | 205 | * |
| | 206 | * noteAdjEnding() - a noun phrase ends in an adjective. This isn't |
| | 207 | * normally an error, but is usually less desirable than interpreting |
| | 208 | * the same noun phrase as ending in a noun (in other words, if a |
| | 209 | * word can be used as both an adjective and a noun, it is usually |
| | 210 | * better to interpret the word as a noun rather than as an adjective |
| | 211 | * when the word appears at the end of a noun phrase, as long as the |
| | 212 | * noun interpretation matches an object in scope). |
| | 213 | * |
| | 214 | * noteIndefinite() - a noun phrase is phrased as an indefinite ("any |
| | 215 | * book", "one book"), meaning that we can arbitrarily choose any |
| | 216 | * matching object in case of ambiguity. Sometimes, an object will |
| | 217 | * have explicit vocabulary that could be taken to be indefinite: a |
| | 218 | * button labeled "1" could be a "1 button", for example, or a subway |
| | 219 | * might have an "A train". By noting the indefinite interpretation, |
| | 220 | * we can give priority to the alternative definite interpretation. |
| | 221 | * |
| | 222 | * noteMatches(matchList) - notifies the results object that the |
| | 223 | * given list of objects is being matched. This allows the results |
| | 224 | * object to inspect the object list for its strength: for example, |
| | 225 | * by noting the presence of truncated words. This should only be |
| | 226 | * called after the nouns have been resolved to the extent possible, |
| | 227 | * so any disambiguation or selection that is to be performed should |
| | 228 | * be performed before this routine is called. |
| | 229 | * |
| | 230 | * noteMiscWordList(txt) - a noun phrase is made up of miscellaneous |
| | 231 | * words. A miscellaneous word list as a noun phrase has non-zero |
| | 232 | * badness, so we will never use a misc word list unless we can't |
| | 233 | * find any more structured interpretation. In most cases, a |
| | 234 | * miscellaneous word list indicates an invalid phrasing, but in some |
| | 235 | * cases objects might use this unstructured type of noun phrase in |
| | 236 | * conjunction with matchName() to perform dynamic or special-case |
| | 237 | * parsing. |
| | 238 | * |
| | 239 | * notePronoun() - a noun phrase is matching as a pronoun. In |
| | 240 | * general, we prefer a match to an object's explicit vocabulary |
| | 241 | * words to a match to a pronoun phrase: if the game goes to the |
| | 242 | * trouble of including a word explicitly among an object's |
| | 243 | * vocabulary, that's a better match than treating the same word as a |
| | 244 | * generic pronoun. |
| | 245 | * |
| | 246 | * notePlural() - a plural phrase is matching. In some cases we |
| | 247 | * require a single object match, in which case a plural phrase is |
| | 248 | * undesirable. (A plural phrase might still match just one object, |
| | 249 | * though, so it can't be ruled out on structural grounds alone.) |
| | 250 | * |
| | 251 | * beginSingleObjSlot() and endSingleObjSlot() are used to bracket |
| | 252 | * resolution of a noun phrase that needs to be resolved as a single |
| | 253 | * object. We use these to explicitly lower the ranking for plural |
| | 254 | * structural phrasings within these slots. |
| | 255 | * |
| | 256 | * incCommandCount() - increment the command counter. This is used |
| | 257 | * to keep track of how many subcommands are in a command tree. |
| | 258 | * |
| | 259 | * noteActorSpecified() - note that the command is explicitly |
| | 260 | * addressed to an actor. |
| | 261 | * |
| | 262 | * noteNounSlots(cnt) - note the number of "noun slots" in the verb |
| | 263 | * phrase. This is the number of objects the verb takes: an |
| | 264 | * intransitive verb has no noun slots; a transitive verb with a |
| | 265 | * direct object only has one; a verb with a direct and indirect |
| | 266 | * object has two. Note this has nothing to do with the number of |
| | 267 | * objects specified in a noun list - TAKE BOX, BOOK, AND BELL has |
| | 268 | * only one noun slot (a direct object) even though the slot is |
| | 269 | * occupied by a list with three objects. |
| | 270 | * |
| | 271 | * noteWeakPhrasing(level) - note that the phrasing is "weak," with |
| | 272 | * the given weakness level - higher is weaker. The exact meaning of |
| | 273 | * the weakness levels is up to the language module to define. The |
| | 274 | * English module, for example, considers VERB IOBJ DOBJ phrasing |
| | 275 | * (with no preposition, as in GIVE BOB BOOK) to be weak when the |
| | 276 | * DOBJ part doesn't have a grammatical marker that clarifies that |
| | 277 | * it's really a separate noun phrase (an article serves this purpose |
| | 278 | * in English: GIVE BOB THE BOOK). |
| | 279 | * |
| | 280 | * allowActionRemapping - returns true if we can remap the action |
| | 281 | * during noun phrase resolution. Remapping is usually allowed only |
| | 282 | * during the actual execution phase, not during the ranking phase. |
| | 283 | * |
| | 284 | * allowEquivalentFiltering - returns true if we can filter an |
| | 285 | * ambiguous resolution list by making an arbitrary choice among |
| | 286 | * equivalent objects. This is normally allowed only during a final |
| | 287 | * resolution phase, not during a tentative resolution phase. |
| | 288 | */ |
| | 289 | ; |
| | 290 | |
| | 291 | /* ------------------------------------------------------------------------ */ |
| | 292 | /* |
| | 293 | * Noun phrase resolver "asker." This type of object can be passed to |
| | 294 | * certain ResolveResults methods in order to customize the messages |
| | 295 | * that the parser generates for interactive prompting. |
| | 296 | */ |
| | 297 | class ResolveAsker: object |
| | 298 | /* |
| | 299 | * Ask for help disambiguating a noun phrase. This asks which of |
| | 300 | * several possible matching objects was intended. This method has |
| | 301 | * the same parameter list as the equivalent message object method. |
| | 302 | */ |
| | 303 | askDisambig(targetActor, promptTxt, curMatchList, fullMatchList, |
| | 304 | requiredNum, askingAgain, dist) |
| | 305 | { |
| | 306 | /* let the target actor's parser message object handle it */ |
| | 307 | targetActor.getParserMessageObj().askDisambig( |
| | 308 | targetActor, promptTxt, curMatchList, fullMatchList, |
| | 309 | requiredNum, askingAgain, dist); |
| | 310 | } |
| | 311 | |
| | 312 | /* |
| | 313 | * Ask for a missing object. This prompts for an object that's |
| | 314 | * structurally required for an action, but which was omitted from |
| | 315 | * the player's command. |
| | 316 | */ |
| | 317 | askMissingObject(targetActor, action, which) |
| | 318 | { |
| | 319 | /* let the target actor's parser message object handle it */ |
| | 320 | targetActor.getParserMessageObj().askMissingObject( |
| | 321 | targetActor, action, which); |
| | 322 | } |
| | 323 | ; |
| | 324 | |
| | 325 | /* ------------------------------------------------------------------------ */ |
| | 326 | /* |
| | 327 | * The resolveNouns() method returns a list of ResolveInfo objects |
| | 328 | * describing the objects matched to the noun phrase. |
| | 329 | */ |
| | 330 | class ResolveInfo: object |
| | 331 | construct(obj, flags) |
| | 332 | { |
| | 333 | /* remember the members */ |
| | 334 | obj_ = obj; |
| | 335 | flags_ = flags; |
| | 336 | } |
| | 337 | |
| | 338 | /* |
| | 339 | * Look for a ResolveInfo item in a list of ResolveInfo items that |
| | 340 | * is equivalent to us. Returns true if we find such an item, nil |
| | 341 | * if not. |
| | 342 | * |
| | 343 | * Another ResolveInfo is equivalent to us if it refers to the same |
| | 344 | * underlying game object that we do, or if it refers to a game |
| | 345 | * object that is indistinguishable from our underlying game object. |
| | 346 | */ |
| | 347 | isEquivalentInList(lst) |
| | 348 | { |
| | 349 | /* |
| | 350 | * if we can find our exact item in the list, or we can find an |
| | 351 | * equivalent object, we have an equivalent |
| | 352 | */ |
| | 353 | return (lst.indexWhich({x: x.obj_ == obj_}) != nil |
| | 354 | || lst.indexWhich( |
| | 355 | {x: x.obj_.isVocabEquivalent(obj_)}) != nil); |
| | 356 | } |
| | 357 | |
| | 358 | /* |
| | 359 | * Look for a ResolveInfo item in a list of ResolveInfo items that |
| | 360 | * is equivalent to us according to a particular Distinguisher. |
| | 361 | */ |
| | 362 | isDistEquivInList(lst, dist) |
| | 363 | { |
| | 364 | /* |
| | 365 | * if we can find our exact item in the list, or we can find an |
| | 366 | * equivalent object, we have an equivalent |
| | 367 | */ |
| | 368 | return (lst.indexWhich({x: x.obj_ == obj_}) != nil |
| | 369 | || lst.indexWhich( |
| | 370 | {x: !dist.canDistinguish(x.obj_, obj_)}) != nil); |
| | 371 | } |
| | 372 | |
| | 373 | /* the object matched */ |
| | 374 | obj_ = nil |
| | 375 | |
| | 376 | /* flags describing how we matched the object */ |
| | 377 | flags_ = 0 |
| | 378 | |
| | 379 | /* |
| | 380 | * By default, each ResolveInfo counts as one object, for the |
| | 381 | * purposes of a quantity specifier (as in "five coins" or "both |
| | 382 | * hats"). However, in some cases, a single resolved object might |
| | 383 | * represent a collection of discrete objects and thus count as more |
| | 384 | * than one for the purposes of the quantifier. |
| | 385 | */ |
| | 386 | quant_ = 1 |
| | 387 | |
| | 388 | /* |
| | 389 | * The possessive ranking, if applicable. If this object is |
| | 390 | * qualified by a possessive phrase ("my books"), we'll set this to |
| | 391 | * a value indicating how strongly the possession applies to our |
| | 392 | * object: 2 indicates that the object is explicitly owned by the |
| | 393 | * object indicated in the possessive phrase, 1 indicates that it's |
| | 394 | * directly held by the named possessor but not explicitly owned, |
| | 395 | * and 0 indicates all else. In cases where there's no posessive |
| | 396 | * qualifier, this will simply be zero. |
| | 397 | */ |
| | 398 | possRank_ = 0 |
| | 399 | |
| | 400 | /* the pronoun type we matched, if any (as a PronounXxx enum) */ |
| | 401 | pronounType_ = nil |
| | 402 | |
| | 403 | /* the noun phrase we parsed to come up with this object */ |
| | 404 | np_ = nil |
| | 405 | ; |
| | 406 | |
| | 407 | /* |
| | 408 | * Intersect two resolved noun lists, returning a list consisting only |
| | 409 | * of the unique objects from the two lists. |
| | 410 | */ |
| | 411 | intersectNounLists(lst1, lst2) |
| | 412 | { |
| | 413 | local ret; |
| | 414 | |
| | 415 | /* we don't have any results yet */ |
| | 416 | ret = []; |
| | 417 | |
| | 418 | /* |
| | 419 | * run through each element of the first list to see if it has a |
| | 420 | * matching object in the second list |
| | 421 | */ |
| | 422 | foreach(local cur in lst1) |
| | 423 | { |
| | 424 | local other; |
| | 425 | /* |
| | 426 | * if this element's object occurs in the second list, we can |
| | 427 | * include it in the result list |
| | 428 | */ |
| | 429 | if ((other = lst2.valWhich({x: x.obj_ == cur.obj_})) != nil) |
| | 430 | { |
| | 431 | /* |
| | 432 | * include this one in the result list, with the combined |
| | 433 | * flags from the two original entries |
| | 434 | */ |
| | 435 | ret += new ResolveInfo(cur.obj_, cur.flags_ | other.flags_); |
| | 436 | } |
| | 437 | } |
| | 438 | |
| | 439 | /* return the result list */ |
| | 440 | return ret; |
| | 441 | } |
| | 442 | |
| | 443 | /* |
| | 444 | * Extract the objects from a list obtained with resolveNouns(). |
| | 445 | * Returns a list composed only of the objects in the resolution |
| | 446 | * information list. |
| | 447 | */ |
| | 448 | getResolvedObjects(lst) |
| | 449 | { |
| | 450 | /* |
| | 451 | * return a list composed only of the objects from the ResolveInfo |
| | 452 | * structures |
| | 453 | */ |
| | 454 | return lst.mapAll({x: x.obj_}); |
| | 455 | } |
| | 456 | |
| | 457 | |
| | 458 | /* ------------------------------------------------------------------------ */ |
| | 459 | /* |
| | 460 | * The basic production node base class. We'll use this as the base |
| | 461 | * class for all of our grammar rule match objects. |
| | 462 | */ |
| | 463 | class BasicProd: object |
| | 464 | /* get the original text of the command for this match */ |
| | 465 | getOrigText() |
| | 466 | { |
| | 467 | /* if we have no token list, return an empty string */ |
| | 468 | if (tokenList == nil) |
| | 469 | return ''; |
| | 470 | |
| | 471 | /* build the string based on my original token list */ |
| | 472 | return cmdTokenizer.buildOrigText(getOrigTokenList()); |
| | 473 | } |
| | 474 | |
| | 475 | /* get my original token list, in canonical tokenizer format */ |
| | 476 | getOrigTokenList() |
| | 477 | { |
| | 478 | /* |
| | 479 | * return the subset of the full token list from my first token |
| | 480 | * to my last token |
| | 481 | */ |
| | 482 | return nilToList(tokenList).sublist( |
| | 483 | firstTokenIndex, lastTokenIndex - firstTokenIndex + 1); |
| | 484 | } |
| | 485 | |
| | 486 | /* |
| | 487 | * Can this object match tree resolve to the given object? We'll |
| | 488 | * resolve the phrase as though it were a topic phrase, then look for |
| | 489 | * the object among the matches. |
| | 490 | */ |
| | 491 | canResolveTo(obj, action, issuingActor, targetActor, which) |
| | 492 | { |
| | 493 | /* set up a topic resolver */ |
| | 494 | local resolver = new TopicResolver( |
| | 495 | action, issuingActor, targetActor, self, which, |
| | 496 | createTopicQualifierResolver(issuingActor, targetActor)); |
| | 497 | |
| | 498 | /* |
| | 499 | * set up a results object - use a tentative results object, |
| | 500 | * since we're only looking at the potential matches, not doing a |
| | 501 | * full resolution pass |
| | 502 | */ |
| | 503 | local results = new TentativeResolveResults(issuingActor, targetActor); |
| | 504 | |
| | 505 | /* resolve the phrase as a topic */ |
| | 506 | local match = resolveNouns(resolver, results); |
| | 507 | |
| | 508 | /* |
| | 509 | * make sure it's packaged in the canonical topic form, as a |
| | 510 | * ResolvedTopic object |
| | 511 | */ |
| | 512 | match = resolver.packageTopicList(match, self); |
| | 513 | |
| | 514 | /* if the topic can match it, it's a possible resolution */ |
| | 515 | return (match != nil |
| | 516 | && match.length() > 0 |
| | 517 | && match[1].obj_.canMatchObject(obj)); |
| | 518 | } |
| | 519 | ; |
| | 520 | |
| | 521 | |
| | 522 | /* ------------------------------------------------------------------------ */ |
| | 523 | /* |
| | 524 | * Basic disambiguation production class |
| | 525 | */ |
| | 526 | class DisambigProd: BasicProd |
| | 527 | /* |
| | 528 | * Remove the "ambiguous" flags from a result list. This can be |
| | 529 | * used to mark the response to a disambiguation query as no longer |
| | 530 | * ambiguous. |
| | 531 | */ |
| | 532 | removeAmbigFlags(lst) |
| | 533 | { |
| | 534 | /* remove the "unclear disambig" flag from each result item */ |
| | 535 | foreach (local cur in lst) |
| | 536 | cur.flags_ &= ~UnclearDisambig; |
| | 537 | |
| | 538 | /* return the list */ |
| | 539 | return lst; |
| | 540 | } |
| | 541 | ; |
| | 542 | |
| | 543 | /* ------------------------------------------------------------------------ */ |
| | 544 | /* |
| | 545 | * Base class for "direction" productions. Each direction (the compass |
| | 546 | * directions, the vertical directions, the shipboard directions, and so |
| | 547 | * on) must have an associated grammar rule, which must produce one of |
| | 548 | * these. This should be subclassed with grammar rules like this: |
| | 549 | * |
| | 550 | * grammar directionName: 'north' | 'n' : DirectionProd |
| | 551 | *. dir = northDirection |
| | 552 | *. ; |
| | 553 | */ |
| | 554 | class DirectionProd: BasicProd |
| | 555 | /* |
| | 556 | * Each direction-specific grammar rule subclass must set this |
| | 557 | * property to the associated direction object (northDirection, |
| | 558 | * etc). |
| | 559 | */ |
| | 560 | dir = nil |
| | 561 | ; |
| | 562 | |
| | 563 | |
| | 564 | /* ------------------------------------------------------------------------ */ |
| | 565 | /* |
| | 566 | * The base class for commands. A command is the root of the grammar |
| | 567 | * match tree for a single action. A command line can consist of a |
| | 568 | * number of commands joined with command separators; in English, |
| | 569 | * command separators are things like periods, semicolons, commas, and |
| | 570 | * the words "and" and "then". |
| | 571 | */ |
| | 572 | class CommandProd: BasicProd |
| | 573 | hasTargetActor() |
| | 574 | { |
| | 575 | /* |
| | 576 | * By default, a command production does not include a |
| | 577 | * specification of a target actor. Command phrases that |
| | 578 | * contain syntax specifically directing the command to a target |
| | 579 | * actor should return true here. |
| | 580 | */ |
| | 581 | return nil; |
| | 582 | } |
| | 583 | |
| | 584 | /* |
| | 585 | * Get the match tree for the target actor phrase, if any. By |
| | 586 | * default, we have no target actor phrase, so just return nil. |
| | 587 | */ |
| | 588 | getActorPhrase = nil |
| | 589 | |
| | 590 | /* |
| | 591 | * "Execute" the actor phrase. This lets us know that the parser |
| | 592 | * has decided to use our phrasing to specify the target actor. |
| | 593 | * We're not required to do anything here; it's just a notification |
| | 594 | * for subclass use. Since we don't have a target actor phrase at |
| | 595 | * all, we obviously don't need to do anything here. |
| | 596 | */ |
| | 597 | execActorPhrase(issuingActor) { } |
| | 598 | ; |
| | 599 | |
| | 600 | /* |
| | 601 | * A first-on-line command. The first command on a command line can |
| | 602 | * optionally start with an actor specification, to give orders to the |
| | 603 | * actor. |
| | 604 | */ |
| | 605 | class FirstCommandProd: CommandProd |
| | 606 | countCommands(results) |
| | 607 | { |
| | 608 | /* count commands in the underlying command */ |
| | 609 | cmd_.countCommands(results); |
| | 610 | } |
| | 611 | |
| | 612 | getTargetActor() |
| | 613 | { |
| | 614 | /* |
| | 615 | * we have no actor specified explicitly, so it's the current |
| | 616 | * player character |
| | 617 | */ |
| | 618 | return libGlobal.playerChar; |
| | 619 | } |
| | 620 | |
| | 621 | /* |
| | 622 | * The tokens of the entire command except for the target actor |
| | 623 | * specification. By default, we take all of the tokens starting |
| | 624 | * with the first command's first token and running to the end of |
| | 625 | * the token list. This assumes that the target actor is specified |
| | 626 | * at the beginning of the command - languages that use some other |
| | 627 | * word ordering must override this accordingly. |
| | 628 | */ |
| | 629 | getCommandTokens() |
| | 630 | { |
| | 631 | return tokenList.sublist(cmd_.firstTokenIndex); |
| | 632 | } |
| | 633 | |
| | 634 | /* |
| | 635 | * Resolve my first action. This returns an instance of a subclass |
| | 636 | * of Action that represents the resolved action. We'll ask our |
| | 637 | * first subcommand to resolve its action. |
| | 638 | */ |
| | 639 | resolveFirstAction(issuingActor, targetActor) |
| | 640 | { |
| | 641 | return cmd_.resolveFirstAction(issuingActor, targetActor); |
| | 642 | } |
| | 643 | |
| | 644 | /* resolve nouns in the command */ |
| | 645 | resolveNouns(issuingActor, targetActor, results) |
| | 646 | { |
| | 647 | /* resolve nouns in the underlying command */ |
| | 648 | cmd_.resolveNouns(issuingActor, targetActor, results); |
| | 649 | |
| | 650 | /* count our commands */ |
| | 651 | countCommands(results); |
| | 652 | } |
| | 653 | |
| | 654 | /* |
| | 655 | * Does this command end a sentence? The exact meaning of a |
| | 656 | * sentence may vary by language; in English, a sentence ends with |
| | 657 | * certain punctuation marks (a period, a semicolon, an exclamation |
| | 658 | * point). |
| | 659 | */ |
| | 660 | isEndOfSentence() |
| | 661 | { |
| | 662 | /* ask the underlying command phrase */ |
| | 663 | return cmd_.isEndOfSentence(); |
| | 664 | } |
| | 665 | |
| | 666 | /* |
| | 667 | * Get the token index of the first command separator token. This |
| | 668 | * is the first token that is not part of the underlying command. |
| | 669 | */ |
| | 670 | getCommandSepIndex() |
| | 671 | { |
| | 672 | /* get the separator index from the underlying command */ |
| | 673 | return cmd_.getCommandSepIndex(); |
| | 674 | } |
| | 675 | |
| | 676 | /* |
| | 677 | * get the token index of the next command - this is the index of |
| | 678 | * the next token after our conjunction if we have one, or after our |
| | 679 | * command if we don't have a conjunction |
| | 680 | */ |
| | 681 | getNextCommandIndex() |
| | 682 | { |
| | 683 | /* get the next command index from the underlying command */ |
| | 684 | return cmd_.getNextCommandIndex(); |
| | 685 | } |
| | 686 | ; |
| | 687 | |
| | 688 | /* |
| | 689 | * Define the simplest grammar rule for a first-on-line command phrase, |
| | 690 | * which is just an ordinary command phrase. The language-specific |
| | 691 | * grammar must define any other alternatives; specifically, the |
| | 692 | * language might provide an "actor, command" syntax to direct a command |
| | 693 | * to a particular actor. |
| | 694 | */ |
| | 695 | grammar firstCommandPhrase(commandOnly): commandPhrase->cmd_ |
| | 696 | : FirstCommandProd |
| | 697 | ; |
| | 698 | |
| | 699 | /* |
| | 700 | * A command with an actor specification. This should be instantiated |
| | 701 | * with grammar rules in a language-specific module. |
| | 702 | * |
| | 703 | * Instantiating grammar rules should set property actor_ to the actor |
| | 704 | * match tree, and cmd_ to the underlying 'commandPhrase' production |
| | 705 | * match tree. |
| | 706 | */ |
| | 707 | class CommandProdWithActor: CommandProd |
| | 708 | hasTargetActor() |
| | 709 | { |
| | 710 | /* this command explicitly specifies an actor */ |
| | 711 | return true; |
| | 712 | } |
| | 713 | getTargetActor() |
| | 714 | { |
| | 715 | /* return my resolved actor object */ |
| | 716 | return resolvedActor_; |
| | 717 | } |
| | 718 | getActorPhrase() |
| | 719 | { |
| | 720 | /* return the actor phrase production */ |
| | 721 | return actor_; |
| | 722 | } |
| | 723 | |
| | 724 | /* |
| | 725 | * Execute the target actor phrase. This is a notification, for use |
| | 726 | * by subclasses; we don't have anything we need to do in this base |
| | 727 | * class implementation. |
| | 728 | */ |
| | 729 | execActorPhrase(issuingActor) { } |
| | 730 | |
| | 731 | /* |
| | 732 | * Resolve nouns. We'll resolve only the nouns in the target actor |
| | 733 | * phrase; we do not resolve nouns in the command phrase, because we |
| | 734 | * must re-parse the command phrase after we've finished resolving |
| | 735 | * the actor phrase, and thus we can't resolve nouns in the command |
| | 736 | * phrase until after the re-parse is completed. |
| | 737 | */ |
| | 738 | resolveNouns(issuingActor, targetActor, results) |
| | 739 | { |
| | 740 | local lst; |
| | 741 | |
| | 742 | /* |
| | 743 | * Resolve the actor, then we're done. Note that we do not |
| | 744 | * attempt to resolve anything in the underlying command yet, |
| | 745 | * because we can only resolve the command after we've fully |
| | 746 | * processed the actor clause. |
| | 747 | */ |
| | 748 | lst = actor_.resolveNouns(getResolver(issuingActor), results); |
| | 749 | |
| | 750 | /* |
| | 751 | * there are no noun phrase slots, since we're not looking at the |
| | 752 | * verb |
| | 753 | */ |
| | 754 | results.noteNounSlots(0); |
| | 755 | |
| | 756 | /* |
| | 757 | * if we have an object in the list, use it - note that our |
| | 758 | * actor phrase is a single-noun production, and hence should |
| | 759 | * never yield more than a single entry in its resolution list |
| | 760 | */ |
| | 761 | if (lst.length() > 0) |
| | 762 | { |
| | 763 | /* remember the resolved actor */ |
| | 764 | resolvedActor_ = lst[1].obj_; |
| | 765 | |
| | 766 | /* note in the results that an actor is specified */ |
| | 767 | results.noteActorSpecified(); |
| | 768 | } |
| | 769 | |
| | 770 | /* count our commands */ |
| | 771 | countCommands(results); |
| | 772 | } |
| | 773 | |
| | 774 | /* get or create my actor resolver */ |
| | 775 | getResolver(issuingActor) |
| | 776 | { |
| | 777 | /* if I don't already have a resolver, create one and cache it */ |
| | 778 | if (resolver_ == nil) |
| | 779 | resolver_ = new ActorResolver(issuingActor); |
| | 780 | |
| | 781 | /* return our cached resolver */ |
| | 782 | return resolver_; |
| | 783 | } |
| | 784 | |
| | 785 | /* my resolved actor object */ |
| | 786 | resolvedActor_ = nil |
| | 787 | |
| | 788 | /* my actor resolver object */ |
| | 789 | resolver_ = nil |
| | 790 | ; |
| | 791 | |
| | 792 | /* |
| | 793 | * First-on-line command with target actor. As with |
| | 794 | * CommandProdWithActor, instantiating grammar rules must set the |
| | 795 | * property actor_ to the actor match tree, and cmd_ to the underlying |
| | 796 | * commandPhrase match. |
| | 797 | */ |
| | 798 | class FirstCommandProdWithActor: CommandProdWithActor, FirstCommandProd |
| | 799 | ; |
| | 800 | |
| | 801 | |
| | 802 | /* ------------------------------------------------------------------------ */ |
| | 803 | /* |
| | 804 | * The 'predicate' production is the grammar rule for all individual |
| | 805 | * command phrases. We don't define anything about the predicate |
| | 806 | * grammar here, since it is completely language-dependent, but we do |
| | 807 | * *use* the predicate production as a sub-production of our |
| | 808 | * commandPhrase rules. |
| | 809 | * |
| | 810 | * The language-dependent implementation of the 'predicate' production |
| | 811 | * must provide the following methods: |
| | 812 | * |
| | 813 | * resolveAction(issuingActor, targetActor): This method returns a |
| | 814 | * newly-created instance of the Action subclass that the command refers |
| | 815 | * to. This method must generally interpret the phrasing of the command |
| | 816 | * to determine what noun phrases are present and what roles they serve, |
| | 817 | * and what verb phrase is present, then create an appropriate Action |
| | 818 | * subclass to represent the verb phrase as applied to the noun phrases |
| | 819 | * in their determined roles. |
| | 820 | * |
| | 821 | * resolveNouns(issuingActor, targetActor, results): This method |
| | 822 | * resolves all of the noun phrases in the predicate, using the |
| | 823 | * 'results' object (an object of class ResolveResults) to store |
| | 824 | * information about the status of the resolution. Generally, this |
| | 825 | * routine should collect information about the roles of the noun phrase |
| | 826 | * production matches, plug these into the Action via appropriate |
| | 827 | * properties (dobjMatch for the direct object of a transitive verb, for |
| | 828 | * example), resolve the Action, and then call the Action to do the |
| | 829 | * resolution. This method has no return value, since the Action is |
| | 830 | * responsible for keeping track of the results of the resolution. |
| | 831 | * |
| | 832 | * For languages like English which encode most information about the |
| | 833 | * phrase roles in word ordering, a good way to design a predicate |
| | 834 | * grammar is to specify the syntax of each possible verb phrase as a |
| | 835 | * 'predicate' rule, and base the match object for that rule on the |
| | 836 | * corresponding Action subclass. For example, the English grammar has |
| | 837 | * this rule to define the syntax for the verb 'take': |
| | 838 | * |
| | 839 | * VerbRule |
| | 840 | *. ('take' | 'pick' 'up' | 'get') dobjList |
| | 841 | *. | 'pick' dobjList 'up' |
| | 842 | *. : TakeAction |
| | 843 | *. ; |
| | 844 | * |
| | 845 | * Since English encodes everything in this command positionally, it's |
| | 846 | * straightforward to write grammar rules for the possible syntax |
| | 847 | * variations of a given action, hence it's easy to associate command |
| | 848 | * syntax directly with its associated action. When each 'predicate' |
| | 849 | * grammar maps to an Action subclass for its match object, |
| | 850 | * resolveAction() is especially easy to implement: it simply returns |
| | 851 | * 'self', since the grammar match and the Action are the same object. |
| | 852 | * It's also easy to plug each noun phrase into its appropriate property |
| | 853 | * slot in the Action subclass: the parser can be told to plug in each |
| | 854 | * noun phrase directly using the "->" notation in the grammar. |
| | 855 | * |
| | 856 | * Many languages encode the roles of noun phrases with case markers or |
| | 857 | * other syntactic devices, and as a result do not impose such strict |
| | 858 | * rules as English does on word order. For such languages, it is |
| | 859 | * usually better to construct the 'predicate' grammar separately from |
| | 860 | * any single action, so that the various acceptable phrase orderings |
| | 861 | * are enumerated, and the verb phrase is just another phrase that plugs |
| | 862 | * into these top-level orderings. In such grammars, the predicate must |
| | 863 | * do some programmatic work in resolveAction(): it must figure out |
| | 864 | * which Action subclass is involved based on the verb phrase sub-match |
| | 865 | * object and the noun phrases present, then must create an instance of |
| | 866 | * that Action subclass. Furthermore, since we can't plug the noun |
| | 867 | * phrases into the Action using the "->" notation in the grammar, the |
| | 868 | * resolveAction() routine must pick out the sub-match objects |
| | 869 | * representing the noun phrases and plug them into the Action itself. |
| | 870 | * |
| | 871 | * Probably the easiest way to accomplish all of this is by designing |
| | 872 | * each verb phrase match object so that it is associated with one or |
| | 873 | * more Action subclasses, according to the specific noun phrases |
| | 874 | * present. The details of this mapping are obviously specific to each |
| | 875 | * language, but it should be possible in most cases to build a base |
| | 876 | * class for all verb phrases that uses parameters to create the Action |
| | 877 | * and noun phrase associations. For example, each verb phrase grammar |
| | 878 | * match object might have a list of possible Action matches, such as |
| | 879 | * this example from a purely hypothetical English grammar based on this |
| | 880 | * technique |
| | 881 | * |
| | 882 | * grammar verbPhrase: 'take' | 'pick' 'up': VerbProd |
| | 883 | *. actionMap = |
| | 884 | *. [ |
| | 885 | *. [TakeAction, BasicProd, &dobjMatch], |
| | 886 | *. [TakeWithAction, BasicProd, &dobjMatch, WithProd, &iobjMatch] |
| | 887 | *. ] |
| | 888 | *. ; |
| | 889 | * |
| | 890 | * The idea is that the base VerbProd class looks through the list given |
| | 891 | * by the actionMap property for a template that matches the number and |
| | 892 | * type of noun phrases present in the predicate; when it finds a match, |
| | 893 | * it creates an instance of the Action subclass given in the template, |
| | 894 | * then plugs the noun phrases into the listed properties of the new |
| | 895 | * Action instance. |
| | 896 | * |
| | 897 | * Note that our verbPhrase example above is not an example of actual |
| | 898 | * working code, because there is no such thing in the |
| | 899 | * language-independent library as a VerbProd base class that reads |
| | 900 | * actionMap properites - this is a purely hypothetical bit of code to |
| | 901 | * illustrate how such a construction might work in a language that |
| | 902 | * requires it, and it is up to the language-specific module to define |
| | 903 | * such a mechanism for its own use. |
| | 904 | */ |
| | 905 | |
| | 906 | |
| | 907 | /* ------------------------------------------------------------------------ */ |
| | 908 | /* |
| | 909 | * Base classes for grammar matches for full commands. |
| | 910 | * |
| | 911 | * There are two kinds of command separators: ambiguous and unambiguous. |
| | 912 | * Unambiguous separators are those that can separate only commands, such |
| | 913 | * as "then" and periods. Ambiguous separators are those that can |
| | 914 | * separate nouns in a noun list as well as commands; for example "and" |
| | 915 | * and commas. |
| | 916 | * |
| | 917 | * First, CommandProdWithDefiniteConj, which is for a single full |
| | 918 | * command, unambiguously terminated with a separator that can only mean |
| | 919 | * that a new command follows. We parse one command at a time when a |
| | 920 | * command line includes several commands, since we can only resolve |
| | 921 | * objects - and thus can only choose structural interpretations - when |
| | 922 | * we reach the game state in which a given command is to be executed. |
| | 923 | * |
| | 924 | * Second, CommandProdWithAmbiguousConj, which is for multiple commands |
| | 925 | * separated by a conjunction that does not end a sentence, but could |
| | 926 | * just as well separate two noun phrases. In this case, we parse both |
| | 927 | * commands, to ensure that we actually have a well-formed command |
| | 928 | * following the conjunction; this allows us to try interpreting the part |
| | 929 | * after the conjunction as a command and also as a noun phrase, to see |
| | 930 | * which interpretation looks better. |
| | 931 | * |
| | 932 | * When we find an unambiguous command separator, we can simply use the |
| | 933 | * '*' grammar match to put off parsing everything after the separator |
| | 934 | * until later, reducing the complexity of the grammar tree. When we |
| | 935 | * find an ambiguous separator, we can't put off parsing the rest with |
| | 936 | * '*', because we need to know if the part after the separator can |
| | 937 | * indeed be construed as a new command. During resolution, we'll take a |
| | 938 | * noun list interpretation of 'and' over a command list whenever doing |
| | 939 | * so would give us resolvable noun phrases. |
| | 940 | */ |
| | 941 | |
| | 942 | /* |
| | 943 | * Match class for a command phrase that is separated from anything that |
| | 944 | * follows with an unambiguous conjunction. |
| | 945 | * |
| | 946 | * Grammar rules based on this match class must set property cmd_ to the |
| | 947 | * underlying 'predicate' production. |
| | 948 | */ |
| | 949 | class CommandProdWithDefiniteConj: CommandProd |
| | 950 | resolveNouns(issuingActor, targetActor, results) |
| | 951 | { |
| | 952 | /* resolve nouns */ |
| | 953 | cmd_.resolveNouns(issuingActor, targetActor, results); |
| | 954 | |
| | 955 | /* count commands */ |
| | 956 | countCommands(results); |
| | 957 | } |
| | 958 | countCommands(results) |
| | 959 | { |
| | 960 | /* we have only one subcommand */ |
| | 961 | if (cmdCounted_++ == 0) |
| | 962 | results.incCommandCount(); |
| | 963 | } |
| | 964 | |
| | 965 | /* counter: have we counted our command in the results object yet? */ |
| | 966 | cmdCounted_ = 0 |
| | 967 | |
| | 968 | /* resolve my first action */ |
| | 969 | resolveFirstAction(issuingActor, targetActor) |
| | 970 | { |
| | 971 | return cmd_.resolveAction(issuingActor, targetActor); |
| | 972 | } |
| | 973 | |
| | 974 | /* does this command end a sentence */ |
| | 975 | isEndOfSentence() |
| | 976 | { |
| | 977 | /* |
| | 978 | * it's the end of the sentence if our predicate encompasses all |
| | 979 | * of the tokens in the command, or the conjunction is a |
| | 980 | * sentence-ending conjunction |
| | 981 | */ |
| | 982 | return conj_ == nil || conj_.isEndOfSentence(); |
| | 983 | } |
| | 984 | |
| | 985 | /* |
| | 986 | * Get the token index of the first command separator token. This |
| | 987 | * is the first token that is not part of the underlying command. |
| | 988 | */ |
| | 989 | getCommandSepIndex() |
| | 990 | { |
| | 991 | /* |
| | 992 | * if we have a conjunction, return the first token index of the |
| | 993 | * conjunction; otherwise, return the index of the next token |
| | 994 | * after the command itself |
| | 995 | */ |
| | 996 | if (conj_ != nil) |
| | 997 | return conj_.firstTokenIndex; |
| | 998 | else |
| | 999 | return cmd_.lastTokenIndex + 1; |
| | 1000 | } |
| | 1001 | |
| | 1002 | /* |
| | 1003 | * get the token index of the next command - this is the index of |
| | 1004 | * the next token after our conjunction if we have one, or after our |
| | 1005 | * command if we don't have a conjunction |
| | 1006 | */ |
| | 1007 | getNextCommandIndex() |
| | 1008 | { |
| | 1009 | return (conj_ == nil ? cmd_ : conj_).lastTokenIndex + 1; |
| | 1010 | } |
| | 1011 | ; |
| | 1012 | |
| | 1013 | /* |
| | 1014 | * Match class for two command phrases separated by an ambiguous |
| | 1015 | * conjunction (i.e., a conjunction that could also separate two noun |
| | 1016 | * phrases). Grammar rules based on this class must set the properties |
| | 1017 | * 'cmd1_' to the underlying 'predicate' production match of the first |
| | 1018 | * command, and 'cmd2_' to the underlying 'commandPhrase' production |
| | 1019 | * match of the second command. |
| | 1020 | */ |
| | 1021 | class CommandProdWithAmbiguousConj: CommandProd |
| | 1022 | resolveNouns(issuingActor, targetActor, results) |
| | 1023 | { |
| | 1024 | /* |
| | 1025 | * Resolve nouns in the first subcommand only. Do NOT resolve |
| | 1026 | * nouns in any of the additional subcommands (there might be |
| | 1027 | * more than one, since cmd2_ can be a list of subcommands, not |
| | 1028 | * just a single subcommand), because we cannot assume that the |
| | 1029 | * current scope will continue to be valid after executing the |
| | 1030 | * first subcommand - the first command could take us to a |
| | 1031 | * different location, or change the lighting conditions, or add |
| | 1032 | * or remove objects from the location, or any number of other |
| | 1033 | * things that would invalidate the current scope. |
| | 1034 | */ |
| | 1035 | cmd1_.resolveNouns(issuingActor, targetActor, results); |
| | 1036 | |
| | 1037 | /* count our commands */ |
| | 1038 | countCommands(results); |
| | 1039 | } |
| | 1040 | countCommands(results) |
| | 1041 | { |
| | 1042 | /* count our first subcommand (cmd1_) */ |
| | 1043 | if (cmdCounted_++ == 0) |
| | 1044 | results.incCommandCount(); |
| | 1045 | |
| | 1046 | /* count results in the rest of the list (cmd2_ and its children) */ |
| | 1047 | cmd2_.countCommands(results); |
| | 1048 | } |
| | 1049 | |
| | 1050 | /* counter: have we counted our command in the results object yet? */ |
| | 1051 | cmdCounted_ = 0 |
| | 1052 | |
| | 1053 | /* resolve my first action */ |
| | 1054 | resolveFirstAction(issuingActor, targetActor) |
| | 1055 | { |
| | 1056 | return cmd1_.resolveAction(issuingActor, targetActor); |
| | 1057 | } |
| | 1058 | |
| | 1059 | /* does this command end a sentence */ |
| | 1060 | isEndOfSentence() |
| | 1061 | { |
| | 1062 | /* |
| | 1063 | * it's the end of the sentence if the conjunction is a |
| | 1064 | * sentence-ending conjunction |
| | 1065 | */ |
| | 1066 | return conj_.isEndOfSentence(); |
| | 1067 | } |
| | 1068 | |
| | 1069 | /* |
| | 1070 | * Get the token index of the first command separator token. This |
| | 1071 | * is the first token that is not part of the underlying command. |
| | 1072 | */ |
| | 1073 | getCommandSepIndex() |
| | 1074 | { |
| | 1075 | /* return the conjunction's first token index */ |
| | 1076 | return conj_.firstTokenIndex; |
| | 1077 | } |
| | 1078 | |
| | 1079 | /* |
| | 1080 | * get the token index of the next command - this is simply the |
| | 1081 | * starting index for our second subcommand tree |
| | 1082 | */ |
| | 1083 | getNextCommandIndex() { return cmd2_.firstTokenIndex; } |
| | 1084 | ; |
| | 1085 | |
| | 1086 | /* |
| | 1087 | * The basic grammar rule for an unambiguous end-of-sentence command. |
| | 1088 | * This matches either a predicate with nothing following, or a predicate |
| | 1089 | * with an unambiguous command-only conjunction following. |
| | 1090 | */ |
| | 1091 | grammar commandPhrase(definiteConj): |
| | 1092 | predicate->cmd_ |
| | 1093 | | predicate->cmd_ commandOnlyConjunction->conj_ * |
| | 1094 | : CommandProdWithDefiniteConj |
| | 1095 | ; |
| | 1096 | |
| | 1097 | /* |
| | 1098 | * the basic grammar rule for a pair of commands with an ambiguous |
| | 1099 | * separator (i.e., a separator that could separate commands or |
| | 1100 | * conjunctions) |
| | 1101 | */ |
| | 1102 | grammar commandPhrase(ambiguousConj): |
| | 1103 | predicate->cmd1_ commandOrNounConjunction->conj_ commandPhrase->cmd2_ |
| | 1104 | : CommandProdWithAmbiguousConj |
| | 1105 | ; |
| | 1106 | |
| | 1107 | /* ------------------------------------------------------------------------ */ |
| | 1108 | /* |
| | 1109 | * We leave almost everything about the grammatical rules of noun |
| | 1110 | * phrases to the language-specific implementation, but we provide a set |
| | 1111 | * of base classes for implementing several conceptual structures that |
| | 1112 | * have equivalents in most languages. |
| | 1113 | */ |
| | 1114 | |
| | 1115 | |
| | 1116 | /* |
| | 1117 | * Basic noun phrase production class. |
| | 1118 | */ |
| | 1119 | class NounPhraseProd: BasicProd |
| | 1120 | /* |
| | 1121 | * Determine whether this kind of noun phrase prefers to keep a |
| | 1122 | * collective or the collective's individuals when filtering. If |
| | 1123 | * this is true, we'll keep a collective and discard its individuals |
| | 1124 | * when filtering a resolution list; otherwise, we'll drop the |
| | 1125 | * collective and keep the individuals. |
| | 1126 | */ |
| | 1127 | filterForCollectives = nil |
| | 1128 | |
| | 1129 | /* |
| | 1130 | * Filter a "verify" result list for the results we'd like to keep |
| | 1131 | * in the final resolution of the noun phrase. This is called after |
| | 1132 | * we've run through the verification process on the list of |
| | 1133 | * candidate matches, so 'lst' is a list of ResolveInfo objects, |
| | 1134 | * sorted in descending order of logicalness. |
| | 1135 | * |
| | 1136 | * By default, we keep only the items that are equally as logical as |
| | 1137 | * the best item in the results. Since the items are sorted in |
| | 1138 | * descending order of goodness, the best item is always the first |
| | 1139 | * item. |
| | 1140 | */ |
| | 1141 | getVerifyKeepers(results) |
| | 1142 | { |
| | 1143 | local best; |
| | 1144 | |
| | 1145 | /* |
| | 1146 | * Reduce the list to the most logical elements - in other |
| | 1147 | * words, keep only the elements that are exactly as logical as |
| | 1148 | * the first element, which we know to have the best logicalness |
| | 1149 | * ranking in the list by virtue of having sorted the list in |
| | 1150 | * descending order of logicalness. |
| | 1151 | */ |
| | 1152 | best = results[1]; |
| | 1153 | return results.subset({x: x.compareTo(best) == 0}); |
| | 1154 | } |
| | 1155 | |
| | 1156 | /* |
| | 1157 | * Filter a match list of results for truncated matches. If we have |
| | 1158 | * a mix of truncated matches and exact matches, we'll keep only the |
| | 1159 | * exact matches. If we have only truncated matches, though, we'll |
| | 1160 | * return the list unchanged, as we don't have a better offer going. |
| | 1161 | */ |
| | 1162 | filterTruncations(lst, resolver) |
| | 1163 | { |
| | 1164 | local exactLst; |
| | 1165 | |
| | 1166 | /* |
| | 1167 | * If we're in "global scope," it means that we're resolving |
| | 1168 | * something like a topic phrase, and we're not limited to the |
| | 1169 | * current physical scope but can choose objects from the entire |
| | 1170 | * game world. In this case, don't apply any truncation |
| | 1171 | * filtering. The reason is that we could have several unrelated |
| | 1172 | * objects in the game with vocabulary that differs only in |
| | 1173 | * truncation, but the user has forgotten about the ones with the |
| | 1174 | * longer names and is thinking of something she's referred to |
| | 1175 | * with truncation in the past, when the longer-named objects |
| | 1176 | * weren't around. We're aware of all of those longer-named |
| | 1177 | * objects, but it's not helpful to the player, since they might |
| | 1178 | * currently be out of sight. |
| | 1179 | */ |
| | 1180 | if (resolver.isGlobalScope) |
| | 1181 | return lst; |
| | 1182 | |
| | 1183 | /* get the list of exact (i.e., untruncated) matches */ |
| | 1184 | exactLst = lst.subset( |
| | 1185 | {x: (x.flags_ & (VocabTruncated | PluralTruncated)) == 0}); |
| | 1186 | |
| | 1187 | /* |
| | 1188 | * if we have any exact matches, use only the exact matches; if |
| | 1189 | * we don't, use the full list as is |
| | 1190 | */ |
| | 1191 | return (exactLst.length() != 0 ? exactLst : lst); |
| | 1192 | } |
| | 1193 | ; |
| | 1194 | |
| | 1195 | /* |
| | 1196 | * Basic noun phrase list production class. |
| | 1197 | */ |
| | 1198 | class NounListProd: BasicProd |
| | 1199 | ; |
| | 1200 | |
| | 1201 | /* |
| | 1202 | * Basic "layered" noun phrase production. It's often useful to define |
| | 1203 | * a grammar rule that simply defers to an underlying grammar rule; we |
| | 1204 | * make this simpler by defining this class that automatically delegates |
| | 1205 | * resolveNouns to the underlying noun phrase given by the property np_. |
| | 1206 | */ |
| | 1207 | class LayeredNounPhraseProd: NounPhraseProd |
| | 1208 | resolveNouns(resolver, results) |
| | 1209 | { |
| | 1210 | /* let the underlying match tree object do the work */ |
| | 1211 | return np_.resolveNouns(resolver, results); |
| | 1212 | } |
| | 1213 | ; |
| | 1214 | |
| | 1215 | |
| | 1216 | /* ------------------------------------------------------------------------ */ |
| | 1217 | /* |
| | 1218 | * A single noun is sometimes required where, structurally, a list is |
| | 1219 | * not allowed. Single nouns should not be used to prohibit lists where |
| | 1220 | * there is no structural reason for the prohibition - these should be |
| | 1221 | * used only where it doesn't make sense to use a list structurally. |
| | 1222 | */ |
| | 1223 | class SingleNounProd: NounPhraseProd |
| | 1224 | resolveNouns(resolver, results) |
| | 1225 | { |
| | 1226 | local lst; |
| | 1227 | |
| | 1228 | /* tell the results object we're resolving a single-object slot */ |
| | 1229 | results.beginSingleObjSlot(); |
| | 1230 | |
| | 1231 | /* resolve the underlying noun phrase */ |
| | 1232 | lst = np_.resolveNouns(resolver, results); |
| | 1233 | |
| | 1234 | /* tell the results object we're done with the single-object slot */ |
| | 1235 | results.endSingleObjSlot(); |
| | 1236 | |
| | 1237 | /* make sure the list has only one element */ |
| | 1238 | if (lst.length() > 1) |
| | 1239 | results.uniqueObjectRequired(getOrigText(), lst); |
| | 1240 | |
| | 1241 | /* return the results */ |
| | 1242 | return lst; |
| | 1243 | } |
| | 1244 | ; |
| | 1245 | |
| | 1246 | /* |
| | 1247 | * A user could attempt to use a noun list where a single noun is |
| | 1248 | * required. This is not a grammatical error, so we accept it |
| | 1249 | * grammatically; however, for disambiguation purposes we score it lower |
| | 1250 | * than a singleNoun production with only one noun phrase, and if we try |
| | 1251 | * to resolve it, we'll fail with an error. |
| | 1252 | */ |
| | 1253 | class SingleNounWithListProd: NounPhraseProd |
| | 1254 | resolveNouns(resolver, results) |
| | 1255 | { |
| | 1256 | /* note the problem */ |
| | 1257 | results.singleObjectRequired(getOrigText()); |
| | 1258 | |
| | 1259 | /* |
| | 1260 | * In case we have any unknown words or other detrimental |
| | 1261 | * aspects, resolve the underlying noun list as well, so we can |
| | 1262 | * score even lower. Note that doing this after noting our |
| | 1263 | * single-object-required problem will avoid any interactive |
| | 1264 | * resolution (such as asking for disambiguation information), |
| | 1265 | * since once we get to the point where we're ready to do |
| | 1266 | * anything interactive, the single-object-required results |
| | 1267 | * notification will fail with an error, so we won't make it to |
| | 1268 | * this point during interactive resolution. |
| | 1269 | */ |
| | 1270 | np_.resolveNouns(resolver, results); |
| | 1271 | |
| | 1272 | /* we have no resolution */ |
| | 1273 | return []; |
| | 1274 | } |
| | 1275 | ; |
| | 1276 | |
| | 1277 | /* |
| | 1278 | * A topic is a noun phrase used in commands like "ask <person> about |
| | 1279 | * <topic>." For our purposes, this works as an ordinary single noun |
| | 1280 | * production. |
| | 1281 | */ |
| | 1282 | class TopicProd: SingleNounProd |
| | 1283 | /* get the original text and tokens from the underlying phrase */ |
| | 1284 | getOrigTokenList() { return np_.getOrigTokenList(); } |
| | 1285 | getOrigText() { return np_.getOrigText(); } |
| | 1286 | ; |
| | 1287 | |
| | 1288 | /* |
| | 1289 | * A literal is a string enclosed in quotes. |
| | 1290 | */ |
| | 1291 | class LiteralProd: BasicProd |
| | 1292 | ; |
| | 1293 | |
| | 1294 | /* |
| | 1295 | * Basic class for pronoun phrases. The specific pronouns are |
| | 1296 | * language-dependent; each instance should define its pronounType |
| | 1297 | * property to an appropriate PronounXxx constant. |
| | 1298 | */ |
| | 1299 | class PronounProd: NounPhraseProd |
| | 1300 | resolveNouns(resolver, results) |
| | 1301 | { |
| | 1302 | local lst; |
| | 1303 | |
| | 1304 | /* |
| | 1305 | * check for a valid anaphoric binding (i.e., a back-reference to |
| | 1306 | * an object mentioned earlier in the current command: ASK BOB |
| | 1307 | * ABOUT HIMSELF) |
| | 1308 | */ |
| | 1309 | lst = checkAnaphoricBinding(resolver, results); |
| | 1310 | |
| | 1311 | /* |
| | 1312 | * if we didn't get an anaphoric binding, find an antecedent from |
| | 1313 | * a previous command |
| | 1314 | */ |
| | 1315 | if (lst == nil) |
| | 1316 | { |
| | 1317 | /* ask the resolver to get the antecedent */ |
| | 1318 | lst = resolver.resolvePronounAntecedent( |
| | 1319 | pronounType, self, results, isPossessive); |
| | 1320 | |
| | 1321 | /* if there are no results, note the error */ |
| | 1322 | if (lst == []) |
| | 1323 | results.noMatchForPronoun(pronounType, |
| | 1324 | getOrigText().toLower()); |
| | 1325 | |
| | 1326 | /* remember the pronoun type in each ResolveInfo */ |
| | 1327 | lst.forEach({x: x.pronounType_ = pronounType}); |
| | 1328 | |
| | 1329 | /* |
| | 1330 | * If the pronoun is singular, but we got multiple potential |
| | 1331 | * antecedents, it means that the previous command had |
| | 1332 | * multiple noun slots (as in UNLOCK DOOR WITH KEY) and |
| | 1333 | * didn't want to decide a priori which one is the antecedent |
| | 1334 | * for future pronouns. So, we have to decide now which of |
| | 1335 | * the potential antecedents to use as the actual antecedent. |
| | 1336 | * Choose the most logical, if there's a clearly more logical |
| | 1337 | * one. |
| | 1338 | */ |
| | 1339 | if (!isPlural && lst.length() > 1) |
| | 1340 | { |
| | 1341 | /* filter the phrase using the normal disambiguation */ |
| | 1342 | lst = resolver.filterAmbiguousNounPhrase(lst, 1, self); |
| | 1343 | |
| | 1344 | /* |
| | 1345 | * if that leaves more than one item, pick the first one |
| | 1346 | * and mark it as unclearly disambiguated |
| | 1347 | */ |
| | 1348 | if (lst.length() > 1) |
| | 1349 | { |
| | 1350 | /* arbitrarily keep the first item only */ |
| | 1351 | lst = lst.sublist(1, 1); |
| | 1352 | |
| | 1353 | /* mark it as an arbitrary choice */ |
| | 1354 | lst[1].flags_ |= UnclearDisambig; |
| | 1355 | } |
| | 1356 | } |
| | 1357 | } |
| | 1358 | |
| | 1359 | /* note that we have phrase involving a pronoun */ |
| | 1360 | results.notePronoun(); |
| | 1361 | |
| | 1362 | /* return the result list */ |
| | 1363 | return lst; |
| | 1364 | } |
| | 1365 | |
| | 1366 | /* |
| | 1367 | * our pronoun specifier - this must be set in each rule instance to |
| | 1368 | * one of the PronounXxx constants to specify which pronoun to use |
| | 1369 | * when resolving the pronoun phrase |
| | 1370 | */ |
| | 1371 | pronounType = nil |
| | 1372 | |
| | 1373 | /* is this a possessive usage? */ |
| | 1374 | isPossessive = nil |
| | 1375 | |
| | 1376 | /* |
| | 1377 | * Is this pronoun a singular or a plural? A pronoun like "it" or |
| | 1378 | * "he" is singular, because it refers to a single antecedent; "them" |
| | 1379 | * is plural. Language modules that define their own custom pronoun |
| | 1380 | * subclasses should override this as needed. |
| | 1381 | */ |
| | 1382 | isPlural = nil |
| | 1383 | |
| | 1384 | /* |
| | 1385 | * Check for an anaphoric binding. Returns a list (which is allowed |
| | 1386 | * to be empty) if this can refer back to an earlier noun phrase in |
| | 1387 | * the same command, nil if not. By default, we consider pronouns |
| | 1388 | * to be non-anaphoric, meaning they refer to something from a |
| | 1389 | * previous sentence, not something in this same sentence. In most |
| | 1390 | * languages, pronouns don't refer to objects in other noun phrases |
| | 1391 | * within the same predicate unless they're reflexive. |
| | 1392 | */ |
| | 1393 | checkAnaphoricBinding(resolver, results) { return nil; } |
| | 1394 | ; |
| | 1395 | |
| | 1396 | /* |
| | 1397 | * For simplicity, define subclasses of PronounProd for the basic set of |
| | 1398 | * pronouns found in most languages. Language-specific grammar |
| | 1399 | * definitions can choose to use these or not, and can add their own |
| | 1400 | * extra subclasses as needed for types of pronouns we don't define |
| | 1401 | * here. |
| | 1402 | */ |
| | 1403 | class ItProd: PronounProd |
| | 1404 | pronounType = PronounIt |
| | 1405 | ; |
| | 1406 | |
| | 1407 | class ThemProd: PronounProd |
| | 1408 | pronounType = PronounThem |
| | 1409 | isPlural = true |
| | 1410 | ; |
| | 1411 | |
| | 1412 | class HimProd: PronounProd |
| | 1413 | pronounType = PronounHim |
| | 1414 | ; |
| | 1415 | |
| | 1416 | class HerProd: PronounProd |
| | 1417 | pronounType = PronounHer |
| | 1418 | ; |
| | 1419 | |
| | 1420 | class MeProd: PronounProd |
| | 1421 | pronounType = PronounMe |
| | 1422 | ; |
| | 1423 | |
| | 1424 | class YouProd: PronounProd |
| | 1425 | pronounType = PronounYou |
| | 1426 | ; |
| | 1427 | |
| | 1428 | /* |
| | 1429 | * Third-person anaphoric reflexive pronouns. These refer to objects |
| | 1430 | * that appeared earlier in the sentence: "ask bob about himself." |
| | 1431 | */ |
| | 1432 | class ReflexivePronounProd: PronounProd |
| | 1433 | resolveNouns(resolver, results) |
| | 1434 | { |
| | 1435 | local lst; |
| | 1436 | |
| | 1437 | /* ask the resolver for the reflexive pronoun binding */ |
| | 1438 | lst = resolver.getReflexiveBinding(pronounType); |
| | 1439 | |
| | 1440 | /* |
| | 1441 | * if the result is empty, the verb will provide the binding |
| | 1442 | * later, on a second pass |
| | 1443 | */ |
| | 1444 | if (lst == []) |
| | 1445 | return lst; |
| | 1446 | |
| | 1447 | /* |
| | 1448 | * If the result is nil, the verb is saying that the reflexive |
| | 1449 | * pronoun makes no sense internally within the predicate |
| | 1450 | * structure. In this case, or if we did get a list that |
| | 1451 | * doesn't agree with the pronoun type (in number or gender, for |
| | 1452 | * example), consider the reflexive to refer back to the actor, |
| | 1453 | * for a construct such as TELL BOB TO HIT HIMSELF. However, |
| | 1454 | * only do this if the issuer and target actor aren't the same, |
| | 1455 | * since we generally don't refer to the PC in the third person. |
| | 1456 | */ |
| | 1457 | if ((lst == nil || !checkAgreement(lst)) |
| | 1458 | && resolver.actor_ != resolver.issuer_) |
| | 1459 | { |
| | 1460 | /* try treating the actor as the reflexive pronoun */ |
| | 1461 | lst = [new ResolveInfo(resolver.actor_, 0)]; |
| | 1462 | } |
| | 1463 | |
| | 1464 | /* if the list is nil, it means reflexives aren't allowed here */ |
| | 1465 | if (lst == nil) |
| | 1466 | { |
| | 1467 | results.reflexiveNotAllowed(pronounType, getOrigText.toLower()); |
| | 1468 | return []; |
| | 1469 | } |
| | 1470 | |
| | 1471 | /* |
| | 1472 | * Check the list for agreement (in gender, number, and so on). |
| | 1473 | * Don't bother if the list is empty, as this is the action's |
| | 1474 | * way of telling us that it doesn't have a binding for us yet |
| | 1475 | * but will provide one on a subsequent attempt at re-resolving |
| | 1476 | * this phrase. |
| | 1477 | */ |
| | 1478 | if (!checkAgreement(lst)) |
| | 1479 | results.wrongReflexive(pronounType, getOrigText().toLower()); |
| | 1480 | |
| | 1481 | /* return the result list */ |
| | 1482 | return lst; |
| | 1483 | } |
| | 1484 | |
| | 1485 | /* |
| | 1486 | * Check that the binding we found for our reflexive pronoun agrees |
| | 1487 | * with the pronoun in gender, number, and anything else that it has |
| | 1488 | * to agree with. This must be defined by each concrete subclass. |
| | 1489 | * Note that language-specific subclasses can and *should* override |
| | 1490 | * this to test agreement for the local language's rules. |
| | 1491 | * |
| | 1492 | * This should return true if we agree, nil if not. |
| | 1493 | */ |
| | 1494 | checkAgreement(lst) { return true; } |
| | 1495 | ; |
| | 1496 | |
| | 1497 | class ItselfProd: ReflexivePronounProd |
| | 1498 | pronounType = PronounIt |
| | 1499 | ; |
| | 1500 | |
| | 1501 | class ThemselvesProd: ReflexivePronounProd |
| | 1502 | pronounType = PronounThem |
| | 1503 | ; |
| | 1504 | |
| | 1505 | class HimselfProd: ReflexivePronounProd |
| | 1506 | pronounType = PronounHim |
| | 1507 | ; |
| | 1508 | |
| | 1509 | class HerselfProd: ReflexivePronounProd |
| | 1510 | pronounType = PronounHer |
| | 1511 | ; |
| | 1512 | |
| | 1513 | /* |
| | 1514 | * The special 'all' constructions are full noun phrases. |
| | 1515 | */ |
| | 1516 | class EverythingProd: BasicProd |
| | 1517 | resolveNouns(resolver, results) |
| | 1518 | { |
| | 1519 | local lst; |
| | 1520 | |
| | 1521 | /* check to make sure 'all' is allowed */ |
| | 1522 | if (!resolver.allowAll()) |
| | 1523 | { |
| | 1524 | /* it's not - flag an error and give up */ |
| | 1525 | results.allNotAllowed(); |
| | 1526 | return []; |
| | 1527 | } |
| | 1528 | |
| | 1529 | /* get the 'all' list */ |
| | 1530 | lst = resolver.getAll(self); |
| | 1531 | |
| | 1532 | /* |
| | 1533 | * set the "always announce" flag for each item - the player |
| | 1534 | * didn't name these items specifically, so always show what we |
| | 1535 | * chose |
| | 1536 | */ |
| | 1537 | foreach (local cur in lst) |
| | 1538 | cur.flags_ |= AlwaysAnnounce | MatchedAll; |
| | 1539 | |
| | 1540 | /* make sure there's something in it */ |
| | 1541 | if (lst.length() == 0) |
| | 1542 | results.noMatchForAll(); |
| | 1543 | |
| | 1544 | /* return the list */ |
| | 1545 | return lst; |
| | 1546 | } |
| | 1547 | |
| | 1548 | /* match Collective objects instead of their individuals */ |
| | 1549 | filterForCollectives = true |
| | 1550 | ; |
| | 1551 | |
| | 1552 | |
| | 1553 | /* ------------------------------------------------------------------------ */ |
| | 1554 | /* |
| | 1555 | * Basic exclusion list ("except the silver one") production base class. |
| | 1556 | */ |
| | 1557 | class ExceptListProd: BasicProd |
| | 1558 | ; |
| | 1559 | |
| | 1560 | /* |
| | 1561 | * Basic "but" rule, which selects a list of plurals minus a list of |
| | 1562 | * specifically excepted objects. This can be used to construct more |
| | 1563 | * specific production classes for things like "everything but the book" |
| | 1564 | * and "all books except the red ones". |
| | 1565 | * |
| | 1566 | * In each grammar rule based on this class, the 'except_' property must |
| | 1567 | * be set to a suitable noun phrase for the exception list. We'll |
| | 1568 | * resolve this list and remove the objects in it from our main list. |
| | 1569 | */ |
| | 1570 | class ButProd: NounPhraseProd |
| | 1571 | resolveNouns(resolver, results) |
| | 1572 | { |
| | 1573 | local mainList; |
| | 1574 | local butList; |
| | 1575 | local butRemapList; |
| | 1576 | local action; |
| | 1577 | local role; |
| | 1578 | local remapProp; |
| | 1579 | |
| | 1580 | /* get our main list of items to include */ |
| | 1581 | mainList = getMainList(resolver, results); |
| | 1582 | |
| | 1583 | /* filter out truncated matches if we have any exact matches */ |
| | 1584 | mainList = filterTruncations(mainList, resolver); |
| | 1585 | |
| | 1586 | /* |
| | 1587 | * resolve the 'except' list - use an 'except' resolver based on |
| | 1588 | * our list so that we resolve these objects in the scope of our |
| | 1589 | * main list |
| | 1590 | */ |
| | 1591 | butList = except_.resolveNouns( |
| | 1592 | new ExceptResolver(mainList, getOrigText(), resolver), |
| | 1593 | new ExceptResults(results)); |
| | 1594 | |
| | 1595 | /* if the exception list is empty, tell the results about it */ |
| | 1596 | if (butList == []) |
| | 1597 | results.noteEmptyBut(); |
| | 1598 | |
| | 1599 | /* |
| | 1600 | * Get the remapping property for this object role for this |
| | 1601 | * action. This property applies to each of the objects we're |
| | 1602 | * resolving, and tells us if the resolved object is going to |
| | 1603 | * remap its handling of this action when the object is used in |
| | 1604 | * this role. For example, the Take action's remapping property |
| | 1605 | * for the direct object would usually be remapDobjTake, so |
| | 1606 | * book.remapDobjTake would tell us if TAKE BOOK were going to |
| | 1607 | * be remapped. |
| | 1608 | */ |
| | 1609 | action = resolver.getAction(); |
| | 1610 | role = resolver.whichObject; |
| | 1611 | remapProp = action.getRemapPropForRole(role); |
| | 1612 | |
| | 1613 | /* get the list of simple synonym remappings for the 'except' list */ |
| | 1614 | butRemapList = butList.mapAll( |
| | 1615 | {x: action.getSimpleSynonymRemap(x.obj_, role, remapProp)}); |
| | 1616 | |
| | 1617 | /* |
| | 1618 | * scan the 'all' list, and remove each item that appears in the |
| | 1619 | * 'except' list |
| | 1620 | */ |
| | 1621 | for (local i = 1, local len = mainList.length() ; i <= len ; ++i) |
| | 1622 | { |
| | 1623 | local curRemap; |
| | 1624 | |
| | 1625 | /* get the current 'all' list element */ |
| | 1626 | local cur = mainList[i].obj_; |
| | 1627 | |
| | 1628 | /* get the simple synonym remapping for this item, if any */ |
| | 1629 | curRemap = action.getSimpleSynonymRemap(cur, role, remapProp); |
| | 1630 | |
| | 1631 | /* |
| | 1632 | * If this item appears in the 'except' list, remove it. |
| | 1633 | * |
| | 1634 | * Similarly, if this item is remapped to something that |
| | 1635 | * appears in the 'except' list, remove it. |
| | 1636 | * |
| | 1637 | * Similarly, if something in the 'except' list is remapped |
| | 1638 | * to this item, remove this item. |
| | 1639 | */ |
| | 1640 | if (butList.indexWhich({x: x.obj_ == cur}) != nil |
| | 1641 | || butRemapList.indexWhich({x: x == cur}) != nil |
| | 1642 | || butList.indexWhich({x: x.obj_ == curRemap}) != nil) |
| | 1643 | { |
| | 1644 | /* remove it and adjust our loop counters accordingly */ |
| | 1645 | mainList = mainList.removeElementAt(i); |
| | 1646 | --i; |
| | 1647 | --len; |
| | 1648 | } |
| | 1649 | } |
| | 1650 | |
| | 1651 | /* if that doesn't leave anything, it's an error */ |
| | 1652 | if (mainList == []) |
| | 1653 | flagAllExcepted(resolver, results); |
| | 1654 | |
| | 1655 | /* perform the final filtering on the list for our subclass */ |
| | 1656 | mainList = filterFinalList(mainList); |
| | 1657 | |
| | 1658 | /* add any flags to the result list that our subclass indicates */ |
| | 1659 | foreach (local cur in mainList) |
| | 1660 | cur.flags_ |= addedFlags; |
| | 1661 | |
| | 1662 | /* note the matched objects in the results */ |
| | 1663 | results.noteMatches(mainList); |
| | 1664 | |
| | 1665 | /* return whatever we have left after the exclusions */ |
| | 1666 | return mainList; |
| | 1667 | } |
| | 1668 | |
| | 1669 | /* get my main list, which is the list of items to include */ |
| | 1670 | getMainList(resolver, results) { return []; } |
| | 1671 | |
| | 1672 | /* flag an error - everything has been excluded by the 'but' list */ |
| | 1673 | flagAllExcepted(resolver, results) { } |
| | 1674 | |
| | 1675 | /* filter the final list - by default we just return the same list */ |
| | 1676 | filterFinalList(lst) { return lst; } |
| | 1677 | |
| | 1678 | /* by default, add no extra flags to our resolved object list */ |
| | 1679 | addedFlags = 0 |
| | 1680 | ; |
| | 1681 | |
| | 1682 | |
| | 1683 | /* ------------------------------------------------------------------------ */ |
| | 1684 | /* |
| | 1685 | * Base class for "all but" rules, which select everything available |
| | 1686 | * except for objects in a specified list of exceptions; for example, in |
| | 1687 | * English, "take everything but the book". |
| | 1688 | */ |
| | 1689 | class EverythingButProd: ButProd |
| | 1690 | /* our main list is given by the "all" list */ |
| | 1691 | getMainList(resolver, results) |
| | 1692 | { |
| | 1693 | /* check to make sure 'all' is allowed */ |
| | 1694 | if (!resolver.allowAll()) |
| | 1695 | { |
| | 1696 | /* it's not - flag an error and give up */ |
| | 1697 | results.allNotAllowed(); |
| | 1698 | return []; |
| | 1699 | } |
| | 1700 | |
| | 1701 | /* return the 'all' list */ |
| | 1702 | return resolver.getAll(self); |
| | 1703 | } |
| | 1704 | |
| | 1705 | /* flag an error - our main list has been completely excluded */ |
| | 1706 | flagAllExcepted(resolver, results) |
| | 1707 | { |
| | 1708 | results.noMatchForAllBut(); |
| | 1709 | } |
| | 1710 | |
| | 1711 | /* |
| | 1712 | * set the "always announce" flag for each item - the player didn't |
| | 1713 | * name the selected items specifically, so always show what we |
| | 1714 | * chose |
| | 1715 | */ |
| | 1716 | addedFlags = AlwaysAnnounce |
| | 1717 | |
| | 1718 | /* match Collective objects instead of their individuals */ |
| | 1719 | filterForCollectives = true |
| | 1720 | ; |
| | 1721 | |
| | 1722 | /* |
| | 1723 | * Base class for "list but" rules, which select everything in an |
| | 1724 | * explicitly provided list minus a set of exceptions; for example, in |
| | 1725 | * English, "take all of the books except the red ones". |
| | 1726 | * |
| | 1727 | * Subclasses defining grammar rules must set the 'np_' property to the |
| | 1728 | * main noun list; we'll resolve this list to find the objects to be |
| | 1729 | * included before exclusions are applied. |
| | 1730 | */ |
| | 1731 | class ListButProd: ButProd |
| | 1732 | /* our main list is given by the 'np_' subproduction */ |
| | 1733 | getMainList(resolver, results) |
| | 1734 | { |
| | 1735 | return np_.resolveNouns(resolver, results); |
| | 1736 | } |
| | 1737 | |
| | 1738 | /* flag an error - everything has been excluded */ |
| | 1739 | flagAllExcepted(resolver, results) |
| | 1740 | { |
| | 1741 | results.noMatchForListBut(); |
| | 1742 | } |
| | 1743 | |
| | 1744 | /* |
| | 1745 | * set the "unclear disambig" flag in our results, so we provide an |
| | 1746 | * indication of which object we chose |
| | 1747 | */ |
| | 1748 | addedFlags = UnclearDisambig |
| | 1749 | ; |
| | 1750 | |
| | 1751 | |
| | 1752 | /* ------------------------------------------------------------------------ */ |
| | 1753 | /* |
| | 1754 | * Pre-resolved phrase production. This isn't normally used in any |
| | 1755 | * actual grammar; instead, this is for use when building actions |
| | 1756 | * programmatically. This allows us to fill in an action tree when we |
| | 1757 | * already know the object we want to resolve. |
| | 1758 | */ |
| | 1759 | class PreResolvedProd: BasicProd |
| | 1760 | construct(obj) |
| | 1761 | { |
| | 1762 | /* if it's not a collection, we need to make it a list */ |
| | 1763 | if (!obj.ofKind(Collection)) |
| | 1764 | { |
| | 1765 | /* if it's not already a ResolveInfo, wrap it in a ResolveInfo */ |
| | 1766 | if (!obj.ofKind(ResolveInfo)) |
| | 1767 | obj = new ResolveInfo(obj, 0); |
| | 1768 | |
| | 1769 | /* the resolved object list is simply the one ResolveInfo */ |
| | 1770 | obj = [obj]; |
| | 1771 | } |
| | 1772 | |
| | 1773 | /* store the new ResolveInfo list */ |
| | 1774 | obj_ = obj; |
| | 1775 | } |
| | 1776 | |
| | 1777 | /* resolve the nouns: this is easy, since we started out resolved */ |
| | 1778 | resolveNouns(resolver, results) |
| | 1779 | { |
| | 1780 | /* return our pre-resolved object */ |
| | 1781 | return obj_; |
| | 1782 | } |
| | 1783 | |
| | 1784 | /* |
| | 1785 | * Our pre-resolved object result. This is a list containing a |
| | 1786 | * single ResolveInfo representing our resolved object, since this is |
| | 1787 | * the form required by callers of resolveNouns. |
| | 1788 | */ |
| | 1789 | obj_ = nil |
| | 1790 | ; |
| | 1791 | |
| | 1792 | /* |
| | 1793 | * A pre-resolved *ambiguous* noun phrase. This is used when the game |
| | 1794 | * or library wants to suggest a specific set of objects for a new |
| | 1795 | * action, then ask which one to use. |
| | 1796 | */ |
| | 1797 | class PreResolvedAmbigProd: DefiniteNounProd |
| | 1798 | construct(objs, asker, phrase) |
| | 1799 | { |
| | 1800 | /* remember my list of possible objects as a resolved object list */ |
| | 1801 | objs_ = objs.mapAll({x: new ResolveInfo(x, 0)}); |
| | 1802 | |
| | 1803 | /* remember the ResolveAsker to use */ |
| | 1804 | asker_ = asker; |
| | 1805 | |
| | 1806 | /* remember the noun phrase to use in disambiguation questions */ |
| | 1807 | phrase_ = phrase; |
| | 1808 | } |
| | 1809 | |
| | 1810 | resolveNouns(resolver, results) |
| | 1811 | { |
| | 1812 | /* resolve our list using definite-phrase rules */ |
| | 1813 | return resolveDefinite(asker_, phrase_, objs_, |
| | 1814 | self, resolver, results); |
| | 1815 | } |
| | 1816 | |
| | 1817 | /* my pre-resolved list of ambiguous objects */ |
| | 1818 | objs_ = nil |
| | 1819 | |
| | 1820 | /* the noun phrase to use in disambiguation questions */ |
| | 1821 | phrase_ = nil |
| | 1822 | |
| | 1823 | /* the ResolveAsker to use when prompting for the selection */ |
| | 1824 | asker_ = nil |
| | 1825 | ; |
| | 1826 | |
| | 1827 | /* |
| | 1828 | * Pre-resolved literal phrase production |
| | 1829 | */ |
| | 1830 | class PreResolvedLiteralProd: BasicProd |
| | 1831 | construct(txt) |
| | 1832 | { |
| | 1833 | /* |
| | 1834 | * If the argument is a ResolveInfo, assume its obj_ property |
| | 1835 | * gives the literal string, and retrieve the string. |
| | 1836 | */ |
| | 1837 | if (txt.ofKind(ResolveInfo)) |
| | 1838 | txt = txt.obj_; |
| | 1839 | |
| | 1840 | /* save the text */ |
| | 1841 | text_ = txt; |
| | 1842 | } |
| | 1843 | |
| | 1844 | /* get the text */ |
| | 1845 | getLiteralText(results, action, which) { return text_; } |
| | 1846 | getTentativeLiteralText() { return text_; } |
| | 1847 | |
| | 1848 | /* our underlying text */ |
| | 1849 | text_ = nil |
| | 1850 | ; |
| | 1851 | |
| | 1852 | |
| | 1853 | /* ------------------------------------------------------------------------ */ |
| | 1854 | /* |
| | 1855 | * Mix-in class for noun phrase productions that use |
| | 1856 | * ResolveResults.ambiguousNounPhrase(). This mix-in provides the |
| | 1857 | * methods that ambiguousNounPhrase() uses to keep track of past |
| | 1858 | * responses to the disambiguation question. |
| | 1859 | */ |
| | 1860 | class AmbigResponseKeeper: object |
| | 1861 | addAmbigResponse(resp) |
| | 1862 | { |
| | 1863 | /* add an ambiguous response to our list */ |
| | 1864 | ambigResponses_ += resp; |
| | 1865 | } |
| | 1866 | |
| | 1867 | getAmbigResponses() |
| | 1868 | { |
| | 1869 | /* return our list of past interactive disambiguation responses */ |
| | 1870 | return ambigResponses_; |
| | 1871 | } |
| | 1872 | |
| | 1873 | /* our list of saved interactive disambiguation responses */ |
| | 1874 | ambigResponses_ = [] |
| | 1875 | ; |
| | 1876 | |
| | 1877 | |
| | 1878 | /* ------------------------------------------------------------------------ */ |
| | 1879 | /* |
| | 1880 | * Base class for noun phrase productions with definite articles. |
| | 1881 | */ |
| | 1882 | class DefiniteNounProd: NounPhraseProd, AmbigResponseKeeper |
| | 1883 | resolveNouns(resolver, results) |
| | 1884 | { |
| | 1885 | /* resolve our underlying noun phrase using definite rules */ |
| | 1886 | return resolveDefinite(ResolveAsker, np_.getOrigText(), |
| | 1887 | np_.resolveNouns(resolver, results), |
| | 1888 | self, resolver, results); |
| | 1889 | } |
| | 1890 | |
| | 1891 | /* |
| | 1892 | * Resolve an underlying phrase using definite noun phrase rules. |
| | 1893 | */ |
| | 1894 | resolveDefinite(asker, origText, lst, responseKeeper, resolver, results) |
| | 1895 | { |
| | 1896 | local scopeList; |
| | 1897 | local fullList; |
| | 1898 | |
| | 1899 | /* filter the list to remove truncations if we have exact matches */ |
| | 1900 | lst = filterTruncations(lst, resolver); |
| | 1901 | |
| | 1902 | /* |
| | 1903 | * Remember the current list, before filtering for logical |
| | 1904 | * matches and before filtering out equivalents, as our full |
| | 1905 | * scope list. If we have to ask for clarification, this is the |
| | 1906 | * scope of the clarification. |
| | 1907 | */ |
| | 1908 | scopeList = lst; |
| | 1909 | |
| | 1910 | /* filter for possessive qualification strength */ |
| | 1911 | lst = resolver.filterPossRank(lst, 1); |
| | 1912 | |
| | 1913 | /* filter out the obvious mismatches */ |
| | 1914 | lst = resolver.filterAmbiguousNounPhrase(lst, 1, self); |
| | 1915 | |
| | 1916 | /* |
| | 1917 | * remember the current list as the full match list, before |
| | 1918 | * filtering equivalents |
| | 1919 | */ |
| | 1920 | fullList = lst; |
| | 1921 | |
| | 1922 | /* |
| | 1923 | * reduce the list to include only one of each set of |
| | 1924 | * equivalents; do this only if the results object allows this |
| | 1925 | * kind of filtering |
| | 1926 | */ |
| | 1927 | if (results.allowEquivalentFiltering) |
| | 1928 | lst = resolver.filterAmbiguousEquivalents(lst, self); |
| | 1929 | |
| | 1930 | /* do any additional subclass-specific filtering on the list */ |
| | 1931 | lst = reduceDefinite(lst, resolver, results); |
| | 1932 | |
| | 1933 | /* |
| | 1934 | * This is (explicitly or implicitly) a definite noun phrase, so |
| | 1935 | * we must resolve to exactly one object. If the list has more |
| | 1936 | * than one object, we must disambiguate it. |
| | 1937 | */ |
| | 1938 | if (lst.length() == 0) |
| | 1939 | { |
| | 1940 | /* there are no objects matching the phrase */ |
| | 1941 | results.noMatch(resolver.getAction(), origText); |
| | 1942 | } |
| | 1943 | else if (lst.length() > 1) |
| | 1944 | { |
| | 1945 | /* |
| | 1946 | * the noun phrase is ambiguous - pass in the full list |
| | 1947 | * (rather than the list with the redundant equivalents |
| | 1948 | * weeded out) so that we can display the appropriate |
| | 1949 | * choices for multiple equivalent items |
| | 1950 | */ |
| | 1951 | lst = results.ambiguousNounPhrase( |
| | 1952 | responseKeeper, asker, origText, lst, fullList, |
| | 1953 | scopeList, 1, resolver); |
| | 1954 | } |
| | 1955 | |
| | 1956 | /* note the matched objects in the results */ |
| | 1957 | results.noteMatches(lst); |
| | 1958 | |
| | 1959 | /* return the results */ |
| | 1960 | return lst; |
| | 1961 | } |
| | 1962 | |
| | 1963 | /* |
| | 1964 | * Do any additional subclass-specific filtering to further reduce |
| | 1965 | * the list before we decide whether or not we have sufficient |
| | 1966 | * specificity. We call this just before deciding whether or not to |
| | 1967 | * prompt for more information ("which book do you mean...?"). By |
| | 1968 | * default, this simply returns the same list unchanged; subclasses |
| | 1969 | * that have some further way of narrowing down the options can use |
| | 1970 | * this opportunity to apply that extra narrowing. |
| | 1971 | */ |
| | 1972 | reduceDefinite(lst, resolver, results) { return lst; } |
| | 1973 | ; |
| | 1974 | |
| | 1975 | /* |
| | 1976 | * Base class for a plural production |
| | 1977 | */ |
| | 1978 | class PluralProd: NounPhraseProd |
| | 1979 | /* |
| | 1980 | * Basic plural noun resolution. We'll retrieve the matching objects |
| | 1981 | * and filter them using filterPluralPhrase. |
| | 1982 | */ |
| | 1983 | basicPluralResolveNouns(resolver, results) |
| | 1984 | { |
| | 1985 | local lst; |
| | 1986 | |
| | 1987 | /* resolve the underlying noun phrase */ |
| | 1988 | lst = np_.resolveNouns(resolver, results); |
| | 1989 | |
| | 1990 | /* if there's nothing in it, it's an error */ |
| | 1991 | if (lst.length() == 0) |
| | 1992 | results.noMatch(resolver.getAction(), np_.getOrigText()); |
| | 1993 | |
| | 1994 | /* filter out truncated matches if we have any exact matches */ |
| | 1995 | lst = filterTruncations(lst, resolver); |
| | 1996 | |
| | 1997 | /* filter the plurals for the logical subset and return the result */ |
| | 1998 | lst = resolver.filterPluralPhrase(lst, self); |
| | 1999 | |
| | 2000 | /* if we have a list, sort it by pluralOrder */ |
| | 2001 | if (lst != nil) |
| | 2002 | lst = lst.sort(SortAsc, |
| | 2003 | {a, b: a.obj_.pluralOrder - b.obj_.pluralOrder}); |
| | 2004 | |
| | 2005 | /* note the matches */ |
| | 2006 | results.noteMatches(lst); |
| | 2007 | |
| | 2008 | /* note the plural in the results */ |
| | 2009 | results.notePlural(); |
| | 2010 | |
| | 2011 | /* return the result */ |
| | 2012 | return lst; |
| | 2013 | } |
| | 2014 | |
| | 2015 | /* |
| | 2016 | * Get the verify "keepers" for a plural phrase. |
| | 2017 | * |
| | 2018 | * If the "filter plural matches" configuration flag is set to true, |
| | 2019 | * we'll return the subset of items which are logical for this |
| | 2020 | * command. If the filter flag is nil, we'll simply return the full |
| | 2021 | * set of vocabulary matches without any filtering. |
| | 2022 | */ |
| | 2023 | getVerifyKeepers(results) |
| | 2024 | { |
| | 2025 | /* check the global "filter plural matches" configuration flag */ |
| | 2026 | if (gameMain.filterPluralMatches) |
| | 2027 | { |
| | 2028 | local sub; |
| | 2029 | |
| | 2030 | /* get the subset of items that don't exclude plurals */ |
| | 2031 | sub = results.subset({x: !x.excludePluralMatches}); |
| | 2032 | |
| | 2033 | /* |
| | 2034 | * if that's non-empty, use it as the result; otherwise, just |
| | 2035 | * use the original list |
| | 2036 | */ |
| | 2037 | return (sub.length() != 0 ? sub : results); |
| | 2038 | } |
| | 2039 | else |
| | 2040 | { |
| | 2041 | /* we don't want any filtering */ |
| | 2042 | return results; |
| | 2043 | } |
| | 2044 | } |
| | 2045 | ; |
| | 2046 | |
| | 2047 | /* |
| | 2048 | * A plural phrase that explicitly selects all of matching objects. For |
| | 2049 | * English, this would be a phrase like "all of the marbles". This type |
| | 2050 | * of phrase matches all of the objects that match the name in the |
| | 2051 | * plural, except that if we have a collective object and we also have |
| | 2052 | * objects that are part of the collective (such as a bag of marbles and |
| | 2053 | * some individual marbles), we'll omit the collective, and match only |
| | 2054 | * the individual items. |
| | 2055 | * |
| | 2056 | * Grammar rule instantiations in language modules should set property |
| | 2057 | * np_ to the plural phrase match tree. |
| | 2058 | */ |
| | 2059 | class AllPluralProd: PluralProd |
| | 2060 | resolveNouns(resolver, results) |
| | 2061 | { |
| | 2062 | /* return the basic plural resolution */ |
| | 2063 | return basicPluralResolveNouns(resolver, results); |
| | 2064 | } |
| | 2065 | |
| | 2066 | /* |
| | 2067 | * since the player explicitly told us to use ALL of the matching |
| | 2068 | * objects, keep everything in the verify list, logical or not |
| | 2069 | */ |
| | 2070 | getVerifyKeepers(results) { return results; } |
| | 2071 | |
| | 2072 | /* prefer to keep individuals over collectives */ |
| | 2073 | filterForCollectives = nil |
| | 2074 | ; |
| | 2075 | |
| | 2076 | /* |
| | 2077 | * A plural phrase qualified by a definite article ("the books"). This |
| | 2078 | * type of phrasing doesn't specify anything about the specific number of |
| | 2079 | * items involved, except that they number more than one. |
| | 2080 | * |
| | 2081 | * In most cases, we take this to imply that all of the matching objects |
| | 2082 | * are intended to be included, with one exception: when we have an |
| | 2083 | * object that can serve as a collective for some of the other objects, |
| | 2084 | * we match only the collective but not the other objects. For example, |
| | 2085 | * if we type "take marbles," and we have five marbles and a bag of |
| | 2086 | * marbles that serves as a collective object for three of the five |
| | 2087 | * marbles, we'll match the bag and two marbles not in the bag, but NOT |
| | 2088 | * the marbles that are in the bag. This is usually desirable when |
| | 2089 | * there's a collective object, since it applies the command to the |
| | 2090 | * object standing in for the group rather than applying the command one |
| | 2091 | * by one to each of the individuals in the group. |
| | 2092 | */ |
| | 2093 | class DefinitePluralProd: PluralProd |
| | 2094 | resolveNouns(resolver, results) |
| | 2095 | { |
| | 2096 | /* return the basic plural resolution */ |
| | 2097 | return basicPluralResolveNouns(resolver, results); |
| | 2098 | } |
| | 2099 | |
| | 2100 | /* prefer to keep collectives instead of their individuals */ |
| | 2101 | filterForCollectives = true |
| | 2102 | ; |
| | 2103 | |
| | 2104 | /* |
| | 2105 | * Quantified plural phrase. |
| | 2106 | */ |
| | 2107 | class QuantifiedPluralProd: PluralProd |
| | 2108 | /* |
| | 2109 | * Resolve the main noun phrase. By default, we simply resolve np_, |
| | 2110 | * but we make this separately overridable to allow this class to be |
| | 2111 | * subclassed for quantifying other types of plural phrases. |
| | 2112 | * |
| | 2113 | * If this is unable to resolve the list, it can flag an appropriate |
| | 2114 | * error via the results object and return nil. If this routine |
| | 2115 | * returns nil, our main resolver will simply return an empty list |
| | 2116 | * without further flagging of any errors. |
| | 2117 | */ |
| | 2118 | resolveMainPhrase(resolver, results) |
| | 2119 | { |
| | 2120 | /* resolve the main noun phrase */ |
| | 2121 | return np_.resolveNouns(resolver, results); |
| | 2122 | } |
| | 2123 | |
| | 2124 | /* |
| | 2125 | * get the quantity specified - by default, this comes from the |
| | 2126 | * quantifier phrase in "quant_" |
| | 2127 | */ |
| | 2128 | getQuantity() { return quant_.getval(); } |
| | 2129 | |
| | 2130 | /* resolve the noun phrase */ |
| | 2131 | resolveNouns(resolver, results) |
| | 2132 | { |
| | 2133 | local lst; |
| | 2134 | local num; |
| | 2135 | |
| | 2136 | /* resolve the underlying noun phrase */ |
| | 2137 | if ((lst = resolveMainPhrase(resolver, results)) == nil) |
| | 2138 | return []; |
| | 2139 | |
| | 2140 | /* filter out truncated matches if we have any exact matches */ |
| | 2141 | lst = filterTruncations(lst, resolver); |
| | 2142 | |
| | 2143 | /* sort the list in ascending order of pluralOrder */ |
| | 2144 | if (lst != nil) |
| | 2145 | lst = lst.sort(SortAsc, |
| | 2146 | {a, b: a.obj_.pluralOrder - b.obj_.pluralOrder}); |
| | 2147 | |
| | 2148 | /* get the quantity desired */ |
| | 2149 | num = getQuantity(); |
| | 2150 | |
| | 2151 | /* |
| | 2152 | * if we have at least the desired number, arbitrarily choose |
| | 2153 | * the desired number; otherwise, it's an error |
| | 2154 | */ |
| | 2155 | if (num == 0) |
| | 2156 | { |
| | 2157 | /* zero objects makes no sense */ |
| | 2158 | results.zeroQuantity(np_.getOrigText()); |
| | 2159 | } |
| | 2160 | else |
| | 2161 | { |
| | 2162 | local qsum; |
| | 2163 | |
| | 2164 | /* if we have too many, disambiguate */ |
| | 2165 | if (lst.length() >= num) |
| | 2166 | { |
| | 2167 | local scopeList; |
| | 2168 | |
| | 2169 | /* remember the list of everything in scope that matches */ |
| | 2170 | scopeList = lst; |
| | 2171 | |
| | 2172 | /* filter for possessive qualifier strength */ |
| | 2173 | lst = resolver.filterPossRank(lst, num); |
| | 2174 | |
| | 2175 | /* |
| | 2176 | * Use the normal disambiguation ranking to find the best |
| | 2177 | * set of possibilities. |
| | 2178 | */ |
| | 2179 | lst = resolver.filterAmbiguousNounPhrase(lst, num, self); |
| | 2180 | |
| | 2181 | /* |
| | 2182 | * If that left us with more than we're looking for, call |
| | 2183 | * our selection routine to select the subset. If it |
| | 2184 | * left us with too few, note it in the results. |
| | 2185 | */ |
| | 2186 | if (lst.length() > num) |
| | 2187 | { |
| | 2188 | /* select the desired exact count */ |
| | 2189 | lst = selectExactCount(lst, num, scopeList, |
| | 2190 | resolver, results); |
| | 2191 | } |
| | 2192 | } |
| | 2193 | |
| | 2194 | /* |
| | 2195 | * Check to make sure we have enough items matching. Go by |
| | 2196 | * the 'quant_' property of the ResolveInfo entries, since we |
| | 2197 | * might have a single ResolveInfo object that represents a |
| | 2198 | * quantity of objects from the player's perspective. |
| | 2199 | */ |
| | 2200 | qsum = 0; |
| | 2201 | lst.forEach({x: qsum += x.quant_}); |
| | 2202 | if (qsum < num) |
| | 2203 | { |
| | 2204 | /* note in the results that there aren't enough matches */ |
| | 2205 | results.insufficientQuantity(np_.getOrigText(), lst, num); |
| | 2206 | } |
| | 2207 | } |
| | 2208 | |
| | 2209 | /* note the matched objects in the results */ |
| | 2210 | results.noteMatches(lst); |
| | 2211 | |
| | 2212 | /* return the results */ |
| | 2213 | return lst; |
| | 2214 | } |
| | 2215 | |
| | 2216 | /* |
| | 2217 | * Select the desired number of matches from what the normal |
| | 2218 | * disambiguation filtering leaves us with. |
| | 2219 | * |
| | 2220 | * Note that this will never be called with 'num' larger than the |
| | 2221 | * number in the current list. This is only called to select a |
| | 2222 | * smaller subset than we currently have. |
| | 2223 | * |
| | 2224 | * By default, we'll simply select an arbitrary subset, since we |
| | 2225 | * simply want any 'num' of the matches. This can be overridden if |
| | 2226 | * other behaviors are needed. |
| | 2227 | */ |
| | 2228 | selectExactCount(lst, num, scopeList, resolver, results) |
| | 2229 | { |
| | 2230 | /* |
| | 2231 | * If we want less than what we actually got, arbitrarily pick |
| | 2232 | * the first 'num' elements; otherwise, return what we have. |
| | 2233 | */ |
| | 2234 | if (lst.length() > num) |
| | 2235 | return lst.sublist(1, num); |
| | 2236 | else |
| | 2237 | return lst; |
| | 2238 | } |
| | 2239 | |
| | 2240 | /* |
| | 2241 | * Since the player explicitly told us to use a given number of |
| | 2242 | * matching objects, keep the required number, logical or not. |
| | 2243 | */ |
| | 2244 | getVerifyKeepers(results) |
| | 2245 | { |
| | 2246 | /* get the quantity desired */ |
| | 2247 | local num = getQuantity(); |
| | 2248 | |
| | 2249 | /* |
| | 2250 | * if we have at least the number required, arbitrarily choose |
| | 2251 | * the initial subset of the desired length; otherwise, use them |
| | 2252 | * all |
| | 2253 | */ |
| | 2254 | if (results.length() > num) |
| | 2255 | return results.sublist(1, num); |
| | 2256 | else |
| | 2257 | return results; |
| | 2258 | } |
| | 2259 | ; |
| | 2260 | |
| | 2261 | /* |
| | 2262 | * Exact quantified plural phrase. This is similar to the normal |
| | 2263 | * quantified plural, but has the additional requirement of matching an |
| | 2264 | * unambiguous set of the exact given number ("the five books" means |
| | 2265 | * that we expect to find exactly five books matching the phrase - no |
| | 2266 | * fewer, and no more). |
| | 2267 | */ |
| | 2268 | class ExactQuantifiedPluralProd: QuantifiedPluralProd, AmbigResponseKeeper |
| | 2269 | /* |
| | 2270 | * Select the desired number of matches. Since we want an exact set |
| | 2271 | * of matches, we'll run disambiguation on the set. |
| | 2272 | */ |
| | 2273 | selectExactCount(lst, num, scopeList, resolver, results) |
| | 2274 | { |
| | 2275 | local fullList; |
| | 2276 | |
| | 2277 | /* remember the list before filtering for redundant equivalents */ |
| | 2278 | fullList = lst; |
| | 2279 | |
| | 2280 | /* reduce the list by removing redundant equivalents, if allowed */ |
| | 2281 | if (results.allowEquivalentFiltering) |
| | 2282 | lst = resolver.filterAmbiguousEquivalents(lst, self); |
| | 2283 | |
| | 2284 | /* |
| | 2285 | * if the reduced list has only one element, everything in the |
| | 2286 | * original list must have been equivalent, so arbitrarily pick |
| | 2287 | * the desired number of items from the original list |
| | 2288 | */ |
| | 2289 | if (lst.length() == 1) |
| | 2290 | return fullList.sublist(1, num); |
| | 2291 | |
| | 2292 | /* we still have too many items, so disambiguate the results */ |
| | 2293 | return results.ambiguousNounPhrase( |
| | 2294 | self, ResolveAsker, np_.getOrigText(), |
| | 2295 | lst, fullList, scopeList, num, resolver); |
| | 2296 | } |
| | 2297 | |
| | 2298 | /* get the keepers in the verify stage */ |
| | 2299 | getVerifyKeepers(results) |
| | 2300 | { |
| | 2301 | /* |
| | 2302 | * keep everything: we want an exact quantity, so we want the |
| | 2303 | * keepers to match the required quantity on their own, without |
| | 2304 | * any arbitrary paring down |
| | 2305 | */ |
| | 2306 | return results; |
| | 2307 | } |
| | 2308 | ; |
| | 2309 | |
| | 2310 | /* |
| | 2311 | * Noun phrase with an indefinite article |
| | 2312 | */ |
| | 2313 | class IndefiniteNounProd: NounPhraseProd |
| | 2314 | /* |
| | 2315 | * resolve the main phrase - this is separately overridable to allow |
| | 2316 | * subclassing |
| | 2317 | */ |
| | 2318 | resolveMainPhrase(resolver, results) |
| | 2319 | { |
| | 2320 | /* by default, resolve the main noun phrase */ |
| | 2321 | return np_.resolveNouns(resolver, results); |
| | 2322 | } |
| | 2323 | |
| | 2324 | resolveNouns(resolver, results) |
| | 2325 | { |
| | 2326 | local lst; |
| | 2327 | local allEquiv = nil; |
| | 2328 | |
| | 2329 | /* resolve the underlying list */ |
| | 2330 | if ((lst = resolveMainPhrase(resolver, results)) == nil) |
| | 2331 | return []; |
| | 2332 | |
| | 2333 | /* filter out truncated matches if we have any exact matches */ |
| | 2334 | lst = filterTruncations(lst, resolver); |
| | 2335 | |
| | 2336 | /* see what we found */ |
| | 2337 | if (lst.length() == 0) |
| | 2338 | { |
| | 2339 | /* it turned up nothing - note the problem */ |
| | 2340 | results.noMatch(resolver.getAction(), np_.getOrigText()); |
| | 2341 | } |
| | 2342 | else if (lst.length() > 1) |
| | 2343 | { |
| | 2344 | /* |
| | 2345 | * There are multiple objects, but the phrase is indefinite, |
| | 2346 | * which means that it doesn't refer to a specific matching |
| | 2347 | * object but could refer to any of them. |
| | 2348 | */ |
| | 2349 | |
| | 2350 | /* start by noting if the choices are all equivalent */ |
| | 2351 | allEquiv = areAllEquiv(lst); |
| | 2352 | |
| | 2353 | /* |
| | 2354 | * Filter using possessive qualifier strength and then normal |
| | 2355 | * disambiguation ranking to find the best set of |
| | 2356 | * possibilities, then pick which we want. |
| | 2357 | */ |
| | 2358 | lst = resolver.filterPossRank(lst, 1); |
| | 2359 | lst = resolver.filterAmbiguousNounPhrase(lst, 1, self); |
| | 2360 | lst = selectFromList(resolver, results, lst); |
| | 2361 | } |
| | 2362 | |
| | 2363 | /* |
| | 2364 | * Set the "unclear disambiguation" flag on the item we picked - |
| | 2365 | * our selection was arbitrary, so it's polite to let the player |
| | 2366 | * know which we chose. However, don't do this if the possible |
| | 2367 | * matches were all equivalent to start with, as the player's |
| | 2368 | * input must already have been as specific as we can be in |
| | 2369 | * reporting the choice. |
| | 2370 | */ |
| | 2371 | if (lst.length() == 1 && !allEquiv) |
| | 2372 | lst[1].flags_ |= UnclearDisambig; |
| | 2373 | |
| | 2374 | /* note the matched objects in the results */ |
| | 2375 | results.noteMatches(lst); |
| | 2376 | |
| | 2377 | /* note that this is an indefinite phrasing */ |
| | 2378 | results.noteIndefinite(); |
| | 2379 | |
| | 2380 | /* return the results */ |
| | 2381 | return lst; |
| | 2382 | } |
| | 2383 | |
| | 2384 | /* are all of the items in the resolve list equivalents? */ |
| | 2385 | areAllEquiv(lst) |
| | 2386 | { |
| | 2387 | local first = lst[1].obj_; |
| | 2388 | |
| | 2389 | /* check each item to see if it's equivalent to the first */ |
| | 2390 | for (local i = 2, local cnt = lst.length() ; i <= cnt ; ++i) |
| | 2391 | { |
| | 2392 | /* |
| | 2393 | * if this one isn't equivalent to the first, then they're |
| | 2394 | * not all equivalent |
| | 2395 | */ |
| | 2396 | if (!first.isVocabEquivalent(lst[i].obj_)) |
| | 2397 | return nil; |
| | 2398 | } |
| | 2399 | |
| | 2400 | /* we didn't find any non-equivalents, so they're all equivalents */ |
| | 2401 | return true; |
| | 2402 | } |
| | 2403 | |
| | 2404 | /* |
| | 2405 | * Select an item from the list of potential matches, given the list |
| | 2406 | * sorted from most likely to least likely (according to the |
| | 2407 | * resolver's ambiguous match filter). We'll ask the resolver to |
| | 2408 | * make the selection, because indefinite noun phrases can mean |
| | 2409 | * different things in different contexts. |
| | 2410 | */ |
| | 2411 | selectFromList(resolver, results, lst) |
| | 2412 | { |
| | 2413 | /* ask the resolver to select */ |
| | 2414 | return resolver.selectIndefinite(results, lst, 1); |
| | 2415 | } |
| | 2416 | |
| | 2417 | ; |
| | 2418 | |
| | 2419 | /* |
| | 2420 | * Noun phrase explicitly asking us to choose an object arbitrarily |
| | 2421 | * (with a word like "any"). This is similar to the indefinite noun |
| | 2422 | * phrase, but differs in that this phrase is *explicitly* arbitrary, |
| | 2423 | * rather than merely indefinite. |
| | 2424 | */ |
| | 2425 | class ArbitraryNounProd: IndefiniteNounProd |
| | 2426 | /* |
| | 2427 | * Select an object from a list of potential matches. Since the |
| | 2428 | * choice is explicitly arbitrary, we simply choose the first |
| | 2429 | * (they're in order from most likely to least likely, so this will |
| | 2430 | * choose the most likely). |
| | 2431 | */ |
| | 2432 | selectFromList(resolver, results, lst) |
| | 2433 | { |
| | 2434 | /* simply select the first item */ |
| | 2435 | return lst.sublist(1, 1); |
| | 2436 | } |
| | 2437 | ; |
| | 2438 | |
| | 2439 | /* |
| | 2440 | * Noun phrase with an indefinite article and an exclusion ("any of the |
| | 2441 | * books except the red one") |
| | 2442 | */ |
| | 2443 | class IndefiniteNounButProd: ButProd |
| | 2444 | /* resolve our main phrase */ |
| | 2445 | resolveMainPhrase(resolver, results) |
| | 2446 | { |
| | 2447 | /* note that this is an indefinite phrasing */ |
| | 2448 | results.noteIndefinite(); |
| | 2449 | |
| | 2450 | /* by default, simply resolve the underlying noun phrase */ |
| | 2451 | return np_.resolveNouns(resolver, results); |
| | 2452 | } |
| | 2453 | |
| | 2454 | /* get our main list */ |
| | 2455 | getMainList(resolver, results) |
| | 2456 | { |
| | 2457 | local lst; |
| | 2458 | |
| | 2459 | /* resolve the underlying list */ |
| | 2460 | if ((lst = resolveMainPhrase(resolver, results)) == nil) |
| | 2461 | return []; |
| | 2462 | |
| | 2463 | /* filter for possessive qualifier strength */ |
| | 2464 | lst = resolver.filterPossRank(lst, 1); |
| | 2465 | |
| | 2466 | /* filter it to pick the most likely objects */ |
| | 2467 | lst = resolver.filterAmbiguousNounPhrase(lst, 1, self); |
| | 2468 | |
| | 2469 | /* return the filtered list */ |
| | 2470 | return lst; |
| | 2471 | } |
| | 2472 | |
| | 2473 | /* flag an error - everything has been excluded */ |
| | 2474 | flagAllExcepted(resolver, results) |
| | 2475 | { |
| | 2476 | results.noMatchForListBut(); |
| | 2477 | } |
| | 2478 | |
| | 2479 | /* filter the final list */ |
| | 2480 | filterFinalList(lst) |
| | 2481 | { |
| | 2482 | /* we want to keep only one item - arbitrarily take the first one */ |
| | 2483 | return (lst.length() == 0 ? [] : lst.sublist(1, 1)); |
| | 2484 | } |
| | 2485 | |
| | 2486 | /* |
| | 2487 | * set the "unclear disambig" flag in our results, so we provide an |
| | 2488 | * indication of which object we chose |
| | 2489 | */ |
| | 2490 | addedFlags = UnclearDisambig |
| | 2491 | ; |
| | 2492 | |
| | 2493 | /* |
| | 2494 | * A qualified plural phrase explicitly including two objects (such as, |
| | 2495 | * in English, "both books"). |
| | 2496 | */ |
| | 2497 | class BothPluralProd: ExactQuantifiedPluralProd |
| | 2498 | /* the quantity specified by a "both" phrase is 2 */ |
| | 2499 | getQuantity() { return 2; } |
| | 2500 | ; |
| | 2501 | |
| | 2502 | /* ------------------------------------------------------------------------ */ |
| | 2503 | /* |
| | 2504 | * Possessive adjectives |
| | 2505 | */ |
| | 2506 | |
| | 2507 | class PossessivePronounAdjProd: PronounProd |
| | 2508 | /* |
| | 2509 | * Possessive pronouns can refer to the earlier noun phrases of the |
| | 2510 | * same predicate, which is to say that they're anaphoric. For |
| | 2511 | * example, in GIVE BOB HIS BOOK, 'his' refers to Bob. |
| | 2512 | */ |
| | 2513 | checkAnaphoricBinding(resolver, results) |
| | 2514 | { |
| | 2515 | local lst; |
| | 2516 | |
| | 2517 | /* if we simply can't be an anaphor, there's no binding */ |
| | 2518 | if (!canBeAnaphor) |
| | 2519 | return nil; |
| | 2520 | |
| | 2521 | /* ask the resolver for the reflexive binding, if any */ |
| | 2522 | lst = resolver.getReflexiveBinding(pronounType); |
| | 2523 | |
| | 2524 | /* |
| | 2525 | * If there's no binding from the verb, or it doesn't match in |
| | 2526 | * number and gender, try an anaphoric binding from the actor. |
| | 2527 | */ |
| | 2528 | if (lst == nil || (lst != [] && !checkAnaphorAgreement(lst))) |
| | 2529 | { |
| | 2530 | /* get the actor's anaphoric possessive binding */ |
| | 2531 | local obj = resolver.actor_.getPossAnaphor(pronounType); |
| | 2532 | |
| | 2533 | /* if we got an object or a list, make a resolve list */ |
| | 2534 | if (obj != nil) |
| | 2535 | { |
| | 2536 | if (obj.ofKind(Collection)) |
| | 2537 | lst = obj.mapAll({x: new ResolveInfo(x, 0)}); |
| | 2538 | else |
| | 2539 | lst = [new ResolveInfo(obj, 0)]; |
| | 2540 | } |
| | 2541 | } |
| | 2542 | |
| | 2543 | /* |
| | 2544 | * If we got a binding, make sure we agree in number and gender; |
| | 2545 | * if not, don't use the anaphoric form. This isn't an error; |
| | 2546 | * it just means we're falling back on the regular antecedent |
| | 2547 | * binding. If we have an empty list, it means the action isn't |
| | 2548 | * ready to tell us the binding yet, so we can't verify it yet. |
| | 2549 | */ |
| | 2550 | if (lst != nil && (lst == [] || checkAnaphorAgreement(lst))) |
| | 2551 | return lst; |
| | 2552 | |
| | 2553 | /* don't use an anaphoric binding */ |
| | 2554 | return nil; |
| | 2555 | } |
| | 2556 | |
| | 2557 | /* this is a possessive usage of the pronoun */ |
| | 2558 | isPossessive = true |
| | 2559 | |
| | 2560 | /* |
| | 2561 | * Can we be an anaphor? By default, we consider third-person |
| | 2562 | * possessive pronouns to be anaphoric, and others to be |
| | 2563 | * non-anaphoric. For example, in GIVE BOB MY BOOK, MY always refers |
| | 2564 | * to the speaker, so it's clearly not anaphoric within the sentence. |
| | 2565 | */ |
| | 2566 | canBeAnaphor = true |
| | 2567 | |
| | 2568 | /* |
| | 2569 | * Check agreement to a given anaphoric pronoun binding. The |
| | 2570 | * language module should override this for each pronoun type to |
| | 2571 | * ensure that the actual contents of the list agree in number and |
| | 2572 | * gender with this type of pronoun. If so, return true; if not, |
| | 2573 | * return nil. It's not an error or a ranking demerit if we don't |
| | 2574 | * agree; it just means that we'll fall back on the regular pronoun |
| | 2575 | * antecedent rather than trying to use an anaphoric binding. |
| | 2576 | */ |
| | 2577 | checkAnaphorAgreement(lst) { return true; } |
| | 2578 | |
| | 2579 | /* |
| | 2580 | * By default, the "main text" of a possessive pronoun is the same as |
| | 2581 | * the actual token text. Languages can override this as needed> |
| | 2582 | */ |
| | 2583 | getOrigMainText() { return getOrigText(); } |
| | 2584 | ; |
| | 2585 | |
| | 2586 | class ItsAdjProd: PossessivePronounAdjProd |
| | 2587 | pronounType = PronounIt |
| | 2588 | ; |
| | 2589 | |
| | 2590 | class HisAdjProd: PossessivePronounAdjProd |
| | 2591 | pronounType = PronounHim |
| | 2592 | ; |
| | 2593 | |
| | 2594 | class HerAdjProd: PossessivePronounAdjProd |
| | 2595 | pronounType = PronounHer |
| | 2596 | ; |
| | 2597 | |
| | 2598 | class TheirAdjProd: PossessivePronounAdjProd |
| | 2599 | pronounType = PronounThem |
| | 2600 | ; |
| | 2601 | |
| | 2602 | class YourAdjProd: PossessivePronounAdjProd |
| | 2603 | pronounType = PronounYou |
| | 2604 | canBeAnaphor = nil |
| | 2605 | ; |
| | 2606 | |
| | 2607 | class MyAdjProd: PossessivePronounAdjProd |
| | 2608 | pronounType = PronounMe |
| | 2609 | canBeAnaphor = nil |
| | 2610 | ; |
| | 2611 | |
| | 2612 | /* |
| | 2613 | * Possessive nouns |
| | 2614 | */ |
| | 2615 | class PossessivePronounNounProd: PronounProd |
| | 2616 | /* this is a possessive usage of the pronoun */ |
| | 2617 | isPossessive = true |
| | 2618 | ; |
| | 2619 | |
| | 2620 | class ItsNounProd: PossessivePronounNounProd |
| | 2621 | pronounType = PronounIt |
| | 2622 | ; |
| | 2623 | |
| | 2624 | class HisNounProd: PossessivePronounNounProd |
| | 2625 | pronounType = PronounHim |
| | 2626 | ; |
| | 2627 | |
| | 2628 | class HersNounProd: PossessivePronounNounProd |
| | 2629 | pronounType = PronounHer |
| | 2630 | ; |
| | 2631 | |
| | 2632 | class TheirsNounProd: PossessivePronounNounProd |
| | 2633 | pronounType = PronounThem |
| | 2634 | ; |
| | 2635 | |
| | 2636 | class YoursNounProd: PossessivePronounNounProd |
| | 2637 | pronounType = PronounYou |
| | 2638 | ; |
| | 2639 | |
| | 2640 | class MineNounProd: PossessivePronounNounProd |
| | 2641 | pronounType = PronounMe |
| | 2642 | ; |
| | 2643 | |
| | 2644 | /* |
| | 2645 | * Basic possessive phrase. The grammar rules for these phrases must map |
| | 2646 | * the possessive qualifier phrase to poss_, and the noun phrase being |
| | 2647 | * qualified to np_. We are based on DefiniteNounProd because we resolve |
| | 2648 | * the possessive qualifier as though it had a definite article. |
| | 2649 | * |
| | 2650 | * The possessive production object poss_ must define the method |
| | 2651 | * getOrigMainText() to return the text of its noun phrase in a format |
| | 2652 | * suitable for disambiguation prompts or error messages. In English, |
| | 2653 | * for example, this means that the getOrigMainText() must omit the |
| | 2654 | * apostrophe-S suffix if present. |
| | 2655 | */ |
| | 2656 | class BasicPossessiveProd: DefiniteNounProd |
| | 2657 | /* |
| | 2658 | * To allow this class to be mixed with other classes that have |
| | 2659 | * mixed-in ambiguous response keepers, create a separate object to |
| | 2660 | * hold our ambiguous response keeper for the possessive phrase. We |
| | 2661 | * will never use our own ambiguous response keeper properties, so |
| | 2662 | * those are available to any other production class we're mixed |
| | 2663 | * into. |
| | 2664 | */ |
| | 2665 | construct() |
| | 2666 | { |
| | 2667 | /* create an AmbigResponseKeeper for the possessive phrase */ |
| | 2668 | npKeeper = new AmbigResponseKeeper; |
| | 2669 | } |
| | 2670 | |
| | 2671 | /* |
| | 2672 | * Resolve the possessive, and perform preliminary resolution of the |
| | 2673 | * qualified noun phrase. We find the owner object and reduce the |
| | 2674 | * resolved objects for the qualified phrase to those owned by the |
| | 2675 | * owner. |
| | 2676 | * |
| | 2677 | * If we fail, we return nil. Otherwise, we return a list of the |
| | 2678 | * tentatively resolved objects. The caller can further resolve |
| | 2679 | * this list as needed. |
| | 2680 | */ |
| | 2681 | resolvePossessive(resolver, results, num) |
| | 2682 | { |
| | 2683 | local lst; |
| | 2684 | |
| | 2685 | /* resolve the underlying noun phrase being qualified */ |
| | 2686 | lst = np_.resolveNouns(resolver, results); |
| | 2687 | |
| | 2688 | /* if we found no matches for the noun phrase, so note */ |
| | 2689 | if (lst.length() == 0) |
| | 2690 | { |
| | 2691 | results.noMatch(resolver.getAction(), np_.getOrigText()); |
| | 2692 | return nil; |
| | 2693 | } |
| | 2694 | |
| | 2695 | /* |
| | 2696 | * resolve the possessive phrase and reduce the list to select |
| | 2697 | * only the items owned by the possessor |
| | 2698 | */ |
| | 2699 | lst = selectWithPossessive(resolver, results, lst, |
| | 2700 | np_.getOrigText(), num); |
| | 2701 | |
| | 2702 | /* return the tentative resolution list for the qualified phrase */ |
| | 2703 | return lst; |
| | 2704 | } |
| | 2705 | |
| | 2706 | /* |
| | 2707 | * Resolve the possessive, and reduce the given match list by |
| | 2708 | * selecting only those items owned by the resolution of the |
| | 2709 | * possessive phrase. |
| | 2710 | * |
| | 2711 | * 'num' is the number of objects we want to select. If the noun |
| | 2712 | * phrase being qualified is singular, this will be 1; if it's |
| | 2713 | * plural, this will be nil, to indicate that there's no specific |
| | 2714 | * target quantity; if the phrase is something like "bob's five |
| | 2715 | * books," the the number will be the qualifying quantity (5, in this |
| | 2716 | * case). |
| | 2717 | */ |
| | 2718 | selectWithPossessive(resolver, results, lst, lstOrigText, num) |
| | 2719 | { |
| | 2720 | local possResolver; |
| | 2721 | local possLst; |
| | 2722 | local owner; |
| | 2723 | local newLst; |
| | 2724 | |
| | 2725 | /* |
| | 2726 | * Create the possessive resolver. Note that we resolve the |
| | 2727 | * possessive phrase in the context of the resolver's indicated |
| | 2728 | * qualifier resolver, which might not be the same as the |
| | 2729 | * resolver for the overall phrase. |
| | 2730 | */ |
| | 2731 | possResolver = resolver.getPossessiveResolver(); |
| | 2732 | |
| | 2733 | /* enter a single-object slot for the possessive phrase */ |
| | 2734 | results.beginSingleObjSlot(); |
| | 2735 | |
| | 2736 | /* resolve the underlying possessive */ |
| | 2737 | possLst = poss_.resolveNouns(possResolver, results); |
| | 2738 | |
| | 2739 | /* perform the normal resolve list filtering */ |
| | 2740 | possLst = resolver.getAction().finishResolveList( |
| | 2741 | possLst, resolver.whichObject, self, nil); |
| | 2742 | |
| | 2743 | /* done with the single-object slot */ |
| | 2744 | results.endSingleObjSlot(); |
| | 2745 | |
| | 2746 | /* |
| | 2747 | * If that resolved to an empty list, return now with an empty |
| | 2748 | * list. The underlying possessive resolver will have noted an |
| | 2749 | * error if this is indeed an error; if it's not an error, it |
| | 2750 | * means that we're pending resolution of the other noun phrase |
| | 2751 | * to resolve an anaphor in the possessive phrase. |
| | 2752 | */ |
| | 2753 | if (possLst == []) |
| | 2754 | { |
| | 2755 | /* |
| | 2756 | * we must have a pending anaphor to resolve - simply return |
| | 2757 | * the current list without any possessive filtering |
| | 2758 | */ |
| | 2759 | return lst; |
| | 2760 | } |
| | 2761 | |
| | 2762 | /* |
| | 2763 | * If the possessive phrase itself is singular, treat the |
| | 2764 | * possessive phrase as a definite phrase, requiring an |
| | 2765 | * unambiguous referent. If it's plural ("the men's books"), |
| | 2766 | * leave it as it is, taking it to mean that we want to select |
| | 2767 | * things that are owned by any/all of the possessors. |
| | 2768 | * |
| | 2769 | * Note that the possessive phrase has no qualifier - any |
| | 2770 | * qualifier applies to the noun phrase our possessive is also |
| | 2771 | * qualifying, not to the possessive phrase itself. |
| | 2772 | */ |
| | 2773 | if (poss_.isPluralPossessive) |
| | 2774 | { |
| | 2775 | /* |
| | 2776 | * The possessive phrase is plural, so don't reduce its match |
| | 2777 | * to a single object; instead, select all of the objects |
| | 2778 | * owned by any of the possessors. The owner is anyone in |
| | 2779 | * the list. |
| | 2780 | */ |
| | 2781 | owner = possLst.mapAll({x: x.obj_}); |
| | 2782 | } |
| | 2783 | else |
| | 2784 | { |
| | 2785 | /* |
| | 2786 | * the possessive phrase is singular, so resolve the |
| | 2787 | * possessive qualifier as a definite noun |
| | 2788 | */ |
| | 2789 | possLst = resolveDefinite( |
| | 2790 | ResolveAsker, poss_.getOrigMainText(), possLst, |
| | 2791 | npKeeper, possResolver, results); |
| | 2792 | |
| | 2793 | /* |
| | 2794 | * if we didn't manage to find a single resolution to the |
| | 2795 | * possessive phrase, we can't resolve the rest of the phrase |
| | 2796 | * yet |
| | 2797 | */ |
| | 2798 | if (possLst.length() != 1) |
| | 2799 | { |
| | 2800 | /* if we got more than one object, it's a separate error */ |
| | 2801 | if (possLst.length() > 1) |
| | 2802 | results.uniqueObjectRequired(poss_.getOrigMainText(), |
| | 2803 | possLst); |
| | 2804 | |
| | 2805 | /* we can't go on */ |
| | 2806 | return []; |
| | 2807 | } |
| | 2808 | |
| | 2809 | /* get the resolved owner object */ |
| | 2810 | owner = [possLst[1].obj_]; |
| | 2811 | } |
| | 2812 | |
| | 2813 | |
| | 2814 | /* select the objects owned by any of the owners */ |
| | 2815 | newLst = lst.subset({x: owner.indexWhich( |
| | 2816 | {y: x.obj_.isOwnedBy(y)}) != nil}); |
| | 2817 | |
| | 2818 | /* |
| | 2819 | * If that didn't leave any results, try one more thing: if the |
| | 2820 | * owner is itself in the list of possessed objects, keep the |
| | 2821 | * owner. This allows for sequences like this: |
| | 2822 | * |
| | 2823 | * >take book |
| | 2824 | *. Which book? |
| | 2825 | *. >the man's |
| | 2826 | *. Which man, Bob or Dave? |
| | 2827 | *. >bob's |
| | 2828 | * |
| | 2829 | * In this case, the qualified object list will be [bob,dave], |
| | 2830 | * and the owner will be [bob], so we want to keep [bob] as the |
| | 2831 | * result list. |
| | 2832 | */ |
| | 2833 | if (newLst == []) |
| | 2834 | newLst = lst.subset({x: owner.indexOf(x.obj_) != nil}); |
| | 2835 | |
| | 2836 | /* use the new list we found */ |
| | 2837 | lst = newLst; |
| | 2838 | |
| | 2839 | /* |
| | 2840 | * Give each item an ownership priority ranking, since the items |
| | 2841 | * are being qualified by owner: explicitly owned items have the |
| | 2842 | * highest ranking, items directly held but not explicitly owned |
| | 2843 | * have the second ranking, and items merely held have the |
| | 2844 | * lowest ranking. If we deem the list to be ambiguous later, |
| | 2845 | * we'll apply the ownership priority ranking first in trying to |
| | 2846 | * disambiguate. |
| | 2847 | */ |
| | 2848 | foreach (local cur in lst) |
| | 2849 | { |
| | 2850 | /* |
| | 2851 | * give the item a ranking: explicitly owned, directly held, |
| | 2852 | * or other |
| | 2853 | */ |
| | 2854 | if (owner.indexOf(cur.obj_.owner) != nil) |
| | 2855 | cur.possRank_ = 2; |
| | 2856 | else if (owner.indexWhich({x: cur.obj_.isDirectlyIn(x)}) != nil) |
| | 2857 | cur.possRank_ = 1; |
| | 2858 | } |
| | 2859 | |
| | 2860 | /* if we found nothing, mention it */ |
| | 2861 | if (lst.length() == 0) |
| | 2862 | { |
| | 2863 | results.noMatchForPossessive(owner, lstOrigText); |
| | 2864 | return []; |
| | 2865 | } |
| | 2866 | |
| | 2867 | /* return the reduced list */ |
| | 2868 | return lst; |
| | 2869 | } |
| | 2870 | |
| | 2871 | /* our ambiguous response keeper */ |
| | 2872 | npKeeper = nil |
| | 2873 | ; |
| | 2874 | |
| | 2875 | /* |
| | 2876 | * Possessive phrase + singular noun phrase. The language grammar rule |
| | 2877 | * must map poss_ to the possessive production and np_ to the noun |
| | 2878 | * phrase being qualified. |
| | 2879 | */ |
| | 2880 | class PossessiveNounProd: BasicPossessiveProd |
| | 2881 | resolveNouns(resolver, results) |
| | 2882 | { |
| | 2883 | local lst; |
| | 2884 | |
| | 2885 | /* |
| | 2886 | * perform the initial qualification; if that fails, give up now |
| | 2887 | * and return an empty list |
| | 2888 | */ |
| | 2889 | if ((lst = resolvePossessive(resolver, results, 1)) == nil) |
| | 2890 | return []; |
| | 2891 | |
| | 2892 | /* now resolve the underlying list definitely */ |
| | 2893 | return resolveDefinite(ResolveAsker, np_.getOrigText(), lst, self, |
| | 2894 | resolver, results); |
| | 2895 | } |
| | 2896 | |
| | 2897 | /* our AmbigResponseKeeper for the qualified noun phrase */ |
| | 2898 | npKeeper = nil |
| | 2899 | ; |
| | 2900 | |
| | 2901 | /* |
| | 2902 | * Possessive phrase + plural noun phrase. The grammar rule must set |
| | 2903 | * poss_ to the possessive and np_ to the plural. |
| | 2904 | */ |
| | 2905 | class PossessivePluralProd: BasicPossessiveProd |
| | 2906 | resolveNouns(resolver, results) |
| | 2907 | { |
| | 2908 | local lst; |
| | 2909 | |
| | 2910 | /* perform the initial qualifier resolution */ |
| | 2911 | if ((lst = resolvePossessive(resolver, results, nil)) == nil) |
| | 2912 | return []; |
| | 2913 | |
| | 2914 | /* filter out truncated matches if we have any exact matches */ |
| | 2915 | lst = filterTruncations(lst, resolver); |
| | 2916 | |
| | 2917 | /* filter the plurals for the logical subset */ |
| | 2918 | lst = resolver.filterPluralPhrase(lst, self); |
| | 2919 | |
| | 2920 | /* if we have a list, sort it by pluralOrder */ |
| | 2921 | if (lst != nil) |
| | 2922 | lst = lst.sort(SortAsc, |
| | 2923 | {a, b: a.obj_.pluralOrder - b.obj_.pluralOrder}); |
| | 2924 | |
| | 2925 | /* note the matched objects in the results */ |
| | 2926 | results.noteMatches(lst); |
| | 2927 | |
| | 2928 | /* we want everything in the list, so return what we found */ |
| | 2929 | return lst; |
| | 2930 | } |
| | 2931 | ; |
| | 2932 | |
| | 2933 | /* |
| | 2934 | * Possessive plural with a specific quantity that must be exact |
| | 2935 | */ |
| | 2936 | class ExactQuantifiedPossessivePluralProd: |
| | 2937 | ExactQuantifiedPluralProd, BasicPossessiveProd |
| | 2938 | |
| | 2939 | /* |
| | 2940 | * resolve the main noun phrase - this is the possessive-qualified |
| | 2941 | * plural phrase |
| | 2942 | */ |
| | 2943 | resolveMainPhrase(resolver, results) |
| | 2944 | { |
| | 2945 | return resolvePossessive(resolver, results, getQuantity()); |
| | 2946 | } |
| | 2947 | ; |
| | 2948 | |
| | 2949 | /* |
| | 2950 | * Possessive noun used in an exclusion list. This is for things like |
| | 2951 | * the "mine" in a phrase like "take keys except mine". |
| | 2952 | */ |
| | 2953 | class ButPossessiveProd: BasicPossessiveProd |
| | 2954 | resolveNouns(resolver, results) |
| | 2955 | { |
| | 2956 | /* |
| | 2957 | * resolve the possessive phrase, and use it to reduce the |
| | 2958 | * resolver's main list (this is the list before the "except" |
| | 2959 | * from which are choosing items to exclude) to those items |
| | 2960 | * owned by the object indicated in the possessive phrase |
| | 2961 | */ |
| | 2962 | return selectWithPossessive(resolver, results, resolver.mainList, |
| | 2963 | resolver.mainListText, nil); |
| | 2964 | } |
| | 2965 | ; |
| | 2966 | |
| | 2967 | /* |
| | 2968 | * Possessive phrase production for disambiguation. This base class can |
| | 2969 | * be used for grammar productions that match possessive phrases in |
| | 2970 | * disambiguation prompt ("which book do you mean...?") responses. |
| | 2971 | */ |
| | 2972 | class DisambigPossessiveProd: BasicPossessiveProd, DisambigProd |
| | 2973 | resolveNouns(resolver, results) |
| | 2974 | { |
| | 2975 | local lst; |
| | 2976 | |
| | 2977 | /* |
| | 2978 | * Remember the original qualified list (this is the list of |
| | 2979 | * objects from which we're trying to choose on the basis of the |
| | 2980 | * possessive phrase we're resolving now). We can feed the |
| | 2981 | * qualified-object list back into the selection process for the |
| | 2982 | * qualifier itself, because we're looking for a qualifier that |
| | 2983 | * makes sense when combined with one of the qualified objects. |
| | 2984 | */ |
| | 2985 | qualifiedList_ = resolver.matchList; |
| | 2986 | |
| | 2987 | /* select from the match list using the possessive phrase */ |
| | 2988 | lst = selectWithPossessive(resolver, results, |
| | 2989 | resolver.matchList, resolver.matchText, 1); |
| | 2990 | |
| | 2991 | /* |
| | 2992 | * if the list has more than one entry, treat the result as |
| | 2993 | * still ambiguous - a simple possessive response to a |
| | 2994 | * disambiguation query is implicitly definite, so must select a |
| | 2995 | * single object |
| | 2996 | */ |
| | 2997 | if (lst != nil && lst.length() > 1) |
| | 2998 | results.ambiguousNounPhrase( |
| | 2999 | self, ResolveAsker, resolver.matchText, |
| | 3000 | lst, resolver.fullMatchList, lst, 1, resolver); |
| | 3001 | |
| | 3002 | /* |
| | 3003 | * if we failed to resolve it, return an empty list; otherwise |
| | 3004 | * return the list |
| | 3005 | */ |
| | 3006 | return (lst == nil ? [] : lst); |
| | 3007 | } |
| | 3008 | |
| | 3009 | /* |
| | 3010 | * Do extra filter during disambiguation. Since we have a list of |
| | 3011 | * objects we're trying to qualify, we can look at that list to see |
| | 3012 | * if some of the possible matches for the qualifier phrase are |
| | 3013 | * owners of things in the qualified list. |
| | 3014 | */ |
| | 3015 | reduceDefinite(lst, resolver, results) |
| | 3016 | { |
| | 3017 | local newLst; |
| | 3018 | |
| | 3019 | /* |
| | 3020 | * try reducing the list to owners of objects that appear in the |
| | 3021 | * qualified object list |
| | 3022 | */ |
| | 3023 | newLst = lst.subset({x: qualifiedList_.indexWhich( |
| | 3024 | {y: y.obj_.isOwnedBy(x.obj_)}) != nil}); |
| | 3025 | |
| | 3026 | /* if there's anything in that list, keep only the subset */ |
| | 3027 | if (newLst.length() > 0) |
| | 3028 | lst = newLst; |
| | 3029 | |
| | 3030 | /* return the result */ |
| | 3031 | return lst; |
| | 3032 | } |
| | 3033 | |
| | 3034 | /* |
| | 3035 | * the list of objects being qualified - this is the list of books, |
| | 3036 | * for example, in "bob's books" |
| | 3037 | */ |
| | 3038 | qualifiedList_ = [] |
| | 3039 | ; |
| | 3040 | |
| | 3041 | /* ------------------------------------------------------------------------ */ |
| | 3042 | /* |
| | 3043 | * A noun phrase with explicit containment. Grammar rules based on this |
| | 3044 | * class must set the property np_ to the main noun phrase, and cont_ to |
| | 3045 | * the noun phrase giving the container. |
| | 3046 | * |
| | 3047 | * We're based on the definite noun phrase production, because we need |
| | 3048 | * to resolve the underlying container phrase to a singe, unambiguous |
| | 3049 | * object. |
| | 3050 | */ |
| | 3051 | class ContainerNounPhraseProd: DefiniteNounProd |
| | 3052 | resolveNouns(resolver, results) |
| | 3053 | { |
| | 3054 | local lst; |
| | 3055 | local cRes; |
| | 3056 | local cLst; |
| | 3057 | local cont; |
| | 3058 | |
| | 3059 | /* |
| | 3060 | * We have two separate noun phrases to resolve: the qualified |
| | 3061 | * noun phrase in np_, and the locational qualifier in cont_. |
| | 3062 | * We then want to filter the object matches for np_ to select |
| | 3063 | * the subset that is contained in cont_. |
| | 3064 | * |
| | 3065 | * We must resolve cont_ to a single, unambiguous object. |
| | 3066 | * However, we want to be smart about it by limiting the range |
| | 3067 | * of choices to objects that actually contain something that |
| | 3068 | * could match the possible resolutions of np_. So, tentatively |
| | 3069 | * resolve np_ first, to get the range of possible matches. |
| | 3070 | */ |
| | 3071 | lst = np_.resolveNouns(resolver, results); |
| | 3072 | |
| | 3073 | /* |
| | 3074 | * We have the tentative resolution of the main noun phrase, so |
| | 3075 | * we can now resolve the locational qualifier phrase. Use our |
| | 3076 | * special container resolver for this step, since this will try |
| | 3077 | * to pick objects that contain something in the tentative |
| | 3078 | * results for the main list. Resolve the container as a |
| | 3079 | * definite noun phrase, since we want a single, unambiguous |
| | 3080 | * match. |
| | 3081 | * |
| | 3082 | * Note that we must base our special container resolver on the |
| | 3083 | * qualifier resolver, not on the main resolver. The location is |
| | 3084 | * a qualifying phrase, so it's resolved in the scope of any |
| | 3085 | * other qualifying phrase. |
| | 3086 | * |
| | 3087 | * The container phrase has to be a single object, so note in the |
| | 3088 | * results that we're working on a single-object slot. |
| | 3089 | */ |
| | 3090 | results.beginSingleObjSlot(); |
| | 3091 | |
| | 3092 | cRes = new ContainerResolver(lst, np_.getOrigText(), |
| | 3093 | resolver.getQualifierResolver()); |
| | 3094 | cLst = resolveDefinite(ResolveAsker, cont_.getOrigText(), |
| | 3095 | cont_.resolveNouns(cRes, results), |
| | 3096 | self, cRes, results); |
| | 3097 | |
| | 3098 | results.endSingleObjSlot(); |
| | 3099 | |
| | 3100 | /* |
| | 3101 | * We need a single object in the container list. If we have |
| | 3102 | * no objects, or more than one object, it's an error. |
| | 3103 | */ |
| | 3104 | if (cLst.length() != 1) |
| | 3105 | { |
| | 3106 | /* it's a separate error if we got more than one object */ |
| | 3107 | if (cLst.length() > 1) |
| | 3108 | results.uniqueObjectRequired(cont_.getOrigText(), cLst); |
| | 3109 | |
| | 3110 | /* we can't go on */ |
| | 3111 | return []; |
| | 3112 | } |
| | 3113 | |
| | 3114 | /* we have a unique item, so it's the container */ |
| | 3115 | cont = cLst[1].obj_; |
| | 3116 | |
| | 3117 | /* reduce the list to those objects inside the container */ |
| | 3118 | lst = lst.subset({x: x.obj_.isNominallyIn(cont)}); |
| | 3119 | |
| | 3120 | /* |
| | 3121 | * If we have some objects directly in the container, and other |
| | 3122 | * objects indirectly in the container, filter the list to |
| | 3123 | * include only the directly contained items. |
| | 3124 | */ |
| | 3125 | if (lst.indexWhich({x: x.obj_.isDirectlyIn(cont)}) != nil) |
| | 3126 | lst = lst.subset({x: x.obj_.isDirectlyIn(cont)}); |
| | 3127 | |
| | 3128 | /* if that leaves nothing, mention it */ |
| | 3129 | if (lst.length() == 0) |
| | 3130 | { |
| | 3131 | results.noMatchForLocation(cont, np_.getOrigText()); |
| | 3132 | return []; |
| | 3133 | } |
| | 3134 | |
| | 3135 | /* return the list */ |
| | 3136 | return lst; |
| | 3137 | } |
| | 3138 | ; |
| | 3139 | |
| | 3140 | /* |
| | 3141 | * Basic container resolver. This is a common subclass for the standard |
| | 3142 | * container resolver and the "vague" container resolver. |
| | 3143 | */ |
| | 3144 | class BasicContainerResolver: ProxyResolver |
| | 3145 | /* we're a sub-phrase resolver */ |
| | 3146 | isSubResolver = true |
| | 3147 | |
| | 3148 | /* resolve any qualifiers in the main scope */ |
| | 3149 | getQualifierResolver() { return origResolver; } |
| | 3150 | |
| | 3151 | /* filter an ambiguous noun phrase */ |
| | 3152 | filterAmbiguousNounPhrase(lst, requiredNum, np) |
| | 3153 | { |
| | 3154 | local outer; |
| | 3155 | local lcl; |
| | 3156 | |
| | 3157 | /* do the normal filtering first */ |
| | 3158 | lst = inherited(lst, requiredNum, np); |
| | 3159 | |
| | 3160 | /* |
| | 3161 | * get the subset that includes only local objects - that is, |
| | 3162 | * objects within the same outermost room as the target actor |
| | 3163 | */ |
| | 3164 | outer = actor_.getOutermostRoom(); |
| | 3165 | lcl = lst.subset({x: x.obj_.isIn(outer)}); |
| | 3166 | |
| | 3167 | /* if there's a local subset, take the subset */ |
| | 3168 | if (lcl.length() != 0) |
| | 3169 | lst = lcl; |
| | 3170 | |
| | 3171 | /* return the result */ |
| | 3172 | return lst; |
| | 3173 | } |
| | 3174 | ; |
| | 3175 | |
| | 3176 | /* |
| | 3177 | * Container Resolver. This is a proxy for the main qualifier resolver |
| | 3178 | * that prefers to match objects that are plausible in the sense that |
| | 3179 | * they contain something in the tentative resolution of the main list. |
| | 3180 | */ |
| | 3181 | class ContainerResolver: BasicContainerResolver |
| | 3182 | construct(mainList, mainText, origResolver) |
| | 3183 | { |
| | 3184 | /* inherit base handling */ |
| | 3185 | inherited(origResolver); |
| | 3186 | |
| | 3187 | /* remember my tentative main match list */ |
| | 3188 | self.mainList = mainList; |
| | 3189 | self.mainListText = mainText; |
| | 3190 | } |
| | 3191 | |
| | 3192 | /* filter ambiguous equivalents */ |
| | 3193 | filterAmbiguousEquivalents(lst, np) |
| | 3194 | { |
| | 3195 | local vec; |
| | 3196 | |
| | 3197 | /* |
| | 3198 | * Check to see if any of the objects in the list are plausible |
| | 3199 | * containers for objects in our main list. If we can find any |
| | 3200 | * plausible entries, keep only the plausible ones. |
| | 3201 | */ |
| | 3202 | vec = new Vector(lst.length()); |
| | 3203 | foreach (local cur in lst) |
| | 3204 | { |
| | 3205 | /* if this item is plausible, add it to our result vector */ |
| | 3206 | if (mainList.indexWhich( |
| | 3207 | {x: x.obj_.isNominallyIn(cur.obj_)}) != nil) |
| | 3208 | vec.append(cur); |
| | 3209 | } |
| | 3210 | |
| | 3211 | /* |
| | 3212 | * if we found anything plausible, return only the plausible |
| | 3213 | * subset; otherwise, return the full original list, since |
| | 3214 | * they're all equally implausible |
| | 3215 | */ |
| | 3216 | if (vec.length() != 0) |
| | 3217 | return vec.toList(); |
| | 3218 | else |
| | 3219 | return lst; |
| | 3220 | } |
| | 3221 | |
| | 3222 | /* the tentative match list for the main phrase we're qualifying */ |
| | 3223 | mainList = nil |
| | 3224 | |
| | 3225 | /* the text of the main phrase we're qualifying */ |
| | 3226 | mainListText = nil |
| | 3227 | ; |
| | 3228 | |
| | 3229 | /* ------------------------------------------------------------------------ */ |
| | 3230 | /* |
| | 3231 | * A "vague" container noun phrase. This is a phrase that specifies a |
| | 3232 | * container but nothing else: "the one in the box", "the ones in the |
| | 3233 | * box", "everything in the box". |
| | 3234 | */ |
| | 3235 | class VagueContainerNounPhraseProd: DefiniteNounProd |
| | 3236 | resolveNouns(resolver, results) |
| | 3237 | { |
| | 3238 | local cRes; |
| | 3239 | local cLst; |
| | 3240 | local lst; |
| | 3241 | local cont; |
| | 3242 | |
| | 3243 | /* resolve the container as a single-object slot */ |
| | 3244 | results.beginSingleObjSlot(); |
| | 3245 | |
| | 3246 | /* |
| | 3247 | * Resolve the container. Use a special resolver that prefers |
| | 3248 | * objects that have any contents. |
| | 3249 | */ |
| | 3250 | cRes = new VagueContainerResolver(resolver.getQualifierResolver()); |
| | 3251 | cLst = resolveDefinite(ResolveAsker, cont_.getOrigText(), |
| | 3252 | cont_.resolveNouns(cRes, results), |
| | 3253 | self, cRes, results); |
| | 3254 | |
| | 3255 | /* done with the single-object slot */ |
| | 3256 | results.endSingleObjSlot(); |
| | 3257 | |
| | 3258 | /* make sure we resolved to a unique container */ |
| | 3259 | if (cLst.length() != 1) |
| | 3260 | { |
| | 3261 | /* it's a separate error if we got more than one object */ |
| | 3262 | if (cLst.length() > 1) |
| | 3263 | results.uniqueObjectRequired(cont_.getOrigText(), cLst); |
| | 3264 | |
| | 3265 | /* we can't go on */ |
| | 3266 | return []; |
| | 3267 | } |
| | 3268 | |
| | 3269 | /* we have a unique item, so it's the container */ |
| | 3270 | cont = cLst[1].obj_; |
| | 3271 | |
| | 3272 | /* |
| | 3273 | * If the container is the nominal drop destination for the |
| | 3274 | * actor's location, then use the actor's location as the actual |
| | 3275 | * container. This way, if we try to get "all on floor" or the |
| | 3276 | * like, we'll correctly get objects that are directly in the |
| | 3277 | * room. |
| | 3278 | */ |
| | 3279 | if (cont == resolver.actor_.location.getNominalDropDestination()) |
| | 3280 | cont = resolver.actor_.location; |
| | 3281 | |
| | 3282 | /* get the contents */ |
| | 3283 | lst = cont.getAllForTakeFrom(resolver.getScopeList()); |
| | 3284 | |
| | 3285 | /* keep only visible objects */ |
| | 3286 | lst = lst.subset({x: resolver.actor_.canSee(x)}); |
| | 3287 | |
| | 3288 | /* map the contents to a resolved object list */ |
| | 3289 | lst = lst.mapAll({x: new ResolveInfo(x, 0)}); |
| | 3290 | |
| | 3291 | /* make sure the list isn't empty */ |
| | 3292 | if (lst.length() == 0) |
| | 3293 | { |
| | 3294 | /* there's nothing in this container */ |
| | 3295 | results.nothingInLocation(cont); |
| | 3296 | return []; |
| | 3297 | } |
| | 3298 | |
| | 3299 | /* make other subclass-specific checks on the list */ |
| | 3300 | lst = checkContentsList(resolver, results, lst, cont); |
| | 3301 | |
| | 3302 | /* note the matches */ |
| | 3303 | results.noteMatches(lst); |
| | 3304 | |
| | 3305 | /* return the list */ |
| | 3306 | return lst; |
| | 3307 | } |
| | 3308 | |
| | 3309 | /* check a contents list */ |
| | 3310 | checkContentsList(resolver, results, lst, cont) |
| | 3311 | { |
| | 3312 | /* by default, just return the list */ |
| | 3313 | return lst; |
| | 3314 | } |
| | 3315 | ; |
| | 3316 | |
| | 3317 | /* |
| | 3318 | * "All in container" |
| | 3319 | */ |
| | 3320 | class AllInContainerNounPhraseProd: VagueContainerNounPhraseProd |
| | 3321 | /* check a contents list */ |
| | 3322 | checkContentsList(resolver, results, lst, cont) |
| | 3323 | { |
| | 3324 | /* keep only items that aren't hidden from "all" */ |
| | 3325 | lst = lst.subset({x: !x.obj_.hideFromAll(resolver.getAction())}); |
| | 3326 | |
| | 3327 | /* set the "matched all" and "always announce as multi" flags */ |
| | 3328 | foreach (local cur in lst) |
| | 3329 | cur.flags_ |= AlwaysAnnounce | MatchedAll; |
| | 3330 | |
| | 3331 | /* if that emptied the list, so note */ |
| | 3332 | if (lst.length() == 0) |
| | 3333 | results.nothingInLocation(cont); |
| | 3334 | |
| | 3335 | /* return the result list */ |
| | 3336 | return lst; |
| | 3337 | } |
| | 3338 | ; |
| | 3339 | |
| | 3340 | /* |
| | 3341 | * A definite vague container phrase. This selects a single object in a |
| | 3342 | * given container ("the one in the box"). If more than one object is |
| | 3343 | * present, we'll try to disambiguate it. |
| | 3344 | * |
| | 3345 | * Grammar rules instantiating this class must set the property |
| | 3346 | * 'mainPhraseText' to the text to display for a disambiguation prompt |
| | 3347 | * involving the main phrase. |
| | 3348 | */ |
| | 3349 | class VagueContainerDefiniteNounPhraseProd: VagueContainerNounPhraseProd |
| | 3350 | construct() |
| | 3351 | { |
| | 3352 | /* create a disambiguator for the main phrase */ |
| | 3353 | npKeeper = new AmbigResponseKeeper(); |
| | 3354 | } |
| | 3355 | |
| | 3356 | /* check a contents list */ |
| | 3357 | checkContentsList(resolver, results, lst, cont) |
| | 3358 | { |
| | 3359 | /* |
| | 3360 | * This production type requires a single object in the |
| | 3361 | * container (since it's for phrases like "the one in the box"). |
| | 3362 | * If we have more than one object, try disambiguating. |
| | 3363 | */ |
| | 3364 | if (lst.length() > 1) |
| | 3365 | { |
| | 3366 | local scopeList; |
| | 3367 | local fullList; |
| | 3368 | |
| | 3369 | /* |
| | 3370 | * There's more than one object in this container. First, |
| | 3371 | * try filtering it by possessive qualifier strength and |
| | 3372 | * then the normal disambiguation ranking. |
| | 3373 | */ |
| | 3374 | scopeList = lst; |
| | 3375 | lst = resolver.filterPossRank(lst, 1); |
| | 3376 | lst = resolver.filterAmbiguousNounPhrase(lst, 1, self); |
| | 3377 | |
| | 3378 | /* try removing redundant equivalents */ |
| | 3379 | fullList = lst; |
| | 3380 | if (results.allowEquivalentFiltering) |
| | 3381 | lst = resolver.filterAmbiguousEquivalents(lst, self); |
| | 3382 | |
| | 3383 | /* if we still have too many objects, it's ambiguous */ |
| | 3384 | if (lst.length() > 1) |
| | 3385 | { |
| | 3386 | /* ask the results object to handle the ambiguous phrase */ |
| | 3387 | results.ambiguousNounPhrase( |
| | 3388 | npKeeper, ResolveAsker, mainPhraseText, |
| | 3389 | lst, fullList, scopeList, 1, resolver); |
| | 3390 | } |
| | 3391 | } |
| | 3392 | |
| | 3393 | /* return the contents of the container */ |
| | 3394 | return lst; |
| | 3395 | } |
| | 3396 | |
| | 3397 | /* our disambiguation result keeper */ |
| | 3398 | npKeeper = nil |
| | 3399 | ; |
| | 3400 | |
| | 3401 | /* |
| | 3402 | * An indefinite vague container phrase. This selects a single object, |
| | 3403 | * choosing arbitrarily if multiple objects are in the container. |
| | 3404 | */ |
| | 3405 | class VagueContainerIndefiniteNounPhraseProd: VagueContainerNounPhraseProd |
| | 3406 | /* check a contents list */ |
| | 3407 | checkContentsList(resolver, results, lst, cont) |
| | 3408 | { |
| | 3409 | /* choose one object arbitrarily */ |
| | 3410 | if (lst.length() > 1) |
| | 3411 | lst = lst.sublist(1, 1); |
| | 3412 | |
| | 3413 | /* return the (possibly trimmed) list */ |
| | 3414 | return lst; |
| | 3415 | } |
| | 3416 | ; |
| | 3417 | |
| | 3418 | |
| | 3419 | /* |
| | 3420 | * Container Resolver for vaguely-specified containment phrases. We'll |
| | 3421 | * select for objects that have contents, but that's about as much as we |
| | 3422 | * can do, since the main phrase is bounded only by the container in |
| | 3423 | * vague containment phrases (and thus provides no information that |
| | 3424 | * would help us narrow down the container itself). |
| | 3425 | */ |
| | 3426 | class VagueContainerResolver: BasicContainerResolver |
| | 3427 | /* filter ambiguous equivalents */ |
| | 3428 | filterAmbiguousEquivalents(lst, np) |
| | 3429 | { |
| | 3430 | local vec; |
| | 3431 | |
| | 3432 | /* prefer objects with contents */ |
| | 3433 | vec = new Vector(lst.length()); |
| | 3434 | foreach (local cur in lst) |
| | 3435 | { |
| | 3436 | /* if this item has any contents, add it to the new list */ |
| | 3437 | if (cur.obj_.contents.length() > 0) |
| | 3438 | vec.append(cur); |
| | 3439 | } |
| | 3440 | |
| | 3441 | /* |
| | 3442 | * if we found anything plausible, return only the plausible |
| | 3443 | * subset; otherwise, return the full original list, since |
| | 3444 | * they're all equally implausible |
| | 3445 | */ |
| | 3446 | if (vec.length() != 0) |
| | 3447 | return vec.toList(); |
| | 3448 | else |
| | 3449 | return lst; |
| | 3450 | } |
| | 3451 | ; |
| | 3452 | |
| | 3453 | |
| | 3454 | /* ------------------------------------------------------------------------ */ |
| | 3455 | /* |
| | 3456 | * Noun phrase with vocabulary resolution. This is a base class for the |
| | 3457 | * various noun phrases that match adjective, noun, and plural tokens. |
| | 3458 | * This class provides dictionary resolution of a vocabulary word into a |
| | 3459 | * list of objects. |
| | 3460 | * |
| | 3461 | * In non-declined languages such as English, the parts of speech of our |
| | 3462 | * words are usually simply 'adjective' and 'noun'. A language |
| | 3463 | * "declines" its noun phrases if the words in a noun phrase have |
| | 3464 | * different forms that depend on the function of the noun phrase in a |
| | 3465 | * sentence; for example, in German, adjectives take suffixes that |
| | 3466 | * depend upon the gender of the noun being modified and the function of |
| | 3467 | * the noun phrase in the sentence (subject, direct object, etc). In a |
| | 3468 | * declined language, it might be desirable to use separate parts of |
| | 3469 | * speech for separate declined adjective and noun forms. |
| | 3470 | */ |
| | 3471 | class NounPhraseWithVocab: NounPhraseProd |
| | 3472 | /* |
| | 3473 | * Get a list of the matches in the main dictionary for the given |
| | 3474 | * token as the given part of speech (&noun, &adjective, &plural, or |
| | 3475 | * others as appropriate for the local language) that are in scope |
| | 3476 | * according to the resolver. |
| | 3477 | */ |
| | 3478 | getWordMatches(tok, partOfSpeech, resolver, flags, truncFlags) |
| | 3479 | { |
| | 3480 | local lst; |
| | 3481 | |
| | 3482 | /* start with the dictionary matches */ |
| | 3483 | lst = cmdDict.findWord(tok, partOfSpeech); |
| | 3484 | |
| | 3485 | /* filter it to eliminate redundant matches */ |
| | 3486 | lst = filterDictMatches(lst); |
| | 3487 | |
| | 3488 | /* add the objects that match the full dictionary word */ |
| | 3489 | lst = inScopeMatches(lst, flags, flags | truncFlags, resolver); |
| | 3490 | |
| | 3491 | /* return the combined results */ |
| | 3492 | return lst; |
| | 3493 | } |
| | 3494 | |
| | 3495 | /* |
| | 3496 | * Combine two word match lists. This simply adds each entry from |
| | 3497 | * the second list that doesn't already have a corresponding entry |
| | 3498 | * in the first list, returning the combined list. |
| | 3499 | */ |
| | 3500 | combineWordMatches(aLst, bLst) |
| | 3501 | { |
| | 3502 | /* create a vector copy of the original 'a' list */ |
| | 3503 | aLst = new Vector(aLst.length(), aLst); |
| | 3504 | |
| | 3505 | /* |
| | 3506 | * add each entry from the second list whose object isn't |
| | 3507 | * already represented in the first list |
| | 3508 | */ |
| | 3509 | foreach (local b in bLst) |
| | 3510 | { |
| | 3511 | local ia; |
| | 3512 | |
| | 3513 | /* look for an existing entry for this object in the 'a' list */ |
| | 3514 | ia = aLst.indexWhich({aCur: aCur.obj_ == b.obj_}); |
| | 3515 | |
| | 3516 | /* |
| | 3517 | * If this 'b' entry isn't already in the 'a' list, simply |
| | 3518 | * add the 'b' entry. If both lists have this entry, combine |
| | 3519 | * the flags into the existing entry. |
| | 3520 | */ |
| | 3521 | if (ia == nil) |
| | 3522 | { |
| | 3523 | /* it's not already in the 'a' list, so simply add it */ |
| | 3524 | aLst += b; |
| | 3525 | } |
| | 3526 | else |
| | 3527 | { |
| | 3528 | /* |
| | 3529 | * Both lists have the entry, so keep the existing 'a' |
| | 3530 | * entry, but merge the flags. Note that the |
| | 3531 | * original 'a' might still be interesting to the |
| | 3532 | * caller separately, so create a copy of it before |
| | 3533 | * modifying it. |
| | 3534 | */ |
| | 3535 | local a = aLst[ia]; |
| | 3536 | aLst[ia] = a = a.createClone(); |
| | 3537 | combineWordMatchItems(a, b); |
| | 3538 | } |
| | 3539 | } |
| | 3540 | |
| | 3541 | /* return the combined list */ |
| | 3542 | return aLst; |
| | 3543 | } |
| | 3544 | |
| | 3545 | /* |
| | 3546 | * Combine the given word match entries. We'll merge the flags of |
| | 3547 | * the two entries to produce a single merged entry in 'a'. |
| | 3548 | */ |
| | 3549 | combineWordMatchItems(a, b) |
| | 3550 | { |
| | 3551 | /* |
| | 3552 | * If one item was matched with truncation and the other wasn't, |
| | 3553 | * remove the truncation flag entirely. This will make the |
| | 3554 | * combined entry reflect the fact that we were able to match the |
| | 3555 | * word without any truncation. The fact that we also matched it |
| | 3556 | * with truncation isn't relevant, since matching without |
| | 3557 | * truncation is the stronger condition. |
| | 3558 | */ |
| | 3559 | if ((b.flags_ & VocabTruncated) == 0) |
| | 3560 | a.flags_ &= ~VocabTruncated; |
| | 3561 | |
| | 3562 | /* likewise for plural truncation */ |
| | 3563 | if ((b.flags_ & PluralTruncated) == 0) |
| | 3564 | a.flags_ &= ~PluralTruncated; |
| | 3565 | |
| | 3566 | /* |
| | 3567 | * Other flags generally are set for the entire list of matching |
| | 3568 | * objects for a production, rather than at the level of |
| | 3569 | * individual matching objects, so we don't have to worry about |
| | 3570 | * combining them - they'll naturally be the same at this point. |
| | 3571 | */ |
| | 3572 | } |
| | 3573 | |
| | 3574 | /* |
| | 3575 | * Filter a dictionary match list. This is called to clean up the |
| | 3576 | * raw match list returned from looking up a vocabulary word in the |
| | 3577 | * dictionary. |
| | 3578 | * |
| | 3579 | * The main purpose of this routine is to eliminate unwanted |
| | 3580 | * redundancy from the dictionary matches; in particular, the |
| | 3581 | * dictionary might have multiple matches for a given word at a given |
| | 3582 | * object, due to truncation, upper/lower folding, accent removal, |
| | 3583 | * and so on. In general, we want to keep only the single strongest |
| | 3584 | * match from the dictionary for a given word matching a given |
| | 3585 | * object. |
| | 3586 | * |
| | 3587 | * The meaning of "stronger" and "exact" matches is |
| | 3588 | * language-dependent, so we abstract these with the separate methods |
| | 3589 | * dictMatchIsExact() and dictMatchIsStronger(). |
| | 3590 | * |
| | 3591 | * Keep in mind that the raw dictionary match list has alternating |
| | 3592 | * entries: object, comparator flags, object, comparator flags, etc. |
| | 3593 | * The return list should be in the same format. |
| | 3594 | */ |
| | 3595 | filterDictMatches(lst) |
| | 3596 | { |
| | 3597 | local ret; |
| | 3598 | |
| | 3599 | /* set up a vector to hold the result */ |
| | 3600 | ret = new Vector(lst.length()); |
| | 3601 | |
| | 3602 | /* |
| | 3603 | * check each inexact element of the list for another entry with |
| | 3604 | * a stronger match; keep only the ones without a stronger match |
| | 3605 | */ |
| | 3606 | truncScan: |
| | 3607 | /* scan for a stronger match */ |
| | 3608 | for (local i = 1, local len = lst.length() ; i < len ; i += 2) |
| | 3609 | { |
| | 3610 | /* get the current object and its flags */ |
| | 3611 | local curObj = lst[i]; |
| | 3612 | local curFlags = lst[i+1]; |
| | 3613 | |
| | 3614 | /* if it's not an exact match, check for a stronger match */ |
| | 3615 | if (!dictMatchIsExact(curFlags)) |
| | 3616 | { |
| | 3617 | /* scan for a stronger match for this same object */ |
| | 3618 | for (local j = 1 ; j < len ; j += 2) |
| | 3619 | { |
| | 3620 | /* |
| | 3621 | * if entry j is different from entry i, and it's |
| | 3622 | * stronger, omit entry i |
| | 3623 | */ |
| | 3624 | if (j != i |
| | 3625 | && lst[j] == curObj |
| | 3626 | && dictMatchIsStronger(lst[j+1], curFlags)) |
| | 3627 | { |
| | 3628 | /* there's a better entry; omit the current one */ |
| | 3629 | continue truncScan; |
| | 3630 | } |
| | 3631 | } |
| | 3632 | } |
| | 3633 | |
| | 3634 | /* keep this entry - add it to the result vector */ |
| | 3635 | ret.append(curObj); |
| | 3636 | ret.append(curFlags); |
| | 3637 | } |
| | 3638 | |
| | 3639 | /* return the result vector */ |
| | 3640 | return ret; |
| | 3641 | } |
| | 3642 | |
| | 3643 | /* |
| | 3644 | * Check a dictionary match's string comparator flags to see if the |
| | 3645 | * match is "exact." The exact meaning of "exact" is dependent on |
| | 3646 | * the language's lexical rules; this generic base version considers |
| | 3647 | * a match to be exact if it doesn't have any string comparator flags |
| | 3648 | * other than the base "matched" flag and the case-fold flag. This |
| | 3649 | * should be suitable for most languages, as (1) case folding usually |
| | 3650 | * doesn't improve match strength, and (2) any additional comparator |
| | 3651 | * flags usually indicate some kind of inexact match status. |
| | 3652 | * |
| | 3653 | * A language that depends on upper/lower case as a marker of match |
| | 3654 | * strength will need to override this to consider the case-fold flag |
| | 3655 | * as significant in determining match exactness. In addition, a |
| | 3656 | * language that uses additional string comparator flags to indicate |
| | 3657 | * better (rather than worse) matches will have to override this to |
| | 3658 | * require the presence of those flags. |
| | 3659 | */ |
| | 3660 | dictMatchIsExact(flags) |
| | 3661 | { |
| | 3662 | /* |
| | 3663 | * the match is exact if it doesn't have any qualifier flags |
| | 3664 | * other than the basic "yes I matched" flag and the case-fold |
| | 3665 | * flag |
| | 3666 | */ |
| | 3667 | return ((flags & ~(StrCompMatch | StrCompCaseFold)) == 0); |
| | 3668 | } |
| | 3669 | |
| | 3670 | /* |
| | 3671 | * Compare two dictionary matches for the same object and determine |
| | 3672 | * if the first one is stronger than the second. Both are for the |
| | 3673 | * same object; the only difference is the string comparator flags. |
| | 3674 | * |
| | 3675 | * Language modules might need to override this to supplement the |
| | 3676 | * filtering with their own rules. This generic base version |
| | 3677 | * considers truncation: an untruncated match is stronger than a |
| | 3678 | * truncated match. Non-English languages might want to consider |
| | 3679 | * other lexical factors in the match strength, such as whether we |
| | 3680 | * matched the exact accented characters or approximated with |
| | 3681 | * unaccented equivalents - this information will, of course, need to |
| | 3682 | * be coordinated with the dictionary's string comparator, and |
| | 3683 | * reflected in the comparator match flags. It's the comparator |
| | 3684 | * match flags that we're looking at here. |
| | 3685 | */ |
| | 3686 | dictMatchIsStronger(flags1, flags2) |
| | 3687 | { |
| | 3688 | /* |
| | 3689 | * if the first match (flags1) indicates no truncation, and the |
| | 3690 | * second (flags2) was truncated, then the first match is |
| | 3691 | * stronger; otherwise, there's no distinction as far as we're |
| | 3692 | * concerned |
| | 3693 | */ |
| | 3694 | return ((flags1 & StrCompTrunc) == 0 |
| | 3695 | && (flags2 & StrCompTrunc) != 0); |
| | 3696 | } |
| | 3697 | |
| | 3698 | /* |
| | 3699 | * Get a list of the matches in the main dictionary for the given |
| | 3700 | * token, intersecting the resulting list with the given list. |
| | 3701 | */ |
| | 3702 | intersectWordMatches(tok, partOfSpeech, resolver, flags, truncFlags, lst) |
| | 3703 | { |
| | 3704 | local newList; |
| | 3705 | |
| | 3706 | /* get the matches to the given word */ |
| | 3707 | newList = getWordMatches(tok, partOfSpeech, resolver, |
| | 3708 | flags, truncFlags); |
| | 3709 | |
| | 3710 | /* intersect the result with the other list */ |
| | 3711 | newList = intersectNounLists(newList, lst); |
| | 3712 | |
| | 3713 | /* return the combined results */ |
| | 3714 | return newList; |
| | 3715 | } |
| | 3716 | |
| | 3717 | /* |
| | 3718 | * Given a list of dictionary matches to a given word, construct a |
| | 3719 | * list of ResolveInfo objects for the matches that are in scope. |
| | 3720 | * For regular resolution, "in scope" means the resolver thinks the |
| | 3721 | * object is in scope. |
| | 3722 | */ |
| | 3723 | inScopeMatches(dictionaryMatches, flags, truncFlags, resolver) |
| | 3724 | { |
| | 3725 | local ret; |
| | 3726 | |
| | 3727 | /* set up a vector to hold the results */ |
| | 3728 | ret = new Vector(dictionaryMatches.length()); |
| | 3729 | |
| | 3730 | /* |
| | 3731 | * Run through the list and include only the words that are in |
| | 3732 | * scope. Note that the list of dictionary matches has |
| | 3733 | * alternating objects and match flags, so we must scan it two |
| | 3734 | * elements at a time. |
| | 3735 | */ |
| | 3736 | for (local i = 1, local cnt = dictionaryMatches.length() ; |
| | 3737 | i <= cnt ; i += 2) |
| | 3738 | { |
| | 3739 | local cur; |
| | 3740 | |
| | 3741 | /* get this object */ |
| | 3742 | cur = dictionaryMatches[i]; |
| | 3743 | |
| | 3744 | /* if it's in scope, add a ResolveInfo for this object */ |
| | 3745 | if (resolver.objInScope(cur)) |
| | 3746 | { |
| | 3747 | local curResults; |
| | 3748 | local curFlags; |
| | 3749 | |
| | 3750 | /* get the comparator match results for this object */ |
| | 3751 | curResults = dictionaryMatches[i+1]; |
| | 3752 | |
| | 3753 | /* |
| | 3754 | * If the results indication truncation, use the the |
| | 3755 | * truncated flags; otherwise use the ordinary flags. |
| | 3756 | */ |
| | 3757 | if (dataType(curResults) == TypeInt |
| | 3758 | && (curResults & StrCompTrunc) != 0) |
| | 3759 | curFlags = truncFlags; |
| | 3760 | else |
| | 3761 | curFlags = flags; |
| | 3762 | |
| | 3763 | /* add the item to the results */ |
| | 3764 | ret.append(new ResolveInfo(cur, curFlags)); |
| | 3765 | } |
| | 3766 | } |
| | 3767 | |
| | 3768 | /* return the results as a list */ |
| | 3769 | return ret.toList(); |
| | 3770 | } |
| | 3771 | |
| | 3772 | /* |
| | 3773 | * Resolve the objects. |
| | 3774 | */ |
| | 3775 | resolveNouns(resolver, results) |
| | 3776 | { |
| | 3777 | local matchList; |
| | 3778 | local starList; |
| | 3779 | |
| | 3780 | /* |
| | 3781 | * get the preliminary match list - this is simply the set of |
| | 3782 | * objects that match all of the words in the noun phrase |
| | 3783 | */ |
| | 3784 | matchList = getVocabMatchList(resolver, results, 0); |
| | 3785 | |
| | 3786 | /* |
| | 3787 | * get a list of any in-scope objects that match '*' for nouns - |
| | 3788 | * these are objects that want to do all of their name parsing |
| | 3789 | * themselves |
| | 3790 | */ |
| | 3791 | starList = inScopeMatches(cmdDict.findWord('*', &noun), |
| | 3792 | 0, 0, resolver); |
| | 3793 | |
| | 3794 | /* combine the lists */ |
| | 3795 | matchList = combineWordMatches(matchList, starList); |
| | 3796 | |
| | 3797 | /* run the results through matchName */ |
| | 3798 | return resolveNounsMatchName(results, resolver, matchList); |
| | 3799 | } |
| | 3800 | |
| | 3801 | /* |
| | 3802 | * Run a set of resolved objects through matchName() or a similar |
| | 3803 | * routine. Returns the filtered results. |
| | 3804 | */ |
| | 3805 | resolveNounsMatchName(results, resolver, matchList) |
| | 3806 | { |
| | 3807 | local origTokens; |
| | 3808 | local adjustedTokens; |
| | 3809 | local objVec; |
| | 3810 | local ret; |
| | 3811 | |
| | 3812 | /* get the original token list for the command */ |
| | 3813 | origTokens = getOrigTokenList(); |
| | 3814 | |
| | 3815 | /* get the adjusted token list for the command */ |
| | 3816 | adjustedTokens = getAdjustedTokens(); |
| | 3817 | |
| | 3818 | /* set up to receive about the same number of results as inputs */ |
| | 3819 | objVec = new Vector(matchList.length()); |
| | 3820 | |
| | 3821 | /* consider each preliminary match */ |
| | 3822 | foreach (local cur in matchList) |
| | 3823 | { |
| | 3824 | local newObj; |
| | 3825 | |
| | 3826 | /* ask this object if it wants to be included */ |
| | 3827 | newObj = resolver.matchName(cur.obj_, origTokens, adjustedTokens); |
| | 3828 | |
| | 3829 | /* check the result */ |
| | 3830 | if (newObj == nil) |
| | 3831 | { |
| | 3832 | /* |
| | 3833 | * it's nil - this means it's not a match for the name |
| | 3834 | * after all, so leave it out of the results |
| | 3835 | */ |
| | 3836 | } |
| | 3837 | else if (newObj.ofKind(Collection)) |
| | 3838 | { |
| | 3839 | /* |
| | 3840 | * it's a collection of some kind - add each element to |
| | 3841 | * the result list, using the same flags as the original |
| | 3842 | */ |
| | 3843 | foreach (local curObj in newObj) |
| | 3844 | objVec.append(new ResolveInfo(curObj, cur.flags_)); |
| | 3845 | } |
| | 3846 | else |
| | 3847 | { |
| | 3848 | /* |
| | 3849 | * it's a single object - add it ito the result list, |
| | 3850 | * using the same flags as the original |
| | 3851 | */ |
| | 3852 | objVec.append(new ResolveInfo(newObj, cur.flags_)); |
| | 3853 | } |
| | 3854 | } |
| | 3855 | |
| | 3856 | /* convert the result vector to a list */ |
| | 3857 | ret = objVec.toList(); |
| | 3858 | |
| | 3859 | /* if our list is empty, note it in the results */ |
| | 3860 | if (ret.length() == 0) |
| | 3861 | { |
| | 3862 | /* |
| | 3863 | * If the adjusted token list contains any tokens of type |
| | 3864 | * "miscWord", send the phrase to the results object for |
| | 3865 | * further consideration. |
| | 3866 | */ |
| | 3867 | if (adjustedTokens.indexOf(&miscWord) != nil) |
| | 3868 | { |
| | 3869 | /* |
| | 3870 | * we have miscWord tokens, so this is a miscWordList |
| | 3871 | * match - let the results object process it specially. |
| | 3872 | */ |
| | 3873 | ret = results.unknownNounPhrase(self, resolver); |
| | 3874 | } |
| | 3875 | |
| | 3876 | /* |
| | 3877 | * if the list is empty, note that we have a noun phrase |
| | 3878 | * whose vocabulary words don't match anything in the game |
| | 3879 | */ |
| | 3880 | if (ret.length() == 0) |
| | 3881 | results.noVocabMatch(resolver.getAction(), getOrigText()); |
| | 3882 | } |
| | 3883 | |
| | 3884 | /* return the result list */ |
| | 3885 | return ret; |
| | 3886 | } |
| | 3887 | |
| | 3888 | /* |
| | 3889 | * Each subclass must override getAdjustedTokens() to provide the |
| | 3890 | * appropriate set of tokens used to match the object. This is |
| | 3891 | * usually simply the original set of tokens, but in some cases it |
| | 3892 | * may differ; for example, spelled-out numbers normally adjust to |
| | 3893 | * the numeral form of the number. |
| | 3894 | * |
| | 3895 | * For each adjusted token, the list must have two entries: the |
| | 3896 | * first is a string giving the token text, and the second is the |
| | 3897 | * property giving the part of speech for the token. |
| | 3898 | */ |
| | 3899 | getAdjustedTokens() { return nil; } |
| | 3900 | |
| | 3901 | /* |
| | 3902 | * Get the vocabulary match list. This is simply the set of objects |
| | 3903 | * that match all of the words in the noun phrase. Each rule |
| | 3904 | * subclass must override this to return an appropriate list. Note |
| | 3905 | * that subclasses should use getWordMatches() and |
| | 3906 | * intersectWordMatches() to build the list. |
| | 3907 | */ |
| | 3908 | getVocabMatchList(resolver, results, extraFlags) { return nil; } |
| | 3909 | ; |
| | 3910 | |
| | 3911 | |
| | 3912 | /* ------------------------------------------------------------------------ */ |
| | 3913 | /* |
| | 3914 | * An empty noun phrase production is one that matches, typically with |
| | 3915 | * non-zero badness value, as a placeholder when a command is missing a |
| | 3916 | * noun phrase where one is required. |
| | 3917 | * |
| | 3918 | * Each grammar rule instance of this rule class must define the |
| | 3919 | * property 'responseProd' to be the production that should be used to |
| | 3920 | * parse any response to an interactive prompt for the missing object. |
| | 3921 | */ |
| | 3922 | class EmptyNounPhraseProd: NounPhraseProd |
| | 3923 | /* customize the way we generate the prompt and parse the response */ |
| | 3924 | setPrompt(response, asker) |
| | 3925 | { |
| | 3926 | /* remember the new response production and ResolveAsker */ |
| | 3927 | responseProd = response; |
| | 3928 | asker_ = asker; |
| | 3929 | } |
| | 3930 | |
| | 3931 | /* resolve the empty phrase */ |
| | 3932 | resolveNouns(resolver, results) |
| | 3933 | { |
| | 3934 | local match; |
| | 3935 | |
| | 3936 | /* |
| | 3937 | * if we've filled in our missing phrase already, return the |
| | 3938 | * resolution of that list |
| | 3939 | */ |
| | 3940 | if (newMatch != nil) |
| | 3941 | return newMatch.resolveNouns(resolver, results); |
| | 3942 | |
| | 3943 | /* |
| | 3944 | * The noun phrase was left out entirely, so try to get an |
| | 3945 | * implied object. |
| | 3946 | */ |
| | 3947 | match = getImpliedObject(resolver, results); |
| | 3948 | |
| | 3949 | /* if that succeeded, return the result */ |
| | 3950 | if (match != nil) |
| | 3951 | return match; |
| | 3952 | |
| | 3953 | /* |
| | 3954 | * There is no implied object, so try to get a result |
| | 3955 | * interactively. Use the production that our rule instance |
| | 3956 | * specifies via the responseProd property to parse the |
| | 3957 | * interactive response. |
| | 3958 | */ |
| | 3959 | match = results.askMissingObject(asker_, resolver, responseProd); |
| | 3960 | |
| | 3961 | /* if we didn't get a match, we have nothing to return */ |
| | 3962 | if (match == nil) |
| | 3963 | return []; |
| | 3964 | |
| | 3965 | /* we got a match - remember it as a proxy for our noun phrase */ |
| | 3966 | newMatch = match; |
| | 3967 | |
| | 3968 | /* return the resolved noun phrase from the proxy match */ |
| | 3969 | return newMatch.resolvedObjects; |
| | 3970 | } |
| | 3971 | |
| | 3972 | /* |
| | 3973 | * Get an implied object to automatically fill in for the missing |
| | 3974 | * noun phrase. By default, we simply ask the 'results' object for |
| | 3975 | * the missing object. |
| | 3976 | */ |
| | 3977 | getImpliedObject(resolver, results) |
| | 3978 | { |
| | 3979 | /* ask the 'results' object for the information */ |
| | 3980 | return results.getImpliedObject(self, resolver); |
| | 3981 | } |
| | 3982 | |
| | 3983 | /* |
| | 3984 | * Get my tokens. If I have a new match tree, return the tokens |
| | 3985 | * from the new match tree. Otherwise, we don't have any tokens, |
| | 3986 | * since we're empty. |
| | 3987 | */ |
| | 3988 | getOrigTokenList() |
| | 3989 | { |
| | 3990 | return (newMatch != nil ? newMatch.getOrigTokenList() : []); |
| | 3991 | } |
| | 3992 | |
| | 3993 | /* |
| | 3994 | * Get my original text. If I have a new match tree, return the |
| | 3995 | * text from the new match tree. Otherwise, we have no original |
| | 3996 | * text, since we're an empty phrase. |
| | 3997 | */ |
| | 3998 | getOrigText() |
| | 3999 | { |
| | 4000 | return (newMatch != nil ? newMatch.getOrigText() : ''); |
| | 4001 | } |
| | 4002 | |
| | 4003 | /* |
| | 4004 | * I'm an empty noun phrase, unless I already have a new match |
| | 4005 | * object. |
| | 4006 | */ |
| | 4007 | isEmptyPhrase { return newMatch == nil; } |
| | 4008 | |
| | 4009 | /* |
| | 4010 | * the new match, when we get an interactive response to a query for |
| | 4011 | * the missing object |
| | 4012 | */ |
| | 4013 | newMatch = nil |
| | 4014 | |
| | 4015 | /* |
| | 4016 | * our "response" production - this is the production we use to |
| | 4017 | * parse the player's input in response to our disambiguation prompt |
| | 4018 | */ |
| | 4019 | responseProd = nil |
| | 4020 | |
| | 4021 | /* |
| | 4022 | * The ResolveAsker we use to generate our prompt. Use the base |
| | 4023 | * ResolveAsker by default; this can be overridden when the prompt |
| | 4024 | * is to be customized. |
| | 4025 | */ |
| | 4026 | asker_ = ResolveAsker |
| | 4027 | ; |
| | 4028 | |
| | 4029 | /* |
| | 4030 | * An empty noun phrase production for verb phrasings that imply an |
| | 4031 | * actor, but don't actually include one by name. |
| | 4032 | * |
| | 4033 | * This is similar to EmptyNounPhraseProd, but has an important |
| | 4034 | * difference: if the actor carrying out the command has a current or |
| | 4035 | * implied conversation partner, then we choose the conversation partner |
| | 4036 | * as the implied object. This is important in that we don't count the |
| | 4037 | * noun phrase as technically missing in this case, for the purposes of |
| | 4038 | * command ranking. This is useful for phrasings that inherently imply |
| | 4039 | * an actor strongly enough that there should be no match-strength |
| | 4040 | * penalty for leaving it out. |
| | 4041 | */ |
| | 4042 | class ImpliedActorNounPhraseProd: EmptyNounPhraseProd |
| | 4043 | /* get my implied object */ |
| | 4044 | getImpliedObject(resolver, results) |
| | 4045 | { |
| | 4046 | local actor; |
| | 4047 | |
| | 4048 | /* |
| | 4049 | * If the actor has a default interlocutor, use that, bypassing |
| | 4050 | * the normal implied object search. |
| | 4051 | */ |
| | 4052 | if ((actor = resolver.actor_.getDefaultInterlocutor()) != nil) |
| | 4053 | return [new ResolveInfo(actor, DefaultObject)]; |
| | 4054 | |
| | 4055 | /* ask the 'results' object for the information */ |
| | 4056 | return results.getImpliedObject(self, resolver); |
| | 4057 | } |
| | 4058 | ; |
| | 4059 | |
| | 4060 | /* |
| | 4061 | * Empty literal phrase - this serves a purpose similar to that of |
| | 4062 | * EmptyNounPhraseProd, but can be used where literal phrases are |
| | 4063 | * required. |
| | 4064 | */ |
| | 4065 | class EmptyLiteralPhraseProd: LiteralProd |
| | 4066 | getLiteralText(results, action, which) |
| | 4067 | { |
| | 4068 | local toks; |
| | 4069 | local prods; |
| | 4070 | |
| | 4071 | /* if we already have an interactive response, return it */ |
| | 4072 | if (newText != nil) |
| | 4073 | return newText; |
| | 4074 | |
| | 4075 | /* |
| | 4076 | * ask for the missing phrase and remember it for the next time |
| | 4077 | * we're asked for our text |
| | 4078 | */ |
| | 4079 | newText = results.askMissingLiteral(action, which); |
| | 4080 | |
| | 4081 | /* |
| | 4082 | * Parse the text (if any) as a literal phrase. In most cases, |
| | 4083 | * anything can be parsed as a literal phrase, so this might |
| | 4084 | * seem kind of pointless; however, this is useful when the |
| | 4085 | * language-specific library defines rules for things like |
| | 4086 | * removing quotes from a quoted string. |
| | 4087 | */ |
| | 4088 | if (newText != nil) |
| | 4089 | { |
| | 4090 | try |
| | 4091 | { |
| | 4092 | /* tokenize the input */ |
| | 4093 | toks = cmdTokenizer.tokenize(newText); |
| | 4094 | } |
| | 4095 | catch (TokErrorNoMatch exc) |
| | 4096 | { |
| | 4097 | /* note the token error */ |
| | 4098 | gLibMessages.invalidCommandToken(exc.curChar_.htmlify()); |
| | 4099 | |
| | 4100 | /* treat the command as empty */ |
| | 4101 | throw new ReplacementCommandStringException(nil, nil, nil); |
| | 4102 | } |
| | 4103 | |
| | 4104 | /* parse the input as a literal phrase */ |
| | 4105 | prods = literalPhrase.parseTokens(toks, cmdDict); |
| | 4106 | |
| | 4107 | /* if we got a match, use it */ |
| | 4108 | if (prods.length() > 0) |
| | 4109 | { |
| | 4110 | /* |
| | 4111 | * we got a match - this will be a LiteralProd, so ask |
| | 4112 | * the matching LiteralProd for its literal text, and |
| | 4113 | * use that as our new literal text |
| | 4114 | */ |
| | 4115 | newText = prods[1].getLiteralText(results, action, which); |
| | 4116 | } |
| | 4117 | } |
| | 4118 | |
| | 4119 | /* return the text */ |
| | 4120 | return newText; |
| | 4121 | } |
| | 4122 | |
| | 4123 | /* |
| | 4124 | * Tentatively get my literal text. This is used for pre-resolution |
| | 4125 | * when we have another phrase we want to resolve first, but we want |
| | 4126 | * to provide a tentative form of the text in the meantime. We won't |
| | 4127 | * attempt to ask for more information interactively, but we'll |
| | 4128 | * return any information we do have. |
| | 4129 | */ |
| | 4130 | getTentativeLiteralText() |
| | 4131 | { |
| | 4132 | /* |
| | 4133 | * if we have a result from a previous interaactive request, |
| | 4134 | * return it; otherwise we have no tentative text |
| | 4135 | */ |
| | 4136 | return newText; |
| | 4137 | } |
| | 4138 | |
| | 4139 | /* I'm an empty phrase, unless I already have a text response */ |
| | 4140 | isEmptyPhrase { return newText == nil; } |
| | 4141 | |
| | 4142 | /* the response to a previous interactive query */ |
| | 4143 | newText = nil |
| | 4144 | ; |
| | 4145 | |
| | 4146 | /* |
| | 4147 | * Empty topic phrase production. This is the topic equivalent of |
| | 4148 | * EmptyNounPhraseProd. |
| | 4149 | */ |
| | 4150 | class EmptyTopicPhraseProd: TopicProd |
| | 4151 | resolveNouns(resolver, results) |
| | 4152 | { |
| | 4153 | local match; |
| | 4154 | |
| | 4155 | /* |
| | 4156 | * if we've filled in our missing phrase already, return the |
| | 4157 | * resolution of that list |
| | 4158 | */ |
| | 4159 | if (newMatch != nil) |
| | 4160 | return newMatch.resolveNouns(resolver, results); |
| | 4161 | |
| | 4162 | /* ask for a topic interactively, using our responseProd */ |
| | 4163 | match = results.askMissingObject(asker_, resolver, responseProd); |
| | 4164 | |
| | 4165 | /* if we didn't get a match, we have nothing to return */ |
| | 4166 | if (match == nil) |
| | 4167 | return []; |
| | 4168 | |
| | 4169 | /* we got a match - remember it as a proxy for our noun phrase */ |
| | 4170 | newMatch = match; |
| | 4171 | |
| | 4172 | /* return the resolved noun phrase from the proxy match */ |
| | 4173 | return newMatch.resolvedObjects; |
| | 4174 | } |
| | 4175 | |
| | 4176 | /* we're an empty phrase if we don't have a new topic yet */ |
| | 4177 | isEmptyPhrase { return newMatch = nil; } |
| | 4178 | |
| | 4179 | /* get my tokens - use the underlying new match tree if we have one */ |
| | 4180 | getOrigTokenList() |
| | 4181 | { |
| | 4182 | return (newMatch != nil ? newMatch.getOrigTokenList() : inherited()); |
| | 4183 | } |
| | 4184 | |
| | 4185 | /* get my original text - use the new match tree if we have one */ |
| | 4186 | getOrigText() |
| | 4187 | { |
| | 4188 | return (newMatch != nil ? newMatch.getOrigText() : inherited()); |
| | 4189 | } |
| | 4190 | |
| | 4191 | /* our new underlying topic phrase */ |
| | 4192 | newMatch = nil |
| | 4193 | |
| | 4194 | /* |
| | 4195 | * by default, parse our interactive response as an ordinary topic |
| | 4196 | * phrase |
| | 4197 | */ |
| | 4198 | responseProd = topicPhrase |
| | 4199 | |
| | 4200 | /* our ResolveAsker object - this is for customizing the prompt */ |
| | 4201 | asker_ = ResolveAsker |
| | 4202 | ; |
| | 4203 | |
| | 4204 | |
| | 4205 | /* ------------------------------------------------------------------------ */ |
| | 4206 | /* |
| | 4207 | * Look for an undefined word in a list of tokens, and give the player a |
| | 4208 | * chance to correct a typo with "OOPS" if appropriate. |
| | 4209 | * |
| | 4210 | * If we find an unknown word and we can prompt for interactive |
| | 4211 | * resolution, we'll do so, and we'll throw an appropriate exception to |
| | 4212 | * handle the response. If we can't resolve the missing word |
| | 4213 | * interactively, we'll throw a parse failure exception. |
| | 4214 | * |
| | 4215 | * If there are no undefined words in the command, we'll simply return. |
| | 4216 | * |
| | 4217 | * tokList is the list of tokens under examination; this is a subset of |
| | 4218 | * the full command token list. cmdTokenList is the full command token |
| | 4219 | * list, in the usual tokenizer format. firstTokenIndex is the index of |
| | 4220 | * the first token in tokList within cmdTokenList. |
| | 4221 | * |
| | 4222 | * cmdType is an rmcXxx code giving the type of input we're reading. |
| | 4223 | */ |
| | 4224 | tryOops(tokList, issuingActor, targetActor, |
| | 4225 | firstTokenIndex, cmdTokenList, cmdType) |
| | 4226 | { |
| | 4227 | /* run the main "oops" processor in the player character sense context */ |
| | 4228 | callWithSenseContext(nil, sight, |
| | 4229 | {: tryOopsMain(tokList, issuingActor, targetActor, |
| | 4230 | firstTokenIndex, cmdTokenList, |
| | 4231 | cmdType) }); |
| | 4232 | } |
| | 4233 | |
| | 4234 | /* main "oops" processor */ |
| | 4235 | tryOopsMain(tokList, issuingActor, targetActor, |
| | 4236 | firstTokenIndex, cmdTokenList, cmdType) |
| | 4237 | { |
| | 4238 | local str; |
| | 4239 | local unknownIdx; |
| | 4240 | local oopsMatch; |
| | 4241 | local toks; |
| | 4242 | local w; |
| | 4243 | |
| | 4244 | /* |
| | 4245 | * Look for a word not in the dictionary. |
| | 4246 | */ |
| | 4247 | for (unknownIdx = nil, local i = 1, local len = tokList.length() ; |
| | 4248 | i <= len ; ++i) |
| | 4249 | { |
| | 4250 | local cur; |
| | 4251 | local typ; |
| | 4252 | |
| | 4253 | /* get the token value for this word */ |
| | 4254 | cur = getTokVal(tokList[i]); |
| | 4255 | typ = getTokType(tokList[i]); |
| | 4256 | |
| | 4257 | /* check to see if this word is defined in the dictionary */ |
| | 4258 | if (typ == tokWord && !cmdDict.isWordDefined(cur)) |
| | 4259 | { |
| | 4260 | /* note that we found an unknown word */ |
| | 4261 | unknownIdx = i; |
| | 4262 | |
| | 4263 | /* no need to look any further */ |
| | 4264 | break; |
| | 4265 | } |
| | 4266 | } |
| | 4267 | |
| | 4268 | /* |
| | 4269 | * if we didn't find an unknown word, there's no need to offer the |
| | 4270 | * user a chance to correct a typo - simply return without any |
| | 4271 | * further processing |
| | 4272 | */ |
| | 4273 | if (unknownIdx == nil) |
| | 4274 | return; |
| | 4275 | |
| | 4276 | /* |
| | 4277 | * We do have an unknown word, but check one more thing: if we were |
| | 4278 | * asking some kind of follow-up question, such as a missing-object |
| | 4279 | * or disambiguation query, check to see if the new entry would parse |
| | 4280 | * successfully as a new command. It's possible that the new entry |
| | 4281 | * is a brand new command rather than a response to our question, and |
| | 4282 | * that the unknown word is valid in the context of a new command - |
| | 4283 | * it could be part of a literal-phrase, for example. |
| | 4284 | */ |
| | 4285 | if (cmdType != rmcCommand) |
| | 4286 | { |
| | 4287 | /* parse the command */ |
| | 4288 | local lst = firstCommandPhrase.parseTokens(cmdTokenList, cmdDict); |
| | 4289 | |
| | 4290 | /* resolve actions */ |
| | 4291 | lst = lst.subset( |
| | 4292 | {x: x.resolveFirstAction(issuingActor, targetActor) != nil}); |
| | 4293 | |
| | 4294 | /* if we managed to match something, treat it as a new command */ |
| | 4295 | if (lst.length() != 0) |
| | 4296 | throw new ReplacementCommandStringException( |
| | 4297 | cmdTokenizer.buildOrigText(cmdTokenList), nil, nil); |
| | 4298 | } |
| | 4299 | |
| | 4300 | |
| | 4301 | /* get the unknown word, in presentable form */ |
| | 4302 | w = getTokOrig(tokList[unknownIdx]).htmlify(); |
| | 4303 | |
| | 4304 | /* |
| | 4305 | * Give them a chance to correct a typo via OOPS if the player |
| | 4306 | * issued the command. If the command came from an actor other than |
| | 4307 | * the player character, simply fail the command. |
| | 4308 | */ |
| | 4309 | if (!issuingActor.isPlayerChar()) |
| | 4310 | { |
| | 4311 | /* we can't do this interactively - treat it as a failure */ |
| | 4312 | throw new ParseFailureException(&wordIsUnknown, w); |
| | 4313 | } |
| | 4314 | |
| | 4315 | /* |
| | 4316 | * tell the player about the unknown word, implicitly asking for |
| | 4317 | * an OOPS to fix it |
| | 4318 | */ |
| | 4319 | targetActor.getParserMessageObj().askUnknownWord(targetActor, w); |
| | 4320 | |
| | 4321 | /* |
| | 4322 | * Prompt for a new command. We'll use the main command prompt, |
| | 4323 | * because we want to pretend that we're asking for a brand new |
| | 4324 | * command, which we'll accept. However, if the player enters |
| | 4325 | * an OOPS command, we'll process it specially. |
| | 4326 | */ |
| | 4327 | getResponse: |
| | 4328 | str = readMainCommandTokens(rmcOops); |
| | 4329 | |
| | 4330 | /* re-enable the transcript, if we have one */ |
| | 4331 | if (gTranscript) |
| | 4332 | gTranscript.activate(); |
| | 4333 | |
| | 4334 | /* |
| | 4335 | * if the command reader fully processed the input via preparsing, |
| | 4336 | * we have nothing more to do here - simply throw a replace-command |
| | 4337 | * exception with the nil string |
| | 4338 | */ |
| | 4339 | if (str == nil) |
| | 4340 | throw new ReplacementCommandStringException(nil, nil, nil); |
| | 4341 | |
| | 4342 | /* extract the tokens and string from the result */ |
| | 4343 | toks = str[2]; |
| | 4344 | str = str[1]; |
| | 4345 | |
| | 4346 | /* try parsing it as an "oops" command */ |
| | 4347 | oopsMatch = oopsCommand.parseTokens(toks, cmdDict); |
| | 4348 | |
| | 4349 | /* |
| | 4350 | * if we found a match, process the OOPS command; otherwise, |
| | 4351 | * treat it as a brand new command |
| | 4352 | */ |
| | 4353 | if (oopsMatch.length() != 0) |
| | 4354 | { |
| | 4355 | local badIdx; |
| | 4356 | |
| | 4357 | /* |
| | 4358 | * if they typed in just OOPS without any tokens, show an error |
| | 4359 | * and ask again |
| | 4360 | */ |
| | 4361 | if (oopsMatch[1].getNewTokens() == nil) |
| | 4362 | { |
| | 4363 | /* tell them they need to supply the missing word */ |
| | 4364 | gLibMessages.oopsMissingWord; |
| | 4365 | |
| | 4366 | /* go back and try reading another response */ |
| | 4367 | goto getResponse; |
| | 4368 | } |
| | 4369 | |
| | 4370 | /* |
| | 4371 | * Build a new token list by removing the errant token, and |
| | 4372 | * splicing the new tokens into the original token list to |
| | 4373 | * replace the errant one. |
| | 4374 | * |
| | 4375 | * Note that we'll arbitrarily take the first "oops" match, |
| | 4376 | * even if there are several. There should be no ambiguity |
| | 4377 | * in the "oops" grammar rule, but even if there is, it |
| | 4378 | * doesn't matter, since ultimately all we care about is the |
| | 4379 | * list of tokens after the "oops". |
| | 4380 | */ |
| | 4381 | badIdx = firstTokenIndex + unknownIdx - 1; |
| | 4382 | cmdTokenList = spliceList(cmdTokenList, badIdx, |
| | 4383 | oopsMatch[1].getNewTokens()); |
| | 4384 | |
| | 4385 | /* |
| | 4386 | * Turn the token list back into a string. Since we've edited |
| | 4387 | * the original text, we want to start over with the new input, |
| | 4388 | * including running pre-parsing on the text. |
| | 4389 | */ |
| | 4390 | str = cmdTokenizer.buildOrigText(cmdTokenList); |
| | 4391 | |
| | 4392 | /* |
| | 4393 | * run it through pre-parsing as the same kind of input the |
| | 4394 | * caller was reading |
| | 4395 | */ |
| | 4396 | str = StringPreParser.runAll(str, cmdType); |
| | 4397 | |
| | 4398 | /* |
| | 4399 | * if it came back nil, it means the preparser fully handled it; |
| | 4400 | * in this case, simply throw a nil replacement command string |
| | 4401 | */ |
| | 4402 | if (str == nil) |
| | 4403 | throw new ReplacementCommandStringException(nil, nil, nil); |
| | 4404 | |
| | 4405 | /* re-tokenize the result of pre-parsing */ |
| | 4406 | cmdTokenList = cmdTokenizer.tokenize(str); |
| | 4407 | |
| | 4408 | /* retry parsing the edited token list */ |
| | 4409 | throw new RetryCommandTokensException(cmdTokenList); |
| | 4410 | } |
| | 4411 | else |
| | 4412 | { |
| | 4413 | /* |
| | 4414 | * they didn't enter something that looks like an "OOPS", so |
| | 4415 | * it must be a brand new command - parse it as a new |
| | 4416 | * command by throwing a replacement command exception with |
| | 4417 | * the new string |
| | 4418 | */ |
| | 4419 | throw new ReplacementCommandStringException(str, nil, nil); |
| | 4420 | } |
| | 4421 | } |
| | 4422 | |
| | 4423 | /* |
| | 4424 | * splice a new sublist into a list, replacing the item at 'idx' |
| | 4425 | */ |
| | 4426 | spliceList(lst, idx, newItems) |
| | 4427 | { |
| | 4428 | return (lst.sublist(1, idx - 1) |
| | 4429 | + newItems |
| | 4430 | + lst.sublist(idx + 1)); |
| | 4431 | } |
| | 4432 | |
| | 4433 | |
| | 4434 | /* ------------------------------------------------------------------------ */ |
| | 4435 | /* |
| | 4436 | * Try reading a response to a missing object question. If we |
| | 4437 | * successfully read a noun phrase that matches the given production |
| | 4438 | * rule, we'll resolve it, stash the resolved list in the |
| | 4439 | * resolvedObjects_ property of the match tree, and return the match |
| | 4440 | * tree. If they enter something that doesn't look like a response to |
| | 4441 | * the question at all, we'll throw a new-command exception to process |
| | 4442 | * it. |
| | 4443 | */ |
| | 4444 | tryAskingForObject(issuingActor, targetActor, |
| | 4445 | resolver, results, responseProd) |
| | 4446 | { |
| | 4447 | local str; |
| | 4448 | local toks; |
| | 4449 | local matchList; |
| | 4450 | local rankings; |
| | 4451 | local match; |
| | 4452 | local objList; |
| | 4453 | local ires; |
| | 4454 | |
| | 4455 | /* |
| | 4456 | * Prompt for a new command. We'll use the main command prompt, |
| | 4457 | * because we want to pretend that we're asking for a brand new |
| | 4458 | * command, which we'll accept. However, if the player enters |
| | 4459 | * something that looks like a response to the missing-object query, |
| | 4460 | * we'll handle it as an answer rather than as a new command. |
| | 4461 | */ |
| | 4462 | str = readMainCommandTokens(rmcAskObject); |
| | 4463 | |
| | 4464 | /* re-enable the transcript, if we have one */ |
| | 4465 | if (gTranscript) |
| | 4466 | gTranscript.activate(); |
| | 4467 | |
| | 4468 | /* |
| | 4469 | * if it came back nil, it means that the preparser fully processed |
| | 4470 | * the input, which means we have nothing more to do here - simply |
| | 4471 | * treat this is a nil replacement command |
| | 4472 | */ |
| | 4473 | if (str == nil) |
| | 4474 | throw new ReplacementCommandStringException(nil, nil, nil); |
| | 4475 | |
| | 4476 | /* extract the input line and tokens */ |
| | 4477 | toks = str[2]; |
| | 4478 | str = str[1]; |
| | 4479 | |
| | 4480 | /* |
| | 4481 | * If it looks like a valid new command, treat it as such. We give |
| | 4482 | * the new command interpretation priority over the noun phrase |
| | 4483 | * interpretation, because anything that looks like both a noun |
| | 4484 | * phrase and a new command probably only looks like a noun phrase |
| | 4485 | * because it's extremely abbreviated; for example, "e" looks like |
| | 4486 | * the noun phrase "east wall," in that "e" is a synonym for the |
| | 4487 | * adjective "east". It's very easy and intuitive for the user to |
| | 4488 | * make a noun phrase reply unambiguous: they merely need to add a |
| | 4489 | * "the" at the front of the phrase ("the east wall") or spell out |
| | 4490 | * the noun phrase more fully. |
| | 4491 | */ |
| | 4492 | matchList = firstCommandPhrase.parseTokens(toks, cmdDict); |
| | 4493 | if (matchList.length() != 0) |
| | 4494 | { |
| | 4495 | /* |
| | 4496 | * it looks like a syntactically valid new command, so treat it |
| | 4497 | * as a new command |
| | 4498 | */ |
| | 4499 | throw new ReplacementCommandStringException(str, nil, nil); |
| | 4500 | } |
| | 4501 | |
| | 4502 | /* keep going as long as we get replacement token lists */ |
| | 4503 | for (;;) |
| | 4504 | { |
| | 4505 | /* try parsing it as an object list */ |
| | 4506 | matchList = responseProd.parseTokens(toks, cmdDict); |
| | 4507 | |
| | 4508 | /* |
| | 4509 | * if we didn't find any match at all, it's probably a brand new |
| | 4510 | * command - go process it as a replacement for the current |
| | 4511 | * command |
| | 4512 | */ |
| | 4513 | if (matchList == []) |
| | 4514 | { |
| | 4515 | /* |
| | 4516 | * they didn't enter something that looks like a valid |
| | 4517 | * response, so assume it's a brand new command - parse it |
| | 4518 | * as a new command by throwing a replacement command |
| | 4519 | * exception with the new string |
| | 4520 | */ |
| | 4521 | throw new ReplacementCommandStringException(str, nil, nil); |
| | 4522 | } |
| | 4523 | |
| | 4524 | /* if we're in debug mode, show the interpretations */ |
| | 4525 | dbgShowGrammarList(matchList); |
| | 4526 | |
| | 4527 | /* create an interactive sub-resolver for resolving the response */ |
| | 4528 | ires = new InteractiveResolver(resolver); |
| | 4529 | |
| | 4530 | /* |
| | 4531 | * rank them using our response ranker - use the original |
| | 4532 | * resolver to resolve the object list |
| | 4533 | */ |
| | 4534 | rankings = MissingObjectRanking.sortByRanking(matchList, ires); |
| | 4535 | |
| | 4536 | /* |
| | 4537 | * If the best item has unknown words, try letting the user |
| | 4538 | * correct typos with OOPS. |
| | 4539 | */ |
| | 4540 | if (rankings[1].nonMatchCount != 0 |
| | 4541 | && rankings[1].unknownWordCount != 0) |
| | 4542 | { |
| | 4543 | try |
| | 4544 | { |
| | 4545 | /* |
| | 4546 | * complain about the unknown word and look for an OOPS |
| | 4547 | * reply |
| | 4548 | */ |
| | 4549 | tryOops(toks, issuingActor, targetActor, |
| | 4550 | 1, toks, rmcAskObject); |
| | 4551 | } |
| | 4552 | catch (RetryCommandTokensException exc) |
| | 4553 | { |
| | 4554 | /* get the new token list */ |
| | 4555 | toks = exc.newTokens_; |
| | 4556 | |
| | 4557 | /* replace the string as well */ |
| | 4558 | str = cmdTokenizer.buildOrigText(toks); |
| | 4559 | |
| | 4560 | /* go back for another try at parsing the response */ |
| | 4561 | continue; |
| | 4562 | } |
| | 4563 | } |
| | 4564 | |
| | 4565 | /* |
| | 4566 | * If the best item we could find has no matches, check to see |
| | 4567 | * if it has miscellaneous noun phrases - if so, it's probably |
| | 4568 | * just a new command, since it doesn't have anything we |
| | 4569 | * recognize as a noun phrase. |
| | 4570 | */ |
| | 4571 | if (rankings[1].nonMatchCount != 0 |
| | 4572 | && rankings[1].miscWordListCount != 0) |
| | 4573 | { |
| | 4574 | /* |
| | 4575 | * it's probably not an answer at all - treat it as a new |
| | 4576 | * command |
| | 4577 | */ |
| | 4578 | throw new ReplacementCommandStringException(str, nil, nil); |
| | 4579 | } |
| | 4580 | |
| | 4581 | /* the highest ranked object is the winner */ |
| | 4582 | match = rankings[1].match; |
| | 4583 | |
| | 4584 | /* show our winning interpretation */ |
| | 4585 | dbgShowGrammarWithCaption('Missing Object Winner', match); |
| | 4586 | |
| | 4587 | /* |
| | 4588 | * actually resolve the response to objects, using the original |
| | 4589 | * results and resolver objects |
| | 4590 | */ |
| | 4591 | objList = match.resolveNouns(ires, results); |
| | 4592 | |
| | 4593 | /* stash the resolved object list in a property of the match tree */ |
| | 4594 | match.resolvedObjects = objList; |
| | 4595 | |
| | 4596 | /* return the match tree */ |
| | 4597 | return match; |
| | 4598 | } |
| | 4599 | } |
| | 4600 | |
| | 4601 | /* |
| | 4602 | * a property we use to hold the resolved objects of a match tree in |
| | 4603 | * tryAskingForObject |
| | 4604 | */ |
| | 4605 | property resolvedObjects; |
| | 4606 | |
| | 4607 | /* |
| | 4608 | * Missing-object response ranker. |
| | 4609 | */ |
| | 4610 | class MissingObjectRanking: CommandRanking |
| | 4611 | /* |
| | 4612 | * missing-object responses have no verb, so they won't count any |
| | 4613 | * noun slots; we just need to give these an arbitrary value so that |
| | 4614 | * we can compare them (and find them equal) |
| | 4615 | */ |
| | 4616 | nounSlotCount = 0 |
| | 4617 | ; |
| | 4618 | |
| | 4619 | /* ------------------------------------------------------------------------ */ |
| | 4620 | /* |
| | 4621 | * An implementation of ResolveResults for normal noun resolution. |
| | 4622 | */ |
| | 4623 | class BasicResolveResults: ResolveResults |
| | 4624 | /* |
| | 4625 | * The target and issuing actors for the command being resolved. |
| | 4626 | */ |
| | 4627 | targetActor_ = nil |
| | 4628 | issuingActor_ = nil |
| | 4629 | |
| | 4630 | /* set up the actor parameters */ |
| | 4631 | setActors(target, issuer) |
| | 4632 | { |
| | 4633 | targetActor_ = target; |
| | 4634 | issuingActor_ = issuer; |
| | 4635 | } |
| | 4636 | |
| | 4637 | /* |
| | 4638 | * Results gatherer methods |
| | 4639 | */ |
| | 4640 | |
| | 4641 | noVocabMatch(action, txt) |
| | 4642 | { |
| | 4643 | /* indicate that we couldn't match the phrase */ |
| | 4644 | throw new ParseFailureException(&noMatch, |
| | 4645 | action, txt.toLower().htmlify()); |
| | 4646 | } |
| | 4647 | |
| | 4648 | noMatch(action, txt) |
| | 4649 | { |
| | 4650 | /* show an error */ |
| | 4651 | throw new ParseFailureException(&noMatch, |
| | 4652 | action, txt.toLower().htmlify()); |
| | 4653 | } |
| | 4654 | |
| | 4655 | allNotAllowed() |
| | 4656 | { |
| | 4657 | /* show an error */ |
| | 4658 | throw new ParseFailureException(&allNotAllowed); |
| | 4659 | } |
| | 4660 | |
| | 4661 | noMatchForAll() |
| | 4662 | { |
| | 4663 | /* show an error */ |
| | 4664 | throw new ParseFailureException(&noMatchForAll); |
| | 4665 | } |
| | 4666 | |
| | 4667 | noteEmptyBut() |
| | 4668 | { |
| | 4669 | /* this isn't an error - ignore it */ |
| | 4670 | } |
| | 4671 | |
| | 4672 | noMatchForAllBut() |
| | 4673 | { |
| | 4674 | /* show an error */ |
| | 4675 | throw new ParseFailureException(&noMatchForAllBut); |
| | 4676 | } |
| | 4677 | |
| | 4678 | noMatchForListBut() |
| | 4679 | { |
| | 4680 | /* show an error */ |
| | 4681 | throw new ParseFailureException(&noMatchForListBut); |
| | 4682 | } |
| | 4683 | |
| | 4684 | noMatchForPronoun(typ, txt) |
| | 4685 | { |
| | 4686 | /* show an error */ |
| | 4687 | throw new ParseFailureException(&noMatchForPronoun, |
| | 4688 | typ, txt.toLower().htmlify()); |
| | 4689 | } |
| | 4690 | |
| | 4691 | reflexiveNotAllowed(typ, txt) |
| | 4692 | { |
| | 4693 | /* show an error */ |
| | 4694 | throw new ParseFailureException(&reflexiveNotAllowed, |
| | 4695 | typ, txt.toLower().htmlify()); |
| | 4696 | } |
| | 4697 | |
| | 4698 | wrongReflexive(typ, txt) |
| | 4699 | { |
| | 4700 | /* show an error */ |
| | 4701 | throw new ParseFailureException(&wrongReflexive, |
| | 4702 | typ, txt.toLower().htmlify()); |
| | 4703 | } |
| | 4704 | |
| | 4705 | noMatchForPossessive(owner, txt) |
| | 4706 | { |
| | 4707 | /* if the owner is a list, it's a plural owner */ |
| | 4708 | if (owner.length() > 1) |
| | 4709 | { |
| | 4710 | /* it's a plural owner */ |
| | 4711 | throw new ParseFailureException(&noMatchForPluralPossessive, txt); |
| | 4712 | } |
| | 4713 | else |
| | 4714 | { |
| | 4715 | /* let the (singular) owner object generate the error */ |
| | 4716 | owner[1].throwNoMatchForPossessive(txt.toLower().htmlify()); |
| | 4717 | } |
| | 4718 | } |
| | 4719 | |
| | 4720 | noMatchForLocation(loc, txt) |
| | 4721 | { |
| | 4722 | /* let the location object generate the error */ |
| | 4723 | loc.throwNoMatchForLocation(txt.toLower().htmlify()); |
| | 4724 | } |
| | 4725 | |
| | 4726 | nothingInLocation(loc) |
| | 4727 | { |
| | 4728 | /* let the location object generate the error */ |
| | 4729 | loc.throwNothingInLocation(); |
| | 4730 | } |
| | 4731 | |
| | 4732 | noteBadPrep() |
| | 4733 | { |
| | 4734 | /* |
| | 4735 | * bad prepositional phrase structure - assume that the |
| | 4736 | * preposition was intended as part of the verb phrase, so |
| | 4737 | * indicate that the entire command is not understood |
| | 4738 | */ |
| | 4739 | throw new ParseFailureException(&commandNotUnderstood); |
| | 4740 | } |
| | 4741 | |
| | 4742 | /* |
| | 4743 | * Service routine - determine if we can interactively resolve a |
| | 4744 | * need for more information. If the issuer is the player, and the |
| | 4745 | * target actor can talk to the player, then we can resolve a |
| | 4746 | * question interactively; otherwise, we cannot. |
| | 4747 | * |
| | 4748 | * We can't interactively resolve a question if the issuer isn't the |
| | 4749 | * player, because there's no interactive user to prompt for more |
| | 4750 | * information. |
| | 4751 | * |
| | 4752 | * We can't interactively resolve a question if the target actor |
| | 4753 | * can't talk to the player, because the question to the player |
| | 4754 | * would be coming out of thin air. |
| | 4755 | */ |
| | 4756 | canResolveInteractively(action) |
| | 4757 | { |
| | 4758 | /* |
| | 4759 | * If this is an implied action, and the target actor is in |
| | 4760 | * "NPC" mode, we can't resolve interactively. Note that if |
| | 4761 | * there's no action, it can't be implicit (we won't have an |
| | 4762 | * action if we're resolving the actor to whom the action is |
| | 4763 | * targeted). |
| | 4764 | */ |
| | 4765 | if (action != nil |
| | 4766 | && action.isImplicit |
| | 4767 | && targetActor_.impliedCommandMode() == ModeNPC) |
| | 4768 | return nil; |
| | 4769 | |
| | 4770 | /* |
| | 4771 | * if the issuer is the player, and either the target is the |
| | 4772 | * player or the target can talk to the player, we can resolve |
| | 4773 | * interactively |
| | 4774 | */ |
| | 4775 | return (issuingActor_.isPlayerChar() |
| | 4776 | && (targetActor_ == issuingActor_ |
| | 4777 | || targetActor_.canTalkTo(issuingActor_))); |
| | 4778 | } |
| | 4779 | |
| | 4780 | /* |
| | 4781 | * Handle an ambiguous noun phrase. We'll first check to see if we |
| | 4782 | * can find a Distinguisher that can tell at least some of the |
| | 4783 | * matches apart; if we fail to do that, we'll just pick the required |
| | 4784 | * number of objects arbitrarily, since we have no way to distinguish |
| | 4785 | * any of them. Once we've chosen a Distinguisher, we'll ask the |
| | 4786 | * user for help interactively if possible. |
| | 4787 | */ |
| | 4788 | ambiguousNounPhrase(keeper, asker, txt, |
| | 4789 | matchList, fullMatchList, scopeList, |
| | 4790 | requiredNum, resolver) |
| | 4791 | { |
| | 4792 | local stillToResolve; |
| | 4793 | local resultList; |
| | 4794 | local disambigResults; |
| | 4795 | local pastResponses; |
| | 4796 | local pastIdx; |
| | 4797 | local everAsked; |
| | 4798 | local askingAgain; |
| | 4799 | local promptTxt; |
| | 4800 | |
| | 4801 | /* put the match list in disambigPromptOrder order */ |
| | 4802 | matchList = matchList.sort( |
| | 4803 | SortAsc, {a, b: (a.obj_.disambigPromptOrder |
| | 4804 | - b.obj_.disambigPromptOrder) }); |
| | 4805 | |
| | 4806 | /* for the prompt, use the lower-cased version of the input text */ |
| | 4807 | promptTxt = txt.toLower().htmlify(); |
| | 4808 | |
| | 4809 | /* ask the response keeper for its list of past responses, if any */ |
| | 4810 | pastResponses = keeper.getAmbigResponses(); |
| | 4811 | |
| | 4812 | /* |
| | 4813 | * set up a results object - use the special disambiguation |
| | 4814 | * results object instead of the basic resolver type |
| | 4815 | */ |
| | 4816 | disambigResults = new DisambigResults(self); |
| | 4817 | |
| | 4818 | /* |
| | 4819 | * start out with an empty still-to-resolve list - we have only |
| | 4820 | * the one list to resolve so far |
| | 4821 | */ |
| | 4822 | stillToResolve = []; |
| | 4823 | |
| | 4824 | /* we have nothing in the result list yet */ |
| | 4825 | resultList = []; |
| | 4826 | |
| | 4827 | /* determine if we can ask for clarification interactively */ |
| | 4828 | if (!canResolveInteractively(resolver.getAction())) |
| | 4829 | { |
| | 4830 | /* |
| | 4831 | * don't attempt to resolve this interactively - just treat |
| | 4832 | * it as a resolution failure |
| | 4833 | */ |
| | 4834 | throw new ParseFailureException( |
| | 4835 | &ambiguousNounPhrase, txt.htmlify(), |
| | 4836 | matchList, fullMatchList); |
| | 4837 | } |
| | 4838 | |
| | 4839 | /* we're asking for the first time */ |
| | 4840 | everAsked = nil; |
| | 4841 | askingAgain = nil; |
| | 4842 | |
| | 4843 | /* |
| | 4844 | * Keep going until we run out of things left to resolve. Each |
| | 4845 | * time an answer looks valid but doesn't completely |
| | 4846 | * disambiguate the results, we'll queue it for another question |
| | 4847 | * iteration, so we must continue until we run out of queued |
| | 4848 | * questions. |
| | 4849 | */ |
| | 4850 | queryLoop: |
| | 4851 | for (pastIdx = 1 ;; ) |
| | 4852 | { |
| | 4853 | local str; |
| | 4854 | local toks; |
| | 4855 | local prodList; |
| | 4856 | local rankings; |
| | 4857 | local disResolver; |
| | 4858 | local scopeDisResolver; |
| | 4859 | local respList; |
| | 4860 | local dist; |
| | 4861 | local curMatchList; |
| | 4862 | |
| | 4863 | /* |
| | 4864 | * Find the first distinguisher that can tell one or more of |
| | 4865 | * these objects apart. Work through the distinguishers in |
| | 4866 | * priority order, which is the order they appear in an |
| | 4867 | * object's 'distinguishers' property. |
| | 4868 | * |
| | 4869 | * If these objects aren't all equivalents, then we'll |
| | 4870 | * immediately find that the basic distinguisher can tell |
| | 4871 | * them all apart. Every object's first distinguisher is |
| | 4872 | * the basic distinguisher. If these objects are all |
| | 4873 | * equivalents, then they'll all have the same set of |
| | 4874 | * distinguishers. In either case, we don't have to worry |
| | 4875 | * about what set of objects we have, because we're |
| | 4876 | * guaranteed to have a suitable set of distinguishers in |
| | 4877 | * common - so just arbitrarily pick an object and work |
| | 4878 | * through its distinguishers. |
| | 4879 | */ |
| | 4880 | foreach (dist in matchList[1].obj_.distinguishers) |
| | 4881 | { |
| | 4882 | /* filter the match list using this distinguisher only */ |
| | 4883 | curMatchList = filterWithDistinguisher(matchList, dist); |
| | 4884 | |
| | 4885 | /* |
| | 4886 | * if this list has more than the required number of |
| | 4887 | * results, then this distinguisher is capable of |
| | 4888 | * telling apart enough of these objects, so use this |
| | 4889 | * distinguisher for now |
| | 4890 | */ |
| | 4891 | if (curMatchList.length() > requiredNum) |
| | 4892 | break; |
| | 4893 | } |
| | 4894 | |
| | 4895 | /* |
| | 4896 | * If we didn't find any distinguishers that could tell |
| | 4897 | * apart enough of the objects, then we've exhausted our |
| | 4898 | * ability to choose among these objects, so return an |
| | 4899 | * arbitrary set of the desired size. |
| | 4900 | */ |
| | 4901 | if (curMatchList.length() <= requiredNum) |
| | 4902 | return fullMatchList.sublist(1, requiredNum); |
| | 4903 | |
| | 4904 | /* |
| | 4905 | * If we have past responses, try reusing the past responses |
| | 4906 | * before asking for new responses. |
| | 4907 | */ |
| | 4908 | if (pastIdx <= pastResponses.length()) |
| | 4909 | { |
| | 4910 | /* we have another past response - use the next one */ |
| | 4911 | str = pastResponses[pastIdx++]; |
| | 4912 | |
| | 4913 | /* tokenize the new command */ |
| | 4914 | toks = cmdTokenizer.tokenize(str); |
| | 4915 | } |
| | 4916 | else |
| | 4917 | { |
| | 4918 | local basicDistList; |
| | 4919 | |
| | 4920 | /* |
| | 4921 | * Try filtering the options with the basic |
| | 4922 | * distinguisher, to see if all of the remaining options |
| | 4923 | * are basic equivalents - if they are, then we can |
| | 4924 | * refer to them by the object name, since every one has |
| | 4925 | * the same object name. |
| | 4926 | */ |
| | 4927 | basicDistList = filterWithDistinguisher( |
| | 4928 | matchList, basicDistinguisher); |
| | 4929 | |
| | 4930 | /* |
| | 4931 | * if we filtered to one object, everything remaining is |
| | 4932 | * a basic equivalent of that one object, so they all |
| | 4933 | * have the same name, so we can use that name |
| | 4934 | */ |
| | 4935 | if (basicDistList.length() == 1) |
| | 4936 | promptTxt = basicDistList[1].obj_.disambigEquivName; |
| | 4937 | |
| | 4938 | /* |
| | 4939 | * let the distinguisher know we're prompting for more |
| | 4940 | * information based on this distinguisher |
| | 4941 | */ |
| | 4942 | dist.notePrompt(curMatchList); |
| | 4943 | |
| | 4944 | /* |
| | 4945 | * We don't have any special insight into which object |
| | 4946 | * the user might have intended, and we have no past |
| | 4947 | * responses to re-use, so simply ask for clarification. |
| | 4948 | * Ask using the full match list, so that we correctly |
| | 4949 | * indicate where there are multiple equivalents. |
| | 4950 | */ |
| | 4951 | asker.askDisambig(targetActor_, promptTxt, |
| | 4952 | curMatchList, fullMatchList, requiredNum, |
| | 4953 | everAsked && askingAgain, dist); |
| | 4954 | |
| | 4955 | /* ask for a new command */ |
| | 4956 | str = readMainCommandTokens(rmcDisambig); |
| | 4957 | |
| | 4958 | /* re-enable the transcript, if we have one */ |
| | 4959 | if (gTranscript) |
| | 4960 | gTranscript.activate(); |
| | 4961 | |
| | 4962 | /* note that we've asked for input interactively */ |
| | 4963 | everAsked = true; |
| | 4964 | |
| | 4965 | /* |
| | 4966 | * if it came back nil, the input was fully processed by |
| | 4967 | * the preparser, so throw a nil replacement string |
| | 4968 | * exception |
| | 4969 | */ |
| | 4970 | if (str == nil) |
| | 4971 | throw new ReplacementCommandStringException( |
| | 4972 | nil, nil, nil); |
| | 4973 | |
| | 4974 | /* extract the tokens and the string result */ |
| | 4975 | toks = str[2]; |
| | 4976 | str = str[1]; |
| | 4977 | } |
| | 4978 | |
| | 4979 | /* presume we won't have to ask again */ |
| | 4980 | askingAgain = nil; |
| | 4981 | |
| | 4982 | retryParse: |
| | 4983 | /* |
| | 4984 | * Check for narrowing syntax. If the command doesn't |
| | 4985 | * appear to match a narrowing syntax, then treat it as an |
| | 4986 | * entirely new command. |
| | 4987 | */ |
| | 4988 | prodList = mainDisambigPhrase.parseTokens(toks, cmdDict); |
| | 4989 | |
| | 4990 | /* |
| | 4991 | * if we didn't get any structural matches for a |
| | 4992 | * disambiguation response, it must be an entirely new |
| | 4993 | * command |
| | 4994 | */ |
| | 4995 | if (prodList == []) |
| | 4996 | throw new ReplacementCommandStringException(str, nil, nil); |
| | 4997 | |
| | 4998 | /* if we're in debug mode, show the interpretations */ |
| | 4999 | dbgShowGrammarList(prodList); |
| | 5000 | |
| | 5001 | /* create a disambiguation response resolver */ |
| | 5002 | disResolver = new DisambigResolver(txt, matchList, fullMatchList, |
| | 5003 | fullMatchList, resolver, dist); |
| | 5004 | |
| | 5005 | /* |
| | 5006 | * create a disambiguation resolver that uses the full scope |
| | 5007 | * list - we'll use this as a fallback if we can't match any |
| | 5008 | * objects with the narrowed possibility list |
| | 5009 | */ |
| | 5010 | scopeDisResolver = |
| | 5011 | new DisambigResolver(txt, matchList, fullMatchList, |
| | 5012 | scopeList, resolver, dist); |
| | 5013 | |
| | 5014 | /* |
| | 5015 | * Run the alternatives through the disambiguation response |
| | 5016 | * ranking process. |
| | 5017 | */ |
| | 5018 | rankings = |
| | 5019 | DisambigRanking.sortByRanking(prodList, disResolver); |
| | 5020 | |
| | 5021 | /* |
| | 5022 | * If the best item has unknown words, try letting the user |
| | 5023 | * correct typos with OOPS. |
| | 5024 | */ |
| | 5025 | if (rankings[1].nonMatchCount != 0 |
| | 5026 | && rankings[1].unknownWordCount != 0) |
| | 5027 | { |
| | 5028 | try |
| | 5029 | { |
| | 5030 | /* |
| | 5031 | * complain about the unknown word and look for an |
| | 5032 | * OOPS reply |
| | 5033 | */ |
| | 5034 | tryOops(toks, issuingActor_, targetActor_, |
| | 5035 | 1, toks, rmcDisambig); |
| | 5036 | } |
| | 5037 | catch (RetryCommandTokensException exc) |
| | 5038 | { |
| | 5039 | /* get the new token list */ |
| | 5040 | toks = exc.newTokens_; |
| | 5041 | |
| | 5042 | /* replace the string as well */ |
| | 5043 | str = cmdTokenizer.buildOrigText(toks); |
| | 5044 | |
| | 5045 | /* go back for another try at parsing the response */ |
| | 5046 | goto retryParse; |
| | 5047 | } |
| | 5048 | } |
| | 5049 | |
| | 5050 | /* |
| | 5051 | * If the best item we could find has no matches, check to |
| | 5052 | * see if it has miscellaneous noun phrases - if so, it's |
| | 5053 | * probably just a new command, since it doesn't have |
| | 5054 | * anything we recognize as a noun phrase. |
| | 5055 | */ |
| | 5056 | if (rankings[1].nonMatchCount != 0 |
| | 5057 | && rankings[1].miscWordListCount != 0) |
| | 5058 | { |
| | 5059 | /* |
| | 5060 | * it's probably not an answer to our disambiguation |
| | 5061 | * question, so treat it as a whole new command - |
| | 5062 | * abandon the current command and start over with the |
| | 5063 | * new string |
| | 5064 | */ |
| | 5065 | throw new ReplacementCommandStringException(str, nil, nil); |
| | 5066 | } |
| | 5067 | |
| | 5068 | /* if we're in debug mode, show the winning intepretation */ |
| | 5069 | dbgShowGrammarWithCaption('Disambig Winner', rankings[1].match); |
| | 5070 | |
| | 5071 | /* get the response list */ |
| | 5072 | respList = rankings[1].match.getResponseList(); |
| | 5073 | |
| | 5074 | /* |
| | 5075 | * Select the objects for each response in our winning list. |
| | 5076 | * The user can select more than one of the objects we |
| | 5077 | * offered, so simply take each one they specify here |
| | 5078 | * separately. |
| | 5079 | */ |
| | 5080 | foreach (local resp in respList) |
| | 5081 | { |
| | 5082 | try |
| | 5083 | { |
| | 5084 | try |
| | 5085 | { |
| | 5086 | /* |
| | 5087 | * select the objects for this match, and add |
| | 5088 | * them into the matches so far |
| | 5089 | */ |
| | 5090 | resultList += |
| | 5091 | resp.resolveNouns(disResolver, disambigResults); |
| | 5092 | } |
| | 5093 | catch (UnmatchedDisambigException udExc) |
| | 5094 | { |
| | 5095 | /* |
| | 5096 | * The response didn't match anything in the |
| | 5097 | * narrowed list. Try again with the full scope |
| | 5098 | * list, in case they actually wanted to apply |
| | 5099 | * the command to something that's in scope but |
| | 5100 | * which didn't make the cut for the narrowed |
| | 5101 | * list we originally offered. |
| | 5102 | */ |
| | 5103 | resultList += |
| | 5104 | resp.resolveNouns(scopeDisResolver, |
| | 5105 | disambigResults); |
| | 5106 | } |
| | 5107 | } |
| | 5108 | catch (StillAmbiguousException saExc) |
| | 5109 | { |
| | 5110 | local newList; |
| | 5111 | local newFullList; |
| | 5112 | |
| | 5113 | /* |
| | 5114 | * Get the new "reduced" list from the exception. |
| | 5115 | * Use our original full list, and reduce it to |
| | 5116 | * include only the elements in the reduced list - |
| | 5117 | * this will ensure that the items in the new list |
| | 5118 | * are in the same order as they were in the |
| | 5119 | * original list, which keeps the next iteration's |
| | 5120 | * question in the same order. |
| | 5121 | */ |
| | 5122 | newList = new Vector(saExc.matchList_.length()); |
| | 5123 | foreach (local cur in fullMatchList) |
| | 5124 | { |
| | 5125 | /* |
| | 5126 | * If this item from the original list has an |
| | 5127 | * equivalent in the reduced list, include it in |
| | 5128 | * the new match list. |
| | 5129 | */ |
| | 5130 | if (cur.isDistEquivInList(saExc.matchList_, dist)) |
| | 5131 | newList.append(cur); |
| | 5132 | } |
| | 5133 | |
| | 5134 | /* convert it to a list */ |
| | 5135 | newList = newList.toList(); |
| | 5136 | |
| | 5137 | /* |
| | 5138 | * If that left us with nothing, just keep the |
| | 5139 | * original list unchanged. This can occasionally |
| | 5140 | * happen, such as when a sub-phrase (a locational |
| | 5141 | * qualifier, for example) is itself ambiguous. |
| | 5142 | */ |
| | 5143 | if (newList == []) |
| | 5144 | newList = matchList; |
| | 5145 | |
| | 5146 | /* |
| | 5147 | * Generate the new "full" list. This is a list of |
| | 5148 | * all of the items from our current "full" list |
| | 5149 | * that either are directly in the new match list, |
| | 5150 | * or are indistinguishable from items in the new |
| | 5151 | * reduced list. |
| | 5152 | * |
| | 5153 | * The exception thrower is not capable of providing |
| | 5154 | * us with a new full list, because it only knows |
| | 5155 | * about the reduced list, as the reduced list is |
| | 5156 | * its scope for narrowing the results. |
| | 5157 | */ |
| | 5158 | newFullList = new Vector(fullMatchList.length()); |
| | 5159 | foreach (local cur in fullMatchList) |
| | 5160 | { |
| | 5161 | /* |
| | 5162 | * if this item is in the new reduced list, or |
| | 5163 | * has an equivalent in the new match list, |
| | 5164 | * include it in the new full list |
| | 5165 | */ |
| | 5166 | if (cur.isDistEquivInList(newList, dist)) |
| | 5167 | { |
| | 5168 | /* |
| | 5169 | * we have this item or something |
| | 5170 | * indistinguishable - include it in the |
| | 5171 | * revised full match list |
| | 5172 | */ |
| | 5173 | newFullList.append(cur); |
| | 5174 | } |
| | 5175 | } |
| | 5176 | |
| | 5177 | /* convert it to a list */ |
| | 5178 | newFullList = newFullList.toList(); |
| | 5179 | |
| | 5180 | /* |
| | 5181 | * They answered, but with insufficient specificity. |
| | 5182 | * Add the given list to the still-to-resolve list, |
| | 5183 | * so that we try again with this new list. |
| | 5184 | */ |
| | 5185 | stillToResolve += |
| | 5186 | new StillToResolveItem(newList, newFullList, |
| | 5187 | saExc.origText_); |
| | 5188 | } |
| | 5189 | catch (DisambigOrdinalOutOfRangeException oorExc) |
| | 5190 | { |
| | 5191 | /* |
| | 5192 | * Explain the problem (note that if we didn't want |
| | 5193 | * to offer another chance here, we could simply |
| | 5194 | * throw this message as a ParseFailureException |
| | 5195 | * instead of continuing with the query loop). |
| | 5196 | * |
| | 5197 | * If we've never asked interactively for input |
| | 5198 | * (because we've obtained all of our input from |
| | 5199 | * past responses to a command we're repeating), |
| | 5200 | * don't show this message, because it makes no |
| | 5201 | * sense when they haven't answered any questions in |
| | 5202 | * the first place. |
| | 5203 | */ |
| | 5204 | if (everAsked) |
| | 5205 | targetActor_.getParserMessageObj(). |
| | 5206 | disambigOrdinalOutOfRange( |
| | 5207 | targetActor_, oorExc.ord_, txt.htmlify()); |
| | 5208 | |
| | 5209 | /* go back to the outer loop to ask for a new response */ |
| | 5210 | askingAgain = true; |
| | 5211 | continue queryLoop; |
| | 5212 | } |
| | 5213 | catch (UnmatchedDisambigException udExc) |
| | 5214 | { |
| | 5215 | local newList; |
| | 5216 | |
| | 5217 | /* |
| | 5218 | * They entered something that looked like a |
| | 5219 | * disambiguation response, but didn't refer to any |
| | 5220 | * of our objects. Try parsing the input as though |
| | 5221 | * it were a new command, and if it matches any |
| | 5222 | * command syntax, treat it as a new command. |
| | 5223 | */ |
| | 5224 | newList = firstCommandPhrase.parseTokens(toks, cmdDict); |
| | 5225 | if (newList.length() != 0) |
| | 5226 | { |
| | 5227 | /* |
| | 5228 | * it appears syntactically to be a new command |
| | 5229 | * - treat it as such by throwing a command |
| | 5230 | * replacement exception |
| | 5231 | */ |
| | 5232 | throw new ReplacementCommandStringException( |
| | 5233 | str, nil, nil); |
| | 5234 | } |
| | 5235 | |
| | 5236 | /* |
| | 5237 | * Explain the problem (note that if we didn't want |
| | 5238 | * to continue with the query loop, we could simply |
| | 5239 | * throw this message as a ParseFailureException). |
| | 5240 | * |
| | 5241 | * Don't show any error if we've never asked |
| | 5242 | * interactively on this turn (which can only be |
| | 5243 | * because we're using interactive responses from a |
| | 5244 | * previous turn that we're repeating), because it |
| | 5245 | * makes no sense when we haven't apparently asked |
| | 5246 | * for anything yet. |
| | 5247 | */ |
| | 5248 | if (everAsked) |
| | 5249 | targetActor_.getParserMessageObj(). |
| | 5250 | noMatchDisambig(targetActor_, txt.htmlify(), |
| | 5251 | udExc.resp_); |
| | 5252 | |
| | 5253 | /* go back to the outer loop to ask for a new response */ |
| | 5254 | askingAgain = true; |
| | 5255 | continue queryLoop; |
| | 5256 | } |
| | 5257 | } |
| | 5258 | |
| | 5259 | /* |
| | 5260 | * If we got this far, the last input we parsed was actually |
| | 5261 | * a response to our question, as opposed to a new command |
| | 5262 | * or something unintelligible. |
| | 5263 | * |
| | 5264 | * If this was an interactive response, add the response to |
| | 5265 | * the response keeper's list of past responses. This will |
| | 5266 | * allow the production to re-use the same list if it has to |
| | 5267 | * re-resolve the phrase in the future, without asking the |
| | 5268 | * user to answer the same questions again |
| | 5269 | */ |
| | 5270 | if (everAsked) |
| | 5271 | keeper.addAmbigResponse(str); |
| | 5272 | |
| | 5273 | /* |
| | 5274 | * if there's nothing left in the still-to-resolve list, we |
| | 5275 | * have nothing left to do, so we can stop working |
| | 5276 | */ |
| | 5277 | if (stillToResolve.length() == 0) |
| | 5278 | break; |
| | 5279 | |
| | 5280 | /* |
| | 5281 | * set up for the next iteration with the first item in the |
| | 5282 | * still-to-resolve list |
| | 5283 | */ |
| | 5284 | matchList = stillToResolve[1].matchList; |
| | 5285 | fullMatchList = stillToResolve[1].fullMatchList; |
| | 5286 | txt = stillToResolve[1].origText; |
| | 5287 | |
| | 5288 | /* remove the first item, since we're going to process it now */ |
| | 5289 | stillToResolve = stillToResolve.sublist(2); |
| | 5290 | } |
| | 5291 | |
| | 5292 | /* success - return the final match list */ |
| | 5293 | return resultList; |
| | 5294 | } |
| | 5295 | |
| | 5296 | /* |
| | 5297 | * filter a match list with a specific Distinguisher |
| | 5298 | */ |
| | 5299 | filterWithDistinguisher(lst, dist) |
| | 5300 | { |
| | 5301 | local result; |
| | 5302 | |
| | 5303 | /* create a vector for the result list */ |
| | 5304 | result = new Vector(lst.length()); |
| | 5305 | |
| | 5306 | /* scan the list */ |
| | 5307 | foreach (local cur in lst) |
| | 5308 | { |
| | 5309 | /* |
| | 5310 | * if we have no equivalent of the current item (for the |
| | 5311 | * purposes of our Distinguisher) in the result list, add |
| | 5312 | * the current item to the result list |
| | 5313 | */ |
| | 5314 | if (!cur.isDistEquivInList(result, dist)) |
| | 5315 | result.append(cur); |
| | 5316 | } |
| | 5317 | |
| | 5318 | /* return the result list */ |
| | 5319 | return result.toList(); |
| | 5320 | } |
| | 5321 | |
| | 5322 | /* |
| | 5323 | * handle a noun phrase that doesn't match any legal grammar rules |
| | 5324 | * for noun phrases |
| | 5325 | */ |
| | 5326 | unknownNounPhrase(match, resolver) |
| | 5327 | { |
| | 5328 | local wordList; |
| | 5329 | local ret; |
| | 5330 | |
| | 5331 | /* |
| | 5332 | * ask the resolver to handle it - if it gives us a resolved |
| | 5333 | * object list, simply return it |
| | 5334 | */ |
| | 5335 | wordList = match.getOrigTokenList(); |
| | 5336 | if ((ret = resolver.resolveUnknownNounPhrase(wordList)) != nil) |
| | 5337 | return ret; |
| | 5338 | |
| | 5339 | /* |
| | 5340 | * The resolver doesn't handle unknown words, so we can't |
| | 5341 | * resolve the phrase. Look for an undefined word and give the |
| | 5342 | * player a chance to correct typos with OOPS. |
| | 5343 | */ |
| | 5344 | tryOops(wordList, issuingActor_, targetActor_, |
| | 5345 | match.firstTokenIndex, match.tokenList, rmcCommand); |
| | 5346 | |
| | 5347 | /* |
| | 5348 | * If we didn't find any unknown words, it means that they used |
| | 5349 | * a word that's in the dictionary in a way that makes no sense |
| | 5350 | * to us. Simply return an empty list and let the resolver |
| | 5351 | * proceed with its normal handling for unmatched noun phrases. |
| | 5352 | */ |
| | 5353 | return []; |
| | 5354 | } |
| | 5355 | |
| | 5356 | getImpliedObject(np, resolver) |
| | 5357 | { |
| | 5358 | /* ask the resolver to supply an implied default object */ |
| | 5359 | return resolver.getDefaultObject(np); |
| | 5360 | } |
| | 5361 | |
| | 5362 | askMissingObject(asker, resolver, responseProd) |
| | 5363 | { |
| | 5364 | /* if we can't resolve this interactively, fail with an error */ |
| | 5365 | if (!canResolveInteractively(resolver.getAction())) |
| | 5366 | { |
| | 5367 | /* interactive resolution isn't allowed - fail */ |
| | 5368 | throw new ParseFailureException(&missingObject, |
| | 5369 | resolver.getAction(), |
| | 5370 | resolver.whichMessageObject); |
| | 5371 | } |
| | 5372 | |
| | 5373 | /* have the action show objects already defaulted */ |
| | 5374 | resolver.getAction().announceAllDefaultObjects(nil); |
| | 5375 | |
| | 5376 | /* ask for a default object */ |
| | 5377 | asker.askMissingObject(targetActor_, resolver.getAction(), |
| | 5378 | resolver.whichMessageObject); |
| | 5379 | |
| | 5380 | /* try reading an object response */ |
| | 5381 | return tryAskingForObject(issuingActor_, targetActor_, |
| | 5382 | resolver, self, responseProd); |
| | 5383 | } |
| | 5384 | |
| | 5385 | noteLiteral(txt) |
| | 5386 | { |
| | 5387 | /* |
| | 5388 | * there's nothing to do with a literal at this point, since |
| | 5389 | * we're not ranking anything |
| | 5390 | */ |
| | 5391 | } |
| | 5392 | |
| | 5393 | askMissingLiteral(action, which) |
| | 5394 | { |
| | 5395 | local ret; |
| | 5396 | |
| | 5397 | /* if we can't resolve this interactively, fail with an error */ |
| | 5398 | if (!canResolveInteractively(action)) |
| | 5399 | { |
| | 5400 | /* interactive resolution isn't allowed - fail */ |
| | 5401 | throw new ParseFailureException(&missingLiteral, action, which); |
| | 5402 | } |
| | 5403 | |
| | 5404 | /* ask for the missing literal */ |
| | 5405 | targetActor_.getParserMessageObj(). |
| | 5406 | askMissingLiteral(targetActor_, action, which); |
| | 5407 | |
| | 5408 | /* read the response */ |
| | 5409 | ret = readMainCommand(rmcAskLiteral); |
| | 5410 | |
| | 5411 | /* re-enable the transcript, if we have one */ |
| | 5412 | if (gTranscript) |
| | 5413 | gTranscript.activate(); |
| | 5414 | |
| | 5415 | /* return the response */ |
| | 5416 | return ret; |
| | 5417 | } |
| | 5418 | |
| | 5419 | emptyNounPhrase(resolver) |
| | 5420 | { |
| | 5421 | /* abort with an error */ |
| | 5422 | throw new ParseFailureException(&emptyNounPhrase); |
| | 5423 | } |
| | 5424 | |
| | 5425 | zeroQuantity(txt) |
| | 5426 | { |
| | 5427 | /* abort with an error */ |
| | 5428 | throw new ParseFailureException(&zeroQuantity, |
| | 5429 | txt.toLower().htmlify()); |
| | 5430 | } |
| | 5431 | |
| | 5432 | insufficientQuantity(txt, matchList, requiredNum) |
| | 5433 | { |
| | 5434 | /* abort with an error */ |
| | 5435 | throw new ParseFailureException( |
| | 5436 | &insufficientQuantity, txt.toLower().htmlify(), |
| | 5437 | matchList, requiredNum); |
| | 5438 | } |
| | 5439 | |
| | 5440 | uniqueObjectRequired(txt, matchList) |
| | 5441 | { |
| | 5442 | /* abort with an error */ |
| | 5443 | throw new ParseFailureException( |
| | 5444 | &uniqueObjectRequired, txt.toLower().htmlify(), matchList); |
| | 5445 | } |
| | 5446 | |
| | 5447 | singleObjectRequired(txt) |
| | 5448 | { |
| | 5449 | /* abort with an error */ |
| | 5450 | throw new ParseFailureException( |
| | 5451 | &singleObjectRequired, txt.toLower().htmlify()); |
| | 5452 | } |
| | 5453 | |
| | 5454 | noteAdjEnding() |
| | 5455 | { |
| | 5456 | /* we don't care about adjective-ending noun phrases at this point */ |
| | 5457 | } |
| | 5458 | |
| | 5459 | noteIndefinite() |
| | 5460 | { |
| | 5461 | /* we don't care about indefinites at this point */ |
| | 5462 | } |
| | 5463 | |
| | 5464 | noteMiscWordList(txt) |
| | 5465 | { |
| | 5466 | /* we don't care about unstructured noun phrases at this point */ |
| | 5467 | } |
| | 5468 | |
| | 5469 | notePronoun() |
| | 5470 | { |
| | 5471 | /* we don't care about pronouns right now */ |
| | 5472 | } |
| | 5473 | |
| | 5474 | noteMatches(matchList) |
| | 5475 | { |
| | 5476 | /* we don't care about the matches just now */ |
| | 5477 | } |
| | 5478 | |
| | 5479 | notePlural() |
| | 5480 | { |
| | 5481 | /* we don't care about these right now */ |
| | 5482 | } |
| | 5483 | |
| | 5484 | beginSingleObjSlot() { } |
| | 5485 | endSingleObjSlot() { } |
| | 5486 | |
| | 5487 | incCommandCount() |
| | 5488 | { |
| | 5489 | /* we don't care about how many subcommands there are */ |
| | 5490 | } |
| | 5491 | |
| | 5492 | noteActorSpecified() |
| | 5493 | { |
| | 5494 | /* |
| | 5495 | * we don't care about this during execution - it only matters |
| | 5496 | * for determining the strength of the command during the |
| | 5497 | * ranking process |
| | 5498 | */ |
| | 5499 | } |
| | 5500 | |
| | 5501 | noteNounSlots(cnt) |
| | 5502 | { |
| | 5503 | /* |
| | 5504 | * we don't care about this during execution; it only matters |
| | 5505 | * for the ranking process |
| | 5506 | */ |
| | 5507 | } |
| | 5508 | |
| | 5509 | noteWeakPhrasing(level) |
| | 5510 | { |
| | 5511 | /* ignore this during execution; it only matters during ranking */ |
| | 5512 | } |
| | 5513 | |
| | 5514 | /* allow remapping the action */ |
| | 5515 | allowActionRemapping = true |
| | 5516 | |
| | 5517 | /* allow making an arbitrary choice among equivalents */ |
| | 5518 | allowEquivalentFiltering = true |
| | 5519 | ; |
| | 5520 | |
| | 5521 | |
| | 5522 | /* |
| | 5523 | * List entry for the still-to-resolve list |
| | 5524 | */ |
| | 5525 | class StillToResolveItem: object |
| | 5526 | construct(lst, fullList, txt) |
| | 5527 | { |
| | 5528 | /* remember the equivalent-reduced and full match lists */ |
| | 5529 | matchList = lst; |
| | 5530 | fullMatchList = fullList; |
| | 5531 | |
| | 5532 | /* note the text */ |
| | 5533 | origText = txt; |
| | 5534 | } |
| | 5535 | |
| | 5536 | /* the reduced (equivalent-eliminated) match list */ |
| | 5537 | matchList = [] |
| | 5538 | |
| | 5539 | /* full (equivalent-inclusive) match list */ |
| | 5540 | fullMatchList = [] |
| | 5541 | |
| | 5542 | /* the original command text being disambiguated */ |
| | 5543 | origText = '' |
| | 5544 | ; |
| | 5545 | |
| | 5546 | /* ------------------------------------------------------------------------ */ |
| | 5547 | /* |
| | 5548 | * Specialized noun-phrase resolution results gatherer for resolving a |
| | 5549 | * command actor (i.e., the target actor of a command). |
| | 5550 | */ |
| | 5551 | class ActorResolveResults: BasicResolveResults |
| | 5552 | construct() |
| | 5553 | { |
| | 5554 | /* do the inherited work */ |
| | 5555 | inherited(); |
| | 5556 | |
| | 5557 | /* |
| | 5558 | * set the initial actor context to the PC - this type of |
| | 5559 | * resolver is set up to determine the actor context, so we don't |
| | 5560 | * usually know the actual actor context yet when setting up this |
| | 5561 | * resolver |
| | 5562 | */ |
| | 5563 | targetActor_ = issuingActor_ = gPlayerChar; |
| | 5564 | } |
| | 5565 | |
| | 5566 | getImpliedObject(np, resolver) |
| | 5567 | { |
| | 5568 | /* |
| | 5569 | * there's no default for the actor - it's usually simply a |
| | 5570 | * syntax error when the actor is omitted |
| | 5571 | */ |
| | 5572 | throw new ParseFailureException(&missingActor); |
| | 5573 | } |
| | 5574 | |
| | 5575 | uniqueObjectRequired(txt, matchList) |
| | 5576 | { |
| | 5577 | /* an actor phrase must address a single actor */ |
| | 5578 | throw new ParseFailureException(&singleActorRequired); |
| | 5579 | } |
| | 5580 | |
| | 5581 | singleObjectRequired(txt) |
| | 5582 | { |
| | 5583 | /* an actor phrase must address a single actor */ |
| | 5584 | throw new ParseFailureException(&singleActorRequired); |
| | 5585 | } |
| | 5586 | |
| | 5587 | /* don't allow action remapping while resolving the actor */ |
| | 5588 | allowActionRemapping = nil |
| | 5589 | ; |
| | 5590 | |
| | 5591 | /* ------------------------------------------------------------------------ */ |
| | 5592 | /* |
| | 5593 | * Command ranking criterion. This is used by the CommandRanking class |
| | 5594 | * to represent one criterion for comparing two parse trees. |
| | 5595 | * |
| | 5596 | * Rankings are performed in two passes. The first pass is the rough, |
| | 5597 | * qualitative pass, meant to determine if one parse tree has big, |
| | 5598 | * obvious differences from another. In most cases, this means that one |
| | 5599 | * tree has a particular type of problem or special advantage that the |
| | 5600 | * other doesn't have at all. |
| | 5601 | * |
| | 5602 | * The second pass is the fine-grained pass. We only reach the second |
| | 5603 | * pass if we can't find any coarse differences on the first rough pass. |
| | 5604 | * In most cases, the second pass compares the magnitude of problems or |
| | 5605 | * advantages to determine if one tree is slightly better than the other. |
| | 5606 | */ |
| | 5607 | class CommandRankingCriterion: object |
| | 5608 | /* |
| | 5609 | * Compare two CommandRanking objects on the basis of this criterion, |
| | 5610 | * for the first, coarse-grained pass. Returns a positive number if |
| | 5611 | * a is better than b, 0 if they're indistinguishable, or -1 if a is |
| | 5612 | * worse than b. |
| | 5613 | */ |
| | 5614 | comparePass1(a, b) { return 0; } |
| | 5615 | |
| | 5616 | /* compare two rankings for the second, fine-grained pass */ |
| | 5617 | comparePass2(a, b) { return 0; } |
| | 5618 | ; |
| | 5619 | |
| | 5620 | /* |
| | 5621 | * A command ranking criterion that measures a "problem" by a count of |
| | 5622 | * occurrences stored in a property of the CommandRanking object. For |
| | 5623 | * example, we could count the number of noun phrases that don't resolve |
| | 5624 | * to any objects. |
| | 5625 | * |
| | 5626 | * On the first, coarse-grained pass, we measure only the presence or |
| | 5627 | * absence of our problem. That is, if one parse tree has zero |
| | 5628 | * occurrences of the problem and the other has a non-zero number of |
| | 5629 | * occurrences of the problem (as measured by our counting property), |
| | 5630 | * then we'll prefer the one with zero occurrences. If both have no |
| | 5631 | * occurrences, or both have a non-zero number of occurrences, we'll |
| | 5632 | * consider the two equivalent for the first pass, since we only care |
| | 5633 | * about the presence or absence of the problem. |
| | 5634 | * |
| | 5635 | * On the second, fine-grained pass, we measure the actual number of |
| | 5636 | * occurrences of the problem, and choose the parse tree with the lower |
| | 5637 | * number. |
| | 5638 | */ |
| | 5639 | class CommandRankingByProblem: CommandRankingCriterion |
| | 5640 | /* |
| | 5641 | * our ranking property - this is a property of the CommandRanking |
| | 5642 | * object that gives us a count of the number of times our "problem" |
| | 5643 | * has occurred in the ranking object's parse tree |
| | 5644 | */ |
| | 5645 | prop_ = nil |
| | 5646 | |
| | 5647 | /* first pass - compare by presence or absence of the problem */ |
| | 5648 | comparePass1(a, b) |
| | 5649 | { |
| | 5650 | local acnt = a.(self.prop_); |
| | 5651 | local bcnt = b.(self.prop_); |
| | 5652 | |
| | 5653 | /* if b has the problem but a doesn't, a is better */ |
| | 5654 | if (acnt == 0 && bcnt != 0) |
| | 5655 | return 1; |
| | 5656 | |
| | 5657 | /* if a has the problem but b doesn't, b is better */ |
| | 5658 | if (acnt != 0 && bcnt == 0) |
| | 5659 | return -1; |
| | 5660 | |
| | 5661 | /* we can't tell the difference at this stage */ |
| | 5662 | return 0; |
| | 5663 | } |
| | 5664 | |
| | 5665 | /* second pass - compare by number of occurrences of the problem */ |
| | 5666 | comparePass2(a, b) |
| | 5667 | { |
| | 5668 | /* |
| | 5669 | * Return the difference in the problem counts. We want to |
| | 5670 | * return >0 if a has fewer problems, <0 if b has fewer problems: |
| | 5671 | * so compute (a-b) and negate it, which is the same as computing |
| | 5672 | * (a-b). |
| | 5673 | */ |
| | 5674 | return b.(self.prop_) - a.(self.prop_); |
| | 5675 | } |
| | 5676 | ; |
| | 5677 | |
| | 5678 | /* |
| | 5679 | * A "weakness" criterion. This is similar to the rank-by-problem |
| | 5680 | * criterion, but rather than ranking on an actual structural problem, it |
| | 5681 | * ranks on a structural weakness. This is suitable for things like |
| | 5682 | * adjective endings and truncations, where the weakness isn't on the |
| | 5683 | * same order as a "problem" but where we'd still rather avoid the |
| | 5684 | * weakness if we can. |
| | 5685 | * |
| | 5686 | * The point of the separate "weakness" criterion is that we only allow |
| | 5687 | * weaknesses to come into play on pass 2, after we've already |
| | 5688 | * discriminated based on problems. If we can discriminate based on |
| | 5689 | * problems, we'll do so in pass 1 and won't even get to pass 2; we'll |
| | 5690 | * only discriminate based on weakness if we can't tell the difference |
| | 5691 | * based on real problems. |
| | 5692 | */ |
| | 5693 | class CommandRankingByWeakness: CommandRankingCriterion |
| | 5694 | /* on pass 1, ignore weaknesses */ |
| | 5695 | comparePass1(a, b) { return 0; } |
| | 5696 | |
| | 5697 | /* on pass 2, compare based on weaknesses */ |
| | 5698 | comparePass2(a, b) { return b.(self.prop_) - a.(self.prop_); } |
| | 5699 | |
| | 5700 | /* our command-ranking property */ |
| | 5701 | prop_ = nil |
| | 5702 | ; |
| | 5703 | |
| | 5704 | /* |
| | 5705 | * command-ranking-by-problem and by-weakness objects for the pre-defined |
| | 5706 | * ranking criteria |
| | 5707 | */ |
| | 5708 | rankByVocabNonMatch: CommandRankingByProblem prop_ = &vocabNonMatchCount; |
| | 5709 | rankByNonMatch: CommandRankingByProblem prop_ = &nonMatchCount; |
| | 5710 | rankByInsufficient: CommandRankingByProblem prop_ = &insufficientCount; |
| | 5711 | rankByListForSingle: CommandRankingByProblem prop_ = &listForSingle; |
| | 5712 | rankByEmptyBut: CommandRankingByProblem prop_ = &emptyButCount; |
| | 5713 | rankByAllExcluded: CommandRankingByProblem prop_ = &allExcludedCount; |
| | 5714 | rankByActorSpecified: CommandRankingByProblem prop_ = &actorSpecifiedCount; |
| | 5715 | rankByMiscWordList: CommandRankingByProblem prop_ = &miscWordListCount; |
| | 5716 | rankByPluralTrunc: CommandRankingByWeakness prop_ = &pluralTruncCount; |
| | 5717 | rankByEndAdj: CommandRankingByWeakness prop_ = &endAdjCount; |
| | 5718 | rankByIndefinite: CommandRankingByProblem prop_ = &indefiniteCount; |
| | 5719 | rankByTrunc: CommandRankingByWeakness prop_ = &truncCount; |
| | 5720 | rankByMissing: CommandRankingByProblem prop_ = &missingCount; |
| | 5721 | rankByPronoun: CommandRankingByWeakness prop_ = &pronounCount; |
| | 5722 | rankByWeakness: CommandRankingByWeakness prop_ = &weaknessLevel; |
| | 5723 | rankByUnwantedPlural: CommandRankingByProblem prop_ = &unwantedPluralCount; |
| | 5724 | |
| | 5725 | /* |
| | 5726 | * Command ranking by literal phrase length. We prefer interpretations |
| | 5727 | * that treat less text as uninterpreted literal text. By "less text," |
| | 5728 | * we simply mean that one has a shorter string treated as literal text |
| | 5729 | * than the other. (We prefer shorter literals because when the parser |
| | 5730 | * matches a string of literal text, it's essentially throwing up its |
| | 5731 | * hands and admitting it can't parse the text; so the less text is |
| | 5732 | * contained in literals, the more text the parser is actually parsing, |
| | 5733 | * and more parsed is better.) |
| | 5734 | */ |
| | 5735 | rankByLiteralLength: CommandRankingCriterion |
| | 5736 | /* first pass */ |
| | 5737 | comparePass1(a, b) |
| | 5738 | { |
| | 5739 | /* |
| | 5740 | * Compare our lengths. We want to return >0 if a is shorter and |
| | 5741 | * <0 if a is longer (and, of course, 0 if they're the same |
| | 5742 | * length). So, we can just compute (a-b) and negate the result, |
| | 5743 | * which is the same as computing (b-a). |
| | 5744 | * |
| | 5745 | * The CommandRanking objects keep track of the length of text in |
| | 5746 | * literals in their literalLength properties. |
| | 5747 | */ |
| | 5748 | return b.literalLength - a.literalLength; |
| | 5749 | } |
| | 5750 | |
| | 5751 | /* |
| | 5752 | * Second pass - we use our full powers of discrimination on the |
| | 5753 | * first pass, so if we make it to the second pass, we couldn't tell |
| | 5754 | * a difference on the first pass and thus can't tell a difference |
| | 5755 | * now. So, just inherit the default implementation, which simply |
| | 5756 | * returns 0 to indicate that there's no difference. |
| | 5757 | */ |
| | 5758 | ; |
| | 5759 | |
| | 5760 | /* |
| | 5761 | * Command ranking by subcommand count: we prefer the match with fewer |
| | 5762 | * subcommands. If one has fewer subcommands than the other, it means |
| | 5763 | * that we were able to interpret ambiguous conjunctions (such as "and") |
| | 5764 | * as noun phrase conjunctions rather than as command conjunctions; other |
| | 5765 | * things being equal, we'd rather take the interpretation that gives us |
| | 5766 | * noun phrases than the one that involves more separate commands. |
| | 5767 | */ |
| | 5768 | rankBySubcommands: CommandRankingCriterion |
| | 5769 | /* first pass - compare subcommand counts */ |
| | 5770 | comparePass1(a, b) |
| | 5771 | { |
| | 5772 | /* |
| | 5773 | * if a has fewer subcommands, return <0, and if b has fewer |
| | 5774 | * subcommands, return >0: so we can just return the negative of |
| | 5775 | * (a-b), or (b-a) |
| | 5776 | */ |
| | 5777 | return b.commandCount - a.commandCount; |
| | 5778 | } |
| | 5779 | |
| | 5780 | /* second pass - do nothing, as we do all of our work on the first pass */ |
| | 5781 | ; |
| | 5782 | |
| | 5783 | /* |
| | 5784 | * Rank by token count. Other things being equal, we'd rather pick a |
| | 5785 | * longer match. If one match is shorter than the other in terms of the |
| | 5786 | * number of tokens it encompasses, then it means that the shorter match |
| | 5787 | * left more tokens at the end of the command to be interpreted as |
| | 5788 | * separate commands. If we have an interpretation that can take more of |
| | 5789 | * those tokens and parse them as part of the current command, that |
| | 5790 | * interpretation is probably better. |
| | 5791 | */ |
| | 5792 | rankByTokenCount: CommandRankingCriterion |
| | 5793 | /* first pass - compare token counts */ |
| | 5794 | comparePass1(a, b) |
| | 5795 | { |
| | 5796 | /* choose the one that matched more tokens */ |
| | 5797 | return a.tokCount - b.tokCount; |
| | 5798 | } |
| | 5799 | |
| | 5800 | /* first pass - we do all our work on the first pass */ |
| | 5801 | ; |
| | 5802 | |
| | 5803 | /* |
| | 5804 | * Rank by "verb structure." This gives more weight to an |
| | 5805 | * interpretation that has more structural noun phrases in the verb. |
| | 5806 | * For example, "DETACH dobj FROM iobj" is given more weight than |
| | 5807 | * "DETACH dobj", because the former has two structural noun phrases |
| | 5808 | * whereas the latter has only one. This will make us prefer to treat |
| | 5809 | * DETACH WIRE FROM BOX as a two-object action, for example, even if we |
| | 5810 | * could treat WIRE FROM BOX as a single "locational" noun phrase. |
| | 5811 | */ |
| | 5812 | rankByVerbStructure: CommandRankingCriterion |
| | 5813 | comparePass2(a, b) |
| | 5814 | { |
| | 5815 | /* take the one with more structural noun slots in the verb phrase */ |
| | 5816 | return a.nounSlotCount - b.nounSlotCount; |
| | 5817 | } |
| | 5818 | ; |
| | 5819 | |
| | 5820 | /* |
| | 5821 | * Rank by ambiguous noun phrases. We apply this criterion on the second |
| | 5822 | * pass only, because it's a weak test: we might end up narrowing things |
| | 5823 | * down through automatic "logicalness" tests during the noun resolution |
| | 5824 | * process, so ambiguity at this stage in the parsing process doesn't |
| | 5825 | * necessarily indicate that there's real ambiguity in the command. |
| | 5826 | * However, if we can already tell that one interpretation is unambiguous |
| | 5827 | * and another is ambiguous, and the two interpretations are otherwise |
| | 5828 | * equally good, pick the one that's already unambiguous: the ambiguous |
| | 5829 | * interpretation might or might not stay ambiguous, but the unambiguous |
| | 5830 | * interpretation will definitely stay unambiguous. |
| | 5831 | */ |
| | 5832 | rankByAmbiguity: CommandRankingCriterion |
| | 5833 | /* |
| | 5834 | * Do nothing on the first pass, because we want any first-pass |
| | 5835 | * criterion to prevail over our weak test. Instead, check for a |
| | 5836 | * difference in ambiguity only on the second pass. |
| | 5837 | */ |
| | 5838 | comparePass2(a, b) |
| | 5839 | { |
| | 5840 | /* the one with lower ambiguity is better */ |
| | 5841 | return b.ambigCount - a.ambigCount; |
| | 5842 | } |
| | 5843 | ; |
| | 5844 | |
| | 5845 | |
| | 5846 | /* |
| | 5847 | * Production match ranking object. We create one of these objects for |
| | 5848 | * each match tree that we wish to rank. |
| | 5849 | * |
| | 5850 | * This class is generally not instantiated by client code - instead, |
| | 5851 | * clients use the sortByRanking() class method to rank a list of |
| | 5852 | * production matches. |
| | 5853 | */ |
| | 5854 | class CommandRanking: ResolveResults |
| | 5855 | /* |
| | 5856 | * Sort a list of productions, as returned from |
| | 5857 | * GrammarProd.parseTokens(), in descending order of command |
| | 5858 | * strength. We return a list of CommandRanking objects whose first |
| | 5859 | * element is the best command interpretation. |
| | 5860 | * |
| | 5861 | * Note that this can be used as a class-level method. |
| | 5862 | */ |
| | 5863 | sortByRanking(lst, [resolveArguments]) |
| | 5864 | { |
| | 5865 | local rankings; |
| | 5866 | |
| | 5867 | /* |
| | 5868 | * create a vector to hold the ranking information - we |
| | 5869 | * need one ranking item per match |
| | 5870 | */ |
| | 5871 | rankings = new Vector(lst.length()); |
| | 5872 | |
| | 5873 | /* get the ranking information for each command */ |
| | 5874 | foreach(local cur in lst) |
| | 5875 | { |
| | 5876 | local curRank; |
| | 5877 | |
| | 5878 | /* create a ranking item for the entry */ |
| | 5879 | curRank = self.createInstance(cur); |
| | 5880 | |
| | 5881 | /* rank this entry */ |
| | 5882 | curRank.calcRanking(resolveArguments); |
| | 5883 | |
| | 5884 | /* add this to our ranking list */ |
| | 5885 | rankings.append(curRank); |
| | 5886 | } |
| | 5887 | |
| | 5888 | /* sort the entries by descending ranking, and return the results */ |
| | 5889 | return rankings.sort(SortDesc, {x, y: x.compareRanking(y)}); |
| | 5890 | } |
| | 5891 | |
| | 5892 | /* create a new entry */ |
| | 5893 | construct(match) |
| | 5894 | { |
| | 5895 | /* remember the match object */ |
| | 5896 | self.match = match; |
| | 5897 | |
| | 5898 | /* remember the number of tokens in the match */ |
| | 5899 | tokCount = match.lastTokenIndex - match.firstTokenIndex + 1; |
| | 5900 | } |
| | 5901 | |
| | 5902 | /* calculate my ranking */ |
| | 5903 | calcRanking(resolveArguments) |
| | 5904 | { |
| | 5905 | /* |
| | 5906 | * Ask the match tree to resolve nouns, using this ranking |
| | 5907 | * object as the resolution results receiver - when an error or |
| | 5908 | * warning occurs during resolution, we'll merely note the |
| | 5909 | * condition rather than say anything about it. |
| | 5910 | * |
| | 5911 | * Note that 'self' is the results object, because the point of |
| | 5912 | * this resolution pass is to gather statistics into this |
| | 5913 | * results object. |
| | 5914 | */ |
| | 5915 | match.resolveNouns(resolveArguments..., self); |
| | 5916 | } |
| | 5917 | |
| | 5918 | /* |
| | 5919 | * Compare two production list entries for ranking purposes. Returns |
| | 5920 | * a negative number if this one ranks worse than the other, 0 if |
| | 5921 | * they have the same ranking, or a positive number if this one ranks |
| | 5922 | * better than the other one. |
| | 5923 | * |
| | 5924 | * This routine is designed to run entirely off of our |
| | 5925 | * rankingCriteria property. In most cases, subclasses should be |
| | 5926 | * able to customize the ranking system simply by overriding the |
| | 5927 | * rankingCriteria property to provide a customized list of criteria |
| | 5928 | * objects. |
| | 5929 | */ |
| | 5930 | compareRanking(other) |
| | 5931 | { |
| | 5932 | local ret; |
| | 5933 | |
| | 5934 | /* |
| | 5935 | * Run through our ranking criteria and apply the first pass to |
| | 5936 | * each one. Return the indication of the first criterion that |
| | 5937 | * can tell a difference. |
| | 5938 | */ |
| | 5939 | foreach (local cur in rankingCriteria) |
| | 5940 | { |
| | 5941 | /* if the rankings differ in this criterion, return the result */ |
| | 5942 | if ((ret = cur.comparePass1(self, other)) != 0) |
| | 5943 | return ret; |
| | 5944 | } |
| | 5945 | |
| | 5946 | /* |
| | 5947 | * We couldn't tell any difference on the first pass, so try |
| | 5948 | * again with the finer-grained second pass. |
| | 5949 | */ |
| | 5950 | foreach (local cur in rankingCriteria) |
| | 5951 | { |
| | 5952 | /* run the second pass */ |
| | 5953 | if ((ret = cur.comparePass2(self, other)) != 0) |
| | 5954 | return ret; |
| | 5955 | } |
| | 5956 | |
| | 5957 | /* we couldn't tell any difference between the two */ |
| | 5958 | return 0; |
| | 5959 | } |
| | 5960 | |
| | 5961 | /* |
| | 5962 | * Our list of ranking criteria. This is a list of |
| | 5963 | * CommandRankingCriterion objects. The list is given in order of |
| | 5964 | * importance: the first criterion is the most important, so if it |
| | 5965 | * can discriminate the two match trees, we use its result; if the |
| | 5966 | * first criterion can't tell any difference, then we move on to the |
| | 5967 | * second criterion; and so on through the list. |
| | 5968 | * |
| | 5969 | * The most important thing is whether or not we have irresolvable |
| | 5970 | * noun phrases (vocabNonMatchCount). If one of us has a noun phrase |
| | 5971 | * that refers to nothing anywhere in the game, it's not as good as a |
| | 5972 | * phrase that at least matches something somewhere. |
| | 5973 | * |
| | 5974 | * Next, if one of us has noun phrases that cannot be resolved to |
| | 5975 | * something in scope (nonMatchCount), and the other can successfully |
| | 5976 | * resolve its noun phrases, the one that can resolve the phrases is |
| | 5977 | * preferred. |
| | 5978 | * |
| | 5979 | * Next, check for insufficient numbers of matches to counted phrases |
| | 5980 | * (insufficientCount). |
| | 5981 | * |
| | 5982 | * Next, check for noun lists in single-noun-only slots |
| | 5983 | * (listForSingle). |
| | 5984 | * |
| | 5985 | * Next, if we have an empty "but" list in one but not the other, |
| | 5986 | * take the one with the non-empty "but" list (emptyButCount). We |
| | 5987 | * prefer a non-empty "but" list with an empty "all" even to a |
| | 5988 | * non-empty "all" list with an empty "but", because in the latter |
| | 5989 | * case we probably failed to exclude anything because we |
| | 5990 | * misinterpreted the noun phrase to be excluded. |
| | 5991 | * |
| | 5992 | * Next, if we have an empty "all" or "any" phrase due to "but" |
| | 5993 | * exclusion, take the one that's not empty (allExcludedCount). |
| | 5994 | * |
| | 5995 | * Next, prefer a command that addresses an actor |
| | 5996 | * (actorSpecifiedCount) - if the actor name looks like a command (we |
| | 5997 | * have someone named "Open Bob," maybe?), we'd prefer to interpret |
| | 5998 | * the name appearing as a command prefix as an actor name. |
| | 5999 | * |
| | 6000 | * Next, prefer no unstructured word lists as noun phrases |
| | 6001 | * (miscWordList phrases) (miscWordListCount). |
| | 6002 | * |
| | 6003 | * Next, prefer interpretations that treat less text as uninterpreted |
| | 6004 | * literal text. By "less text," we simply mean that one has a |
| | 6005 | * shorter string treated as a literal than the other. |
| | 6006 | * |
| | 6007 | * Prefer no indefinite noun phrases (indefiniteCount). |
| | 6008 | * |
| | 6009 | * Prefer no truncated plurals (pluralTruncCount). |
| | 6010 | * |
| | 6011 | * Prefer no noun phrases ending in adjectives (endAdjCount). |
| | 6012 | * |
| | 6013 | * Prefer no truncated words of any kind (truncCount). |
| | 6014 | * |
| | 6015 | * Prefer fewer pronouns. If we have an interpretation that matches |
| | 6016 | * a word to explicit vocabulary, take it over matching a word as a |
| | 6017 | * pronoun: if a word is given explicitly as vocabulary for an |
| | 6018 | * object, use it if possible. |
| | 6019 | * |
| | 6020 | * Prefer no missing phrases (missingCount). |
| | 6021 | * |
| | 6022 | * Prefer the one with fewer subcommands - if one has fewer |
| | 6023 | * subcommands than the other, it means that we were able to |
| | 6024 | * interpret ambiguous conjunctions (such as "and") as noun phrase |
| | 6025 | * conjunctions rather than as command conjunctions; since we know by |
| | 6026 | * now that we both either have or don't have unresolved noun |
| | 6027 | * phrases, we'd rather take the interpretation that gives us noun |
| | 6028 | * phrases than the one that involves more separate commands. |
| | 6029 | * |
| | 6030 | * Prefer the tree that matches more tokens. |
| | 6031 | * |
| | 6032 | * Prefer the one with more structural noun phrases in the verb. For |
| | 6033 | * example, if we have one interpretation that's DETACH (X FROM Y) |
| | 6034 | * (where X FROM Y is a 'locational' phrase that we treat as the |
| | 6035 | * direct object), and one that's DETACH X FROM Y (where X is the |
| | 6036 | * direct object and Y is in the indirect object), prefer the latter, |
| | 6037 | * because it has both direct and indirect object phrases, whereas |
| | 6038 | * the former has only a direct object phrase. English speakers |
| | 6039 | * almost always try to put prepositions into a structural role in |
| | 6040 | * the verb phrase like this when they could be either in the verb |
| | 6041 | * phrase or part of a noun phrase. |
| | 6042 | * |
| | 6043 | * If all else fails, prefer the one that is initially less |
| | 6044 | * ambiguous. Ambiguity is a weak test at this point, since we might |
| | 6045 | * end up narrowing things down through automatic "logicalness" tests |
| | 6046 | * later, but it's slightly better to have the match be less |
| | 6047 | * ambiguous now, all other things being equal. |
| | 6048 | */ |
| | 6049 | rankingCriteria = [rankByVocabNonMatch, |
| | 6050 | rankByNonMatch, |
| | 6051 | rankByInsufficient, |
| | 6052 | rankByListForSingle, |
| | 6053 | rankByEmptyBut, |
| | 6054 | rankByAllExcluded, |
| | 6055 | rankByActorSpecified, |
| | 6056 | rankByUnwantedPlural, |
| | 6057 | rankByMiscWordList, |
| | 6058 | rankByWeakness, |
| | 6059 | rankByLiteralLength, |
| | 6060 | rankByIndefinite, |
| | 6061 | rankByPluralTrunc, |
| | 6062 | rankByEndAdj, |
| | 6063 | rankByTrunc, |
| | 6064 | rankByPronoun, |
| | 6065 | rankByMissing, |
| | 6066 | rankBySubcommands, |
| | 6067 | rankByTokenCount, |
| | 6068 | rankByVerbStructure, |
| | 6069 | rankByAmbiguity] |
| | 6070 | |
| | 6071 | /* the match tree I'm ranking */ |
| | 6072 | match = nil |
| | 6073 | |
| | 6074 | /* the number of tokens my match tree consumes */ |
| | 6075 | tokCount = 0 |
| | 6076 | |
| | 6077 | /* |
| | 6078 | * Ranking information. calcRanking() fills in these members, and |
| | 6079 | * compareRanking() uses these to calculate the relative ranking. |
| | 6080 | */ |
| | 6081 | |
| | 6082 | /* |
| | 6083 | * The number of structural "noun phrase slots" in the verb. An |
| | 6084 | * intransitive verb has no noun phrase slots; a transitive verb |
| | 6085 | * with a direct object has one; a verb with a direct and indirect |
| | 6086 | * object has two slots. |
| | 6087 | */ |
| | 6088 | nounSlotCount = nil |
| | 6089 | |
| | 6090 | /* number of noun phrases matching nothing anywhere in the game */ |
| | 6091 | vocabNonMatchCount = 0 |
| | 6092 | |
| | 6093 | /* number of noun phrases matching nothing in scope */ |
| | 6094 | nonMatchCount = 0 |
| | 6095 | |
| | 6096 | /* number of phrases requiring quantity higher than can be fulfilled */ |
| | 6097 | insufficientCount = 0 |
| | 6098 | |
| | 6099 | /* number of noun lists in single-noun slots */ |
| | 6100 | listForSingle = 0 |
| | 6101 | |
| | 6102 | /* number of empty "but" lists */ |
| | 6103 | emptyButCount = 0 |
| | 6104 | |
| | 6105 | /* number of "all" or "any" lists totally excluded by "but" */ |
| | 6106 | allExcludedCount = 0 |
| | 6107 | |
| | 6108 | /* missing phrases (structurally omitted, as in "put book") */ |
| | 6109 | missingCount = 0 |
| | 6110 | |
| | 6111 | /* number of truncated plurals */ |
| | 6112 | pluralTruncCount = 0 |
| | 6113 | |
| | 6114 | /* number of phrases ending in adjectives */ |
| | 6115 | endAdjCount = 0 |
| | 6116 | |
| | 6117 | /* number of phrases with indefinite noun phrase structure */ |
| | 6118 | indefiniteCount = 0 |
| | 6119 | |
| | 6120 | /* number of miscellaneous word lists as noun phrases */ |
| | 6121 | miscWordListCount = 0 |
| | 6122 | |
| | 6123 | /* number of truncated words overall */ |
| | 6124 | truncCount = 0 |
| | 6125 | |
| | 6126 | /* number of ambiguous noun phrases */ |
| | 6127 | ambigCount = 0 |
| | 6128 | |
| | 6129 | /* number of subcommands in the command */ |
| | 6130 | commandCount = 0 |
| | 6131 | |
| | 6132 | /* an actor is specified */ |
| | 6133 | actorSpecifiedCount = 0 |
| | 6134 | |
| | 6135 | /* unknown words */ |
| | 6136 | unknownWordCount = 0 |
| | 6137 | |
| | 6138 | /* total character length of literal text phrases */ |
| | 6139 | literalLength = 0 |
| | 6140 | |
| | 6141 | /* number of pronoun phrases */ |
| | 6142 | pronounCount = 0 |
| | 6143 | |
| | 6144 | /* weakness level (for noteWeakPhrasing) */ |
| | 6145 | weaknessLevel = 0 |
| | 6146 | |
| | 6147 | /* number of plural phrases encountered in single-object slots */ |
| | 6148 | unwantedPluralCount = 0 |
| | 6149 | |
| | 6150 | /* -------------------------------------------------------------------- */ |
| | 6151 | /* |
| | 6152 | * ResolveResults implementation. We use this results receiver when |
| | 6153 | * we're comparing the semantic strengths of multiple structural |
| | 6154 | * matches, so we merely note each error condition without showing |
| | 6155 | * any message to the user or asking the user for any input. Once |
| | 6156 | * we've ranked all of the matches, we'll choose the one with the |
| | 6157 | * best attributes and then resolve it for real, at which point if |
| | 6158 | * we chose one with any errors, we'll finally get around to showing |
| | 6159 | * the errors to the user. |
| | 6160 | */ |
| | 6161 | |
| | 6162 | noVocabMatch(action, txt) |
| | 6163 | { |
| | 6164 | /* note the unknown phrase */ |
| | 6165 | ++vocabNonMatchCount; |
| | 6166 | } |
| | 6167 | |
| | 6168 | noMatch(action, txt) |
| | 6169 | { |
| | 6170 | /* note that we have a noun phrase that matches nothing */ |
| | 6171 | ++nonMatchCount; |
| | 6172 | } |
| | 6173 | |
| | 6174 | allNotAllowed() |
| | 6175 | { |
| | 6176 | /* treat this as a non-matching noun phrase */ |
| | 6177 | ++nonMatchCount; |
| | 6178 | } |
| | 6179 | |
| | 6180 | noMatchForAll() |
| | 6181 | { |
| | 6182 | /* treat this as any other noun phrase that matches nothing */ |
| | 6183 | ++nonMatchCount; |
| | 6184 | } |
| | 6185 | |
| | 6186 | noteEmptyBut() |
| | 6187 | { |
| | 6188 | /* note it */ |
| | 6189 | ++emptyButCount; |
| | 6190 | } |
| | 6191 | |
| | 6192 | noMatchForAllBut() |
| | 6193 | { |
| | 6194 | /* count the total exclusion */ |
| | 6195 | ++allExcludedCount; |
| | 6196 | } |
| | 6197 | |
| | 6198 | noMatchForListBut() |
| | 6199 | { |
| | 6200 | /* treat this as any other noun phrase that matches nothing */ |
| | 6201 | ++allExcludedCount; |
| | 6202 | } |
| | 6203 | |
| | 6204 | noMatchForPronoun(typ, txt) |
| | 6205 | { |
| | 6206 | /* treat this as any other noun phrase that matches nothing */ |
| | 6207 | ++nonMatchCount; |
| | 6208 | } |
| | 6209 | |
| | 6210 | reflexiveNotAllowed(typ, txt) |
| | 6211 | { |
| | 6212 | /* treat this as any other noun phrase that matches nothing */ |
| | 6213 | ++nonMatchCount; |
| | 6214 | } |
| | 6215 | |
| | 6216 | wrongReflexive(typ, txt) |
| | 6217 | { |
| | 6218 | /* treat this as any other noun phrase that matches nothing */ |
| | 6219 | ++nonMatchCount; |
| | 6220 | } |
| | 6221 | |
| | 6222 | noMatchForPossessive(owner, txt) |
| | 6223 | { |
| | 6224 | /* treat this as any other noun phrase that matches nothing */ |
| | 6225 | ++nonMatchCount; |
| | 6226 | } |
| | 6227 | |
| | 6228 | noMatchForLocation(loc, txt) |
| | 6229 | { |
| | 6230 | /* treat this as any other noun phrase that matches nothing */ |
| | 6231 | ++nonMatchCount; |
| | 6232 | } |
| | 6233 | |
| | 6234 | noteBadPrep() |
| | 6235 | { |
| | 6236 | /* don't do anything at this point */ |
| | 6237 | } |
| | 6238 | |
| | 6239 | nothingInLocation(txt) |
| | 6240 | { |
| | 6241 | /* treat this as any other noun phrase that matches nothing */ |
| | 6242 | ++nonMatchCount; |
| | 6243 | } |
| | 6244 | |
| | 6245 | ambiguousNounPhrase(keeper, asker, txt, |
| | 6246 | matchList, fullMatchList, scopeList, |
| | 6247 | requiredNum, resolver) |
| | 6248 | { |
| | 6249 | local lst; |
| | 6250 | |
| | 6251 | /* note the ambiguity */ |
| | 6252 | ++ambigCount; |
| | 6253 | |
| | 6254 | /* |
| | 6255 | * There's no need to disambiguate the list at this stage, since |
| | 6256 | * we're only testing the strength of the structure. |
| | 6257 | * |
| | 6258 | * As a tentative approximation of the results, return a list |
| | 6259 | * consisting of the required number only, but stash away the |
| | 6260 | * remainder of the full list as a property of the first element |
| | 6261 | * of the return list so we can find the full list again later. |
| | 6262 | */ |
| | 6263 | lst = matchList.sublist(1, requiredNum); |
| | 6264 | if (matchList.length() > requiredNum && lst.length() >= 1) |
| | 6265 | lst[1].extraObjects = matchList.sublist(requiredNum + 1); |
| | 6266 | |
| | 6267 | /* return the abbreviated list */ |
| | 6268 | return lst; |
| | 6269 | } |
| | 6270 | |
| | 6271 | unknownNounPhrase(match, resolver) |
| | 6272 | { |
| | 6273 | local wordList; |
| | 6274 | local ret; |
| | 6275 | |
| | 6276 | /* |
| | 6277 | * if the resolver can handle this set of unknown words, treat |
| | 6278 | * it as a good noun phrase; otherwise, treat it as an unmatched |
| | 6279 | * noun phrase |
| | 6280 | */ |
| | 6281 | wordList = match.getOrigTokenList(); |
| | 6282 | if ((ret = resolver.resolveUnknownNounPhrase(wordList)) == nil) |
| | 6283 | { |
| | 6284 | /* count the unmatchable phrase */ |
| | 6285 | ++nonMatchCount; |
| | 6286 | |
| | 6287 | /* count the unknown word */ |
| | 6288 | ++unknownWordCount; |
| | 6289 | |
| | 6290 | /* |
| | 6291 | * since this is only a ranking pass, resolve to an empty |
| | 6292 | * list for now |
| | 6293 | */ |
| | 6294 | ret = []; |
| | 6295 | } |
| | 6296 | |
| | 6297 | /* return the results */ |
| | 6298 | return ret; |
| | 6299 | } |
| | 6300 | |
| | 6301 | getImpliedObject(np, resolver) |
| | 6302 | { |
| | 6303 | /* count the missing object phrase */ |
| | 6304 | ++missingCount; |
| | 6305 | return nil; |
| | 6306 | } |
| | 6307 | |
| | 6308 | askMissingObject(asker, resolver, responseProd) |
| | 6309 | { |
| | 6310 | /* |
| | 6311 | * no need to do anything here - we'll count the missing object |
| | 6312 | * in getImpliedObject, and we don't want to ask for anything |
| | 6313 | * interactively at this point |
| | 6314 | */ |
| | 6315 | return nil; |
| | 6316 | } |
| | 6317 | |
| | 6318 | noteLiteral(txt) |
| | 6319 | { |
| | 6320 | /* add the length of this literal to the total literal length */ |
| | 6321 | literalLength += txt.length(); |
| | 6322 | } |
| | 6323 | |
| | 6324 | emptyNounPhrase(resolver) |
| | 6325 | { |
| | 6326 | /* treat this as a non-matching noun phrase */ |
| | 6327 | ++nonMatchCount; |
| | 6328 | return []; |
| | 6329 | } |
| | 6330 | |
| | 6331 | zeroQuantity(txt) |
| | 6332 | { |
| | 6333 | /* treat this as a non-matching noun phrase */ |
| | 6334 | ++nonMatchCount; |
| | 6335 | } |
| | 6336 | |
| | 6337 | insufficientQuantity(txt, matchList, requiredNum) |
| | 6338 | { |
| | 6339 | /* treat this as a non-matching noun phrase */ |
| | 6340 | ++insufficientCount; |
| | 6341 | } |
| | 6342 | |
| | 6343 | singleObjectRequired(txt) |
| | 6344 | { |
| | 6345 | /* treat this as a non-matching noun phrase */ |
| | 6346 | ++listForSingle; |
| | 6347 | } |
| | 6348 | |
| | 6349 | uniqueObjectRequired(txt, matchList) |
| | 6350 | { |
| | 6351 | /* |
| | 6352 | * ignore this for now - we might get a unique object via |
| | 6353 | * disambiguation during the execution phase |
| | 6354 | */ |
| | 6355 | } |
| | 6356 | |
| | 6357 | noteAdjEnding() |
| | 6358 | { |
| | 6359 | /* count it */ |
| | 6360 | ++endAdjCount; |
| | 6361 | } |
| | 6362 | |
| | 6363 | noteIndefinite() |
| | 6364 | { |
| | 6365 | /* count it */ |
| | 6366 | ++indefiniteCount; |
| | 6367 | } |
| | 6368 | |
| | 6369 | noteMiscWordList(txt) |
| | 6370 | { |
| | 6371 | /* note the presence of an unstructured noun phrase */ |
| | 6372 | ++miscWordListCount; |
| | 6373 | |
| | 6374 | /* count this as a literal as well */ |
| | 6375 | noteLiteral(txt); |
| | 6376 | } |
| | 6377 | |
| | 6378 | notePronoun() |
| | 6379 | { |
| | 6380 | /* note the presence of a pronoun */ |
| | 6381 | ++pronounCount; |
| | 6382 | } |
| | 6383 | |
| | 6384 | noteMatches(matchList) |
| | 6385 | { |
| | 6386 | /* |
| | 6387 | * Run through the match list and note each weak flag. Note |
| | 6388 | * that each element of the match list is a ResolveInfo |
| | 6389 | * instance. |
| | 6390 | */ |
| | 6391 | foreach (local cur in matchList) |
| | 6392 | { |
| | 6393 | /* if this object was matched with a truncated word, note it */ |
| | 6394 | if ((cur.flags_ & VocabTruncated) != 0) |
| | 6395 | ++truncCount; |
| | 6396 | |
| | 6397 | /* if this object was matched with a truncated plural, note it */ |
| | 6398 | if ((cur.flags_ & PluralTruncated) != 0) |
| | 6399 | ++pluralTruncCount; |
| | 6400 | } |
| | 6401 | } |
| | 6402 | |
| | 6403 | beginSingleObjSlot() { ++inSingleObjSlot; } |
| | 6404 | endSingleObjSlot() { --inSingleObjSlot; } |
| | 6405 | inSingleObjSlot = 0 |
| | 6406 | |
| | 6407 | notePlural() |
| | 6408 | { |
| | 6409 | /* |
| | 6410 | * if we're resolving a single-object slot, we want to avoid |
| | 6411 | * plurals |
| | 6412 | */ |
| | 6413 | if (inSingleObjSlot) |
| | 6414 | ++unwantedPluralCount; |
| | 6415 | } |
| | 6416 | |
| | 6417 | incCommandCount() |
| | 6418 | { |
| | 6419 | /* increase our subcommand counter */ |
| | 6420 | ++commandCount; |
| | 6421 | } |
| | 6422 | |
| | 6423 | noteActorSpecified() |
| | 6424 | { |
| | 6425 | /* note it */ |
| | 6426 | ++actorSpecifiedCount; |
| | 6427 | } |
| | 6428 | |
| | 6429 | noteNounSlots(cnt) |
| | 6430 | { |
| | 6431 | /* |
| | 6432 | * If this is the first noun slot count we've received, remember |
| | 6433 | * it. If we already have a count, ignore the new one - we only |
| | 6434 | * want to consider the first verb phrase if there are multiple |
| | 6435 | * verb phrases, since we'll reconsider the next verb phrase when |
| | 6436 | * we're ready to execute it. |
| | 6437 | */ |
| | 6438 | if (nounSlotCount == nil) |
| | 6439 | nounSlotCount = cnt; |
| | 6440 | } |
| | 6441 | |
| | 6442 | noteWeakPhrasing(level) |
| | 6443 | { |
| | 6444 | /* note the weak phrasing level */ |
| | 6445 | weaknessLevel = level; |
| | 6446 | } |
| | 6447 | |
| | 6448 | /* don't allow action remapping while ranking */ |
| | 6449 | allowActionRemapping = nil |
| | 6450 | ; |
| | 6451 | |
| | 6452 | /* |
| | 6453 | * Another preliminary results gatherer that does everything the way the |
| | 6454 | * CommandRanking results object does, except that we perform |
| | 6455 | * interactive resolution of unknown words via OOPS. |
| | 6456 | */ |
| | 6457 | class OopsResults: CommandRanking |
| | 6458 | construct(issuingActor, targetActor) |
| | 6459 | { |
| | 6460 | /* remember the actors */ |
| | 6461 | issuingActor_ = issuingActor; |
| | 6462 | targetActor_ = targetActor; |
| | 6463 | } |
| | 6464 | |
| | 6465 | /* |
| | 6466 | * handle a phrase with unknown words |
| | 6467 | */ |
| | 6468 | unknownNounPhrase(match, resolver) |
| | 6469 | { |
| | 6470 | local wordList; |
| | 6471 | local ret; |
| | 6472 | |
| | 6473 | /* |
| | 6474 | * if the resolver can handle this set of unknown words, treat |
| | 6475 | * it as a good noun phrase |
| | 6476 | */ |
| | 6477 | wordList = match.getOrigTokenList(); |
| | 6478 | if ((ret = resolver.resolveUnknownNounPhrase(wordList)) != nil) |
| | 6479 | return ret; |
| | 6480 | |
| | 6481 | /* |
| | 6482 | * we still can't resolve it; try prompting for a correction of |
| | 6483 | * any misspelled words in the phrase |
| | 6484 | */ |
| | 6485 | tryOops(wordList, issuingActor_, targetActor_, |
| | 6486 | match.firstTokenIndex, match.tokenList, rmcCommand); |
| | 6487 | |
| | 6488 | /* |
| | 6489 | * if we got this far, we still haven't resolved it; resolve to |
| | 6490 | * an empty phrase |
| | 6491 | */ |
| | 6492 | return []; |
| | 6493 | } |
| | 6494 | |
| | 6495 | /* the command's issuing actor */ |
| | 6496 | issuingActor_ = nil |
| | 6497 | |
| | 6498 | /* the command's target actor */ |
| | 6499 | targetActor_ = nil |
| | 6500 | ; |
| | 6501 | |
| | 6502 | /* ------------------------------------------------------------------------ */ |
| | 6503 | /* |
| | 6504 | * Exception list resolver. We use this type of resolution for noun |
| | 6505 | * phrases in the "but" list of an "all but" construct. |
| | 6506 | * |
| | 6507 | * We scope the "all but" list to the objects in the "all" list, since |
| | 6508 | * there's no point in excluding objects that aren't in the "all" list. |
| | 6509 | * In addition, if a phrase in the exclusion list matches more than one |
| | 6510 | * object in the "all" list, we consider it a match to all of those |
| | 6511 | * objects, even if it's a definite phrase - this means that items in |
| | 6512 | * the "but" list are never ambiguous. |
| | 6513 | */ |
| | 6514 | class ExceptResolver: ProxyResolver |
| | 6515 | construct(mainList, mainListText, resolver) |
| | 6516 | { |
| | 6517 | /* invoke the base class constructor */ |
| | 6518 | inherited(resolver); |
| | 6519 | |
| | 6520 | /* remember the main list, from which we're excluding items */ |
| | 6521 | self.mainList = mainList; |
| | 6522 | self.mainListText = mainListText; |
| | 6523 | } |
| | 6524 | |
| | 6525 | /* we're a sub-phrase resolver */ |
| | 6526 | isSubResolver = true |
| | 6527 | |
| | 6528 | /* |
| | 6529 | * match an object's name - we'll use the disambiguation name |
| | 6530 | * resolver, so that they can give us partial names just like in |
| | 6531 | * answer to a disambiguation question |
| | 6532 | */ |
| | 6533 | matchName(obj, origTokens, adjustedTokens) |
| | 6534 | { |
| | 6535 | return obj.matchNameDisambig(origTokens, adjustedTokens); |
| | 6536 | } |
| | 6537 | |
| | 6538 | /* |
| | 6539 | * Resolve qualifiers in the enclosing main scope, since qualifier |
| | 6540 | * phrases are not part of the narrowed list - qualifiers apply to |
| | 6541 | * the main phrase from which we're excluding, not to the exclusion |
| | 6542 | * list itself. |
| | 6543 | */ |
| | 6544 | getQualifierResolver() { return origResolver; } |
| | 6545 | |
| | 6546 | /* |
| | 6547 | * determine if an object is in scope - it's in scope if it's in the |
| | 6548 | * original main list |
| | 6549 | */ |
| | 6550 | objInScope(obj) |
| | 6551 | { |
| | 6552 | return mainList.indexWhich({x: x.obj_ == obj}) != nil; |
| | 6553 | } |
| | 6554 | |
| | 6555 | /* for 'all', simply return the whole original list */ |
| | 6556 | getAll(np) |
| | 6557 | { |
| | 6558 | return mainList; |
| | 6559 | } |
| | 6560 | |
| | 6561 | /* filter ambiguous equivalents */ |
| | 6562 | filterAmbiguousEquivalents(lst, np) |
| | 6563 | { |
| | 6564 | /* |
| | 6565 | * keep all of the equivalent items in an exception list, |
| | 6566 | * because we want to exclude all of the equivalent items from |
| | 6567 | * the main list |
| | 6568 | */ |
| | 6569 | return lst; |
| | 6570 | } |
| | 6571 | |
| | 6572 | /* filter an ambiguous noun list */ |
| | 6573 | filterAmbiguousNounPhrase(lst, requiredNum, np) |
| | 6574 | { |
| | 6575 | /* |
| | 6576 | * noun phrases in an exception list are never ambiguous, |
| | 6577 | * because they implicitly refer to everything they match - |
| | 6578 | * simply return the full matching list |
| | 6579 | */ |
| | 6580 | return lst; |
| | 6581 | } |
| | 6582 | |
| | 6583 | /* filter a plural noun list */ |
| | 6584 | filterPluralPhrase(lst, np) |
| | 6585 | { |
| | 6586 | /* return all of the original plural matches */ |
| | 6587 | return lst; |
| | 6588 | } |
| | 6589 | |
| | 6590 | /* the main list from which we're excluding things */ |
| | 6591 | mainList = nil |
| | 6592 | |
| | 6593 | /* the original text for the main list */ |
| | 6594 | mainListText = '' |
| | 6595 | |
| | 6596 | /* the original underlying resolver */ |
| | 6597 | origResolver = nil |
| | 6598 | ; |
| | 6599 | |
| | 6600 | /* |
| | 6601 | * Except list results object |
| | 6602 | */ |
| | 6603 | class ExceptResults: object |
| | 6604 | construct(results) |
| | 6605 | { |
| | 6606 | /* remember the original results object */ |
| | 6607 | origResults = results; |
| | 6608 | } |
| | 6609 | |
| | 6610 | /* |
| | 6611 | * ignore failed matches in the exception list - if they try to |
| | 6612 | * exclude something that's not in the original list, the object is |
| | 6613 | * excluded to begin with |
| | 6614 | */ |
| | 6615 | noMatch(action, txt) { } |
| | 6616 | noVocabMatch(action, txt) { } |
| | 6617 | |
| | 6618 | /* ignore failed matches for possessives in the exception list */ |
| | 6619 | noMatchForPossessive(owner, txt) { } |
| | 6620 | |
| | 6621 | /* ignore failed matches for location in the exception list */ |
| | 6622 | noMatchForLocation(loc, txt) { } |
| | 6623 | |
| | 6624 | /* ignore failed matches for location in the exception list */ |
| | 6625 | nothingInLocation(loc) { } |
| | 6626 | |
| | 6627 | /* |
| | 6628 | * in case of ambiguity, simply keep everything and treat it as |
| | 6629 | * unambiguous - if they say "take coin except copper", we simply |
| | 6630 | * want to treat "copper" as unambiguously excluding every copper |
| | 6631 | * coin in the original list |
| | 6632 | */ |
| | 6633 | ambiguousNounPhrase(keeper, asker, txt, |
| | 6634 | matchList, fullMatchList, scopeList, |
| | 6635 | requiredNum, resolver) |
| | 6636 | { |
| | 6637 | /* return the full match list - exclude everything that matches */ |
| | 6638 | return fullMatchList; |
| | 6639 | } |
| | 6640 | |
| | 6641 | /* proxy anything we don't override to the underlying results object */ |
| | 6642 | propNotDefined(prop, [args]) |
| | 6643 | { |
| | 6644 | return origResults.(prop)(args...); |
| | 6645 | } |
| | 6646 | |
| | 6647 | /* my original underlying results object */ |
| | 6648 | origResults = nil |
| | 6649 | ; |
| | 6650 | |
| | 6651 | |
| | 6652 | /* ------------------------------------------------------------------------ */ |
| | 6653 | /* |
| | 6654 | * Base class for parser exceptions |
| | 6655 | */ |
| | 6656 | class ParserException: Exception |
| | 6657 | ; |
| | 6658 | |
| | 6659 | /* |
| | 6660 | * Terminate Command exception - when the parser encounters an error |
| | 6661 | * that makes it impossible to go any further processing a command, we |
| | 6662 | * throw this error to abandon the current command and proceed to the |
| | 6663 | * next. This indicates a syntax error or semantic resolution error |
| | 6664 | * that renders the command meaningless or makes it impossible to |
| | 6665 | * proceed. |
| | 6666 | * |
| | 6667 | * When this exception is thrown, all processing of the current command |
| | 6668 | * termintes immediately. No further action processing is performed; we |
| | 6669 | * don't continue iterating the command on any additional objects for a |
| | 6670 | * multi-object command; and we discard any remaining commands on the |
| | 6671 | * same command line. |
| | 6672 | */ |
| | 6673 | class TerminateCommandException: ParserException |
| | 6674 | ; |
| | 6675 | |
| | 6676 | /* |
| | 6677 | * Cancel Command Line exception. This is used to cancel any *remaining* |
| | 6678 | * commands on a command line after finishing execution of one command on |
| | 6679 | * the line. For example, if the player types "TAKE BOX AND GO NORTH", |
| | 6680 | * the handler for TAKE BOX can throw this exception to cancel everything |
| | 6681 | * later on the command line (in this case, the GO NORTH part). |
| | 6682 | * |
| | 6683 | * This is handled almost identically to TerminateCommandException. The |
| | 6684 | * only difference is that some games might want to alert the player with |
| | 6685 | * an explanation that extra commands are being ignored. |
| | 6686 | */ |
| | 6687 | class CancelCommandLineException: TerminateCommandException |
| | 6688 | ; |
| | 6689 | |
| | 6690 | |
| | 6691 | /* |
| | 6692 | * Parsing failure exception. This exception is parameterized with |
| | 6693 | * message information describing the failure, and can be used to route |
| | 6694 | * the failure notification to the issuing actor. |
| | 6695 | */ |
| | 6696 | class ParseFailureException: ParserException |
| | 6697 | construct(messageProp, [args]) |
| | 6698 | { |
| | 6699 | /* remember the message property and the parameters */ |
| | 6700 | message_ = messageProp; |
| | 6701 | args_ = args; |
| | 6702 | } |
| | 6703 | |
| | 6704 | /* notify the issuing actor of the problem */ |
| | 6705 | notifyActor(targetActor, issuingActor) |
| | 6706 | { |
| | 6707 | /* |
| | 6708 | * Tell the target actor to notify the issuing actor. We route |
| | 6709 | * the notification from the target to the issuer in keeping |
| | 6710 | * with conversation we're modelling: the issuer asked the |
| | 6711 | * target to do something, so the target is now replying with |
| | 6712 | * information explaining why the target can't do as asked. |
| | 6713 | */ |
| | 6714 | targetActor.notifyParseFailure(issuingActor, message_, args_); |
| | 6715 | } |
| | 6716 | |
| | 6717 | displayException() { "Parse failure exception"; } |
| | 6718 | |
| | 6719 | /* the message property ID */ |
| | 6720 | message_ = nil |
| | 6721 | |
| | 6722 | /* the (varargs) parameters to the message */ |
| | 6723 | args_ = nil |
| | 6724 | ; |
| | 6725 | |
| | 6726 | /* |
| | 6727 | * Exception: Retry a command with new tokens. In some cases, the |
| | 6728 | * parser processes a command by replacing the command with a new one |
| | 6729 | * and processing the new one instead of the original. When this |
| | 6730 | * happens, the parser will throw this exception, filling in newTokens_ |
| | 6731 | * with the replacement token list. |
| | 6732 | * |
| | 6733 | * Note that this is meant to replace the current command only - this |
| | 6734 | * exception effectively *edits* the current command. Any pending |
| | 6735 | * tokens after the current command should be retained when this |
| | 6736 | * exception is thrown. |
| | 6737 | */ |
| | 6738 | class RetryCommandTokensException: ParserException |
| | 6739 | construct(lst) |
| | 6740 | { |
| | 6741 | /* remember the new token list */ |
| | 6742 | newTokens_ = lst; |
| | 6743 | } |
| | 6744 | |
| | 6745 | /* |
| | 6746 | * The replacement token list. Note that this is in the same format |
| | 6747 | * as the token list returned from the tokenizer, so this is a list |
| | 6748 | * consisting of two sublists - one for the token strings, the other |
| | 6749 | * for the corresponding token types. |
| | 6750 | */ |
| | 6751 | newTokens_ = [] |
| | 6752 | ; |
| | 6753 | |
| | 6754 | /* |
| | 6755 | * Replacement command string exception. Abort any current command |
| | 6756 | * line, and start over with a brand new input string. Note that any |
| | 6757 | * pending, unparsed tokens on the previous command line should be |
| | 6758 | * discarded. |
| | 6759 | */ |
| | 6760 | class ReplacementCommandStringException: ParserException |
| | 6761 | construct(str, issuer, target) |
| | 6762 | { |
| | 6763 | /* remember the new command string */ |
| | 6764 | newCommand_ = str; |
| | 6765 | |
| | 6766 | /* |
| | 6767 | * note the issuing actor; if the caller specified this as nil, |
| | 6768 | * use the current player character as the default |
| | 6769 | */ |
| | 6770 | issuingActor_ = (issuer == nil ? libGlobal.playerChar : issuer); |
| | 6771 | |
| | 6772 | /* note the default target actor, defaulting to the player */ |
| | 6773 | targetActor_ = (target == nil ? libGlobal.playerChar : target); |
| | 6774 | } |
| | 6775 | |
| | 6776 | /* the new command string */ |
| | 6777 | newCommand_ = '' |
| | 6778 | |
| | 6779 | /* the actor issuing the command */ |
| | 6780 | issuingActor_ = nil |
| | 6781 | |
| | 6782 | /* the default target actor of the command */ |
| | 6783 | targetActor_ = nil |
| | 6784 | ; |
| | 6785 | |
| | 6786 | /* ------------------------------------------------------------------------ */ |
| | 6787 | /* |
| | 6788 | * Parser debugging helpers |
| | 6789 | */ |
| | 6790 | |
| | 6791 | #ifdef PARSER_DEBUG |
| | 6792 | /* |
| | 6793 | * Show a list of match trees |
| | 6794 | */ |
| | 6795 | showGrammarList(matchList) |
| | 6796 | { |
| | 6797 | /* show the list only if we're in debug mode */ |
| | 6798 | if (libGlobal.parserDebugMode) |
| | 6799 | { |
| | 6800 | /* show each match tree in the list */ |
| | 6801 | foreach (local cur in matchList) |
| | 6802 | { |
| | 6803 | "\n----------\n"; |
| | 6804 | showGrammar(cur, 0); |
| | 6805 | } |
| | 6806 | } |
| | 6807 | } |
| | 6808 | |
| | 6809 | /* |
| | 6810 | * Show a winning match tree |
| | 6811 | */ |
| | 6812 | showGrammarWithCaption(headline, match) |
| | 6813 | { |
| | 6814 | /* show the list only if we're in debug mode */ |
| | 6815 | if (libGlobal.parserDebugMode) |
| | 6816 | { |
| | 6817 | "\n----- <<headline>> -----\n"; |
| | 6818 | showGrammar(match, 0); |
| | 6819 | } |
| | 6820 | } |
| | 6821 | |
| | 6822 | /* |
| | 6823 | * Show a grammar tree |
| | 6824 | */ |
| | 6825 | showGrammar(prod, indent) |
| | 6826 | { |
| | 6827 | local info; |
| | 6828 | |
| | 6829 | /* if we're not in parser debug mode, do nothing */ |
| | 6830 | if (!libGlobal.parserDebugMode) |
| | 6831 | return; |
| | 6832 | |
| | 6833 | /* indent to the requested level */ |
| | 6834 | for (local i = 0 ; i < indent ; ++i) |
| | 6835 | "\ \ "; |
| | 6836 | |
| | 6837 | /* check for non-production objects */ |
| | 6838 | if (prod == nil) |
| | 6839 | { |
| | 6840 | /* this tree element isn't used - skip it */ |
| | 6841 | return; |
| | 6842 | } |
| | 6843 | else if (dataType(prod) == TypeSString) |
| | 6844 | { |
| | 6845 | /* show the item literally, and we're done */ |
| | 6846 | "'<<prod>>'\n"; |
| | 6847 | return; |
| | 6848 | } |
| | 6849 | |
| | 6850 | /* get the information for this item */ |
| | 6851 | info = prod.grammarInfo(); |
| | 6852 | |
| | 6853 | /* if it's nil, there's nothing more to do */ |
| | 6854 | if (info == nil) |
| | 6855 | { |
| | 6856 | "<no information>\n"; |
| | 6857 | return; |
| | 6858 | } |
| | 6859 | |
| | 6860 | /* show the name */ |
| | 6861 | "<<info[1]>> [<<prod.getOrigText()>>]\n"; |
| | 6862 | |
| | 6863 | /* show the subproductions */ |
| | 6864 | for (local i = 2 ; i <= info.length ; ++i) |
| | 6865 | showGrammar(info[i], indent + 1); |
| | 6866 | } |
| | 6867 | #endif /* PARSER_DEBUG */ |
| | 6868 | |