| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | *. Past-tense extensions written by Michel Nizette, and incorporated by |
| | 6 | * permission. |
| | 7 | * |
| | 8 | * TADS 3 Library - English (United States variant) implementation |
| | 9 | * |
| | 10 | * This defines the parts of the TADS 3 library that are specific to the |
| | 11 | * English language as spoken (and written) in the United States. |
| | 12 | * |
| | 13 | * We have attempted to isolate here the parts of the library that are |
| | 14 | * language-specific, so that translations to other languages or dialects |
| | 15 | * can be created by replacing this module, without changing the rest of |
| | 16 | * the library. |
| | 17 | * |
| | 18 | * In addition to this module, a separate set of US English messages are |
| | 19 | * defined in the various msg_xxx.t modules. Those modules define |
| | 20 | * messages in English for different stylistic variations. For a given |
| | 21 | * game, the author must select one of the message modules - but only |
| | 22 | * one, since they all define variations of the same messages. To |
| | 23 | * translate the library, a translator must create at least one module |
| | 24 | * defining those messages as well; only one message module is required |
| | 25 | * per language. |
| | 26 | * |
| | 27 | * The past-tense system was contributed by Michel Nizette. |
| | 28 | * |
| | 29 | *. ----- |
| | 30 | * |
| | 31 | * "Watch an immigrant struggling with a second language or a stroke |
| | 32 | * patient with a first one, or deconstruct a snatch of baby talk, or try |
| | 33 | * to program a computer to understand English, and ordinary speech |
| | 34 | * begins to look different." |
| | 35 | * |
| | 36 | *. Stephen Pinker, "The Language Instinct" |
| | 37 | */ |
| | 38 | |
| | 39 | #include "tads.h" |
| | 40 | #include "tok.h" |
| | 41 | #include "adv3.h" |
| | 42 | #include "en_us.h" |
| | 43 | #include <vector.h> |
| | 44 | #include <dict.h> |
| | 45 | #include <gramprod.h> |
| | 46 | #include <strcomp.h> |
| | 47 | |
| | 48 | |
| | 49 | /* ------------------------------------------------------------------------ */ |
| | 50 | /* |
| | 51 | * Fill in the default language for the GameInfo metadata class. |
| | 52 | */ |
| | 53 | modify GameInfoModuleID |
| | 54 | languageCode = 'en-US' |
| | 55 | ; |
| | 56 | |
| | 57 | /* ------------------------------------------------------------------------ */ |
| | 58 | /* |
| | 59 | * Simple yes/no confirmation. The caller must display a prompt; we'll |
| | 60 | * read a command line response, then return true if it's an affirmative |
| | 61 | * response, nil if not. |
| | 62 | */ |
| | 63 | yesOrNo() |
| | 64 | { |
| | 65 | /* switch to no-command mode for the interactive input */ |
| | 66 | "<.commandnone>"; |
| | 67 | |
| | 68 | /* |
| | 69 | * Read a line of input. Do not allow real-time event processing; |
| | 70 | * this type of prompt is used in the middle of a command, so we |
| | 71 | * don't want any interruptions. Note that the caller must display |
| | 72 | * any desired prompt, and since we don't allow interruptions, we |
| | 73 | * won't need to redisplay the prompt, so we pass nil for the prompt |
| | 74 | * callback. |
| | 75 | */ |
| | 76 | local str = inputManager.getInputLine(nil, nil); |
| | 77 | |
| | 78 | /* switch back to mid-command mode */ |
| | 79 | "<.commandmid>"; |
| | 80 | |
| | 81 | /* |
| | 82 | * If they answered with something starting with 'Y', it's |
| | 83 | * affirmative, otherwise it's negative. In reading the response, |
| | 84 | * ignore any leading whitespace. |
| | 85 | */ |
| | 86 | return rexMatch('<space>*[yY]', str) != nil; |
| | 87 | } |
| | 88 | |
| | 89 | /* ------------------------------------------------------------------------ */ |
| | 90 | /* |
| | 91 | * During start-up, install a case-insensitive truncating comparator in |
| | 92 | * the main dictionary. |
| | 93 | */ |
| | 94 | PreinitObject |
| | 95 | execute() |
| | 96 | { |
| | 97 | /* set up the main dictionary's comparator */ |
| | 98 | languageGlobals.setStringComparator( |
| | 99 | new StringComparator(gameMain.parserTruncLength, nil, [])); |
| | 100 | } |
| | 101 | |
| | 102 | /* |
| | 103 | * Make sure we run BEFORE the main library preinitializer, so that |
| | 104 | * we install the comparator in the dictionary before we add the |
| | 105 | * vocabulary words to the dictionary. This doesn't make any |
| | 106 | * difference in terms of the correctness of the dictionary, since |
| | 107 | * the dictionary will automatically rebuild itself whenever we |
| | 108 | * install a new comparator, but it makes the preinitialization run |
| | 109 | * a tiny bit faster by avoiding that rebuild step. |
| | 110 | */ |
| | 111 | execAfterMe = [adv3LibPreinit] |
| | 112 | ; |
| | 113 | |
| | 114 | /* ------------------------------------------------------------------------ */ |
| | 115 | /* |
| | 116 | * Language-specific globals |
| | 117 | */ |
| | 118 | languageGlobals: object |
| | 119 | /* |
| | 120 | * Set the StringComparator object for the parser. This sets the |
| | 121 | * comparator that's used in the main command parser dictionary. |
| | 122 | */ |
| | 123 | setStringComparator(sc) |
| | 124 | { |
| | 125 | /* remember it globally, and set it in the main dictionary */ |
| | 126 | dictComparator = sc; |
| | 127 | cmdDict.setComparator(sc); |
| | 128 | } |
| | 129 | |
| | 130 | /* |
| | 131 | * The character to use to separate groups of digits in large |
| | 132 | * numbers. US English uses commas; most Europeans use periods. |
| | 133 | * |
| | 134 | * Note that this setting does not affect system-level BigNumber |
| | 135 | * formatting, but this information can be passed when calling |
| | 136 | * BigNumber formatting routines. |
| | 137 | */ |
| | 138 | digitGroupSeparator = ',' |
| | 139 | |
| | 140 | /* |
| | 141 | * The decimal point to display in floating-point numbers. US |
| | 142 | * English uses a period; most Europeans use a comma. |
| | 143 | * |
| | 144 | * Note that this setting doesn't affect system-level BigNumber |
| | 145 | * formatting, but this information can be passed when calling |
| | 146 | * BigNumber formatting routines. |
| | 147 | */ |
| | 148 | decimalPointCharacter = '.' |
| | 149 | |
| | 150 | /* the main dictionary's string comparator */ |
| | 151 | dictComparator = nil |
| | 152 | ; |
| | 153 | |
| | 154 | |
| | 155 | /* ------------------------------------------------------------------------ */ |
| | 156 | /* |
| | 157 | * Language-specific extension of the default gameMain object |
| | 158 | * implementation. |
| | 159 | */ |
| | 160 | modify GameMainDef |
| | 161 | /* |
| | 162 | * Option setting: the parser's truncation length for player input. |
| | 163 | * As a convenience to the player, we can allow the player to |
| | 164 | * truncate long words, entering only the first, say, 6 characters. |
| | 165 | * For example, rather than typing "x flashlight", we could allow the |
| | 166 | * player to simply type "x flashl" - truncating "flashlight" to six |
| | 167 | * letters. |
| | 168 | * |
| | 169 | * We use a default truncation length of 6, but games can change this |
| | 170 | * by overriding this property in gameMain. We use a default of 6 |
| | 171 | * mostly because that's what the old Infocom games did - many |
| | 172 | * long-time IF players are accustomed to six-letter truncation from |
| | 173 | * those games. Shorter lengths are superficially more convenient |
| | 174 | * for the player, obviously, but there's a trade-off, which is that |
| | 175 | * shorter truncation lengths create more potential for ambiguity. |
| | 176 | * For some games, a longer length might actually be better for the |
| | 177 | * player, because it would reduce spurious ambiguity due to the |
| | 178 | * parser matching short input against long vocabulary words. |
| | 179 | * |
| | 180 | * If you don't want to allow the player to truncate long words at |
| | 181 | * all, set this to nil. This will require the player to type every |
| | 182 | * word in its entirety. |
| | 183 | * |
| | 184 | * Note that changing this property dynamicaly will have no effect. |
| | 185 | * The library only looks at it once, during library initialization |
| | 186 | * at the very start of the game. If you want to change the |
| | 187 | * truncation length dynamically, you must instead create a new |
| | 188 | * StringComparator object with the new truncation setting, and call |
| | 189 | * languageGlobals.setStringComparator() to select the new object. |
| | 190 | */ |
| | 191 | parserTruncLength = 6 |
| | 192 | |
| | 193 | /* |
| | 194 | * Option: are we currently using a past tense narrative? By |
| | 195 | * default, we aren't. |
| | 196 | * |
| | 197 | * This property can be reset at any time during the game in order to |
| | 198 | * switch between the past and present tenses. The macro |
| | 199 | * setPastTense can be used for this purpose: it just provides a |
| | 200 | * shorthand for setting gameMain.usePastTense directly. |
| | 201 | * |
| | 202 | * Authors who want their game to start in the past tense can achieve |
| | 203 | * this by overriding this property on their gameMain object and |
| | 204 | * giving it a value of true. |
| | 205 | */ |
| | 206 | usePastTense = nil |
| | 207 | ; |
| | 208 | |
| | 209 | |
| | 210 | /* ------------------------------------------------------------------------ */ |
| | 211 | /* |
| | 212 | * Language-specific modifications for ThingState. |
| | 213 | */ |
| | 214 | modify ThingState |
| | 215 | /* |
| | 216 | * Our state-specific tokens. This is a list of vocabulary words |
| | 217 | * that are state-specific: that is, if a word is in this list, the |
| | 218 | * word can ONLY refer to this object if the object is in a state |
| | 219 | * with that word in its list. |
| | 220 | * |
| | 221 | * The idea is that you set up the object's "static" vocabulary with |
| | 222 | * the *complete* list of words for all of its possible states. For |
| | 223 | * example: |
| | 224 | * |
| | 225 | *. + Matchstick 'lit unlit match'; |
| | 226 | * |
| | 227 | * Then, you define the states: in the "lit" state, the word 'lit' is |
| | 228 | * in the stateTokens list; in the "unlit" state, the word 'unlit' is |
| | 229 | * in the list. By putting the words in the state lists, you |
| | 230 | * "reserve" the words to their respective states. When the player |
| | 231 | * enters a command, the parser will limit object matches so that the |
| | 232 | * reserved state-specific words can only refer to objects in the |
| | 233 | * corresponding states. Hence, if the player refers to a "lit |
| | 234 | * match", the word 'lit' will only match an object in the "lit" |
| | 235 | * state, because 'lit' is a reserved state-specific word associated |
| | 236 | * with the "lit" state. |
| | 237 | * |
| | 238 | * You can re-use a word in multiple states. For example, you could |
| | 239 | * have a "red painted" state and a "blue painted" state, along with |
| | 240 | * an "unpainted" state. |
| | 241 | */ |
| | 242 | stateTokens = [] |
| | 243 | |
| | 244 | /* |
| | 245 | * Match the name of an object in this state. We'll check the token |
| | 246 | * list for any words that apply only to *other* states the object |
| | 247 | * can assume; if we find any, we'll reject the match, since the |
| | 248 | * phrase must be referring to an object in a different state. |
| | 249 | */ |
| | 250 | matchName(obj, origTokens, adjustedTokens, states) |
| | 251 | { |
| | 252 | /* scan each word in our adjusted token list */ |
| | 253 | for (local i = 1, local len = adjustedTokens.length() ; |
| | 254 | i <= len ; i += 2) |
| | 255 | { |
| | 256 | /* get the current token */ |
| | 257 | local cur = adjustedTokens[i]; |
| | 258 | |
| | 259 | /* |
| | 260 | * If this token is in our own state-specific token list, |
| | 261 | * it's acceptable as a match to this object. (It doesn't |
| | 262 | * matter whether or not it's in any other state's token list |
| | 263 | * if it's in our own, because its presence in our own makes |
| | 264 | * it an acceptable matching word when we're in this state.) |
| | 265 | */ |
| | 266 | if (stateTokens.indexWhich({t: t == cur}) != nil) |
| | 267 | continue; |
| | 268 | |
| | 269 | /* |
| | 270 | * It's not in our own state-specific token list. Check to |
| | 271 | * see if the word appears in ANOTHER state's token list: if |
| | 272 | * it does, then this word CAN'T match an object in this |
| | 273 | * state, because the token is special to that other state |
| | 274 | * and thus can't refer to an object in a state without the |
| | 275 | * token. |
| | 276 | */ |
| | 277 | if (states.indexWhich( |
| | 278 | {s: s.stateTokens.indexOf(cur) != nil}) != nil) |
| | 279 | return nil; |
| | 280 | } |
| | 281 | |
| | 282 | /* we didn't find any objection, so we can match this phrase */ |
| | 283 | return obj; |
| | 284 | } |
| | 285 | |
| | 286 | /* |
| | 287 | * Check a token list for any tokens matching any of our |
| | 288 | * state-specific words. Returns true if we find any such words, |
| | 289 | * nil if not. |
| | 290 | * |
| | 291 | * 'toks' is the *adjusted* token list used in matchName(). |
| | 292 | */ |
| | 293 | findStateToken(toks) |
| | 294 | { |
| | 295 | /* |
| | 296 | * Scan the token list for a match to any of our state-specific |
| | 297 | * words. Since we're using the adjusted token list, every |
| | 298 | * other entry is a part of speech, so work through the list in |
| | 299 | * pairs. |
| | 300 | */ |
| | 301 | for (local i = 1, local len = toks.length() ; i <= len ; i += 2) |
| | 302 | { |
| | 303 | /* |
| | 304 | * if this token matches any of our state tokens, indicate |
| | 305 | * that we found a match |
| | 306 | */ |
| | 307 | if (stateTokens.indexWhich({x: x == toks[i]}) != nil) |
| | 308 | return true; |
| | 309 | } |
| | 310 | |
| | 311 | /* we didn't find a match */ |
| | 312 | return nil; |
| | 313 | } |
| | 314 | |
| | 315 | /* get our name */ |
| | 316 | listName(lst) { return listName_; } |
| | 317 | |
| | 318 | /* |
| | 319 | * our list name setting - we define this so that we can be easily |
| | 320 | * initialized with a template (we can't initialize listName() |
| | 321 | * directly in this manner because it's a method, but we define the |
| | 322 | * listName() method to simply return this property value, which we |
| | 323 | * can initialize with a template) |
| | 324 | */ |
| | 325 | listName_ = nil |
| | 326 | ; |
| | 327 | |
| | 328 | /* ------------------------------------------------------------------------ */ |
| | 329 | /* |
| | 330 | * Language-specific modifications for VocabObject. |
| | 331 | */ |
| | 332 | modify VocabObject |
| | 333 | /* |
| | 334 | * The vocabulary initializer string for the object - this string |
| | 335 | * can be initialized (most conveniently via a template) to a string |
| | 336 | * of this format: |
| | 337 | * |
| | 338 | * 'adj adj adj noun/noun/noun*plural plural plural' |
| | 339 | * |
| | 340 | * The noun part of the string can be a hyphen, '-', in which case |
| | 341 | * it means that the string doesn't specify a noun or plural at all. |
| | 342 | * This can be useful when nouns and plurals are all inherited from |
| | 343 | * base classes, and only adjectives are to be specified. (In fact, |
| | 344 | * any word that consists of a single hyphen will be ignored, but |
| | 345 | * this is generally only useful for the adjective-only case.) |
| | 346 | * |
| | 347 | * During preinitialization, we'll parse this string and generate |
| | 348 | * dictionary entries and individual vocabulary properties for the |
| | 349 | * parts of speech we find. |
| | 350 | * |
| | 351 | * Note that the format described above is specific to the English |
| | 352 | * version of the library. Non-English versions will probably want |
| | 353 | * to use different formats to conveniently encode appropriate |
| | 354 | * language-specific information in the initializer string. See the |
| | 355 | * comments for initializeVocabWith() for more details. |
| | 356 | * |
| | 357 | * You can use the special wildcard # to match any numeric |
| | 358 | * adjective. This only works as a wildcard when it stands alone, |
| | 359 | * so a string like "7#" is matched as that literal string, not as a |
| | 360 | * wildcard. If you want to use a pound sign as a literal |
| | 361 | * adjective, just put it in double quotes. |
| | 362 | * |
| | 363 | * You can use the special wildcard "\u0001" (include the double |
| | 364 | * quotes within the string) to match any literal adjective. This |
| | 365 | * is the literal adjective equivalent of the pound sign. We use |
| | 366 | * this funny character value because it is unlikely ever to be |
| | 367 | * interesting in user input. |
| | 368 | * |
| | 369 | * If you want to match any string for a noun and/or adjective, you |
| | 370 | * can't do it with this property. Instead, just add the property |
| | 371 | * value noun='*' to the object. |
| | 372 | */ |
| | 373 | vocabWords = '' |
| | 374 | |
| | 375 | /* |
| | 376 | * On dynamic construction, initialize our vocabulary words and add |
| | 377 | * them to the dictionary. |
| | 378 | */ |
| | 379 | construct() |
| | 380 | { |
| | 381 | /* initialize our vocabulary words from vocabWords */ |
| | 382 | initializeVocab(); |
| | 383 | |
| | 384 | /* add our vocabulary words to the dictionary */ |
| | 385 | addToDictionary(&noun); |
| | 386 | addToDictionary(&adjective); |
| | 387 | addToDictionary(&plural); |
| | 388 | addToDictionary(&adjApostS); |
| | 389 | addToDictionary(&literalAdjective); |
| | 390 | } |
| | 391 | |
| | 392 | /* add the words from a dictionary property to the global dictionary */ |
| | 393 | addToDictionary(prop) |
| | 394 | { |
| | 395 | /* if we have any words defined, add them to the dictionary */ |
| | 396 | if (self.(prop) != nil) |
| | 397 | cmdDict.addWord(self, self.(prop), prop); |
| | 398 | } |
| | 399 | |
| | 400 | /* initialize the vocabulary from vocabWords */ |
| | 401 | initializeVocab() |
| | 402 | { |
| | 403 | /* inherit vocabulary from this class and its superclasses */ |
| | 404 | inheritVocab(self, new Vector(10)); |
| | 405 | } |
| | 406 | |
| | 407 | /* |
| | 408 | * Inherit vocabulary from this class and its superclasses, adding |
| | 409 | * the words to the given target object. 'target' is the object to |
| | 410 | * which we add our vocabulary words, and 'done' is a vector of |
| | 411 | * classes that have been visited so far. |
| | 412 | * |
| | 413 | * Since a class can be inherited more than once in an inheritance |
| | 414 | * tree (for example, a class can have multiple superclasses, each |
| | 415 | * of which have a common base class), we keep a vector of all of |
| | 416 | * the classes we've visited. If we're already in the vector, we'll |
| | 417 | * skip adding vocabulary for this class or its superclasses, since |
| | 418 | * we must have already traversed this branch of the tree from |
| | 419 | * another subclass. |
| | 420 | */ |
| | 421 | inheritVocab(target, done) |
| | 422 | { |
| | 423 | /* |
| | 424 | * if we're in the list of classes handled already, don't bother |
| | 425 | * visiting me again |
| | 426 | */ |
| | 427 | if (done.indexOf(self) != nil) |
| | 428 | return; |
| | 429 | |
| | 430 | /* add myself to the list of classes handled already */ |
| | 431 | done.append(self); |
| | 432 | |
| | 433 | /* |
| | 434 | * add words from our own vocabWords to the target object (but |
| | 435 | * only if it's our own - not if it's only inherited, as we'll |
| | 436 | * pick up the inherited ones explicitly in a bit) |
| | 437 | */ |
| | 438 | if (propDefined(&vocabWords, PropDefDirectly)) |
| | 439 | target.initializeVocabWith(vocabWords); |
| | 440 | |
| | 441 | /* add vocabulary from each of our superclasses */ |
| | 442 | foreach (local sc in getSuperclassList()) |
| | 443 | sc.inheritVocab(target, done); |
| | 444 | } |
| | 445 | |
| | 446 | /* |
| | 447 | * Initialize our vocabulary from the given string. This parses the |
| | 448 | * given vocabulary initializer string and adds the words defined in |
| | 449 | * the string to the dictionary. |
| | 450 | * |
| | 451 | * Note that this parsing is intentionally located in the |
| | 452 | * English-specific part of the library, because it is expected that |
| | 453 | * other languages will want to define their own vocabulary |
| | 454 | * initialization string formats. For example, a language with |
| | 455 | * gendered nouns might want to use gendered articles in the |
| | 456 | * initializer string as an author-friendly way of defining noun |
| | 457 | * gender; languages with inflected (declined) nouns and/or |
| | 458 | * adjectives might want to encode inflected forms in the |
| | 459 | * initializer. Non-English language implementations are free to |
| | 460 | * completely redefine the format - there's no need to follow the |
| | 461 | * conventions of the English format in other languages where |
| | 462 | * different formats would be more convenient. |
| | 463 | */ |
| | 464 | initializeVocabWith(str) |
| | 465 | { |
| | 466 | local sectPart; |
| | 467 | local modList = []; |
| | 468 | |
| | 469 | /* start off in the adjective section */ |
| | 470 | sectPart = &adjective; |
| | 471 | |
| | 472 | /* scan the string until we run out of text */ |
| | 473 | while (str != '') |
| | 474 | { |
| | 475 | local len; |
| | 476 | local cur; |
| | 477 | |
| | 478 | /* |
| | 479 | * if it starts with a quote, find the close quote; |
| | 480 | * otherwise, find the end of the current token by seeking |
| | 481 | * the next delimiter |
| | 482 | */ |
| | 483 | if (str.startsWith('"')) |
| | 484 | { |
| | 485 | /* find the close quote */ |
| | 486 | len = str.find('"', 2); |
| | 487 | } |
| | 488 | else |
| | 489 | { |
| | 490 | /* no quotes - find the next delimiter */ |
| | 491 | len = rexMatch('<^space|star|/>*', str); |
| | 492 | } |
| | 493 | |
| | 494 | /* if there's no match, use the whole rest of the string */ |
| | 495 | if (len == nil) |
| | 496 | len = str.length(); |
| | 497 | |
| | 498 | /* if there's anything before the delimiter, extract it */ |
| | 499 | if (len != 0) |
| | 500 | { |
| | 501 | /* extract the part up to but not including the delimiter */ |
| | 502 | cur = str.substr(1, len); |
| | 503 | |
| | 504 | /* |
| | 505 | * if we're in the adjectives, and either this is the |
| | 506 | * last token or the next delimiter is not a space, this |
| | 507 | * is implicitly a noun |
| | 508 | */ |
| | 509 | if (sectPart == &adjective |
| | 510 | && (len == str.length() |
| | 511 | || str.substr(len + 1, 1) != ' ')) |
| | 512 | { |
| | 513 | /* move to the noun section */ |
| | 514 | sectPart = &noun; |
| | 515 | } |
| | 516 | |
| | 517 | /* |
| | 518 | * if the word isn't a single hyphen (in which case it's |
| | 519 | * a null word placeholder, not an actual vocabulary |
| | 520 | * word), add it to our own appropriate part-of-speech |
| | 521 | * property and to the dictionary |
| | 522 | */ |
| | 523 | if (cur != '-') |
| | 524 | { |
| | 525 | /* |
| | 526 | * by default, use the part of speech of the current |
| | 527 | * string section as the part of speech for this |
| | 528 | * word |
| | 529 | */ |
| | 530 | local wordPart = sectPart; |
| | 531 | |
| | 532 | /* |
| | 533 | * Check for parentheses, which indicate that the |
| | 534 | * token is "weak." This doesn't affect anything |
| | 535 | * about the token or its part of speech except that |
| | 536 | * we must include the token in our list of weak |
| | 537 | * tokens. |
| | 538 | */ |
| | 539 | if (cur.startsWith('(') && cur.endsWith(')')) |
| | 540 | { |
| | 541 | /* it's a weak token - remove the parens */ |
| | 542 | cur = cur.substr(2, cur.length() - 2); |
| | 543 | |
| | 544 | /* |
| | 545 | * if we don't have a weak token list yet, |
| | 546 | * create the list |
| | 547 | */ |
| | 548 | if (weakTokens == nil) |
| | 549 | weakTokens = []; |
| | 550 | |
| | 551 | /* add the token to the weak list */ |
| | 552 | weakTokens += cur; |
| | 553 | } |
| | 554 | |
| | 555 | /* |
| | 556 | * Check for special formats: quoted strings, |
| | 557 | * apostrophe-S words. These formats are mutually |
| | 558 | * exclusive. |
| | 559 | */ |
| | 560 | if (cur.startsWith('"')) |
| | 561 | { |
| | 562 | /* |
| | 563 | * It's a quoted string, so it's a literal |
| | 564 | * adjective. |
| | 565 | */ |
| | 566 | |
| | 567 | /* remove the quote(s) */ |
| | 568 | if (cur.endsWith('"')) |
| | 569 | cur = cur.substr(2, cur.length() - 2); |
| | 570 | else |
| | 571 | cur = cur.substr(2); |
| | 572 | |
| | 573 | /* change the part of speech to 'literal adjective' */ |
| | 574 | wordPart = &literalAdjective; |
| | 575 | } |
| | 576 | else if (cur.endsWith('\'s')) |
| | 577 | { |
| | 578 | /* |
| | 579 | * It's an apostrophe-s word. Remove the "'s" |
| | 580 | * suffix and add the root word using adjApostS |
| | 581 | * as the part of speech. The grammar rules are |
| | 582 | * defined to allow this part of speech to be |
| | 583 | * used exclusively with "'s" suffixes in input. |
| | 584 | * Since the tokenizer always pulls the "'s" |
| | 585 | * suffix off of a word in the input, we have to |
| | 586 | * store any vocabulary words with "'s" suffixes |
| | 587 | * the same way, with the "'s" suffixes removed. |
| | 588 | */ |
| | 589 | |
| | 590 | /* change the part of speech to adjApostS */ |
| | 591 | wordPart = &adjApostS; |
| | 592 | |
| | 593 | /* remove the "'s" suffix from the string */ |
| | 594 | cur = cur.substr(1, cur.length() - 2); |
| | 595 | } |
| | 596 | |
| | 597 | /* add the word to our own list for this part of speech */ |
| | 598 | if (self.(wordPart) == nil) |
| | 599 | self.(wordPart) = [cur]; |
| | 600 | else |
| | 601 | self.(wordPart) += cur; |
| | 602 | |
| | 603 | /* add it to the dictionary */ |
| | 604 | cmdDict.addWord(self, cur, wordPart); |
| | 605 | |
| | 606 | if (cur.endsWith('.')) |
| | 607 | { |
| | 608 | local abbr; |
| | 609 | |
| | 610 | /* |
| | 611 | * It ends with a period, so this is an |
| | 612 | * abbreviated word. Enter the abbreviation |
| | 613 | * both with and without the period. The normal |
| | 614 | * handling will enter it with the period, so we |
| | 615 | * only need to enter it specifically without. |
| | 616 | */ |
| | 617 | abbr = cur.substr(1, cur.length() - 1); |
| | 618 | self.(wordPart) += abbr; |
| | 619 | cmdDict.addWord(self, abbr, wordPart); |
| | 620 | } |
| | 621 | |
| | 622 | /* note that we added to this list */ |
| | 623 | if (modList.indexOf(wordPart) == nil) |
| | 624 | modList += wordPart; |
| | 625 | } |
| | 626 | } |
| | 627 | |
| | 628 | /* if we have a delimiter, see what we have */ |
| | 629 | if (len + 1 < str.length()) |
| | 630 | { |
| | 631 | /* check the delimiter */ |
| | 632 | switch(str.substr(len + 1, 1)) |
| | 633 | { |
| | 634 | case ' ': |
| | 635 | /* stick with the current part */ |
| | 636 | break; |
| | 637 | |
| | 638 | case '*': |
| | 639 | /* start plurals */ |
| | 640 | sectPart = &plural; |
| | 641 | break; |
| | 642 | |
| | 643 | case '/': |
| | 644 | /* start alternative nouns */ |
| | 645 | sectPart = &noun; |
| | 646 | break; |
| | 647 | } |
| | 648 | |
| | 649 | /* remove the part up to and including the delimiter */ |
| | 650 | str = str.substr(len + 2); |
| | 651 | |
| | 652 | /* skip any additional spaces following the delimiter */ |
| | 653 | if ((len = rexMatch('<space>+', str)) != nil) |
| | 654 | str = str.substr(len + 1); |
| | 655 | } |
| | 656 | else |
| | 657 | { |
| | 658 | /* we've exhausted the string - we're done */ |
| | 659 | break; |
| | 660 | } |
| | 661 | } |
| | 662 | |
| | 663 | /* uniquify each word list we updated */ |
| | 664 | foreach (local p in modList) |
| | 665 | self.(p) = self.(p).getUnique(); |
| | 666 | } |
| | 667 | ; |
| | 668 | |
| | 669 | /* ------------------------------------------------------------------------ */ |
| | 670 | /* |
| | 671 | * Language-specific modifications for Thing. This class contains the |
| | 672 | * methods and properties of Thing that need to be replaced when the |
| | 673 | * library is translated to another language. |
| | 674 | * |
| | 675 | * The properties and methods defined here should generally never be used |
| | 676 | * by language-independent library code, because everything defined here |
| | 677 | * is specific to English. Translators are thus free to change the |
| | 678 | * entire scheme defined here. For example, the notions of number and |
| | 679 | * gender are confined to the English part of the library; other language |
| | 680 | * implementations can completely replace these attributes, so they're |
| | 681 | * not constrained to emulate their own number and gender systems with |
| | 682 | * the English system. |
| | 683 | */ |
| | 684 | modify Thing |
| | 685 | /* |
| | 686 | * Flag that this object's name is rendered as a plural (this |
| | 687 | * applies to both a singular noun with plural usage, such as |
| | 688 | * "pants" or "scissors," and an object used in the world model to |
| | 689 | * represent a collection of real-world objects, such as "shrubs"). |
| | 690 | */ |
| | 691 | isPlural = nil |
| | 692 | |
| | 693 | /* |
| | 694 | * Flag that this is object's name is a "mass noun" - that is, a |
| | 695 | * noun denoting a continuous (effectively infinitely divisible) |
| | 696 | * substance or material, such as water, wood, or popcorn; and |
| | 697 | * certain abstract concepts, such as knowledge or beauty. Mass |
| | 698 | * nouns are never rendered in the plural, and use different |
| | 699 | * determiners than ordinary ("count") nouns: "some popcorn" vs "a |
| | 700 | * kernel", for example. |
| | 701 | */ |
| | 702 | isMassNoun = nil |
| | 703 | |
| | 704 | /* |
| | 705 | * Flags indicating that the object should be referred to with |
| | 706 | * gendered pronouns (such as 'he' or 'she' rather than 'it'). |
| | 707 | * |
| | 708 | * Note that these flags aren't mutually exclusive, so it's legal |
| | 709 | * for the object to have both masculine and feminine usage. This |
| | 710 | * can be useful when creating collective objects that represent |
| | 711 | * more than one individual, for example. |
| | 712 | */ |
| | 713 | isHim = nil |
| | 714 | isHer = nil |
| | 715 | |
| | 716 | /* |
| | 717 | * Flag indicating that the object can be referred to with a neuter |
| | 718 | * pronoun ('it'). By default, this is true if the object has |
| | 719 | * neither masculine nor feminine gender, but it can be overridden |
| | 720 | * so that an object has both gendered and ungendered usage. This |
| | 721 | * can be useful for collective objects, as well as for cases where |
| | 722 | * gendered usage varies by speaker or situation, such as animals. |
| | 723 | */ |
| | 724 | isIt |
| | 725 | { |
| | 726 | /* by default, we're an 'it' if we're not a 'him' or a 'her' */ |
| | 727 | return !(isHim || isHer); |
| | 728 | } |
| | 729 | |
| | 730 | /* |
| | 731 | * Test to see if we can match the pronouns 'him', 'her', 'it', and |
| | 732 | * 'them'. By default, these simply test the corresponding isXxx |
| | 733 | * flags (except 'canMatchThem', which tests 'isPlural' to see if the |
| | 734 | * name has plural usage). |
| | 735 | */ |
| | 736 | canMatchHim = (isHim) |
| | 737 | canMatchHer = (isHer) |
| | 738 | canMatchIt = (isIt) |
| | 739 | canMatchThem = (isPlural) |
| | 740 | |
| | 741 | /* can we match the given PronounXxx pronoun type specifier? */ |
| | 742 | canMatchPronounType(typ) |
| | 743 | { |
| | 744 | /* check the type, and return the appropriate indicator property */ |
| | 745 | switch (typ) |
| | 746 | { |
| | 747 | case PronounHim: |
| | 748 | return canMatchHim; |
| | 749 | |
| | 750 | case PronounHer: |
| | 751 | return canMatchHer; |
| | 752 | |
| | 753 | case PronounIt: |
| | 754 | return canMatchIt; |
| | 755 | |
| | 756 | case PronounThem: |
| | 757 | return canMatchThem; |
| | 758 | |
| | 759 | default: |
| | 760 | return nil; |
| | 761 | } |
| | 762 | } |
| | 763 | |
| | 764 | /* |
| | 765 | * The grammatical cardinality of this item when it appears in a |
| | 766 | * list. This is used to ensure verb agreement when mentioning the |
| | 767 | * item in a list of items. ("Cardinality" is a fancy word for "how |
| | 768 | * many items does this look like"). |
| | 769 | * |
| | 770 | * English only distinguishes two degrees of cardinality in its |
| | 771 | * grammar: one, or many. That is, when constructing a sentence, the |
| | 772 | * only thing the grammar cares about is whether an object is |
| | 773 | * singular or plural: IT IS on the table, THEY ARE on the table. |
| | 774 | * Since English only distinguishes these two degrees, two is the |
| | 775 | * same as a hundred is the same as a million for grammatical |
| | 776 | * purposes, so we'll consider our cardinality to be 2 if we're |
| | 777 | * plural, 1 otherwise. |
| | 778 | * |
| | 779 | * Some languages don't express cardinality at all in their grammar, |
| | 780 | * and others distinguish cardinality in greater detail than just |
| | 781 | * singular-vs-plural, which is why this method has to be in the |
| | 782 | * language-specific part of the library. |
| | 783 | */ |
| | 784 | listCardinality(lister) { return isPlural ? 2 : 1; } |
| | 785 | |
| | 786 | /* |
| | 787 | * Proper name flag. This indicates that the 'name' property is the |
| | 788 | * name of a person or place. We consider proper names to be fully |
| | 789 | * qualified, so we don't add articles for variations on the name |
| | 790 | * such as 'theName'. |
| | 791 | */ |
| | 792 | isProperName = nil |
| | 793 | |
| | 794 | /* |
| | 795 | * Qualified name flag. This indicates that the object name, as |
| | 796 | * given by the 'name' property, is already fully qualified, so |
| | 797 | * doesn't need qualification by an article like "the" or "a" when |
| | 798 | * it appears in a sentence. By default, a name is considered |
| | 799 | * qualified if it's a proper name, but this can be overridden to |
| | 800 | * mark a non-proper name as qualified when needed. |
| | 801 | */ |
| | 802 | isQualifiedName = (isProperName) |
| | 803 | |
| | 804 | /* |
| | 805 | * The name of the object - this is a string giving the object's |
| | 806 | * short description, for constructing sentences that refer to the |
| | 807 | * object by name. Each instance should override this to define the |
| | 808 | * name of the object. This string should not contain any articles; |
| | 809 | * we use this string as the root to generate various forms of the |
| | 810 | * object's name for use in different places in sentences. |
| | 811 | */ |
| | 812 | name = '' |
| | 813 | |
| | 814 | /* |
| | 815 | * The name of the object, for the purposes of disambiguation |
| | 816 | * prompts. This should almost always be the object's ordinary |
| | 817 | * name, so we return self.name by default. |
| | 818 | * |
| | 819 | * In rare cases, it might be desirable to override this. In |
| | 820 | * particular, if a game has two objects that are NOT defined as |
| | 821 | * basic equivalents of one another (which means that the parser |
| | 822 | * will always ask for disambiguation when the two are ambiguous |
| | 823 | * with one another), but the two nonetheless have identical 'name' |
| | 824 | * properties, this property should be overridden for one or both |
| | 825 | * objects to give them different names. This will ensure that we |
| | 826 | * avoid asking questions of the form "which do you mean, the coin, |
| | 827 | * or the coin?". In most cases, non-equivalent objects will have |
| | 828 | * distinct 'name' properties to begin with, so this is not usually |
| | 829 | * an issue. |
| | 830 | * |
| | 831 | * When overriding this method, take care to override |
| | 832 | * theDisambigName, aDisambigName, countDisambigName, and/or |
| | 833 | * pluralDisambigName as needed. Those routines must be overridden |
| | 834 | * only when the default algorithms for determining articles and |
| | 835 | * plurals fail to work properly for the disambigName (for example, |
| | 836 | * the indefinite article algorithm fails with silent-h words like |
| | 837 | * "hour", so if disambigName is "hour", aDisambigName must be |
| | 838 | * overridden). In most cases, the automatic algorithms will |
| | 839 | * produce acceptable results, so the default implementations of |
| | 840 | * these other routines can be used without customization. |
| | 841 | */ |
| | 842 | disambigName = (name) |
| | 843 | |
| | 844 | /* |
| | 845 | * The "equivalence key" is the value we use to group equivalent |
| | 846 | * objects. Note that we can only treat objects as equivalent when |
| | 847 | * they're explicitly marked with isEquivalent=true, so the |
| | 848 | * equivalence key is irrelevant for objects not so marked. |
| | 849 | * |
| | 850 | * Since the main point of equivalence is to allow creation of groups |
| | 851 | * of like-named objects that are interchangeable in listings and in |
| | 852 | * command input, we use the basic disambiguation name as the |
| | 853 | * equivalence key. |
| | 854 | */ |
| | 855 | equivalenceKey = (disambigName) |
| | 856 | |
| | 857 | /* |
| | 858 | * The definite-article name for disambiguation prompts. |
| | 859 | * |
| | 860 | * By default, if the disambiguation name is identical to the |
| | 861 | * regular name (i.e, the string returned by self.disambigName is |
| | 862 | * the same as the string returned by self.name), then we simply |
| | 863 | * return self.theName. Since the base name is the same in either |
| | 864 | * case, presumably the definite article names should be the same as |
| | 865 | * well. This way, if the object overrides theName to do something |
| | 866 | * special, then we'll use the same definite-article name for |
| | 867 | * disambiguation prompts. |
| | 868 | * |
| | 869 | * If the disambigName isn't the same as the regular name, then |
| | 870 | * we'll apply the same algorithm to the base disambigName that we |
| | 871 | * normally do to the regular name to produce the theName. This |
| | 872 | * way, if the disambigName is overridden, we'll use the overridden |
| | 873 | * disambigName to produce the definite-article version, using the |
| | 874 | * standard definite-article algorithm. |
| | 875 | * |
| | 876 | * Note that there's an aspect of this conditional approach that |
| | 877 | * might not be obvious. It might look as though the test is |
| | 878 | * redundant: if name == disambigName, after all, and the default |
| | 879 | * theName returns theNameFrom(name), then this ought to be |
| | 880 | * identical to returning theNameFrom(disambigName). The subtlety |
| | 881 | * is that theName could be overridden to produce a custom result, |
| | 882 | * in which case returning theNameFrom(disambigName) would return |
| | 883 | * something different, which probably wouldn't be correct: the |
| | 884 | * whole reason theName would be overridden is that the algorithmic |
| | 885 | * determination (theNameFrom) gets it wrong. So, by calling |
| | 886 | * theName directly when disambigName is the same as name, we are |
| | 887 | * assured that we pick up any override in theName. |
| | 888 | * |
| | 889 | * Note that in rare cases, neither of these default approaches will |
| | 890 | * produce the right result; this will happen if the object uses a |
| | 891 | * custom disambigName, but that name doesn't fit the normal |
| | 892 | * algorithmic pattern for applying a definite article. In these |
| | 893 | * cases, the object should simply override this method to specify |
| | 894 | * the custom name. |
| | 895 | */ |
| | 896 | theDisambigName = (name == disambigName |
| | 897 | ? theName : theNameFrom(disambigName)) |
| | 898 | |
| | 899 | /* |
| | 900 | * The indefinite-article name for disambiguation prompts. We use |
| | 901 | * the same logic here as in theDisambigName. |
| | 902 | */ |
| | 903 | aDisambigName = (name == disambigName ? aName : aNameFrom(disambigName)) |
| | 904 | |
| | 905 | /* |
| | 906 | * The counted name for disambiguation prompts. We use the same |
| | 907 | * logic here as in theDisambigName. |
| | 908 | */ |
| | 909 | countDisambigName(cnt) |
| | 910 | { |
| | 911 | return (name == disambigName && pluralName == pluralDisambigName |
| | 912 | ? countName(cnt) |
| | 913 | : countNameFrom(cnt, disambigName, pluralDisambigName)); |
| | 914 | } |
| | 915 | |
| | 916 | /* |
| | 917 | * The plural name for disambiguation prompts. We use the same |
| | 918 | * logic here as in theDisambigName. |
| | 919 | */ |
| | 920 | pluralDisambigName = (name == disambigName |
| | 921 | ? pluralName : pluralNameFrom(disambigName)) |
| | 922 | |
| | 923 | /* |
| | 924 | * The name of the object, for the purposes of disambiguation prompts |
| | 925 | * to disambiguation among this object and basic equivalents of this |
| | 926 | * object (i.e., objects of the same class marked with |
| | 927 | * isEquivalent=true). |
| | 928 | * |
| | 929 | * This is used in disambiguation prompts in place of the actual text |
| | 930 | * typed by the user. For example, suppose the user types ">take |
| | 931 | * coin", then we ask for help disambiguating, and the player types |
| | 932 | * ">gold". This narrows things down to, say, three gold coins, but |
| | 933 | * they're in different locations so we need to ask for further |
| | 934 | * disambiguation. Normally, we ask "which gold do you mean", |
| | 935 | * because the player typed "gold" in the input. Once we're down to |
| | 936 | * equivalents, we don't have to rely on the input text any more, |
| | 937 | * which is good because the input text could be fragmentary (as in |
| | 938 | * our present example). Since we have only equivalents, we can use |
| | 939 | * the actual name of the objects (they're all the same, after all). |
| | 940 | * This property gives the name we use. |
| | 941 | * |
| | 942 | * For English, this is simply the object's ordinary disambiguation |
| | 943 | * name. This property is separate from 'name' and 'disambigName' |
| | 944 | * for the sake of languages that need to use an inflected form in |
| | 945 | * this context. |
| | 946 | */ |
| | 947 | disambigEquivName = (disambigName) |
| | 948 | |
| | 949 | /* |
| | 950 | * Single-item listing description. This is used to display the |
| | 951 | * item when it appears as a single (non-grouped) item in a list. |
| | 952 | * By default, we just show the indefinite article description. |
| | 953 | */ |
| | 954 | listName = (aName) |
| | 955 | |
| | 956 | /* |
| | 957 | * Return a string giving the "counted name" of the object - that is, |
| | 958 | * a phrase describing the given number of the object. For example, |
| | 959 | * for a red book, and a count of 5, we might return "five red |
| | 960 | * books". By default, we use countNameFrom() to construct a phrase |
| | 961 | * from the count and either our regular (singular) 'name' property |
| | 962 | * or our 'pluralName' property, according to whether count is 1 or |
| | 963 | * more than 1. |
| | 964 | */ |
| | 965 | countName(count) { return countNameFrom(count, name, pluralName); } |
| | 966 | |
| | 967 | /* |
| | 968 | * Returns a string giving a count applied to the name string. The |
| | 969 | * name must be given in both singular and plural forms. |
| | 970 | */ |
| | 971 | countNameFrom(count, singularStr, pluralStr) |
| | 972 | { |
| | 973 | /* if the count is one, use 'one' plus the singular name */ |
| | 974 | if (count == 1) |
| | 975 | return 'one ' + singularStr; |
| | 976 | |
| | 977 | /* |
| | 978 | * Get the number followed by a space - spell out numbers below |
| | 979 | * 100, but use numerals to denote larger numbers. Append the |
| | 980 | * plural name to the number and return the result. |
| | 981 | */ |
| | 982 | return spellIntBelowExt(count, 100, 0, DigitFormatGroupSep) |
| | 983 | + ' ' + pluralStr; |
| | 984 | } |
| | 985 | |
| | 986 | /* |
| | 987 | * Get the 'pronoun selector' for the various pronoun methods. This |
| | 988 | * returns: |
| | 989 | * |
| | 990 | *. - singular neuter = 1 |
| | 991 | *. - singular masculine = 2 |
| | 992 | *. - singular feminine = 3 |
| | 993 | *. - plural = 4 |
| | 994 | */ |
| | 995 | pronounSelector = (isPlural ? 4 : isHer ? 3 : isHim ? 2 : 1) |
| | 996 | |
| | 997 | /* |
| | 998 | * get a string with the appropriate pronoun for the object for the |
| | 999 | * nominative case, objective case, possessive adjective, possessive |
| | 1000 | * noun |
| | 1001 | */ |
| | 1002 | itNom { return ['it', 'he', 'she', 'they'][pronounSelector]; } |
| | 1003 | itObj { return ['it', 'him', 'her', 'them'][pronounSelector]; } |
| | 1004 | itPossAdj { return ['its', 'his', 'her', 'their'][pronounSelector]; } |
| | 1005 | itPossNoun { return ['its', 'his', 'hers', 'theirs'][pronounSelector]; } |
| | 1006 | |
| | 1007 | /* get the object reflexive pronoun (itself, etc) */ |
| | 1008 | itReflexive |
| | 1009 | { |
| | 1010 | return ['itself', 'himself', 'herself', 'themselves'] |
| | 1011 | [pronounSelector]; |
| | 1012 | } |
| | 1013 | |
| | 1014 | /* demonstrative pronouns ('that' or 'those') */ |
| | 1015 | thatNom { return ['that', 'he', 'she', 'those'][pronounSelector]; } |
| | 1016 | thatIsContraction |
| | 1017 | { |
| | 1018 | return thatNom + tSel(isPlural ? ' are' : '’s', ' ' + verbToBe); |
| | 1019 | } |
| | 1020 | thatObj { return ['that', 'him', 'her', 'those'][pronounSelector]; } |
| | 1021 | |
| | 1022 | /* |
| | 1023 | * get a string with the appropriate pronoun for the object plus the |
| | 1024 | * correct conjugation of 'to be' |
| | 1025 | */ |
| | 1026 | itIs { return itNom + ' ' + verbToBe; } |
| | 1027 | |
| | 1028 | /* get a pronoun plus a 'to be' contraction */ |
| | 1029 | itIsContraction |
| | 1030 | { |
| | 1031 | return itNom |
| | 1032 | + tSel(isPlural ? '’re' : '’s', ' ' + verbToBe); |
| | 1033 | } |
| | 1034 | |
| | 1035 | /* |
| | 1036 | * get a string with the appropriate pronoun for the object plus the |
| | 1037 | * correct conjugation of the given regular verb for the appropriate |
| | 1038 | * person |
| | 1039 | */ |
| | 1040 | itVerb(verb) |
| | 1041 | { |
| | 1042 | return itNom + ' ' + conjugateRegularVerb(verb); |
| | 1043 | } |
| | 1044 | |
| | 1045 | /* |
| | 1046 | * Conjugate a regular verb in the present or past tense for our |
| | 1047 | * person and number. |
| | 1048 | * |
| | 1049 | * In the present tense, this is pretty easy: we add an 's' for the |
| | 1050 | * third person singular, and leave the verb unchanged for plural (it |
| | 1051 | * asks, they ask). The only complication is that we must check some |
| | 1052 | * special cases to add the -s suffix: -y -> -ies (it carries), -o -> |
| | 1053 | * -oes (it goes). |
| | 1054 | * |
| | 1055 | * In the past tense, we can equally easily figure out when to use |
| | 1056 | * -d, -ed, or -ied. However, we have a more serious problem: for |
| | 1057 | * some verbs, the last consonant of the verb stem should be repeated |
| | 1058 | * (as in deter -> deterred), and for others it shouldn't (as in |
| | 1059 | * gather -> gathered). To figure out which rule applies, we would |
| | 1060 | * sometimes need to know whether the last syllable is stressed, and |
| | 1061 | * unfortunately there is no easy way to determine that |
| | 1062 | * programmatically. |
| | 1063 | * |
| | 1064 | * Therefore, we do *not* handle the case where the last consonant is |
| | 1065 | * repeated in the past tense. You shouldn't use this method for |
| | 1066 | * this case; instead, treat it as you would handle an irregular |
| | 1067 | * verb, by explicitly specifying the correct past tense form via the |
| | 1068 | * tSel macro. For example, to generate the properly conjugated form |
| | 1069 | * of the verb "deter" for an object named "thing", you could use an |
| | 1070 | * expression such as: |
| | 1071 | * |
| | 1072 | * 'deter' + tSel(thing.verbEndingS, 'red') |
| | 1073 | * |
| | 1074 | * This would correctly generate "deter", "deters", or "deterred" |
| | 1075 | * depending on the number of the object named "thing" and on the |
| | 1076 | * current narrative tense. |
| | 1077 | */ |
| | 1078 | conjugateRegularVerb(verb) |
| | 1079 | { |
| | 1080 | /* |
| | 1081 | * Which tense are we currently using? |
| | 1082 | */ |
| | 1083 | if (gameMain.usePastTense) |
| | 1084 | { |
| | 1085 | /* |
| | 1086 | * We want the past tense form. |
| | 1087 | * |
| | 1088 | * If the last letter is 'e', simply add 'd'. |
| | 1089 | */ |
| | 1090 | if (verb.endsWith('e')) return verb + 'd'; |
| | 1091 | |
| | 1092 | /* |
| | 1093 | * Otherwise, if the verb ending would become 'ies' in the |
| | 1094 | * third-person singular present, then it becomes 'ied' in |
| | 1095 | * the past. |
| | 1096 | */ |
| | 1097 | else if (rexMatch(iesEndingPat, verb)) |
| | 1098 | return verb.substr(1, verb.length() - 1) + 'ied'; |
| | 1099 | |
| | 1100 | /* |
| | 1101 | * Otherwise, use 'ed' as the ending. Don't try to determine |
| | 1102 | * if the last consonant should be repeated: that's too |
| | 1103 | * complicated. We'll just ignore the possibility. |
| | 1104 | */ |
| | 1105 | else return verb + 'ed'; |
| | 1106 | } |
| | 1107 | else |
| | 1108 | { |
| | 1109 | /* |
| | 1110 | * We want the present tense form. |
| | 1111 | * |
| | 1112 | * Check our number and person. |
| | 1113 | */ |
| | 1114 | if (isPlural) |
| | 1115 | { |
| | 1116 | /* |
| | 1117 | * We're plural, so simply use the base verb form ("they |
| | 1118 | * ask"). |
| | 1119 | */ |
| | 1120 | return verb; |
| | 1121 | } |
| | 1122 | else |
| | 1123 | { |
| | 1124 | /* |
| | 1125 | * Third-person singular, so we must add the -s suffix. |
| | 1126 | * Check for special spelling cases: |
| | 1127 | * |
| | 1128 | * '-y' changes to '-ies', unless the 'y' is preceded by |
| | 1129 | * a vowel |
| | 1130 | * |
| | 1131 | * '-sh', '-ch', and '-o' endings add suffix '-es' |
| | 1132 | */ |
| | 1133 | if (rexMatch(iesEndingPat, verb)) |
| | 1134 | return verb.substr(1, verb.length() - 1) + 'ies'; |
| | 1135 | else if (rexMatch(esEndingPat, verb)) |
| | 1136 | return verb + 'es'; |
| | 1137 | else |
| | 1138 | return verb + 's'; |
| | 1139 | } |
| | 1140 | } |
| | 1141 | } |
| | 1142 | |
| | 1143 | /* verb-ending patterns for figuring out which '-s' ending to add */ |
| | 1144 | iesEndingPat = static new RexPattern('.*[^aeiou]y$') |
| | 1145 | esEndingPat = static new RexPattern('.*(o|ch|sh)$') |
| | 1146 | |
| | 1147 | /* |
| | 1148 | * Get the name with a definite article ("the box"). By default, we |
| | 1149 | * use our standard definite article algorithm to apply an article |
| | 1150 | * to self.name. |
| | 1151 | * |
| | 1152 | * The name returned must be in the nominative case (which makes no |
| | 1153 | * difference unless the name is a pronoun, since in English |
| | 1154 | * ordinary nouns don't vary according to how they're used in a |
| | 1155 | * sentence). |
| | 1156 | */ |
| | 1157 | theName = (theNameFrom(name)) |
| | 1158 | |
| | 1159 | /* |
| | 1160 | * theName in objective case. In most cases, this is identical to |
| | 1161 | * the normal theName, so we use that by default. This must be |
| | 1162 | * overridden if theName is a pronoun (which is usually only the |
| | 1163 | * case for player character actors; see our language-specific Actor |
| | 1164 | * modifications for information on that case). |
| | 1165 | */ |
| | 1166 | theNameObj { return theName; } |
| | 1167 | |
| | 1168 | /* |
| | 1169 | * Generate the definite-article name from the given name string. |
| | 1170 | * If my name is already qualified, don't add an article; otherwise, |
| | 1171 | * add a 'the' as the prefixed definite article. |
| | 1172 | */ |
| | 1173 | theNameFrom(str) { return (isQualifiedName ? '' : 'the ') + str; } |
| | 1174 | |
| | 1175 | /* |
| | 1176 | * theName as a possessive adjective (Bob's book, your book). If the |
| | 1177 | * name's usage is singular (i.e., isPlural is nil), we'll simply add |
| | 1178 | * an apostrophe-S. If the name is plural, and it ends in an "s", |
| | 1179 | * we'll just add an apostrophe (no S). If it's plural and doesn't |
| | 1180 | * end in "s", we'll add an apostrophe-S. |
| | 1181 | * |
| | 1182 | * Note that some people disagree about the proper usage for |
| | 1183 | * singular-usage words (especially proper names) that end in 's'. |
| | 1184 | * Some people like to use a bare apostrophe for any name that ends |
| | 1185 | * in 's' (so Chris -> Chris'); other people use apostrophe-s for |
| | 1186 | * singular words that end in an "s" sound and a bare apostrophe for |
| | 1187 | * words that end in an "s" that sounds like a "z" (so Charles |
| | 1188 | * Dickens -> Charles Dickens'). However, most usage experts agree |
| | 1189 | * that proper names take an apostrophe-S in almost all cases, even |
| | 1190 | * when ending with an "s": "Chris's", "Charles Dickens's". That's |
| | 1191 | * what we do here. |
| | 1192 | * |
| | 1193 | * Note that this algorithm doesn't catch all of the special |
| | 1194 | * exceptions in conventional English usage. For example, Greek |
| | 1195 | * names ending with "-es" are usually written with the bare |
| | 1196 | * apostrophe, but we don't have a property that tells us whether the |
| | 1197 | * name is Greek or not, so we can't catch this case. Likewise, some |
| | 1198 | * authors like to possessive-ize words that end with an "s" sound |
| | 1199 | * with a bare apostrophe, as in "for appearance' sake", and we don't |
| | 1200 | * attempt to catch these either. For any of these exceptions, you |
| | 1201 | * must override this method for the individual object. |
| | 1202 | */ |
| | 1203 | theNamePossAdj |
| | 1204 | { |
| | 1205 | /* add apostrophe-S, unless it's a plural ending with 's' */ |
| | 1206 | return theName |
| | 1207 | + (isPlural && theName.endsWith('s') ? '’' : '’s'); |
| | 1208 | } |
| | 1209 | |
| | 1210 | /* |
| | 1211 | * TheName as a possessive noun (that is Bob's, that is yours). We |
| | 1212 | * simply return the possessive adjective name, since the two forms |
| | 1213 | * are usually identical in English (except for pronouns, where they |
| | 1214 | * sometimes differ: "her" for the adjective vs "hers" for the noun). |
| | 1215 | */ |
| | 1216 | theNamePossNoun = (theNamePossAdj) |
| | 1217 | |
| | 1218 | /* |
| | 1219 | * theName with my nominal owner explicitly stated, if we have a |
| | 1220 | * nominal owner: "your backpack," "Bob's flashlight." If we have |
| | 1221 | * no nominal owner, this is simply my theName. |
| | 1222 | */ |
| | 1223 | theNameWithOwner() |
| | 1224 | { |
| | 1225 | local owner; |
| | 1226 | |
| | 1227 | /* |
| | 1228 | * if we have a nominal owner, show with our owner name; |
| | 1229 | * otherwise, just show our regular theName |
| | 1230 | */ |
| | 1231 | if ((owner = getNominalOwner()) != nil) |
| | 1232 | return owner.theNamePossAdj + ' ' + name; |
| | 1233 | else |
| | 1234 | return theName; |
| | 1235 | } |
| | 1236 | |
| | 1237 | /* |
| | 1238 | * Default preposition to use when an object is in/on this object. |
| | 1239 | * By default, we use 'in' as the preposition; subclasses can |
| | 1240 | * override to use others (such as 'on' for a surface). |
| | 1241 | */ |
| | 1242 | objInPrep = 'in' |
| | 1243 | |
| | 1244 | /* |
| | 1245 | * Default preposition to use when an actor is in/on this object (as |
| | 1246 | * a nested location), and full prepositional phrase, with no article |
| | 1247 | * and with an indefinite article. By default, we use the objInPrep |
| | 1248 | * for actors as well. |
| | 1249 | */ |
| | 1250 | actorInPrep = (objInPrep) |
| | 1251 | |
| | 1252 | /* preposition to use when an actor is being removed from this location */ |
| | 1253 | actorOutOfPrep = 'out of' |
| | 1254 | |
| | 1255 | /* preposition to use when an actor is being moved into this location */ |
| | 1256 | actorIntoPrep |
| | 1257 | { |
| | 1258 | if (actorInPrep is in ('in', 'on')) |
| | 1259 | return actorInPrep + 'to'; |
| | 1260 | else |
| | 1261 | return actorInPrep; |
| | 1262 | } |
| | 1263 | |
| | 1264 | /* |
| | 1265 | * describe an actor as being in/being removed from/being moved into |
| | 1266 | * this location |
| | 1267 | */ |
| | 1268 | actorInName = (actorInPrep + ' ' + theNameObj) |
| | 1269 | actorInAName = (actorInPrep + ' ' + aNameObj) |
| | 1270 | actorOutOfName = (actorOutOfPrep + ' ' + theNameObj) |
| | 1271 | actorIntoName = (actorIntoPrep + ' ' + theNameObj) |
| | 1272 | |
| | 1273 | /* |
| | 1274 | * A prepositional phrase that can be used to describe things that |
| | 1275 | * are in this room as seen from a remote point of view. This |
| | 1276 | * should be something along the lines of "in the airlock", "at the |
| | 1277 | * end of the alley", or "on the lawn". |
| | 1278 | * |
| | 1279 | * 'pov' is the point of view from which we're seeing this room; |
| | 1280 | * this might be |
| | 1281 | * |
| | 1282 | * We use this phrase in cases where we need to describe things in |
| | 1283 | * this room when viewed from a point of view outside of the room |
| | 1284 | * (i.e., in a different top-level room). By default, we'll use our |
| | 1285 | * actorInName. |
| | 1286 | */ |
| | 1287 | inRoomName(pov) { return actorInName; } |
| | 1288 | |
| | 1289 | /* |
| | 1290 | * Provide the prepositional phrase for an object being put into me. |
| | 1291 | * For a container, for example, this would say "into the box"; for |
| | 1292 | * a surface, it would say "onto the table." By default, we return |
| | 1293 | * our library message given by our putDestMessage property; this |
| | 1294 | * default is suitable for most cases, but individual objects can |
| | 1295 | * customize as needed. When customizing this, be sure to make the |
| | 1296 | * phrase suitable for use in sentences like "You put the book |
| | 1297 | * <<putInName>>" and "The book falls <<putInName>>" - the phrase |
| | 1298 | * should be suitable for a verb indicating active motion by the |
| | 1299 | * object being received. |
| | 1300 | */ |
| | 1301 | putInName() { return gLibMessages.(putDestMessage)(self); } |
| | 1302 | |
| | 1303 | /* |
| | 1304 | * Get a description of an object within this object, describing the |
| | 1305 | * object's location as this object. By default, we'll append "in |
| | 1306 | * <theName>" to the given object name. |
| | 1307 | */ |
| | 1308 | childInName(childName) |
| | 1309 | { return childInNameGen(childName, theName); } |
| | 1310 | |
| | 1311 | /* |
| | 1312 | * Get a description of an object within this object, showing the |
| | 1313 | * owner of this object. This is similar to childInName, but |
| | 1314 | * explicitly shows the owner of the containing object, if any: "the |
| | 1315 | * flashlight in bob's backpack". |
| | 1316 | */ |
| | 1317 | childInNameWithOwner(childName) |
| | 1318 | { return childInNameGen(childName, theNameWithOwner); } |
| | 1319 | |
| | 1320 | /* |
| | 1321 | * get a description of an object within this object, as seen from a |
| | 1322 | * remote location |
| | 1323 | */ |
| | 1324 | childInRemoteName(childName, pov) |
| | 1325 | { return childInNameGen(childName, inRoomName(pov)); } |
| | 1326 | |
| | 1327 | /* |
| | 1328 | * Base routine for generating childInName and related names. Takes |
| | 1329 | * the name to use for the child and the name to use for me, and |
| | 1330 | * combines them appropriately. |
| | 1331 | * |
| | 1332 | * In most cases, this is the only one of the various childInName |
| | 1333 | * methods that needs to be overridden per subclass, since the others |
| | 1334 | * are defined in terms of this one. Note also that if the only |
| | 1335 | * thing you need to do is change the preposition from 'in' to |
| | 1336 | * something else, you can just override objInPrep instead. |
| | 1337 | */ |
| | 1338 | childInNameGen(childName, myName) |
| | 1339 | { return childName + ' ' + objInPrep + ' ' + myName; } |
| | 1340 | |
| | 1341 | /* |
| | 1342 | * Get my name (in various forms) distinguished by my owner or |
| | 1343 | * location. |
| | 1344 | * |
| | 1345 | * If the object has an owner, and either we're giving priority to |
| | 1346 | * the owner or our immediate location is the same as the owner, |
| | 1347 | * we'll show using a possessive form with the owner ("bob's |
| | 1348 | * flashlight"). Otherwise, we'll show the name distinguished by |
| | 1349 | * our immediate container ("the flashlight in the backpack"). |
| | 1350 | * |
| | 1351 | * These are used by the ownership and location distinguishers to |
| | 1352 | * list objects according to owners in disambiguation lists. The |
| | 1353 | * ownership distinguisher gives priority to naming by ownership, |
| | 1354 | * regardless of the containment relationship between owner and |
| | 1355 | * self; the location distinguisher gives priority to naming by |
| | 1356 | * location, showing the owner only if the owner is the same as the |
| | 1357 | * location. |
| | 1358 | * |
| | 1359 | * We will presume that objects with proper names are never |
| | 1360 | * indistinguishable from other objects with proper names, so we |
| | 1361 | * won't worry about cases like "Bob's Bill". This leaves us free |
| | 1362 | * to use appropriate articles in all cases. |
| | 1363 | */ |
| | 1364 | aNameOwnerLoc(ownerPriority) |
| | 1365 | { |
| | 1366 | local owner; |
| | 1367 | |
| | 1368 | /* show in owner or location format, as appropriate */ |
| | 1369 | if ((owner = getNominalOwner()) != nil |
| | 1370 | && (ownerPriority || isDirectlyIn(owner))) |
| | 1371 | { |
| | 1372 | local ret; |
| | 1373 | |
| | 1374 | /* |
| | 1375 | * we have an owner - show as "one of Bob's items" (or just |
| | 1376 | * "Bob's items" if this is a mass noun or a proper name) |
| | 1377 | */ |
| | 1378 | ret = owner.theNamePossAdj + ' ' + pluralName; |
| | 1379 | if (!isMassNoun && !isPlural) |
| | 1380 | ret = 'one of ' + ret; |
| | 1381 | |
| | 1382 | /* return the result */ |
| | 1383 | return ret; |
| | 1384 | } |
| | 1385 | else |
| | 1386 | { |
| | 1387 | /* we have no owner - show as "an item in the location" */ |
| | 1388 | return location.childInNameWithOwner(aName); |
| | 1389 | } |
| | 1390 | } |
| | 1391 | theNameOwnerLoc(ownerPriority) |
| | 1392 | { |
| | 1393 | local owner; |
| | 1394 | |
| | 1395 | /* show in owner or location format, as appropriate */ |
| | 1396 | if ((owner = getNominalOwner()) != nil |
| | 1397 | && (ownerPriority || isDirectlyIn(owner))) |
| | 1398 | { |
| | 1399 | /* we have an owner - show as "Bob's item" */ |
| | 1400 | return owner.theNamePossAdj + ' ' + name; |
| | 1401 | } |
| | 1402 | else |
| | 1403 | { |
| | 1404 | /* we have no owner - show as "the item in the location" */ |
| | 1405 | return location.childInNameWithOwner(theName); |
| | 1406 | } |
| | 1407 | } |
| | 1408 | countNameOwnerLoc(cnt, ownerPriority) |
| | 1409 | { |
| | 1410 | local owner; |
| | 1411 | |
| | 1412 | /* show in owner or location format, as appropriate */ |
| | 1413 | if ((owner = getNominalOwner()) != nil |
| | 1414 | && (ownerPriority || isDirectlyIn(owner))) |
| | 1415 | { |
| | 1416 | /* we have an owner - show as "Bob's five items" */ |
| | 1417 | return owner.theNamePossAdj + ' ' + countName(cnt); |
| | 1418 | } |
| | 1419 | else |
| | 1420 | { |
| | 1421 | /* we have no owner - show as "the five items in the location" */ |
| | 1422 | return location.childInNameWithOwner('the ' + countName(cnt)); |
| | 1423 | } |
| | 1424 | } |
| | 1425 | |
| | 1426 | /* |
| | 1427 | * Note that I'm being used in a disambiguation prompt by |
| | 1428 | * owner/location. If we're showing the owner, we'll set the |
| | 1429 | * antecedent for the owner's pronoun, if the owner is a 'him' or |
| | 1430 | * 'her'; this allows the player to refer back to our prompt text |
| | 1431 | * with appropriate pronouns. |
| | 1432 | */ |
| | 1433 | notePromptByOwnerLoc(ownerPriority) |
| | 1434 | { |
| | 1435 | local owner; |
| | 1436 | |
| | 1437 | /* show in owner or location format, as appropriate */ |
| | 1438 | if ((owner = getNominalOwner()) != nil |
| | 1439 | && (ownerPriority || isDirectlyIn(owner))) |
| | 1440 | { |
| | 1441 | /* we are showing by owner - let the owner know about it */ |
| | 1442 | owner.notePromptByPossAdj(); |
| | 1443 | } |
| | 1444 | } |
| | 1445 | |
| | 1446 | /* |
| | 1447 | * Note that we're being used in a prompt question with our |
| | 1448 | * possessive adjective. If we're a 'him' or a 'her', set our |
| | 1449 | * pronoun antecedent so that the player's response to the prompt |
| | 1450 | * question can refer back to the prompt text by pronoun. |
| | 1451 | */ |
| | 1452 | notePromptByPossAdj() |
| | 1453 | { |
| | 1454 | if (isHim) |
| | 1455 | gPlayerChar.setHim(self); |
| | 1456 | if (isHer) |
| | 1457 | gPlayerChar.setHer(self); |
| | 1458 | } |
| | 1459 | |
| | 1460 | /* |
| | 1461 | * My name with an indefinite article. By default, we figure out |
| | 1462 | * which article to use (a, an, some) automatically. |
| | 1463 | * |
| | 1464 | * In rare cases, the automatic determination might get it wrong, |
| | 1465 | * since some English spellings defy all of the standard |
| | 1466 | * orthographic rules and must simply be handled as special cases; |
| | 1467 | * for example, the algorithmic determination doesn't know about |
| | 1468 | * silent-h words like "hour". When the automatic determination |
| | 1469 | * gets it wrong, simply override this routine to specify the |
| | 1470 | * correct article explicitly. |
| | 1471 | */ |
| | 1472 | aName = (aNameFrom(name)) |
| | 1473 | |
| | 1474 | /* the indefinite-article name in the objective case */ |
| | 1475 | aNameObj { return aName; } |
| | 1476 | |
| | 1477 | /* |
| | 1478 | * Apply an indefinite article ("a box", "an orange", "some lint") |
| | 1479 | * to the given name. We'll try to figure out which indefinite |
| | 1480 | * article to use based on what kind of noun phrase we use for our |
| | 1481 | * name (singular, plural, or a "mass noun" like "lint"), and our |
| | 1482 | * spelling. |
| | 1483 | * |
| | 1484 | * By default, we'll use the article "a" if the name starts with a |
| | 1485 | * consonant, or "an" if it starts with a vowel. |
| | 1486 | * |
| | 1487 | * If the name starts with a "y", we'll look at the second letter; |
| | 1488 | * if it's a consonant, we'll use "an", otherwise "a" (hence "an |
| | 1489 | * yttrium block" but "a yellow brick"). |
| | 1490 | * |
| | 1491 | * If the object is marked as having plural usage, we will use |
| | 1492 | * "some" as the article ("some pants" or "some shrubs"). |
| | 1493 | * |
| | 1494 | * Some objects will want to override the default behavior, because |
| | 1495 | * the lexical rules about when to use "a" and "an" are not without |
| | 1496 | * exception. For example, silent-"h" words ("honor") are written |
| | 1497 | * with "an", and "h" words with a pronounced but weakly stressed |
| | 1498 | * initial "h" are sometimes used with "an" ("an historian"). Also, |
| | 1499 | * some 'y' words might not follow the generic 'y' rule. |
| | 1500 | * |
| | 1501 | * 'U' words are especially likely not to follow any lexical rule - |
| | 1502 | * any 'u' word that sounds like it starts with 'y' should use 'a' |
| | 1503 | * rather than 'an', but there's no good way to figure that out just |
| | 1504 | * looking at the spelling (consider "a universal symbol" and "an |
| | 1505 | * unimportant word", or "a unanimous decision" and "an unassuming |
| | 1506 | * man"). We simply always use 'an' for a word starting with 'u', |
| | 1507 | * but this will have to be overridden when the 'u' sounds like 'y'. |
| | 1508 | */ |
| | 1509 | aNameFrom(str) |
| | 1510 | { |
| | 1511 | /* remember the original source string */ |
| | 1512 | local inStr = str; |
| | 1513 | |
| | 1514 | /* |
| | 1515 | * The complete list of unaccented, accented, and ligaturized |
| | 1516 | * Latin vowels from the Unicode character set. (The Unicode |
| | 1517 | * database doesn't classify characters as vowels or the like, |
| | 1518 | * so it seems the only way we can come up with this list is |
| | 1519 | * simply to enumerate the vowels.) |
| | 1520 | * |
| | 1521 | * These are all lower-case letters; all of these are either |
| | 1522 | * exclusively lower-case or have upper-case equivalents that |
| | 1523 | * map to these lower-case letters. |
| | 1524 | * |
| | 1525 | * (Note an implementation detail: the compiler will append all |
| | 1526 | * of these strings together at compile time, so we don't have |
| | 1527 | * to perform all of this concatenation work each time we |
| | 1528 | * execute this method.) |
| | 1529 | * |
| | 1530 | * Note that we consider any word starting with an '8' to start |
| | 1531 | * with a vowel, since 'eight' and 'eighty' both take 'an'. |
| | 1532 | */ |
| | 1533 | local vowels = '8aeiou\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6' |
| | 1534 | + '\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF' |
| | 1535 | + '\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8\u00F9\u00FA' |
| | 1536 | + '\u00FB\u00FC\u0101\u0103\u0105\u0113\u0115\u0117' |
| | 1537 | + '\u0119\u011B\u0129\u012B\u012D\u012F\u014D\u014F' |
| | 1538 | + '\u0151\u0169\u016B\u016D\u016F\u0171\u0173\u01A1' |
| | 1539 | + '\u01A3\u01B0\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8' |
| | 1540 | + '\u01DA\u01DC\u01DF\u01E1\u01E3\u01EB\u01ED\u01FB' |
| | 1541 | + '\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B' |
| | 1542 | + '\u020D\u020F\u0215\u0217\u0254\u025B\u0268\u0289' |
| | 1543 | + '\u1E01\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E2D\u1E2F' |
| | 1544 | + '\u1E4D\u1E4F\u1E51\u1E53\u1E73\u1E75\u1E77\u1E79' |
| | 1545 | + '\u1E7B\u1E9A\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB' |
| | 1546 | + '\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB' |
| | 1547 | + '\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB' |
| | 1548 | + '\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB' |
| | 1549 | + '\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB' |
| | 1550 | + '\u1EED\u1EEF\u1EF1\uFF41\uFF4F\uFF55'; |
| | 1551 | |
| | 1552 | /* |
| | 1553 | * A few upper-case vowels in unicode don't have lower-case |
| | 1554 | * mappings - consider them separately. |
| | 1555 | */ |
| | 1556 | local vowelsUpperOnly = '\u0130\u019f'; |
| | 1557 | |
| | 1558 | /* |
| | 1559 | * the various accented forms of the letter 'y' - these are all |
| | 1560 | * lower-case versions; the upper-case versions all map to these |
| | 1561 | */ |
| | 1562 | local ys = 'y\u00FD\u00FF\u0177\u01B4\u1E8F\u1E99\u1EF3' |
| | 1563 | + '\u1EF5\u1EF7\u1EF9\u24B4\uFF59'; |
| | 1564 | |
| | 1565 | /* if the name is already qualified, don't add an article at all */ |
| | 1566 | if (isQualifiedName) |
| | 1567 | return str; |
| | 1568 | |
| | 1569 | /* if it's plural or a mass noun, use "some" as the article */ |
| | 1570 | if (isPlural || isMassNoun) |
| | 1571 | { |
| | 1572 | /* use "some" as the article */ |
| | 1573 | return 'some ' + str; |
| | 1574 | } |
| | 1575 | else |
| | 1576 | { |
| | 1577 | local firstChar; |
| | 1578 | local firstCharLower; |
| | 1579 | |
| | 1580 | /* if it's empty, just use "a" */ |
| | 1581 | if (inStr == '') |
| | 1582 | return 'a'; |
| | 1583 | |
| | 1584 | /* get the first character of the name */ |
| | 1585 | firstChar = inStr.substr(1, 1); |
| | 1586 | |
| | 1587 | /* skip any leading HTML tags */ |
| | 1588 | if (rexMatch(patTagOrQuoteChar, firstChar) != nil) |
| | 1589 | { |
| | 1590 | /* |
| | 1591 | * Scan for tags. Note that this pattern isn't quite |
| | 1592 | * perfect, as it won't properly ignore close-brackets |
| | 1593 | * that are inside quoted material, but it should be good |
| | 1594 | * enough for nearly all cases in practice. In cases too |
| | 1595 | * complex for this pattern, the object will simply have |
| | 1596 | * to override aDesc. |
| | 1597 | */ |
| | 1598 | local len = rexMatch(patLeadingTagOrQuote, inStr); |
| | 1599 | |
| | 1600 | /* if we got a match, strip out the leading tags */ |
| | 1601 | if (len != nil) |
| | 1602 | { |
| | 1603 | /* strip off the leading tags */ |
| | 1604 | inStr = inStr.substr(len + 1); |
| | 1605 | |
| | 1606 | /* re-fetch the first character */ |
| | 1607 | firstChar = inStr.substr(1, 1); |
| | 1608 | } |
| | 1609 | } |
| | 1610 | |
| | 1611 | /* get the lower-case version of the first character */ |
| | 1612 | firstCharLower = firstChar.toLower(); |
| | 1613 | |
| | 1614 | /* |
| | 1615 | * if the first word of the name is only one letter long, |
| | 1616 | * treat it specially |
| | 1617 | */ |
| | 1618 | if (rexMatch(patOneLetterWord, inStr) != nil) |
| | 1619 | { |
| | 1620 | /* |
| | 1621 | * We have a one-letter first word, such as "I-beam" or |
| | 1622 | * "M-ray sensor", or just "A". Choose the article based |
| | 1623 | * on the pronunciation of the letter as a letter. |
| | 1624 | */ |
| | 1625 | return (rexMatch(patOneLetterAnWord, inStr) != nil |
| | 1626 | ? 'an ' : 'a ') + str; |
| | 1627 | } |
| | 1628 | |
| | 1629 | /* |
| | 1630 | * look for the first character in the lower-case and |
| | 1631 | * upper-case-only vowel lists - if we find it, it takes |
| | 1632 | * 'an' |
| | 1633 | */ |
| | 1634 | if (vowels.find(firstCharLower) != nil |
| | 1635 | || vowelsUpperOnly.find(firstChar) != nil) |
| | 1636 | { |
| | 1637 | /* it starts with a vowel */ |
| | 1638 | return 'an ' + str; |
| | 1639 | } |
| | 1640 | else if (ys.find(firstCharLower) != nil) |
| | 1641 | { |
| | 1642 | local secondChar; |
| | 1643 | |
| | 1644 | /* get the second character, if there is one */ |
| | 1645 | secondChar = inStr.substr(2, 1); |
| | 1646 | |
| | 1647 | /* |
| | 1648 | * It starts with 'y' - if the second letter is a |
| | 1649 | * consonant, assume the 'y' is a vowel sound, hence we |
| | 1650 | * should use 'an'; otherwise assume the 'y' is a |
| | 1651 | * diphthong 'ei' sound, which means we should use 'a'. |
| | 1652 | * If there's no second character at all, or the second |
| | 1653 | * character isn't alphabetic, use 'a' - "a Y" or "a |
| | 1654 | * Y-connector". |
| | 1655 | */ |
| | 1656 | if (secondChar == '' |
| | 1657 | || rexMatch(patIsAlpha, secondChar) == nil |
| | 1658 | || vowels.find(secondChar.toLower()) != nil |
| | 1659 | || vowelsUpperOnly.find(secondChar) != nil) |
| | 1660 | { |
| | 1661 | /* |
| | 1662 | * it's just one character, or the second character |
| | 1663 | * is non-alphabetic, or the second character is a |
| | 1664 | * vowel - in any of these cases, use 'a' |
| | 1665 | */ |
| | 1666 | return 'a ' + str; |
| | 1667 | } |
| | 1668 | else |
| | 1669 | { |
| | 1670 | /* the second character is a consonant - use 'an' */ |
| | 1671 | return 'an ' + str; |
| | 1672 | } |
| | 1673 | } |
| | 1674 | else if (rexMatch(patElevenEighteen, inStr) != nil) |
| | 1675 | { |
| | 1676 | /* |
| | 1677 | * it starts with '11' or '18', so it takes 'an' ('an |
| | 1678 | * 11th-hour appeal', 'an 18-hole golf course') |
| | 1679 | */ |
| | 1680 | return 'an ' + str; |
| | 1681 | } |
| | 1682 | else |
| | 1683 | { |
| | 1684 | /* it starts with a consonant */ |
| | 1685 | return 'a ' + str; |
| | 1686 | } |
| | 1687 | } |
| | 1688 | } |
| | 1689 | |
| | 1690 | /* pre-compile some regular expressions for aName */ |
| | 1691 | patTagOrQuoteChar = static new RexPattern('[<"\']') |
| | 1692 | patLeadingTagOrQuote = static new RexPattern( |
| | 1693 | '(<langle><^rangle>+<rangle>|"|\')+') |
| | 1694 | patOneLetterWord = static new RexPattern('<alpha>(<^alpha>|$)') |
| | 1695 | patOneLetterAnWord = static new RexPattern('<nocase>[aefhilmnorsx]') |
| | 1696 | patIsAlpha = static new RexPattern('<alpha>') |
| | 1697 | patElevenEighteen = static new RexPattern('1[18](<^digit>|$)') |
| | 1698 | |
| | 1699 | /* |
| | 1700 | * Get the default plural name. By default, we'll use the |
| | 1701 | * algorithmic plural determination, which is based on the spelling |
| | 1702 | * of the name. |
| | 1703 | * |
| | 1704 | * The algorithm won't always get it right, since some English |
| | 1705 | * plurals are irregular ("men", "children", "Attorneys General"). |
| | 1706 | * When the name doesn't fit the regular spelling patterns for |
| | 1707 | * plurals, the object should simply override this routine to return |
| | 1708 | * the correct plural name string. |
| | 1709 | */ |
| | 1710 | pluralName = (pluralNameFrom(name)) |
| | 1711 | |
| | 1712 | /* |
| | 1713 | * Get the plural form of the given name string. If the name ends in |
| | 1714 | * anything other than 'y', we'll add an 's'; otherwise we'll replace |
| | 1715 | * the 'y' with 'ies'. We also handle abbreviations and individual |
| | 1716 | * letters specially. |
| | 1717 | * |
| | 1718 | * This can only deal with simple adjective-noun forms. For more |
| | 1719 | * complicated forms, particularly for compound words, it must be |
| | 1720 | * overridden (e.g., "Attorney General" -> "Attorneys General", |
| | 1721 | * "man-of-war" -> "men-of-war"). Likewise, names with irregular |
| | 1722 | * plurals ('child' -> 'children', 'man' -> 'men') must be handled |
| | 1723 | * with overrides. |
| | 1724 | */ |
| | 1725 | pluralNameFrom(str) |
| | 1726 | { |
| | 1727 | local len; |
| | 1728 | local lastChar; |
| | 1729 | local lastPair; |
| | 1730 | |
| | 1731 | /* |
| | 1732 | * if it's marked as having plural usage, just use the ordinary |
| | 1733 | * name, since it's already plural |
| | 1734 | */ |
| | 1735 | if (isPlural) |
| | 1736 | return str; |
| | 1737 | |
| | 1738 | /* check for a 'phrase of phrase' format */ |
| | 1739 | if (rexMatch(patOfPhrase, str) != nil) |
| | 1740 | { |
| | 1741 | local ofSuffix; |
| | 1742 | |
| | 1743 | /* |
| | 1744 | * Pull out the two parts - the part up to the 'of' is the |
| | 1745 | * part we'll actually pluralize, and the rest is a suffix |
| | 1746 | * we'll stick on the end of the pluralized part. |
| | 1747 | */ |
| | 1748 | str = rexGroup(1)[3]; |
| | 1749 | ofSuffix = rexGroup(2)[3]; |
| | 1750 | |
| | 1751 | /* |
| | 1752 | * now pluralize the part up to the 'of' using the normal |
| | 1753 | * rules, then add the rest back in at the end |
| | 1754 | */ |
| | 1755 | return pluralNameFrom(str) + ofSuffix; |
| | 1756 | } |
| | 1757 | |
| | 1758 | /* if there's no short description, return an empty string */ |
| | 1759 | len = str.length(); |
| | 1760 | if (len == 0) |
| | 1761 | return ''; |
| | 1762 | |
| | 1763 | /* |
| | 1764 | * If it's only one character long, handle it specially. If it's |
| | 1765 | * a lower-case letter, add an apostrophe-S. If it's a capital |
| | 1766 | * A, E, I, M, U, or V, we'll add apostrophe-S (because these |
| | 1767 | * could be confused with words or common abbreviations if we |
| | 1768 | * just added "s": As, Es, Is, Ms, Us, Vs). If it's anything |
| | 1769 | * else (any other capital letter, or any non-letter character), |
| | 1770 | * we'll just add an "s". |
| | 1771 | */ |
| | 1772 | if (len == 1) |
| | 1773 | { |
| | 1774 | if (rexMatch(patSingleApostropheS, str) != nil) |
| | 1775 | return str + '’s'; |
| | 1776 | else |
| | 1777 | return str + 's'; |
| | 1778 | } |
| | 1779 | |
| | 1780 | /* get the last character of the name, and the last pair of chars */ |
| | 1781 | lastChar = str.substr(len, 1); |
| | 1782 | lastPair = (len == 1 ? lastChar : str.substr(len - 1, 2)); |
| | 1783 | |
| | 1784 | /* |
| | 1785 | * If the last letter is a capital letter, assume it's an |
| | 1786 | * abbreviation without embedded periods (CPA, PC), in which case |
| | 1787 | * we just add an "s" (CPAs, PCs). Likewise, if it's a number, |
| | 1788 | * just add "s": "the 1940s", "the low 20s". |
| | 1789 | */ |
| | 1790 | if (rexMatch(patUpperOrDigit, lastChar) != nil) |
| | 1791 | return str + 's'; |
| | 1792 | |
| | 1793 | /* |
| | 1794 | * If the last character is a period, it must be an abbreviation |
| | 1795 | * with embedded periods (B.A., B.S., Ph.D.). In these cases, |
| | 1796 | * add an apostrophe-S. |
| | 1797 | */ |
| | 1798 | if (lastChar == '.') |
| | 1799 | return str + '’s'; |
| | 1800 | |
| | 1801 | /* |
| | 1802 | * If it ends in a non-vowel followed by 'y', change -y to -ies. |
| | 1803 | * (This doesn't apply if a vowel precedes a terminal 'y'; in |
| | 1804 | * such cases, we'll use the normal '-s' ending instead: "survey" |
| | 1805 | * -> "surveys", "essay" -> "essays", "day" -> "days".) |
| | 1806 | */ |
| | 1807 | if (rexMatch(patVowelY, lastPair) != nil) |
| | 1808 | return str.substr(1, len - 1) + 'ies'; |
| | 1809 | |
| | 1810 | /* if it ends in s, x, z, or h, add -es */ |
| | 1811 | if ('sxzh'.find(lastChar) != nil) |
| | 1812 | return str + 'es'; |
| | 1813 | |
| | 1814 | /* for anything else, just add -s */ |
| | 1815 | return str + 's'; |
| | 1816 | } |
| | 1817 | |
| | 1818 | /* some pre-compiled patterns for pluralName */ |
| | 1819 | patSingleApostropheS = static new RexPattern('<case><lower|A|E|I|M|U|V>') |
| | 1820 | patUpperOrDigit = static new RexPattern('<upper|digit>') |
| | 1821 | patVowelY = static new RexPattern('[^aeoiu]y') |
| | 1822 | patOfPhrase = static new RexPattern( |
| | 1823 | '<nocase>(.+?)(<space>+of<space>+.+)') |
| | 1824 | |
| | 1825 | /* get my name plus a being verb ("the box is") */ |
| | 1826 | nameIs { return theName + ' ' + verbToBe; } |
| | 1827 | |
| | 1828 | /* get my name plus a negative being verb ("the box isn't") */ |
| | 1829 | nameIsnt { return nameIs + 'n’t'; } |
| | 1830 | |
| | 1831 | /* |
| | 1832 | * My name with the given regular verb in agreement: in the present |
| | 1833 | * tense, if my name has singular usage, we'll add 's' to the verb, |
| | 1834 | * otherwise we won't. In the past tense, we'll add 'd' (or 'ed'). |
| | 1835 | * This can't be used with irregular verbs, or with regular verbs |
| | 1836 | * that have the last consonant repeated before the past -ed ending, |
| | 1837 | * such as "deter". |
| | 1838 | */ |
| | 1839 | nameVerb(verb) { return theName + ' ' + conjugateRegularVerb(verb); } |
| | 1840 | |
| | 1841 | /* being verb agreeing with this object as subject */ |
| | 1842 | verbToBe |
| | 1843 | { |
| | 1844 | return tSel(isPlural ? 'are' : 'is', isPlural ? 'were' : 'was'); |
| | 1845 | } |
| | 1846 | |
| | 1847 | /* past tense being verb agreeing with object as subject */ |
| | 1848 | verbWas { return tSel(isPlural ? 'were' : 'was', 'had been'); } |
| | 1849 | |
| | 1850 | /* 'have' verb agreeing with this object as subject */ |
| | 1851 | verbToHave { return tSel(isPlural ? 'have' : 'has', 'had'); } |
| | 1852 | |
| | 1853 | /* |
| | 1854 | * A few common irregular verbs and name-plus-verb constructs, |
| | 1855 | * defined for convenience. |
| | 1856 | */ |
| | 1857 | verbToDo = (tSel('do' + verbEndingEs, 'did')) |
| | 1858 | nameDoes = (theName + ' ' + verbToDo) |
| | 1859 | verbToGo = (tSel('go' + verbEndingEs, 'went')) |
| | 1860 | verbToCome = (tSel('come' + verbEndingS, 'came')) |
| | 1861 | verbToLeave = (tSel('leave' + verbEndingS, 'left')) |
| | 1862 | verbToSee = (tSel('see' + verbEndingS, 'saw')) |
| | 1863 | nameSees = (theName + ' ' + verbToSee) |
| | 1864 | verbToSay = (tSel('say' + verbEndingS, 'said')) |
| | 1865 | nameSays = (theName + ' ' + verbToSay) |
| | 1866 | verbMust = (tSel('must', 'had to')) |
| | 1867 | verbCan = (tSel('can', 'could')) |
| | 1868 | verbCannot = (tSel('cannot', 'could not')) |
| | 1869 | verbCant = (tSel('can’t', 'couldn’t')) |
| | 1870 | verbWill = (tSel('will', 'would')) |
| | 1871 | verbWont = (tSel('won’t', 'wouldn’t')) |
| | 1872 | |
| | 1873 | /* |
| | 1874 | * Verb endings for regular '-s' verbs, agreeing with this object as |
| | 1875 | * the subject. We define several methods each of which handles the |
| | 1876 | * past tense differently. |
| | 1877 | * |
| | 1878 | * verbEndingS doesn't try to handle the past tense at all - use it |
| | 1879 | * only in places where you know for certain that you'll never need |
| | 1880 | * the past tense form, or in expressions constructed with the tSel |
| | 1881 | * macro: use verbEndingS as the macro's first argument, and specify |
| | 1882 | * the past tense ending explicitly as the second argument. For |
| | 1883 | * example, you could generate the correctly conjugated form of the |
| | 1884 | * verb "to fit" for an object named "key" with an expression such |
| | 1885 | * as: |
| | 1886 | * |
| | 1887 | * 'fit' + tSel(key.verbEndingS, 'ted') |
| | 1888 | * |
| | 1889 | * This would generate 'fit', 'fits', or 'fitted' according to number |
| | 1890 | * and tense. |
| | 1891 | * |
| | 1892 | * verbEndingSD and verbEndingSEd return 'd' and 'ed' respectively in |
| | 1893 | * the past tense. |
| | 1894 | * |
| | 1895 | * verbEndingSMessageBuilder_ is for internal use only: it assumes |
| | 1896 | * that the correct ending to be displayed in the past tense is |
| | 1897 | * stored in langMessageBuilder.pastEnding_. It is used as part of |
| | 1898 | * the string parameter substitution mechanism. |
| | 1899 | */ |
| | 1900 | verbEndingS { return isPlural ? '' : 's'; } |
| | 1901 | verbEndingSD = (tSel(verbEndingS, 'd')) |
| | 1902 | verbEndingSEd = (tSel(verbEndingS, 'ed')) |
| | 1903 | verbEndingSMessageBuilder_ = |
| | 1904 | (tSel(verbEndingS, langMessageBuilder.pastEnding_)) |
| | 1905 | |
| | 1906 | /* |
| | 1907 | * Verb endings (present or past) for regular '-es/-ed' and |
| | 1908 | * '-y/-ies/-ied' verbs, agreeing with this object as the subject. |
| | 1909 | */ |
| | 1910 | verbEndingEs { return tSel(isPlural ? '' : 'es', 'ed'); } |
| | 1911 | verbEndingIes { return tSel(isPlural ? 'y' : 'ies', 'ied'); } |
| | 1912 | |
| | 1913 | /* |
| | 1914 | * Dummy name - this simply displays nothing; it's used for cases |
| | 1915 | * where messageBuilder substitutions want to refer to an object (for |
| | 1916 | * internal bookkeeping) without actually showing the name of the |
| | 1917 | * object in the output text. This should always simply return an |
| | 1918 | * empty string. |
| | 1919 | */ |
| | 1920 | dummyName = '' |
| | 1921 | |
| | 1922 | /* |
| | 1923 | * Invoke a property (with an optional argument list) on this object |
| | 1924 | * while temporarily switching to the present tense, and return the |
| | 1925 | * result. |
| | 1926 | */ |
| | 1927 | propWithPresent(prop, [args]) |
| | 1928 | { |
| | 1929 | return withPresent({: self.(prop)(args...)}); |
| | 1930 | } |
| | 1931 | |
| | 1932 | /* |
| | 1933 | * Method for internal use only: invoke on this object the property |
| | 1934 | * stored in langMessageBuilder.fixedTenseProp_ while temporarily |
| | 1935 | * switching to the present tense, and return the result. This is |
| | 1936 | * used as part of the string parameter substitution mechanism. |
| | 1937 | */ |
| | 1938 | propWithPresentMessageBuilder_ |
| | 1939 | { |
| | 1940 | return propWithPresent(langMessageBuilder.fixedTenseProp_); |
| | 1941 | } |
| | 1942 | |
| | 1943 | /* |
| | 1944 | * For the most part, "strike" has the same meaning as "hit", so |
| | 1945 | * define this as a synonym for "attack" most objects. There are a |
| | 1946 | * few English idioms where "strike" means something different, as |
| | 1947 | * in "strike match" or "strike tent." |
| | 1948 | */ |
| | 1949 | dobjFor(Strike) asDobjFor(Attack) |
| | 1950 | ; |
| | 1951 | |
| | 1952 | /* ------------------------------------------------------------------------ */ |
| | 1953 | /* |
| | 1954 | * An object that uses the same name as another object. This maps all of |
| | 1955 | * the properties involved in supplying the object's name, number, and |
| | 1956 | * other usage information from this object to a given target object, so |
| | 1957 | * that all messages involving this object use the same name as the |
| | 1958 | * target object. This is a mix-in class that can be used with any other |
| | 1959 | * class. |
| | 1960 | * |
| | 1961 | * Note that we map only the *reported* name for the object. We do NOT |
| | 1962 | * give this object any vocabulary from the other object; in other words, |
| | 1963 | * we don't enter this object into the dictionary with the other object's |
| | 1964 | * vocabulary words. |
| | 1965 | */ |
| | 1966 | class NameAsOther: object |
| | 1967 | /* the target object - we'll use the same name as this object */ |
| | 1968 | targetObj = nil |
| | 1969 | |
| | 1970 | /* map our naming and usage properties to the target object */ |
| | 1971 | isPlural = (targetObj.isPlural) |
| | 1972 | isMassNoun = (targetObj.isMassNoun) |
| | 1973 | isHim = (targetObj.isHim) |
| | 1974 | isHer = (targetObj.isHer) |
| | 1975 | isIt = (targetObj.isIt) |
| | 1976 | isProperName = (targetObj.isProperName) |
| | 1977 | isQualifiedName = (targetObj.isQualifiedName) |
| | 1978 | name = (targetObj.name) |
| | 1979 | |
| | 1980 | /* map the derived name properties as well, in case any are overridden */ |
| | 1981 | disambigName = (targetObj.disambigName) |
| | 1982 | theDisambigName = (targetObj.theDisambigName) |
| | 1983 | aDisambigName = (targetObj.aDisambigName) |
| | 1984 | countDisambigName(cnt) { return targetObj.countDisambigName(cnt); } |
| | 1985 | disambigEquivName = (targetObj.disambigEquivName) |
| | 1986 | listName = (targetObj.listName) |
| | 1987 | countName(cnt) { return targetObj.countName(cnt); } |
| | 1988 | |
| | 1989 | /* map the pronoun properites, in case any are overridden */ |
| | 1990 | itNom = (targetObj.itNom) |
| | 1991 | itObj = (targetObj.itObj) |
| | 1992 | itPossAdj = (targetObj.itPossAdj) |
| | 1993 | itPossNoun = (targetObj.itPossNoun) |
| | 1994 | itReflexive = (targetObj.itReflexive) |
| | 1995 | thatNom = (targetObj.thatNom) |
| | 1996 | thatObj = (targetObj.thatObj) |
| | 1997 | thatIsContraction = (targetObj.thatIsContraction) |
| | 1998 | itIs = (targetObj.itIs) |
| | 1999 | itIsContraction = (targetObj.itIsContraction) |
| | 2000 | itVerb(verb) { return targetObj.itVerb(verb); } |
| | 2001 | conjugateRegularVerb(verb) |
| | 2002 | { return targetObj.conjugateRegularVerb(verb); } |
| | 2003 | theName = (targetObj.theName) |
| | 2004 | theNameObj = (targetObj.theNameObj) |
| | 2005 | theNamePossAdj = (targetObj.theNamePossAdj) |
| | 2006 | theNamePossNoun = (targetObj.theNamePossNoun) |
| | 2007 | theNameWithOwner = (targetObj.theNameWithOwner) |
| | 2008 | aNameOwnerLoc(ownerPri) |
| | 2009 | { return targetObj.aNameOwnerLoc(ownerPri); } |
| | 2010 | theNameOwnerLoc(ownerPri) |
| | 2011 | { return targetObj.theNameOwnerLoc(ownerPri); } |
| | 2012 | countNameOwnerLoc(cnt, ownerPri) |
| | 2013 | { return targetObj.countNameOwnerLoc(cnt, ownerPri); } |
| | 2014 | notePromptByOwnerLoc(ownerPri) |
| | 2015 | { targetObj.notePromptByOwnerLoc(ownerPri); } |
| | 2016 | notePromptByPossAdj() |
| | 2017 | { targetObj.notePromptByPossAdj(); } |
| | 2018 | aName = (targetObj.aName) |
| | 2019 | aNameObj = (targetObj.aNameObj) |
| | 2020 | pluralName = (targetObj.pluralName) |
| | 2021 | nameIs = (targetObj.nameIs) |
| | 2022 | nameIsnt = (targetObj.nameIsnt) |
| | 2023 | nameVerb(verb) { return targetObj.nameVerb(verb); } |
| | 2024 | verbToBe = (targetObj.verbToBe) |
| | 2025 | verbWas = (targetObj.verbWas) |
| | 2026 | verbToHave = (targetObj.verbToHave) |
| | 2027 | verbToDo = (targetObj.verbToDo) |
| | 2028 | nameDoes = (targetObj.nameDoes) |
| | 2029 | verbToGo = (targetObj.verbToGo) |
| | 2030 | verbToCome = (targetObj.verbToCome) |
| | 2031 | verbToLeave = (targetObj.verbToLeave) |
| | 2032 | verbToSee = (targetObj.verbToSee) |
| | 2033 | nameSees = (targetObj.nameSees) |
| | 2034 | verbToSay = (targetObj.verbToSay) |
| | 2035 | nameSays = (targetObj.nameSays) |
| | 2036 | verbMust = (targetObj.verbMust) |
| | 2037 | verbCan = (targetObj.verbCan) |
| | 2038 | verbCannot = (targetObj.verbCannot) |
| | 2039 | verbCant = (targetObj.verbCant) |
| | 2040 | verbWill = (targetObj.verbWill) |
| | 2041 | verbWont = (targetObj.verbWont) |
| | 2042 | |
| | 2043 | verbEndingS = (targetObj.verbEndingS) |
| | 2044 | verbEndingSD = (targetObj.verbEndingSD) |
| | 2045 | verbEndingSEd = (targetObj.verbEndingSEd) |
| | 2046 | verbEndingEs = (targetObj.verbEndingEs) |
| | 2047 | verbEndingIes = (targetObj.verbEndingIes) |
| | 2048 | ; |
| | 2049 | |
| | 2050 | /* |
| | 2051 | * Name as Parent - this is a special case of NameAsOther that uses the |
| | 2052 | * lexical parent of a nested object as the target object. (The lexical |
| | 2053 | * parent is the enclosing object in a nested object definition; in other |
| | 2054 | * words, it's the object in which the nested object is embedded.) |
| | 2055 | */ |
| | 2056 | class NameAsParent: NameAsOther |
| | 2057 | targetObj = (lexicalParent) |
| | 2058 | ; |
| | 2059 | |
| | 2060 | /* |
| | 2061 | * ChildNameAsOther is a mix-in class that can be used with NameAsOther |
| | 2062 | * to add the various childInXxx naming to the mapped properties. The |
| | 2063 | * childInXxx names are the names generated when another object is |
| | 2064 | * described as located within this object; by mapping these properties |
| | 2065 | * to our target object, we ensure that we use exactly the same phrasing |
| | 2066 | * as we would if the contained object were actually contained by our |
| | 2067 | * target rather than by us. |
| | 2068 | * |
| | 2069 | * Note that this should always be used in combination with NameAsOther: |
| | 2070 | * |
| | 2071 | * myObj: NameAsOther, ChildNameAsOther, Thing ... |
| | 2072 | * |
| | 2073 | * You can also use it the same way in combination with a subclass of |
| | 2074 | * NameAsOther, such as NameAsParent. |
| | 2075 | */ |
| | 2076 | class ChildNameAsOther: object |
| | 2077 | objInPrep = (targetObj.objInPrep) |
| | 2078 | actorInPrep = (targetObj.actorInPrep) |
| | 2079 | actorOutOfPrep = (targetObj.actorOutOfPrep) |
| | 2080 | actorIntoPrep = (targetObj.actorIntoPrep) |
| | 2081 | childInName(childName) { return targetObj.childInName(childName); } |
| | 2082 | childInNameWithOwner(childName) |
| | 2083 | { return targetObj.childInNameWithOwner(childName); } |
| | 2084 | childInNameGen(childName, myName) |
| | 2085 | { return targetObj.childInNameGen(childName, myName); } |
| | 2086 | actorInName = (targetObj.actorInName) |
| | 2087 | actorOutOfName = (targetObj.actorOutOfName) |
| | 2088 | actorIntoName = (targetObj.actorIntoName) |
| | 2089 | actorInAName = (targetObj.actorInAName) |
| | 2090 | ; |
| | 2091 | |
| | 2092 | |
| | 2093 | /* ------------------------------------------------------------------------ */ |
| | 2094 | /* |
| | 2095 | * Language modifications for the specialized container types |
| | 2096 | */ |
| | 2097 | modify Surface |
| | 2098 | /* |
| | 2099 | * objects contained in a Surface are described as being on the |
| | 2100 | * Surface |
| | 2101 | */ |
| | 2102 | objInPrep = 'on' |
| | 2103 | actorInPrep = 'on' |
| | 2104 | actorOutOfPrep = 'off of' |
| | 2105 | ; |
| | 2106 | |
| | 2107 | modify Underside |
| | 2108 | objInPrep = 'under' |
| | 2109 | actorInPrep = 'under' |
| | 2110 | actorOutOfPrep = 'from under' |
| | 2111 | ; |
| | 2112 | |
| | 2113 | modify RearContainer |
| | 2114 | objInPrep = 'behind' |
| | 2115 | actorInPrep = 'behind' |
| | 2116 | actorOutOfPrep = 'from behind' |
| | 2117 | ; |
| | 2118 | |
| | 2119 | /* ------------------------------------------------------------------------ */ |
| | 2120 | /* |
| | 2121 | * Language modifications for Actor. |
| | 2122 | * |
| | 2123 | * An Actor has a "referral person" setting, which determines how we |
| | 2124 | * refer to the actor; this is almost exclusively for the use of the |
| | 2125 | * player character. The typical convention is that we refer to the |
| | 2126 | * player character in the second person, but a game can override this on |
| | 2127 | * an actor-by-actor basis. |
| | 2128 | */ |
| | 2129 | modify Actor |
| | 2130 | /* by default, use my pronoun for my name */ |
| | 2131 | name = (itNom) |
| | 2132 | |
| | 2133 | /* |
| | 2134 | * Pronoun selector. This returns an index for selecting pronouns |
| | 2135 | * or other words based on number and gender, taking into account |
| | 2136 | * person, number, and gender. The value returned is the sum of the |
| | 2137 | * following components: |
| | 2138 | * |
| | 2139 | * number/gender: |
| | 2140 | *. - singular neuter = 1 |
| | 2141 | *. - singular masculine = 2 |
| | 2142 | *. - singular feminine = 3 |
| | 2143 | *. - plural = 4 |
| | 2144 | * |
| | 2145 | * person: |
| | 2146 | *. - first person = 0 |
| | 2147 | *. - second person = 4 |
| | 2148 | *. - third person = 8 |
| | 2149 | * |
| | 2150 | * The result can be used as a list selector as follows (1=first |
| | 2151 | * person, etc; s=singular, p=plural; n=neuter, m=masculine, |
| | 2152 | * f=feminine): |
| | 2153 | * |
| | 2154 | * [1/s/n, 1/s/m, 1/s/f, 1/p, 2/s/n, 2/s/m, 2/s/f, 2/p, |
| | 2155 | *. 3/s/n, 3/s/m, 3/s/f, 3/p] |
| | 2156 | */ |
| | 2157 | pronounSelector |
| | 2158 | { |
| | 2159 | return ((referralPerson - FirstPerson)*4 |
| | 2160 | + (isPlural ? 4 : isHim ? 2 : isHer ? 3 : 1)); |
| | 2161 | } |
| | 2162 | |
| | 2163 | /* |
| | 2164 | * get the verb form selector index for the person and number: |
| | 2165 | * |
| | 2166 | * [1/s, 2/s, 3/s, 1/p, 2/p, 3/p] |
| | 2167 | */ |
| | 2168 | conjugationSelector |
| | 2169 | { |
| | 2170 | return (referralPerson + (isPlural ? 3 : 0)); |
| | 2171 | } |
| | 2172 | |
| | 2173 | /* |
| | 2174 | * get an appropriate pronoun for the object in the appropriate |
| | 2175 | * person for the nominative case, objective case, possessive |
| | 2176 | * adjective, possessive noun, and objective reflexive |
| | 2177 | */ |
| | 2178 | itNom |
| | 2179 | { |
| | 2180 | return ['I', 'I', 'I', 'we', |
| | 2181 | 'you', 'you', 'you', 'you', |
| | 2182 | 'it', 'he', 'she', 'they'][pronounSelector]; |
| | 2183 | } |
| | 2184 | itObj |
| | 2185 | { |
| | 2186 | return ['me', 'me', 'me', 'us', |
| | 2187 | 'you', 'you', 'you', 'you', |
| | 2188 | 'it', 'him', 'her', 'them'][pronounSelector]; |
| | 2189 | } |
| | 2190 | itPossAdj |
| | 2191 | { |
| | 2192 | return ['my', 'my', 'my', 'our', |
| | 2193 | 'your', 'your', 'your', 'your', |
| | 2194 | 'its', 'his', 'her', 'their'][pronounSelector]; |
| | 2195 | } |
| | 2196 | itPossNoun |
| | 2197 | { |
| | 2198 | return ['mine', 'mine', 'mine', 'ours', |
| | 2199 | 'yours', 'yours', 'yours', 'yours', |
| | 2200 | 'its', 'his', 'hers', 'theirs'][pronounSelector]; |
| | 2201 | } |
| | 2202 | itReflexive |
| | 2203 | { |
| | 2204 | return ['myself', 'myself', 'myself', 'ourselves', |
| | 2205 | 'yourself', 'yourself', 'yourself', 'yourselves', |
| | 2206 | 'itself', 'himself', 'herself', 'themselves'][pronounSelector]; |
| | 2207 | } |
| | 2208 | |
| | 2209 | /* |
| | 2210 | * Demonstrative pronoun, nominative case. We'll use personal a |
| | 2211 | * personal pronoun if we have a gender or we're in the first or |
| | 2212 | * second person, otherwise we'll use 'that' or 'those' as we would |
| | 2213 | * for an inanimate object. |
| | 2214 | */ |
| | 2215 | thatNom |
| | 2216 | { |
| | 2217 | return ['I', 'I', 'I', 'we', |
| | 2218 | 'you', 'you', 'you', 'you', |
| | 2219 | 'that', 'he', 'she', 'those'][pronounSelector]; |
| | 2220 | } |
| | 2221 | |
| | 2222 | /* demonstrative pronoun, objective case */ |
| | 2223 | thatObj |
| | 2224 | { |
| | 2225 | return ['me', 'me', 'me', 'us', |
| | 2226 | 'you', 'you', 'you', 'you', |
| | 2227 | 'that', 'him', 'her', 'those'][pronounSelector]; |
| | 2228 | } |
| | 2229 | |
| | 2230 | /* demonstrative pronoun, nominative case with 'is' contraction */ |
| | 2231 | thatIsContraction |
| | 2232 | { |
| | 2233 | return thatNom |
| | 2234 | + tSel(['’m', '’re', '’s', |
| | 2235 | '’re', '’re', ' are'][conjugationSelector], |
| | 2236 | ' ' + verbToBe); |
| | 2237 | } |
| | 2238 | |
| | 2239 | /* |
| | 2240 | * We don't need to override itIs: the base class handling works for |
| | 2241 | * actors too. |
| | 2242 | */ |
| | 2243 | |
| | 2244 | /* get my pronoun with a being verb contraction ("the box's") */ |
| | 2245 | itIsContraction |
| | 2246 | { |
| | 2247 | return itNom + tSel( |
| | 2248 | '’' |
| | 2249 | + ['m', 're', 's', 're', 're', 're'][conjugationSelector], |
| | 2250 | ' ' + verbToBe); |
| | 2251 | } |
| | 2252 | |
| | 2253 | /* |
| | 2254 | * Conjugate a regular verb in the present or past tense for our |
| | 2255 | * person and number. |
| | 2256 | * |
| | 2257 | * In the present tense, this is pretty easy: we add an 's' for the |
| | 2258 | * third person singular, and leave the verb unchanged for every |
| | 2259 | * other case. The only complication is that we must check some |
| | 2260 | * special cases to add the -s suffix: -y -> -ies, -o -> -oes. |
| | 2261 | * |
| | 2262 | * In the past tense, we use the inherited handling since the past |
| | 2263 | * tense ending doesn't vary with person. |
| | 2264 | */ |
| | 2265 | conjugateRegularVerb(verb) |
| | 2266 | { |
| | 2267 | /* |
| | 2268 | * If we're in the third person or if we use the past tense, |
| | 2269 | * inherit the default handling; otherwise, use the base verb |
| | 2270 | * form regardless of number (regular verbs use the same |
| | 2271 | * conjugated forms for every case but third person singular: I |
| | 2272 | * ask, you ask, we ask, they ask). |
| | 2273 | */ |
| | 2274 | if (referralPerson != ThirdPerson && !gameMain.usePastTense) |
| | 2275 | { |
| | 2276 | /* |
| | 2277 | * we're not using the third-person or the past tense, so the |
| | 2278 | * conjugation is the same as the base verb form |
| | 2279 | */ |
| | 2280 | return verb; |
| | 2281 | } |
| | 2282 | else |
| | 2283 | { |
| | 2284 | /* |
| | 2285 | * we're using the third person or the past tense, so inherit |
| | 2286 | * the base class handling, which conjugates these forms |
| | 2287 | */ |
| | 2288 | return inherited(verb); |
| | 2289 | } |
| | 2290 | } |
| | 2291 | |
| | 2292 | /* |
| | 2293 | * Get the name with a definite article ("the box"). If the |
| | 2294 | * narrator refers to us in the first or second person, use a |
| | 2295 | * pronoun rather than the short description. |
| | 2296 | */ |
| | 2297 | theName |
| | 2298 | { return (referralPerson == ThirdPerson ? inherited : itNom); } |
| | 2299 | |
| | 2300 | /* theName in objective case */ |
| | 2301 | theNameObj |
| | 2302 | { return (referralPerson == ThirdPerson ? inherited : itObj); } |
| | 2303 | |
| | 2304 | /* theName as a possessive adjective */ |
| | 2305 | theNamePossAdj |
| | 2306 | { return (referralPerson == ThirdPerson ? inherited : itPossAdj); } |
| | 2307 | |
| | 2308 | /* theName as a possessive noun */ |
| | 2309 | theNamePossNoun |
| | 2310 | { return (referralPerson == ThirdPerson ? inherited : itPossNoun); } |
| | 2311 | |
| | 2312 | /* |
| | 2313 | * Get the name with an indefinite article. Use the same rules of |
| | 2314 | * referral person as for definite articles. |
| | 2315 | */ |
| | 2316 | aName { return (referralPerson == ThirdPerson ? inherited : itNom); } |
| | 2317 | |
| | 2318 | /* aName in objective case */ |
| | 2319 | aNameObj { return (referralPerson == ThirdPerson ? inherited : itObj); } |
| | 2320 | |
| | 2321 | /* being verb agreeing with this object as subject */ |
| | 2322 | verbToBe |
| | 2323 | { |
| | 2324 | return tSel(['am', 'are', 'is', 'are', 'are', 'are'], |
| | 2325 | ['was', 'were', 'was', 'were', 'were', 'were']) |
| | 2326 | [conjugationSelector]; |
| | 2327 | } |
| | 2328 | |
| | 2329 | /* past tense being verb agreeing with this object as subject */ |
| | 2330 | verbWas |
| | 2331 | { |
| | 2332 | return tSel(['was', 'were', 'was', 'were', 'were', 'were'] |
| | 2333 | [conjugationSelector], 'had been'); |
| | 2334 | } |
| | 2335 | |
| | 2336 | /* 'have' verb agreeing with this object as subject */ |
| | 2337 | verbToHave |
| | 2338 | { |
| | 2339 | return tSel(['have', 'have', 'has', 'have', 'have', 'have'] |
| | 2340 | [conjugationSelector], 'had'); |
| | 2341 | } |
| | 2342 | |
| | 2343 | /* |
| | 2344 | * verb endings for regular '-s' and '-es' verbs, agreeing with this |
| | 2345 | * object as the subject |
| | 2346 | */ |
| | 2347 | verbEndingS |
| | 2348 | { |
| | 2349 | return ['', '', 's', '', '', ''][conjugationSelector]; |
| | 2350 | } |
| | 2351 | verbEndingEs |
| | 2352 | { |
| | 2353 | return tSel(['', '', 'es', '', '', ''][conjugationSelector], 'ed'); |
| | 2354 | } |
| | 2355 | verbEndingIes |
| | 2356 | { |
| | 2357 | return tSel(['y', 'y', 'ies', 'y', 'y', 'y'][conjugationSelector], |
| | 2358 | 'ied'); |
| | 2359 | } |
| | 2360 | |
| | 2361 | /* "I'm not" doesn't fit the regular "+n't" rule */ |
| | 2362 | nameIsnt |
| | 2363 | { |
| | 2364 | return conjugationSelector == 1 && !gameMain.usePastTense |
| | 2365 | ? 'I’m not' : inherited; |
| | 2366 | } |
| | 2367 | |
| | 2368 | /* |
| | 2369 | * Show my name for an arrival/departure message. If we've been seen |
| | 2370 | * before by the player character, we'll show our definite name, |
| | 2371 | * otherwise our indefinite name. |
| | 2372 | */ |
| | 2373 | travelerName(arriving) |
| | 2374 | { say(gPlayerChar.hasSeen(self) ? theName : aName); } |
| | 2375 | |
| | 2376 | /* |
| | 2377 | * Test to see if we can match the third-person pronouns. We'll |
| | 2378 | * match these if our inherited test says we match them AND we can |
| | 2379 | * be referred to in the third person. |
| | 2380 | */ |
| | 2381 | canMatchHim = (inherited && canMatch3rdPerson) |
| | 2382 | canMatchHer = (inherited && canMatch3rdPerson) |
| | 2383 | canMatchIt = (inherited && canMatch3rdPerson) |
| | 2384 | canMatchThem = (inherited && canMatch3rdPerson) |
| | 2385 | |
| | 2386 | /* |
| | 2387 | * Test to see if we can match a third-person pronoun ('it', 'him', |
| | 2388 | * 'her', 'them'). We can unless we're the player character and the |
| | 2389 | * player character is referred to in the first or second person. |
| | 2390 | */ |
| | 2391 | canMatch3rdPerson = (!isPlayerChar || referralPerson == ThirdPerson) |
| | 2392 | |
| | 2393 | /* |
| | 2394 | * Set a pronoun antecedent to the given list of ResolveInfo objects. |
| | 2395 | * Pronoun handling is language-specific, so this implementation is |
| | 2396 | * part of the English library, not the generic library. |
| | 2397 | * |
| | 2398 | * If only one object is present, we'll set the object to be the |
| | 2399 | * antecedent of 'it', 'him', or 'her', according to the object's |
| | 2400 | * gender. We'll also set the object as the single antecedent for |
| | 2401 | * 'them'. |
| | 2402 | * |
| | 2403 | * If we have multiple objects present, we'll set the list to be the |
| | 2404 | * antecedent of 'them', and we'll forget about any antecedent for |
| | 2405 | * 'it'. |
| | 2406 | * |
| | 2407 | * Note that the input is a list of ResolveInfo objects, so we must |
| | 2408 | * pull out the underlying game objects when setting the antecedents. |
| | 2409 | */ |
| | 2410 | setPronoun(lst) |
| | 2411 | { |
| | 2412 | /* if the list is empty, ignore it */ |
| | 2413 | if (lst == []) |
| | 2414 | return; |
| | 2415 | |
| | 2416 | /* |
| | 2417 | * if we have multiple objects, the entire list is the antecedent |
| | 2418 | * for 'them'; otherwise, it's a singular antecedent which |
| | 2419 | * depends on its gender |
| | 2420 | */ |
| | 2421 | if (lst.length() > 1) |
| | 2422 | { |
| | 2423 | local objs = lst.mapAll({x: x.obj_}); |
| | 2424 | |
| | 2425 | /* it's 'them' */ |
| | 2426 | setThem(objs); |
| | 2427 | |
| | 2428 | /* forget any 'it' */ |
| | 2429 | setIt(nil); |
| | 2430 | } |
| | 2431 | else if (lst.length() == 1) |
| | 2432 | { |
| | 2433 | /* |
| | 2434 | * We have only one object, so set it as an antecedent |
| | 2435 | * according to its gender. |
| | 2436 | */ |
| | 2437 | setPronounObj(lst[1].obj_); |
| | 2438 | } |
| | 2439 | } |
| | 2440 | |
| | 2441 | /* |
| | 2442 | * Set a pronoun to refer to multiple potential antecedents. This is |
| | 2443 | * used when the verb has multiple noun slots - UNLOCK DOOR WITH KEY. |
| | 2444 | * For verbs like this, we have no way of knowing in advance whether |
| | 2445 | * a future pronoun will refer back to the direct object or the |
| | 2446 | * indirect object (etc) - we could just assume that 'it' will refer |
| | 2447 | * to the direct object, but this won't always be what the player |
| | 2448 | * intended. In natural English, pronoun antecedents must often be |
| | 2449 | * inferred from context at the time of use - so we use the same |
| | 2450 | * approach. |
| | 2451 | * |
| | 2452 | * Pass an argument list consisting of ResolveInfo lists - that is, |
| | 2453 | * pass one argument per noun slot in the verb, and make each |
| | 2454 | * argument a list of ResolveInfo objects. In other words, you call |
| | 2455 | * this just as you would setPronoun(), except you can pass more than |
| | 2456 | * one list argument. |
| | 2457 | * |
| | 2458 | * We'll store the multiple objects as antecedents. When we need to |
| | 2459 | * resolve a future singular pronoun, we'll figure out which of the |
| | 2460 | * multiple antecedents is the most logical choice in the context of |
| | 2461 | * the pronoun's usage. |
| | 2462 | */ |
| | 2463 | setPronounMulti([args]) |
| | 2464 | { |
| | 2465 | local lst, subLst; |
| | 2466 | local gotThem; |
| | 2467 | |
| | 2468 | /* |
| | 2469 | * If there's a plural list, it's 'them'. Arbitrarily use only |
| | 2470 | * the first plural list if there's more than one. |
| | 2471 | */ |
| | 2472 | if ((lst = args.valWhich({x: x.length() > 1})) != nil) |
| | 2473 | { |
| | 2474 | /* set 'them' to the plural list */ |
| | 2475 | setPronoun(lst); |
| | 2476 | |
| | 2477 | /* note that we had a clear 'them' */ |
| | 2478 | gotThem = true; |
| | 2479 | } |
| | 2480 | |
| | 2481 | /* from now on, consider only the sublists with exactly one item */ |
| | 2482 | args = args.subset({x: x.length() == 1}); |
| | 2483 | |
| | 2484 | /* get a list of the singular items from the lists */ |
| | 2485 | lst = args.mapAll({x: x[1].obj_}); |
| | 2486 | |
| | 2487 | /* |
| | 2488 | * Set 'it' to all of the items that can match 'it'; do likewise |
| | 2489 | * with 'him' and 'her'. If there are no objects that can match |
| | 2490 | * a given pronoun, leave that pronoun unchanged. |
| | 2491 | */ |
| | 2492 | if ((subLst = lst.subset({x: x.canMatchIt})).length() > 0) |
| | 2493 | setIt(subLst); |
| | 2494 | if ((subLst = lst.subset({x: x.canMatchHim})).length() > 0) |
| | 2495 | setHim(subLst); |
| | 2496 | if ((subLst = lst.subset({x: x.canMatchHer})).length() > 0) |
| | 2497 | setHer(subLst); |
| | 2498 | |
| | 2499 | /* |
| | 2500 | * set 'them' to the potential 'them' matches, if we didn't |
| | 2501 | * already find a clear plural list |
| | 2502 | */ |
| | 2503 | if (!gotThem |
| | 2504 | && (subLst = lst.subset({x: x.canMatchThem})).length() > 0) |
| | 2505 | setThem(subLst); |
| | 2506 | } |
| | 2507 | |
| | 2508 | /* |
| | 2509 | * Set a pronoun antecedent to the given ResolveInfo list, for the |
| | 2510 | * specified type of pronoun. We don't have to worry about setting |
| | 2511 | * other types of pronouns to this antecedent - we specifically want |
| | 2512 | * to set the given pronoun type. This is language-dependent |
| | 2513 | * because we still have to figure out the number (i.e. singular or |
| | 2514 | * plural) of the pronoun type. |
| | 2515 | */ |
| | 2516 | setPronounByType(typ, lst) |
| | 2517 | { |
| | 2518 | /* check for singular or plural pronouns */ |
| | 2519 | if (typ == PronounThem) |
| | 2520 | { |
| | 2521 | /* it's plural - set a list antecedent */ |
| | 2522 | setPronounAntecedent(typ, lst.mapAll({x: x.obj_})); |
| | 2523 | } |
| | 2524 | else |
| | 2525 | { |
| | 2526 | /* it's singular - set an individual antecedent */ |
| | 2527 | setPronounAntecedent(typ, lst[1].obj_); |
| | 2528 | } |
| | 2529 | } |
| | 2530 | |
| | 2531 | /* |
| | 2532 | * Set a pronoun antecedent to the given simulation object (usually |
| | 2533 | * an object descended from Thing). |
| | 2534 | */ |
| | 2535 | setPronounObj(obj) |
| | 2536 | { |
| | 2537 | /* |
| | 2538 | * Actually use the object's "identity object" as the antecedent |
| | 2539 | * rather than the object itself. In some cases, we use multiple |
| | 2540 | * program objects to represent what appears to be a single |
| | 2541 | * object in the game; in these cases, the internal program |
| | 2542 | * objects all point to the "real" object as their identity |
| | 2543 | * object. Whenever we're manipulating one of these internal |
| | 2544 | * program objects, we want to make sure that its the |
| | 2545 | * player-visible object - the identity object - that appears as |
| | 2546 | * the antecedent for subsequent references. |
| | 2547 | */ |
| | 2548 | obj = obj.getIdentityObject(); |
| | 2549 | |
| | 2550 | /* |
| | 2551 | * Set the appropriate pronoun antecedent, depending on the |
| | 2552 | * object's gender. |
| | 2553 | * |
| | 2554 | * Note that we'll set an object to be the antecedent for both |
| | 2555 | * 'him' and 'her' if the object has both masculine and feminine |
| | 2556 | * usage. |
| | 2557 | */ |
| | 2558 | |
| | 2559 | /* check for masculine usage */ |
| | 2560 | if (obj.canMatchHim) |
| | 2561 | setHim(obj); |
| | 2562 | |
| | 2563 | /* check for feminine usage */ |
| | 2564 | if (obj.canMatchHer) |
| | 2565 | setHer(obj); |
| | 2566 | |
| | 2567 | /* check for neuter usage */ |
| | 2568 | if (obj.canMatchIt) |
| | 2569 | setIt(obj); |
| | 2570 | |
| | 2571 | /* check for third-person plural usage */ |
| | 2572 | if (obj.canMatchThem) |
| | 2573 | setThem([obj]); |
| | 2574 | } |
| | 2575 | |
| | 2576 | /* set a possessive anaphor */ |
| | 2577 | setPossAnaphorObj(obj) |
| | 2578 | { |
| | 2579 | /* check for each type of usage */ |
| | 2580 | if (obj.canMatchHim) |
| | 2581 | possAnaphorTable[PronounHim] = obj; |
| | 2582 | if (obj.canMatchHer) |
| | 2583 | possAnaphorTable[PronounHer] = obj; |
| | 2584 | if (obj.canMatchIt) |
| | 2585 | possAnaphorTable[PronounIt] = obj; |
| | 2586 | if (obj.canMatchThem) |
| | 2587 | possAnaphorTable[PronounThem] = [obj]; |
| | 2588 | } |
| | 2589 | ; |
| | 2590 | |
| | 2591 | /* ------------------------------------------------------------------------ */ |
| | 2592 | /* |
| | 2593 | * Give the postures some additional attributes |
| | 2594 | */ |
| | 2595 | |
| | 2596 | modify Posture |
| | 2597 | |
| | 2598 | /* |
| | 2599 | * Intransitive and transitive forms of the verb, for use in library |
| | 2600 | * messages. Each of these methods simply calls one of the two |
| | 2601 | * corresponding fixed-tense properties, depending on the current |
| | 2602 | * tense. |
| | 2603 | */ |
| | 2604 | msgVerbI = (tSel(msgVerbIPresent, msgVerbIPast)) |
| | 2605 | msgVerbT = (tSel(msgVerbTPresent, msgVerbTPast)) |
| | 2606 | |
| | 2607 | /* |
| | 2608 | * Fixed-tense versions of the above properties, to be defined |
| | 2609 | * individually by each instance of the Posture class. |
| | 2610 | */ |
| | 2611 | |
| | 2612 | /* our present-tense intransitive form ("he stands up") */ |
| | 2613 | // msgVerbIPresent = 'stand{s} up' |
| | 2614 | |
| | 2615 | /* our past-tense intransitive form ("he stood up") */ |
| | 2616 | // msgVerbIPast = 'stood up' |
| | 2617 | |
| | 2618 | /* our present-tense transitive form ("he stands on the chair") */ |
| | 2619 | // msgVerbTPresent = 'stand{s}' |
| | 2620 | |
| | 2621 | /* our past-tense transitive form ("he stood on the chair") */ |
| | 2622 | // msgVerbTPast = 'stood' |
| | 2623 | |
| | 2624 | /* our participle form */ |
| | 2625 | // participle = 'standing' |
| | 2626 | ; |
| | 2627 | |
| | 2628 | modify standing |
| | 2629 | msgVerbIPresent = 'stand{s} up' |
| | 2630 | msgVerbIPast = 'stood up' |
| | 2631 | msgVerbTPresent = 'stand{s}' |
| | 2632 | msgVerbTPast = 'stood' |
| | 2633 | participle = 'standing' |
| | 2634 | ; |
| | 2635 | |
| | 2636 | modify sitting |
| | 2637 | msgVerbIPresent = 'sit{s} down' |
| | 2638 | msgVerbIPast = 'sat down' |
| | 2639 | msgVerbTPresent = 'sit{s}' |
| | 2640 | msgVerbTPast = 'sat' |
| | 2641 | participle = 'sitting' |
| | 2642 | ; |
| | 2643 | |
| | 2644 | modify lying |
| | 2645 | msgVerbIPresent = 'lie{s} down' |
| | 2646 | msgVerbIPast = 'lay down' |
| | 2647 | msgVerbTPresent = 'lie{s}' |
| | 2648 | msgVerbTPast = 'lay' |
| | 2649 | participle = 'lying' |
| | 2650 | ; |
| | 2651 | |
| | 2652 | /* ------------------------------------------------------------------------ */ |
| | 2653 | /* |
| | 2654 | * For our various topic suggestion types, we can infer the full name |
| | 2655 | * from the short name fairly easily. |
| | 2656 | */ |
| | 2657 | modify SuggestedAskTopic |
| | 2658 | fullName = ('ask {it targetActor/him} about ' + name) |
| | 2659 | ; |
| | 2660 | |
| | 2661 | modify SuggestedTellTopic |
| | 2662 | fullName = ('tell {it targetActor/him} about ' + name) |
| | 2663 | ; |
| | 2664 | |
| | 2665 | modify SuggestedAskForTopic |
| | 2666 | fullName = ('ask {it targetActor/him} for ' + name) |
| | 2667 | ; |
| | 2668 | |
| | 2669 | modify SuggestedGiveTopic |
| | 2670 | fullName = ('give {it targetActor/him} ' + name) |
| | 2671 | ; |
| | 2672 | |
| | 2673 | modify SuggestedShowTopic |
| | 2674 | fullName = ('show {it targetActor/him} ' + name) |
| | 2675 | ; |
| | 2676 | |
| | 2677 | modify SuggestedYesTopic |
| | 2678 | name = 'yes' |
| | 2679 | fullName = 'say yes' |
| | 2680 | ; |
| | 2681 | |
| | 2682 | modify SuggestedNoTopic |
| | 2683 | name = 'no' |
| | 2684 | fullName = 'say no' |
| | 2685 | ; |
| | 2686 | |
| | 2687 | /* ------------------------------------------------------------------------ */ |
| | 2688 | /* |
| | 2689 | * Provide custom processing of the player input for matching |
| | 2690 | * SpecialTopic patterns. When we're trying to match a player's command |
| | 2691 | * to a set of active special topics, we'll run the input through this |
| | 2692 | * processing to produce the string that we actually match against the |
| | 2693 | * special topics. |
| | 2694 | * |
| | 2695 | * First, we'll remove any punctuation marks. This ensures that we'll |
| | 2696 | * still match a special topic, for example, if the player puts a period |
| | 2697 | * or a question mark at the end of the command. |
| | 2698 | * |
| | 2699 | * Second, if the user's input starts with "A" or "T" (the super-short |
| | 2700 | * forms of the ASK ABOUT and TELL ABOUT commands), remove the "A" or "T" |
| | 2701 | * and keep the rest of the input. Some users might think that special |
| | 2702 | * topic suggestions are meant as ask/tell topics, so they might |
| | 2703 | * instinctively try these as A/T commands. |
| | 2704 | * |
| | 2705 | * Users *probably* won't be tempted to do the same thing with the full |
| | 2706 | * forms of the commands (e.g., ASK BOB ABOUT APOLOGIZE, TELL BOB ABOUT |
| | 2707 | * EXPLAIN). It's more a matter of habit of using A or T for interaction |
| | 2708 | * that would tempt a user to phrase a special topic this way; once |
| | 2709 | * you're typing out the full form of the command, it generally won't be |
| | 2710 | * grammatical, as special topics generally contain the sense of a verb |
| | 2711 | * in their phrasing. |
| | 2712 | */ |
| | 2713 | modify specialTopicPreParser |
| | 2714 | processInputStr(str) |
| | 2715 | { |
| | 2716 | /* |
| | 2717 | * remove most punctuation from the string - we generally want to |
| | 2718 | * ignore these, as we mostly just want to match keywords |
| | 2719 | */ |
| | 2720 | str = rexReplace(punctPat, str, '', ReplaceAll); |
| | 2721 | |
| | 2722 | /* if it starts with "A" or "T", strip off the leading verb */ |
| | 2723 | if (rexMatch(aOrTPat, str) != nil) |
| | 2724 | str = rexGroup(1)[3]; |
| | 2725 | |
| | 2726 | /* return the processed result */ |
| | 2727 | return str; |
| | 2728 | } |
| | 2729 | |
| | 2730 | /* pattern for string starting with "A" or "T" verbs */ |
| | 2731 | aOrTPat = static new RexPattern( |
| | 2732 | '<nocase><space>*[at]<space>+(<^space>.*)$') |
| | 2733 | |
| | 2734 | /* pattern to eliminate punctuation marks from the string */ |
| | 2735 | punctPat = static new RexPattern('[.?!,;:]'); |
| | 2736 | ; |
| | 2737 | |
| | 2738 | /* |
| | 2739 | * For SpecialTopic matches, treat some strings as "weak": if the user's |
| | 2740 | * input consists of just one of these weak strings and nothing else, |
| | 2741 | * don't match the topic. |
| | 2742 | */ |
| | 2743 | modify SpecialTopic |
| | 2744 | matchPreParse(str, procStr) |
| | 2745 | { |
| | 2746 | /* if it's one of our 'weak' strings, don't match */ |
| | 2747 | if (rexMatch(weakPat, str) != nil) |
| | 2748 | return nil; |
| | 2749 | |
| | 2750 | /* it's not a weak string, so match as usual */ |
| | 2751 | return inherited(str, procStr); |
| | 2752 | } |
| | 2753 | |
| | 2754 | /* |
| | 2755 | * Our "weak" strings - 'i', 'l', 'look': these are weak because a |
| | 2756 | * user typing one of these strings by itself is probably actually |
| | 2757 | * trying to enter the command of the same name, rather than entering |
| | 2758 | * a special topic. These come up in cases where the special topic |
| | 2759 | * is something like "say I don't know" or "tell him you'll look into |
| | 2760 | * it". |
| | 2761 | */ |
| | 2762 | weakPat = static new RexPattern('<nocase><space>*(i|l|look)<space>*$') |
| | 2763 | ; |
| | 2764 | |
| | 2765 | /* ------------------------------------------------------------------------ */ |
| | 2766 | /* |
| | 2767 | * English-specific Traveler changes |
| | 2768 | */ |
| | 2769 | modify Traveler |
| | 2770 | /* |
| | 2771 | * Get my location's name, from the PC's perspective, for describing |
| | 2772 | * my arrival to or departure from my current location. We'll |
| | 2773 | * simply return our location's destName, or "the area" if it |
| | 2774 | * doesn't have one. |
| | 2775 | */ |
| | 2776 | travelerLocName() |
| | 2777 | { |
| | 2778 | /* get our location's name from the PC's perspective */ |
| | 2779 | local nm = location.getDestName(gPlayerChar, gPlayerChar.location); |
| | 2780 | |
| | 2781 | /* if there's a name, return it; otherwise, use "the area" */ |
| | 2782 | return (nm != nil ? nm : 'the area'); |
| | 2783 | } |
| | 2784 | |
| | 2785 | /* |
| | 2786 | * Get my "remote" location name, from the PC's perspective. This |
| | 2787 | * returns my location name, but only if my location is remote from |
| | 2788 | * the PC's perspective - that is, my location has to be outside of |
| | 2789 | * the PC's top-level room. If we're within the PC's top-level |
| | 2790 | * room, we'll simply return an empty string. |
| | 2791 | */ |
| | 2792 | travelerRemoteLocName() |
| | 2793 | { |
| | 2794 | /* |
| | 2795 | * if my location is outside of the PC's outermost room, we're |
| | 2796 | * remote, so return my location name; otherwise, we're local, |
| | 2797 | * so we don't need a remote name at all |
| | 2798 | */ |
| | 2799 | if (isIn(gPlayerChar.getOutermostRoom())) |
| | 2800 | return ''; |
| | 2801 | else |
| | 2802 | return travelerLocName; |
| | 2803 | } |
| | 2804 | ; |
| | 2805 | |
| | 2806 | /* ------------------------------------------------------------------------ */ |
| | 2807 | /* |
| | 2808 | * English-specific Vehicle changes |
| | 2809 | */ |
| | 2810 | modify Vehicle |
| | 2811 | /* |
| | 2812 | * Display the name of the traveler, for use in an arrival or |
| | 2813 | * departure message. |
| | 2814 | */ |
| | 2815 | travelerName(arriving) |
| | 2816 | { |
| | 2817 | /* |
| | 2818 | * By default, start with the indefinite name if we're arriving, |
| | 2819 | * or the definite name if we're leaving. |
| | 2820 | * |
| | 2821 | * If we're leaving, presumably they've seen us before, since we |
| | 2822 | * were already in the room to start with. Since we've been |
| | 2823 | * seen before, the definite is appropriate. |
| | 2824 | * |
| | 2825 | * If we're arriving, even if we're not being seen for the first |
| | 2826 | * time, we haven't been seen yet in this place around this |
| | 2827 | * time, so the indefinite is appropriate. |
| | 2828 | */ |
| | 2829 | say(arriving ? aName : theName); |
| | 2830 | |
| | 2831 | /* show the list of actors aboard */ |
| | 2832 | aboardVehicleListerObj.showList( |
| | 2833 | libGlobal.playerChar, nil, allContents(), 0, 0, |
| | 2834 | libGlobal.playerChar.visibleInfoTable(), nil); |
| | 2835 | } |
| | 2836 | ; |
| | 2837 | |
| | 2838 | /* ------------------------------------------------------------------------ */ |
| | 2839 | /* |
| | 2840 | * English-specific PushTraveler changes |
| | 2841 | */ |
| | 2842 | modify PushTraveler |
| | 2843 | /* |
| | 2844 | * When an actor is pushing an object from one room to another, show |
| | 2845 | * its name with an additional clause indicating the object being |
| | 2846 | * moved along with us. |
| | 2847 | */ |
| | 2848 | travelerName(arriving) |
| | 2849 | { |
| | 2850 | "<<gPlayerChar.hasSeen(self) ? theName : aName>>, |
| | 2851 | pushing <<obj_.theNameObj>>,"; |
| | 2852 | } |
| | 2853 | ; |
| | 2854 | |
| | 2855 | |
| | 2856 | /* ------------------------------------------------------------------------ */ |
| | 2857 | /* |
| | 2858 | * English-specific travel connector changes |
| | 2859 | */ |
| | 2860 | |
| | 2861 | modify PathPassage |
| | 2862 | /* treat "take path" the same as "enter path" or "go through path" */ |
| | 2863 | dobjFor(Take) maybeRemapTo( |
| | 2864 | gAction.getEnteredVerbPhrase() == 'take (dobj)', TravelVia, self) |
| | 2865 | |
| | 2866 | dobjFor(Enter) |
| | 2867 | { |
| | 2868 | verify() { logicalRank(50, 'enter path'); } |
| | 2869 | } |
| | 2870 | dobjFor(GoThrough) |
| | 2871 | { |
| | 2872 | verify() { logicalRank(50, 'enter path'); } |
| | 2873 | } |
| | 2874 | ; |
| | 2875 | |
| | 2876 | modify AskConnector |
| | 2877 | /* |
| | 2878 | * This is the noun phrase we'll use when asking disambiguation |
| | 2879 | * questions for this travel connector: "Which *one* do you want to |
| | 2880 | * enter..." |
| | 2881 | */ |
| | 2882 | travelObjsPhrase = 'one' |
| | 2883 | ; |
| | 2884 | |
| | 2885 | /* ------------------------------------------------------------------------ */ |
| | 2886 | /* |
| | 2887 | * English-specific changes for various nested room types. |
| | 2888 | */ |
| | 2889 | modify BasicChair |
| | 2890 | /* by default, one sits *on* a chair */ |
| | 2891 | objInPrep = 'on' |
| | 2892 | actorInPrep = 'on' |
| | 2893 | actorOutOfPrep = 'off of' |
| | 2894 | ; |
| | 2895 | |
| | 2896 | modify BasicPlatform |
| | 2897 | /* by default, one stands *on* a platform */ |
| | 2898 | objInPrep = 'on' |
| | 2899 | actorInPrep = 'on' |
| | 2900 | actorOutOfPrep = 'off of' |
| | 2901 | ; |
| | 2902 | |
| | 2903 | modify Booth |
| | 2904 | /* by default, one is *in* a booth */ |
| | 2905 | objInPrep = 'in' |
| | 2906 | actorInPrep = 'in' |
| | 2907 | actorOutOfPrep = 'out of' |
| | 2908 | ; |
| | 2909 | |
| | 2910 | /* ------------------------------------------------------------------------ */ |
| | 2911 | /* |
| | 2912 | * Language modifications for Matchstick |
| | 2913 | */ |
| | 2914 | modify Matchstick |
| | 2915 | /* "strike match" means "light match" */ |
| | 2916 | dobjFor(Strike) asDobjFor(Burn) |
| | 2917 | |
| | 2918 | /* "light match" means "burn match" */ |
| | 2919 | dobjFor(Light) asDobjFor(Burn) |
| | 2920 | ; |
| | 2921 | |
| | 2922 | /* |
| | 2923 | * Match state objects. We show "lit" as the state for a lit match, |
| | 2924 | * nothing for an unlit match. |
| | 2925 | */ |
| | 2926 | matchStateLit: ThingState 'lit' |
| | 2927 | stateTokens = ['lit'] |
| | 2928 | ; |
| | 2929 | matchStateUnlit: ThingState |
| | 2930 | stateTokens = ['unlit'] |
| | 2931 | ; |
| | 2932 | |
| | 2933 | |
| | 2934 | /* ------------------------------------------------------------------------ */ |
| | 2935 | /* |
| | 2936 | * English-specific modifications for Room. |
| | 2937 | */ |
| | 2938 | modify Room |
| | 2939 | /* |
| | 2940 | * The ordinary 'name' property is used the same way it's used for |
| | 2941 | * any other object, to refer to the room when it shows up in |
| | 2942 | * library messages and the like: "You can't take the hallway." |
| | 2943 | * |
| | 2944 | * By default, we derive the name from the roomName by converting |
| | 2945 | * the roomName to lower case. Virtually every room will need a |
| | 2946 | * custom room name setting, since the room name is used mostly as a |
| | 2947 | * title for the room, and good titles are hard to generate |
| | 2948 | * mechanically. Many times, converting the roomName to lower case |
| | 2949 | * will produce a decent name to use in messages: "Ice Cave" gives |
| | 2950 | * us "You can't eat the ice cave." However, games will want to |
| | 2951 | * customize the ordinary name separately in many cases, because the |
| | 2952 | * elliptical, title-like format of the room name doesn't always |
| | 2953 | * translate well to an object name: "West of Statue" gives us the |
| | 2954 | * unworkable "You can't eat the west of statue"; better to make the |
| | 2955 | * ordinary name something like "plaza". Note also that some rooms |
| | 2956 | * have proper names that want to preserve their capitalization in |
| | 2957 | * the ordinary name: "You can't eat the Hall of the Ancient Kings." |
| | 2958 | * These cases need to be customized as well. |
| | 2959 | */ |
| | 2960 | name = (roomName.toLower()) |
| | 2961 | |
| | 2962 | /* |
| | 2963 | * The "destination name" of the room. This is primarily intended |
| | 2964 | * for use in showing exit listings, to describe the destination of |
| | 2965 | * a travel connector leading away from our current location, if the |
| | 2966 | * destination is known to the player character. We also use this |
| | 2967 | * as the default source of the name in similar contexts, such as |
| | 2968 | * when we can see this room from another room connected by a sense |
| | 2969 | * connector. |
| | 2970 | * |
| | 2971 | * The destination name usually mirrors the room name, but we use |
| | 2972 | * the name in prepositional phrases involving the room ("east, to |
| | 2973 | * the alley"), so this name should include a leading article |
| | 2974 | * (usually definite - "the") unless the name is proper ("east, to |
| | 2975 | * Dinsley Plaza"). So, by default, we simply use the "theName" of |
| | 2976 | * the room. In many cases, it's better to specify a custom |
| | 2977 | * destName, because this name is used when the PC is outside of the |
| | 2978 | * room, and thus can benefit from a more detailed description than |
| | 2979 | * we'd normally use for the basic name. For example, the ordinary |
| | 2980 | * name might simply be something like "hallway", but since we want |
| | 2981 | * to be clear about exactly which hallway we're talking about when |
| | 2982 | * we're elsewhere, we might want to use a destName like "the |
| | 2983 | * basement hallway" or "the hallway outside the operating room". |
| | 2984 | */ |
| | 2985 | destName = (theName) |
| | 2986 | |
| | 2987 | /* |
| | 2988 | * For top-level rooms, describe an object as being in the room by |
| | 2989 | * describing it as being in the room's nominal drop destination, |
| | 2990 | * since that's the nominal location for the objects directly in the |
| | 2991 | * room. (In most cases, the nominal drop destination for a room is |
| | 2992 | * its floor.) |
| | 2993 | * |
| | 2994 | * If the player character isn't in the same outermost room as this |
| | 2995 | * container, use our remote name instead of the nominal drop |
| | 2996 | * destination. The nominal drop destination is usually something |
| | 2997 | * like the floor or the ground, so it's only suitable when we're in |
| | 2998 | * the same location as what we're describing. |
| | 2999 | */ |
| | 3000 | childInName(childName) |
| | 3001 | { |
| | 3002 | /* if the PC isn't inside us, we're viewing this remotely */ |
| | 3003 | if (!gPlayerChar.isIn(self)) |
| | 3004 | return childInRemoteName(childName, gPlayerChar); |
| | 3005 | else |
| | 3006 | return getNominalDropDestination().childInName(childName); |
| | 3007 | } |
| | 3008 | childInNameWithOwner(chiName) |
| | 3009 | { |
| | 3010 | /* if the PC isn't inside us, we're viewing this remotely */ |
| | 3011 | if (!gPlayerChar.isIn(self)) |
| | 3012 | return inherited(chiName); |
| | 3013 | else |
| | 3014 | return getNominalDropDestination().childInNameWithOwner(chiName); |
| | 3015 | } |
| | 3016 | ; |
| | 3017 | |
| | 3018 | /* ------------------------------------------------------------------------ */ |
| | 3019 | /* |
| | 3020 | * English-specific modifications for the default room parts. |
| | 3021 | */ |
| | 3022 | |
| | 3023 | modify Floor |
| | 3024 | childInNameGen(childName, myName) { return childName + ' on ' + myName; } |
| | 3025 | objInPrep = 'on' |
| | 3026 | actorInPrep = 'on' |
| | 3027 | actorOutOfPrep = 'off of' |
| | 3028 | ; |
| | 3029 | |
| | 3030 | modify defaultFloor |
| | 3031 | noun = 'floor' 'ground' |
| | 3032 | name = 'floor' |
| | 3033 | ; |
| | 3034 | |
| | 3035 | modify defaultGround |
| | 3036 | noun = 'ground' 'floor' |
| | 3037 | name = 'ground' |
| | 3038 | ; |
| | 3039 | |
| | 3040 | modify DefaultWall noun='wall' plural='walls' name='wall'; |
| | 3041 | modify defaultCeiling noun='ceiling' 'roof' name='ceiling'; |
| | 3042 | modify defaultNorthWall adjective='n' 'north' name='north wall'; |
| | 3043 | modify defaultSouthWall adjective='s' 'south' name='south wall'; |
| | 3044 | modify defaultEastWall adjective='e' 'east' name='east wall'; |
| | 3045 | modify defaultWestWall adjective='w' 'west' name='west wall'; |
| | 3046 | modify defaultSky noun='sky' name='sky'; |
| | 3047 | |
| | 3048 | |
| | 3049 | /* ------------------------------------------------------------------------ */ |
| | 3050 | /* |
| | 3051 | * The English-specific modifications for directions. |
| | 3052 | */ |
| | 3053 | modify Direction |
| | 3054 | /* describe a traveler arriving from this direction */ |
| | 3055 | sayArriving(traveler) |
| | 3056 | { |
| | 3057 | /* show the generic arrival message */ |
| | 3058 | gLibMessages.sayArriving(traveler); |
| | 3059 | } |
| | 3060 | |
| | 3061 | /* describe a traveler departing in this direction */ |
| | 3062 | sayDeparting(traveler) |
| | 3063 | { |
| | 3064 | /* show the generic departure message */ |
| | 3065 | gLibMessages.sayDeparting(traveler); |
| | 3066 | } |
| | 3067 | ; |
| | 3068 | |
| | 3069 | /* |
| | 3070 | * The English-specific modifications for compass directions. |
| | 3071 | */ |
| | 3072 | modify CompassDirection |
| | 3073 | /* describe a traveler arriving from this direction */ |
| | 3074 | sayArriving(traveler) |
| | 3075 | { |
| | 3076 | /* show the generic compass direction description */ |
| | 3077 | gLibMessages.sayArrivingDir(traveler, name); |
| | 3078 | } |
| | 3079 | |
| | 3080 | /* describe a traveler departing in this direction */ |
| | 3081 | sayDeparting(traveler) |
| | 3082 | { |
| | 3083 | /* show the generic compass direction description */ |
| | 3084 | gLibMessages.sayDepartingDir(traveler, name); |
| | 3085 | } |
| | 3086 | ; |
| | 3087 | |
| | 3088 | /* |
| | 3089 | * The English-specific definitions for the compass direction objects. |
| | 3090 | * In addition to modifying the direction objects to define the name of |
| | 3091 | * the direction, we add a 'directionName' grammar rule. |
| | 3092 | */ |
| | 3093 | #define DefineLangDir(root, dirNames, backPre) \ |
| | 3094 | grammar directionName(root): dirNames: DirectionProd \ |
| | 3095 | dir = root##Direction \ |
| | 3096 | ; \ |
| | 3097 | \ |
| | 3098 | modify root##Direction \ |
| | 3099 | name = #@root \ |
| | 3100 | backToPrefix = backPre |
| | 3101 | |
| | 3102 | DefineLangDir(north, 'north' | 'n', 'back to the'); |
| | 3103 | DefineLangDir(south, 'south' | 's', 'back to the'); |
| | 3104 | DefineLangDir(east, 'east' | 'e', 'back to the'); |
| | 3105 | DefineLangDir(west, 'west' | 'w', 'back to the'); |
| | 3106 | DefineLangDir(northeast, 'northeast' | 'ne', 'back to the'); |
| | 3107 | DefineLangDir(northwest, 'northwest' | 'nw', 'back to the'); |
| | 3108 | DefineLangDir(southeast, 'southeast' | 'se', 'back to the'); |
| | 3109 | DefineLangDir(southwest, 'southwest' | 'sw', 'back to the'); |
| | 3110 | DefineLangDir(up, 'up' | 'u', 'back'); |
| | 3111 | DefineLangDir(down, 'down' | 'd', 'back'); |
| | 3112 | DefineLangDir(in, 'in', 'back'); |
| | 3113 | DefineLangDir(out, 'out', 'back'); |
| | 3114 | |
| | 3115 | /* |
| | 3116 | * The English-specific shipboard direction modifications. Certain of |
| | 3117 | * the ship directions have no natural descriptions for arrival and/or |
| | 3118 | * departure; for example, there's no good way to say "arriving from |
| | 3119 | * fore." Others don't fit any regular pattern: "he goes aft" rather |
| | 3120 | * than "he departs to aft." As a result, these are a bit irregular |
| | 3121 | * compared to the compass directions and so are individually defined |
| | 3122 | * below. |
| | 3123 | */ |
| | 3124 | |
| | 3125 | DefineLangDir(port, 'port' | 'p', 'back to') |
| | 3126 | sayArriving(trav) |
| | 3127 | { gLibMessages.sayArrivingShipDir(trav, 'the port direction'); } |
| | 3128 | sayDeparting(trav) |
| | 3129 | { gLibMessages.sayDepartingShipDir(trav, 'port'); } |
| | 3130 | ; |
| | 3131 | |
| | 3132 | DefineLangDir(starboard, 'starboard' | 'sb', 'back to') |
| | 3133 | sayArriving(trav) |
| | 3134 | { gLibMessages.sayArrivingShipDir(trav, 'starboard'); } |
| | 3135 | sayDeparting(trav) |
| | 3136 | { gLibMessages.sayDepartingShipDir(trav, 'starboard'); } |
| | 3137 | ; |
| | 3138 | |
| | 3139 | DefineLangDir(aft, 'aft' | 'a', 'back to') |
| | 3140 | sayArriving(trav) { gLibMessages.sayArrivingShipDir(trav, 'aft'); } |
| | 3141 | sayDeparting(trav) { gLibMessages.sayDepartingAft(trav); } |
| | 3142 | ; |
| | 3143 | |
| | 3144 | DefineLangDir(fore, 'fore' | 'forward' | 'f', 'back to') |
| | 3145 | sayArriving(trav) { gLibMessages.sayArrivingShipDir(trav, 'forward'); } |
| | 3146 | sayDeparting(trav) { gLibMessages.sayDepartingFore(trav); } |
| | 3147 | ; |
| | 3148 | |
| | 3149 | /* ------------------------------------------------------------------------ */ |
| | 3150 | /* |
| | 3151 | * Some helper routines for the library messages. |
| | 3152 | */ |
| | 3153 | class MessageHelper: object |
| | 3154 | /* |
| | 3155 | * Show a list of objects for a disambiguation query. If |
| | 3156 | * 'showIndefCounts' is true, we'll show the number of equivalent |
| | 3157 | * items for each equivalent item; otherwise, we'll just show an |
| | 3158 | * indefinite noun phrase for each equivalent item. |
| | 3159 | */ |
| | 3160 | askDisambigList(matchList, fullMatchList, showIndefCounts, dist) |
| | 3161 | { |
| | 3162 | /* show each item */ |
| | 3163 | for (local i = 1, local len = matchList.length() ; i <= len ; ++i) |
| | 3164 | { |
| | 3165 | local equivCnt; |
| | 3166 | local obj; |
| | 3167 | |
| | 3168 | /* get the current object */ |
| | 3169 | obj = matchList[i].obj_; |
| | 3170 | |
| | 3171 | /* |
| | 3172 | * if this isn't the first, add a comma; if this is the |
| | 3173 | * last, add an "or" as well |
| | 3174 | */ |
| | 3175 | if (i == len) |
| | 3176 | ", or "; |
| | 3177 | else if (i != 1) |
| | 3178 | ", "; |
| | 3179 | |
| | 3180 | /* |
| | 3181 | * Check to see if more than one equivalent of this item |
| | 3182 | * appears in the full list. |
| | 3183 | */ |
| | 3184 | for (equivCnt = 0, local j = 1, |
| | 3185 | local fullLen = fullMatchList.length() ; j <= fullLen ; ++j) |
| | 3186 | { |
| | 3187 | /* |
| | 3188 | * if this item is equivalent for the purposes of the |
| | 3189 | * current distinguisher, count it |
| | 3190 | */ |
| | 3191 | if (!dist.canDistinguish(obj, fullMatchList[j].obj_)) |
| | 3192 | { |
| | 3193 | /* it's equivalent - count it */ |
| | 3194 | ++equivCnt; |
| | 3195 | } |
| | 3196 | } |
| | 3197 | |
| | 3198 | /* show this item with the appropriate article */ |
| | 3199 | if (equivCnt > 1) |
| | 3200 | { |
| | 3201 | /* |
| | 3202 | * we have multiple equivalents - show either with an |
| | 3203 | * indefinite article or with a count, depending on the |
| | 3204 | * flags the caller provided |
| | 3205 | */ |
| | 3206 | if (showIndefCounts) |
| | 3207 | { |
| | 3208 | /* a count is desired for each equivalent group */ |
| | 3209 | say(dist.countName(obj, equivCnt)); |
| | 3210 | } |
| | 3211 | else |
| | 3212 | { |
| | 3213 | /* no counts desired - show with an indefinite article */ |
| | 3214 | say(dist.aName(obj)); |
| | 3215 | } |
| | 3216 | } |
| | 3217 | else |
| | 3218 | { |
| | 3219 | /* there's only one - show with a definite article */ |
| | 3220 | say(dist.theName(obj)); |
| | 3221 | } |
| | 3222 | } |
| | 3223 | } |
| | 3224 | |
| | 3225 | /* |
| | 3226 | * For a TAction result, select the short-form or long-form message, |
| | 3227 | * according to the disambiguation status of the action. This is for |
| | 3228 | * the ultra-terse default messages, such as "Taken" or "Dropped", |
| | 3229 | * that sometimes need more descriptive variations. |
| | 3230 | * |
| | 3231 | * If there was no disambiguation involved, we'll use the short |
| | 3232 | * version of the message. |
| | 3233 | * |
| | 3234 | * If there was unclear disambiguation involved (meaning that there |
| | 3235 | * was more than one logical object matching a noun phrase, but the |
| | 3236 | * parser was able to decide based on likelihood rankings), we'll |
| | 3237 | * still use the short version, because we assume that the parser |
| | 3238 | * will have generated a parenthetical announcement to point out its |
| | 3239 | * choice. |
| | 3240 | * |
| | 3241 | * If there was clear disambiguation involved (meaning that more than |
| | 3242 | * one in-scope object matched a noun phrase, but there was only one |
| | 3243 | * choice that passed the logicalness tests), AND the announcement |
| | 3244 | * mode (in gameMain.ambigAnnounceMode) is DescribeClear, we'll |
| | 3245 | * choose the long-form message. |
| | 3246 | */ |
| | 3247 | shortTMsg(short, long) |
| | 3248 | { |
| | 3249 | /* check the disambiguation flags and the announcement mode */ |
| | 3250 | if ((gAction.getDobjFlags() & (ClearDisambig | AlwaysAnnounce)) |
| | 3251 | == ClearDisambig |
| | 3252 | && gAction.getDobjCount() == 1 |
| | 3253 | && gameMain.ambigAnnounceMode == DescribeClear) |
| | 3254 | { |
| | 3255 | /* clear disambig and DescribeClear mode - use the long message */ |
| | 3256 | return long; |
| | 3257 | } |
| | 3258 | else |
| | 3259 | { |
| | 3260 | /* in other cases, use the short message */ |
| | 3261 | return short; |
| | 3262 | } |
| | 3263 | } |
| | 3264 | |
| | 3265 | /* |
| | 3266 | * For a TIAction result, select the short-form or long-form message. |
| | 3267 | * This works just like shortTIMsg(), but takes into account both the |
| | 3268 | * direct and indirect objects. |
| | 3269 | */ |
| | 3270 | shortTIMsg(short, long) |
| | 3271 | { |
| | 3272 | /* check the disambiguation flags and the announcement mode */ |
| | 3273 | if (((gAction.getDobjFlags() & (ClearDisambig | AlwaysAnnounce)) |
| | 3274 | == ClearDisambig |
| | 3275 | || (gAction.getIobjFlags() & (ClearDisambig | AlwaysAnnounce)) |
| | 3276 | == ClearDisambig) |
| | 3277 | && gAction.getDobjCount() == 1 |
| | 3278 | && gAction.getIobjCount() == 1 |
| | 3279 | && gameMain.ambigAnnounceMode == DescribeClear) |
| | 3280 | { |
| | 3281 | /* clear disambig and DescribeClear mode - use the long message */ |
| | 3282 | return long; |
| | 3283 | } |
| | 3284 | else |
| | 3285 | { |
| | 3286 | /* in other cases, use the short message */ |
| | 3287 | return short; |
| | 3288 | } |
| | 3289 | } |
| | 3290 | ; |
| | 3291 | |
| | 3292 | /* ------------------------------------------------------------------------ */ |
| | 3293 | /* |
| | 3294 | * Custom base resolver |
| | 3295 | */ |
| | 3296 | modify Resolver |
| | 3297 | /* |
| | 3298 | * Get the default in-scope object list for a given pronoun. We'll |
| | 3299 | * look for a unique object in scope that matches the desired |
| | 3300 | * pronoun, and return a ResolveInfo list if we find one. If there |
| | 3301 | * aren't any objects in scope that match the pronoun, or multiple |
| | 3302 | * objects are in scope, there's no default. |
| | 3303 | */ |
| | 3304 | getPronounDefault(typ, np) |
| | 3305 | { |
| | 3306 | local map = [PronounHim, &canMatchHim, |
| | 3307 | PronounHer, &canMatchHer, |
| | 3308 | PronounIt, &canMatchIt]; |
| | 3309 | local idx = map.indexOf(typ); |
| | 3310 | local filterProp = (idx != nil ? map[idx + 1] : nil); |
| | 3311 | local lst; |
| | 3312 | |
| | 3313 | /* if we couldn't find a filter for the pronoun, ignore it */ |
| | 3314 | if (filterProp == nil) |
| | 3315 | return []; |
| | 3316 | |
| | 3317 | /* |
| | 3318 | * filter the list of all possible defaults to those that match |
| | 3319 | * the given pronoun |
| | 3320 | */ |
| | 3321 | lst = getAllDefaults.subset({x: x.obj_.(filterProp)}); |
| | 3322 | |
| | 3323 | /* |
| | 3324 | * if the list contains exactly one element, then there's a |
| | 3325 | * unique default; otherwise, there's either nothing here that |
| | 3326 | * matches the pronoun or the pronoun is ambiguous, so there's |
| | 3327 | * no default |
| | 3328 | */ |
| | 3329 | if (lst.length() == 1) |
| | 3330 | { |
| | 3331 | /* |
| | 3332 | * we have a unique object, so they must be referring to it; |
| | 3333 | * because this is just a guess, though, mark it as vague |
| | 3334 | */ |
| | 3335 | lst[1].flags_ |= UnclearDisambig; |
| | 3336 | |
| | 3337 | /* return the list */ |
| | 3338 | return lst; |
| | 3339 | } |
| | 3340 | else |
| | 3341 | { |
| | 3342 | /* |
| | 3343 | * the pronoun doesn't have a unique in-scope referent, so |
| | 3344 | * we can't guess what they mean |
| | 3345 | */ |
| | 3346 | return []; |
| | 3347 | } |
| | 3348 | } |
| | 3349 | ; |
| | 3350 | |
| | 3351 | /* ------------------------------------------------------------------------ */ |
| | 3352 | /* |
| | 3353 | * Custom interactive resolver. This is used for responses to |
| | 3354 | * disambiguation questions and prompts for missing noun phrases. |
| | 3355 | */ |
| | 3356 | modify InteractiveResolver |
| | 3357 | /* |
| | 3358 | * Resolve a pronoun antecedent. We'll resolve a third-person |
| | 3359 | * singular pronoun to the target actor if the target actor matches |
| | 3360 | * in gender, and the target actor isn't the PC. This allows |
| | 3361 | * exchanges like this: |
| | 3362 | * |
| | 3363 | *. >bob, examine |
| | 3364 | *. What do you want Bob to look at? |
| | 3365 | *. |
| | 3366 | *. >his book |
| | 3367 | * |
| | 3368 | * In the above exchange, we'll treat "his" as referring to Bob, the |
| | 3369 | * target actor of the action, because we have referred to Bob in |
| | 3370 | * the partial command (the "BOB, EXAMINE") that triggered the |
| | 3371 | * interactive question. |
| | 3372 | */ |
| | 3373 | resolvePronounAntecedent(typ, np, results, poss) |
| | 3374 | { |
| | 3375 | local lst; |
| | 3376 | |
| | 3377 | /* try resolving with the target actor as the antecedent */ |
| | 3378 | if ((lst = resolvePronounAsTargetActor(typ)) != nil) |
| | 3379 | return lst; |
| | 3380 | |
| | 3381 | /* use the inherited result */ |
| | 3382 | return inherited(typ, np, results, poss); |
| | 3383 | } |
| | 3384 | |
| | 3385 | /* |
| | 3386 | * Get the reflexive third-person pronoun binding (himself, herself, |
| | 3387 | * itself, themselves). If the target actor isn't the PC, and the |
| | 3388 | * gender of the pronoun matches, we'll consider this as referring |
| | 3389 | * to the target actor. This allows exchanges of this form: |
| | 3390 | * |
| | 3391 | *. >bob, examine |
| | 3392 | *. What do you want Bob to examine? |
| | 3393 | *. |
| | 3394 | *. >himself |
| | 3395 | */ |
| | 3396 | getReflexiveBinding(typ) |
| | 3397 | { |
| | 3398 | local lst; |
| | 3399 | |
| | 3400 | /* try resolving with the target actor as the antecedent */ |
| | 3401 | if ((lst = resolvePronounAsTargetActor(typ)) != nil) |
| | 3402 | return lst; |
| | 3403 | |
| | 3404 | /* use the inherited result */ |
| | 3405 | return inherited(typ); |
| | 3406 | } |
| | 3407 | |
| | 3408 | /* |
| | 3409 | * Try matching the given pronoun type to the target actor. If it |
| | 3410 | * matches in gender, and the target actor isn't the PC, we'll |
| | 3411 | * return a resolve list consisting of the target actor. If we |
| | 3412 | * don't have a match, we'll return nil. |
| | 3413 | */ |
| | 3414 | resolvePronounAsTargetActor(typ) |
| | 3415 | { |
| | 3416 | /* |
| | 3417 | * if the target actor isn't the player character, and the |
| | 3418 | * target actor can match the given pronoun type, resolve the |
| | 3419 | * pronoun as the target actor |
| | 3420 | */ |
| | 3421 | if (actor_.canMatchPronounType(typ) && !actor_.isPlayerChar()) |
| | 3422 | { |
| | 3423 | /* the match is the target actor */ |
| | 3424 | return [new ResolveInfo(actor_, 0)]; |
| | 3425 | } |
| | 3426 | |
| | 3427 | /* we didn't match it */ |
| | 3428 | return nil; |
| | 3429 | } |
| | 3430 | ; |
| | 3431 | |
| | 3432 | /* |
| | 3433 | * Custom disambiguation resolver. |
| | 3434 | */ |
| | 3435 | modify DisambigResolver |
| | 3436 | /* |
| | 3437 | * Perform special resolution on pronouns used in interactive |
| | 3438 | * responses. If the pronoun is HIM or HER, then look through the |
| | 3439 | * list of possible matches for a matching gendered object, and use |
| | 3440 | * it as the result if we find one. If we find more than one, then |
| | 3441 | * use the default handling instead, treating the pronoun as |
| | 3442 | * referring back to the simple antecedent previously set. |
| | 3443 | */ |
| | 3444 | resolvePronounAntecedent(typ, np, results, poss) |
| | 3445 | { |
| | 3446 | /* if it's a non-possessive HIM or HER, use our special handling */ |
| | 3447 | if (!poss && typ is in (PronounHim, PronounHer)) |
| | 3448 | { |
| | 3449 | local prop; |
| | 3450 | local sub; |
| | 3451 | |
| | 3452 | /* get the gender indicator property for the pronoun */ |
| | 3453 | prop = (typ == PronounHim ? &canMatchHim : &canMatchHer); |
| | 3454 | |
| | 3455 | /* |
| | 3456 | * Scan through the match list to find the objects that |
| | 3457 | * match the gender of the pronoun. Note that if the player |
| | 3458 | * character isn't referred to in the third person, we'll |
| | 3459 | * ignore the player character for the purposes of matching |
| | 3460 | * this pronoun - if we're calling the PC 'you', then we |
| | 3461 | * wouldn't expect the player to refer to the PC as 'him' or |
| | 3462 | * 'her'. |
| | 3463 | */ |
| | 3464 | sub = matchList.subset({x: x.obj_.(prop)}); |
| | 3465 | |
| | 3466 | /* if the list has a single entry, then use it as the match */ |
| | 3467 | if (sub.length() == 1) |
| | 3468 | return sub; |
| | 3469 | |
| | 3470 | /* |
| | 3471 | * if it has more than one entry, it's still ambiguous, but |
| | 3472 | * we might have narrowed it down, so throw a |
| | 3473 | * still-ambiguous exception and let the interactive |
| | 3474 | * disambiguation ask for further clarification |
| | 3475 | */ |
| | 3476 | results.ambiguousNounPhrase(nil, ResolveAsker, 'one', |
| | 3477 | sub, matchList, matchList, |
| | 3478 | 1, self); |
| | 3479 | return []; |
| | 3480 | } |
| | 3481 | |
| | 3482 | /* |
| | 3483 | * if we get this far, it means we didn't use our special |
| | 3484 | * handling, so use the inherited behavior |
| | 3485 | */ |
| | 3486 | return inherited(typ, np, results, poss); |
| | 3487 | } |
| | 3488 | ; |
| | 3489 | |
| | 3490 | /* ------------------------------------------------------------------------ */ |
| | 3491 | /* |
| | 3492 | * Distinguisher customizations for English. |
| | 3493 | * |
| | 3494 | * Each distinguisher must provide a method that gets the name of an item |
| | 3495 | * for a disamgiguation query. Since these are inherently |
| | 3496 | * language-specific, they're defined here. |
| | 3497 | */ |
| | 3498 | |
| | 3499 | /* |
| | 3500 | * The null distinguisher tells objects apart based strictly on the name |
| | 3501 | * string. When we list objects, we simply show the basic name - since |
| | 3502 | * we can tell apart our objects based on the base name, there's no need |
| | 3503 | * to resort to other names. |
| | 3504 | */ |
| | 3505 | modify nullDistinguisher |
| | 3506 | /* we can tell objects apart if they have different base names */ |
| | 3507 | canDistinguish(a, b) { return a.name != b.name; } |
| | 3508 | |
| | 3509 | name(obj) { return obj.name; } |
| | 3510 | aName(obj) { return obj.aName; } |
| | 3511 | theName(obj) { return obj.theName; } |
| | 3512 | countName(obj, cnt) { return obj.countName(cnt); } |
| | 3513 | ; |
| | 3514 | |
| | 3515 | |
| | 3516 | /* |
| | 3517 | * The basic distinguisher can tell apart objects that are not "basic |
| | 3518 | * equivalents" of one another. Thus, we need make no distinctions when |
| | 3519 | * listing objects apart from showing their names. |
| | 3520 | */ |
| | 3521 | modify basicDistinguisher |
| | 3522 | name(obj) { return obj.disambigName; } |
| | 3523 | aName(obj) { return obj.aDisambigName; } |
| | 3524 | theName(obj) { return obj.theDisambigName; } |
| | 3525 | countName(obj, cnt) { return obj.countDisambigName(cnt); } |
| | 3526 | ; |
| | 3527 | |
| | 3528 | /* |
| | 3529 | * The ownership distinguisher tells objects apart based on who "owns" |
| | 3530 | * them, so it shows the owner or location name when listing the object. |
| | 3531 | */ |
| | 3532 | modify ownershipDistinguisher |
| | 3533 | name(obj) { return obj.theNameOwnerLoc(true); } |
| | 3534 | aName(obj) { return obj.aNameOwnerLoc(true); } |
| | 3535 | theName(obj) { return obj.theNameOwnerLoc(true); } |
| | 3536 | countName(obj, cnt) { return obj.countNameOwnerLoc(cnt, true); } |
| | 3537 | |
| | 3538 | /* note that we're prompting based on this distinguisher */ |
| | 3539 | notePrompt(lst) |
| | 3540 | { |
| | 3541 | /* |
| | 3542 | * notify each object that we're referring to it by |
| | 3543 | * owner/location in a disambiguation prompt |
| | 3544 | */ |
| | 3545 | foreach (local cur in lst) |
| | 3546 | cur.obj_.notePromptByOwnerLoc(true); |
| | 3547 | } |
| | 3548 | ; |
| | 3549 | |
| | 3550 | /* |
| | 3551 | * The location distinguisher tells objects apart based on their |
| | 3552 | * containers, so it shows the location name when listing the object. |
| | 3553 | */ |
| | 3554 | modify locationDistinguisher |
| | 3555 | name(obj) { return obj.theNameOwnerLoc(nil); } |
| | 3556 | aName(obj) { return obj.aNameOwnerLoc(nil); } |
| | 3557 | theName(obj) { return obj.theNameOwnerLoc(nil); } |
| | 3558 | countName(obj, cnt) { return obj.countNameOwnerLoc(cnt, nil); } |
| | 3559 | |
| | 3560 | /* note that we're prompting based on this distinguisher */ |
| | 3561 | notePrompt(lst) |
| | 3562 | { |
| | 3563 | /* notify the objects of their use in a disambiguation prompt */ |
| | 3564 | foreach (local cur in lst) |
| | 3565 | cur.obj_.notePromptByOwnerLoc(nil); |
| | 3566 | } |
| | 3567 | ; |
| | 3568 | |
| | 3569 | /* |
| | 3570 | * The lit/unlit distinguisher tells apart objects based on whether |
| | 3571 | * they're lit or unlit, so we list objects as lit or unlit explicitly. |
| | 3572 | */ |
| | 3573 | modify litUnlitDistinguisher |
| | 3574 | name(obj) { return obj.nameLit; } |
| | 3575 | aName(obj) { return obj.aNameLit; } |
| | 3576 | theName(obj) { return obj.theNameLit; } |
| | 3577 | countName(obj, cnt) { return obj.pluralNameLit; } |
| | 3578 | ; |
| | 3579 | |
| | 3580 | /* ------------------------------------------------------------------------ */ |
| | 3581 | /* |
| | 3582 | * Enligh-specific light source modifications |
| | 3583 | */ |
| | 3584 | modify LightSource |
| | 3585 | /* provide lit/unlit names for litUnlitDistinguisher */ |
| | 3586 | nameLit = ((isLit ? 'lit ' : 'unlit ') + name) |
| | 3587 | aNameLit() |
| | 3588 | { |
| | 3589 | /* |
| | 3590 | * if this is a mass noun or a plural name, just use the name |
| | 3591 | * with lit/unlit; otherwise, add "a" |
| | 3592 | */ |
| | 3593 | if (isPlural || isMassNoun) |
| | 3594 | return (isLit ? 'lit ' : 'unlit ') + name; |
| | 3595 | else |
| | 3596 | return (isLit ? 'a lit ' : 'an unlit ') + name; |
| | 3597 | } |
| | 3598 | theNameLit = ((isLit ? 'the lit ' : 'the unlit ') + name) |
| | 3599 | pluralNameLit = ((isLit ? 'lit ' : 'unlit ') + pluralName) |
| | 3600 | |
| | 3601 | /* |
| | 3602 | * Allow 'lit' and 'unlit' as adjectives - but even though we define |
| | 3603 | * these as our adjectives in the dictionary, we'll only accept the |
| | 3604 | * one appropriate for our current state, thanks to our state |
| | 3605 | * objects. |
| | 3606 | */ |
| | 3607 | adjective = 'lit' 'unlit' |
| | 3608 | ; |
| | 3609 | |
| | 3610 | /* |
| | 3611 | * Light source list states. An illuminated light source shows its |
| | 3612 | * status as "providing light"; an unlit light source shows no extra |
| | 3613 | * status. |
| | 3614 | */ |
| | 3615 | lightSourceStateOn: ThingState 'providing light' |
| | 3616 | stateTokens = ['lit'] |
| | 3617 | ; |
| | 3618 | lightSourceStateOff: ThingState |
| | 3619 | stateTokens = ['unlit'] |
| | 3620 | ; |
| | 3621 | |
| | 3622 | /* ------------------------------------------------------------------------ */ |
| | 3623 | /* |
| | 3624 | * Wearable states - a wearable item can be either worn or not worn. |
| | 3625 | */ |
| | 3626 | |
| | 3627 | /* "worn" */ |
| | 3628 | wornState: ThingState 'being worn' |
| | 3629 | /* |
| | 3630 | * In listings of worn items, don't bother mentioning our 'worn' |
| | 3631 | * status, as the entire list consists of items being worn - it |
| | 3632 | * would be redundant to point out that the items in a list of items |
| | 3633 | * being worn are being worn. |
| | 3634 | */ |
| | 3635 | wornName(lst) { return nil; } |
| | 3636 | ; |
| | 3637 | |
| | 3638 | /* |
| | 3639 | * "Unworn" state. Don't bother mentioning the status of an unworn item, |
| | 3640 | * since this is the default for everything. |
| | 3641 | */ |
| | 3642 | unwornState: ThingState; |
| | 3643 | |
| | 3644 | |
| | 3645 | /* ------------------------------------------------------------------------ */ |
| | 3646 | /* |
| | 3647 | * Typographical effects output filter. This filter looks for certain |
| | 3648 | * sequences in the text and converts them to typographical equivalents. |
| | 3649 | * Authors could simply write the HTML for the typographical markups in |
| | 3650 | * the first place, but it's easier to write the typewriter-like |
| | 3651 | * sequences and let this filter convert to HTML. |
| | 3652 | * |
| | 3653 | * We perform the following conversions: |
| | 3654 | * |
| | 3655 | * '---' -> &zwnbsp;&emdash; |
| | 3656 | *. '--' -> &zwnbsp;&endash; |
| | 3657 | *. sentence-ending punctuation -> same +   |
| | 3658 | * |
| | 3659 | * Since this routine is called so frequently, we hard-code the |
| | 3660 | * replacement strings, rather than using properties, for slightly faster |
| | 3661 | * performance. Since this routine is so simple, games that want to |
| | 3662 | * customize the replacement style should simply replace this entire |
| | 3663 | * routine with a new routine that applies the customizations. |
| | 3664 | * |
| | 3665 | * Note that we define this filter in the English-specific part of the |
| | 3666 | * library, because it seems almost certain that each language will want |
| | 3667 | * to customize it for local conventions. |
| | 3668 | */ |
| | 3669 | typographicalOutputFilter: OutputFilter |
| | 3670 | filterText(ostr, val) |
| | 3671 | { |
| | 3672 | /* |
| | 3673 | * Look for sentence-ending punctuation, and put an 'en' space |
| | 3674 | * after each occurrence. Recognize ends of sentences even if we |
| | 3675 | * have closing quotes, parentheses, or other grouping characters |
| | 3676 | * following the punctuation. Do this before the hyphen |
| | 3677 | * substitutions so that we can look for ordinary hyphens rather |
| | 3678 | * than all of the expanded versions. |
| | 3679 | */ |
| | 3680 | val = rexReplace(eosPattern, val, '%1\u2002', ReplaceAll); |
| | 3681 | |
| | 3682 | /* undo any abbreviations we mistook for sentence endings */ |
| | 3683 | val = rexReplace(abbrevPat, val, '%1. ', ReplaceAll); |
| | 3684 | |
| | 3685 | /* |
| | 3686 | * Replace dashes with typographical hyphens. Note that we check |
| | 3687 | * for the three-hyphen sequence first, because if we did it the |
| | 3688 | * other way around, we'd incorrectly find the first two hyphens |
| | 3689 | * of each '---' sequence and replace them with an en-dash, |
| | 3690 | * causing us to miss the '---' sequences entirely. |
| | 3691 | * |
| | 3692 | * We put a no-break marker (\uFEFF) just before each hyphen, and |
| | 3693 | * an okay-to-break marker (\u200B) just after, to ensure that we |
| | 3694 | * won't have a line break between the preceding text and the |
| | 3695 | * hyphen, and to indicate that a line break is specifically |
| | 3696 | * allowed if needed to the right of the hyphen. |
| | 3697 | */ |
| | 3698 | val = val.findReplace('---', '\uFEFF—\u200B', ReplaceAll); |
| | 3699 | val = val.findReplace('--', '\uFEFF–\u200B', ReplaceAll); |
| | 3700 | |
| | 3701 | /* return the result */ |
| | 3702 | return val; |
| | 3703 | } |
| | 3704 | |
| | 3705 | /* |
| | 3706 | * The end-of-sentence pattern. This looks a bit complicated, but |
| | 3707 | * all we're looking for is a period, exclamation point, or question |
| | 3708 | * mark, optionally followed by any number of closing group marks |
| | 3709 | * (right parentheses or square brackets, closing HTML tags, or |
| | 3710 | * double or single quotes in either straight or curly styles), all |
| | 3711 | * followed by an ordinary space. |
| | 3712 | * |
| | 3713 | * If a lower-case letter follows the space, though, we won't |
| | 3714 | * consider it a sentence ending. This applies most commonly after |
| | 3715 | * quoted passages ending with what would normally be sentence-ending |
| | 3716 | * punctuation: "'Who are you?' he asked." In these cases, the |
| | 3717 | * enclosing sentence isn't ending, so we don't want the extra space. |
| | 3718 | * We can tell the enclosing sentence isn't ending because a |
| | 3719 | * non-capital letter follows. |
| | 3720 | * |
| | 3721 | * Note that we specifically look only for ordinary spaces. Any |
| | 3722 | * sentence-ending punctuation that's followed by a quoted space or |
| | 3723 | * any typographical space overrides this substitution. |
| | 3724 | */ |
| | 3725 | eosPattern = static new RexPattern( |
| | 3726 | '<case>' |
| | 3727 | + '(' |
| | 3728 | + '[.!?]' |
| | 3729 | + '(' |
| | 3730 | + '<rparen|rsquare|dquote|squote|\u2019|\u201D>' |
| | 3731 | + '|<langle><^rangle>*<rangle>' |
| | 3732 | + ')*' |
| | 3733 | + ')' |
| | 3734 | + ' +(?![-a-z])' |
| | 3735 | ) |
| | 3736 | |
| | 3737 | /* pattern for abbreviations that were mistaken for sentence endings */ |
| | 3738 | abbrevPat = static new RexPattern( |
| | 3739 | '<nocase>%<(' + abbreviations + ')<dot>\u2002') |
| | 3740 | |
| | 3741 | /* |
| | 3742 | * Common abbreviations. These are excluded from being treated as |
| | 3743 | * sentence endings when they appear with a trailing period. |
| | 3744 | * |
| | 3745 | * Note that abbrevPat must be rebuilt manually if you change this on |
| | 3746 | * the fly - abbrevPat is static, so it picks up the initial value of |
| | 3747 | * this property at start-up, and doesn't re-evaluate it while the |
| | 3748 | * game is running. |
| | 3749 | */ |
| | 3750 | abbreviations = 'mr|mrs|ms|dr|prof' |
| | 3751 | ; |
| | 3752 | |
| | 3753 | /* ------------------------------------------------------------------------ */ |
| | 3754 | /* |
| | 3755 | * The English-specific message builder. |
| | 3756 | */ |
| | 3757 | langMessageBuilder: MessageBuilder |
| | 3758 | |
| | 3759 | /* |
| | 3760 | * The English message substitution parameter table. |
| | 3761 | * |
| | 3762 | * Note that we specify two additional elements for each table entry |
| | 3763 | * beyond the standard language-independent complement: |
| | 3764 | * |
| | 3765 | * info[4] = reflexive property - this is the property to invoke |
| | 3766 | * when the parameter is used reflexively (in other words, its |
| | 3767 | * target object is the same as the most recent target object used |
| | 3768 | * in the nominative case). If this is nil, the parameter has no |
| | 3769 | * reflexive form. |
| | 3770 | * |
| | 3771 | * info[5] = true if this is a nominative usage, nil if not. We use |
| | 3772 | * this to determine which target objects are used in the nominative |
| | 3773 | * case, so that we can remember those objects for subsequent |
| | 3774 | * reflexive usages. |
| | 3775 | */ |
| | 3776 | paramList_ = |
| | 3777 | [ |
| | 3778 | /* parameters that imply the actor as the target object */ |
| | 3779 | ['you/he', &theName, 'actor', nil, true], |
| | 3780 | ['you/she', &theName, 'actor', nil, true], |
| | 3781 | ['you\'re/he\'s', &itIsContraction, 'actor', nil, true], |
| | 3782 | ['you\'re/she\'s', &itIsContraction, 'actor', nil, true], |
| | 3783 | ['you\'re', &itIsContraction, 'actor', nil, true], |
| | 3784 | ['you/him', &theNameObj, 'actor', &itReflexive, nil], |
| | 3785 | ['you/her', &theNameObj, 'actor', &itReflexive, nil], |
| | 3786 | ['your/her', &theNamePossAdj, 'actor', nil, nil], |
| | 3787 | ['your/his', &theNamePossAdj, 'actor', nil, nil], |
| | 3788 | ['your', &theNamePossAdj, 'actor', nil, nil], |
| | 3789 | ['yours/hers', &theNamePossNoun, 'actor', nil, nil], |
| | 3790 | ['yours/his', &theNamePossNoun, 'actor', nil, nil], |
| | 3791 | ['yours', &theNamePossNoun, 'actor', nil, nil], |
| | 3792 | ['yourself/himself', &itReflexive, 'actor', nil, nil], |
| | 3793 | ['yourself/herself', &itReflexive, 'actor', nil, nil], |
| | 3794 | ['yourself', &itReflexive, 'actor', nil, nil], |
| | 3795 | |
| | 3796 | /* parameters that don't imply any target object */ |
| | 3797 | ['the/he', &theName, nil, nil, true], |
| | 3798 | ['the/she', &theName, nil, nil, true], |
| | 3799 | ['the/him', &theNameObj, nil, &itReflexive, nil], |
| | 3800 | ['the/her', &theNameObj, nil, &itReflexive, nil], |
| | 3801 | ['the\'s/her', &theNamePossAdj, nil, &itPossAdj, nil], |
| | 3802 | ['the\'s/hers', &theNamePossNoun, nil, &itPossNoun, nil], |
| | 3803 | |
| | 3804 | /* |
| | 3805 | * Verb 's' endings. In most cases, you should use 's/d', 's/ed', |
| | 3806 | * or 's/?ed' rather than 's', except in places where you know you |
| | 3807 | * will never need the past tense form, because 's' doesn't handle |
| | 3808 | * the past tense. Don't use 's/?ed' with a literal question |
| | 3809 | * mark; put a consonant in place of the question mark instead. |
| | 3810 | */ |
| | 3811 | ['s', &verbEndingS, nil, nil, true], |
| | 3812 | ['s/d', &verbEndingSD, nil, nil, true], |
| | 3813 | ['s/ed', &verbEndingSEd, nil, nil, true], |
| | 3814 | ['s/?ed', &verbEndingSMessageBuilder_, nil, nil, true], |
| | 3815 | |
| | 3816 | ['es', &verbEndingEs, nil, nil, true], |
| | 3817 | ['es/ed', &verbEndingEs, nil, nil, true], |
| | 3818 | ['ies', &verbEndingIes, nil, nil, true], |
| | 3819 | ['ies/ied', &verbEndingIes, nil, nil, true], |
| | 3820 | ['is', &verbToBe, nil, nil, true], |
| | 3821 | ['are', &verbToBe, nil, nil, true], |
| | 3822 | ['was', &verbWas, nil, nil, true], |
| | 3823 | ['were', &verbWas, nil, nil, true], |
| | 3824 | ['has', &verbToHave, nil, nil, true], |
| | 3825 | ['have', &verbToHave, nil, nil, true], |
| | 3826 | ['does', &verbToDo, nil, nil, true], |
| | 3827 | ['do', &verbToDo, nil, nil, true], |
| | 3828 | ['goes', &verbToGo, nil, nil, true], |
| | 3829 | ['go', &verbToGo, nil, nil, true], |
| | 3830 | ['comes', &verbToCome, nil, nil, true], |
| | 3831 | ['come', &verbToCome, nil, nil, true], |
| | 3832 | ['leaves', &verbToLeave, nil, nil, true], |
| | 3833 | ['leave', &verbToLeave, nil, nil, true], |
| | 3834 | ['sees', &verbToSee, nil, nil, true], |
| | 3835 | ['see', &verbToSee, nil, nil, true], |
| | 3836 | ['says', &verbToSay, nil, nil, true], |
| | 3837 | ['say', &verbToSay, nil, nil, true], |
| | 3838 | ['must', &verbMust, nil, nil, true], |
| | 3839 | ['can', &verbCan, nil, nil, true], |
| | 3840 | ['cannot', &verbCannot, nil, nil, true], |
| | 3841 | ['can\'t', &verbCant, nil, nil, true], |
| | 3842 | ['will', &verbWill, nil, nil, true], |
| | 3843 | ['won\'t', &verbWont, nil, nil, true], |
| | 3844 | ['a/he', &aName, nil, nil, true], |
| | 3845 | ['an/he', &aName, nil, nil, true], |
| | 3846 | ['a/she', &aName, nil, nil, true], |
| | 3847 | ['an/she', &aName, nil, nil, true], |
| | 3848 | ['a/him', &aNameObj, nil, &itReflexive, nil], |
| | 3849 | ['an/him', &aNameObj, nil, &itReflexive, nil], |
| | 3850 | ['a/her', &aNameObj, nil, &itReflexive, nil], |
| | 3851 | ['an/her', &aNameObj, nil, &itReflexive, nil], |
| | 3852 | ['it/he', &itNom, nil, nil, true], |
| | 3853 | ['it/she', &itNom, nil, nil, true], |
| | 3854 | ['it/him', &itObj, nil, &itReflexive, nil], |
| | 3855 | ['it/her', &itObj, nil, &itReflexive, nil], |
| | 3856 | |
| | 3857 | /* |
| | 3858 | * note that we don't have its/his, because that leaves |
| | 3859 | * ambiguous whether we want an adjective or noun form - so we |
| | 3860 | * only use the feminine pronouns with these, to make the |
| | 3861 | * meaning unambiguous |
| | 3862 | */ |
| | 3863 | ['its/her', &itPossAdj, nil, nil, nil], |
| | 3864 | ['its/hers', &itPossNoun, nil, nil, nil], |
| | 3865 | |
| | 3866 | ['it\'s/he\'s', &itIsContraction, nil, nil, true], |
| | 3867 | ['it\'s/she\'s', &itIsContraction, nil, nil, true], |
| | 3868 | ['it\'s', &itIsContraction, nil, nil, true], |
| | 3869 | ['that/he', &thatNom, nil, nil, true], |
| | 3870 | ['that/she', &thatNom, nil, nil, true], |
| | 3871 | ['that/him', &thatObj, nil, &itReflexive, nil], |
| | 3872 | ['that/her', &thatObj, nil, &itReflexive, nil], |
| | 3873 | ['that\'s', &thatIsContraction, nil, nil, true], |
| | 3874 | ['itself', &itReflexive, nil, nil, nil], |
| | 3875 | ['itself/himself', &itReflexive, nil, nil, nil], |
| | 3876 | ['itself/herself', &itReflexive, nil, nil, nil], |
| | 3877 | |
| | 3878 | /* default preposition for standing in/on something */ |
| | 3879 | ['on', &actorInName, nil, nil, nil], |
| | 3880 | ['in', &actorInName, nil, nil, nil], |
| | 3881 | ['outof', &actorOutOfName, nil, nil, nil], |
| | 3882 | ['offof', &actorOutOfName, nil, nil, nil], |
| | 3883 | ['onto', &actorIntoName, nil, nil, nil], |
| | 3884 | ['into', &actorIntoName, nil, nil, nil], |
| | 3885 | |
| | 3886 | /* |
| | 3887 | * The special invisible subject marker - this can be used to |
| | 3888 | * mark the subject in sentences that vary from the |
| | 3889 | * subject-verb-object structure that most English sentences |
| | 3890 | * take. The usual SVO structure allows the message builder to |
| | 3891 | * see the subject first in most sentences naturally, but in |
| | 3892 | * unusual sentence forms it is sometimes useful to be able to |
| | 3893 | * mark the subject explicitly. This doesn't actually result in |
| | 3894 | * any output; it's purely for marking the subject for our |
| | 3895 | * internal book-keeping. |
| | 3896 | * |
| | 3897 | * (The main reason the message builder wants to know the subject |
| | 3898 | * in the first place is so that it can use a reflexive pronoun |
| | 3899 | * if the same object ends up being used as a direct or indirect |
| | 3900 | * object: "you can't open yourself" rather than "you can't open |
| | 3901 | * you.") |
| | 3902 | */ |
| | 3903 | ['subj', &dummyName, nil, nil, true] |
| | 3904 | ] |
| | 3905 | |
| | 3906 | /* |
| | 3907 | * Add a hook to the generateMessage method, which we use to |
| | 3908 | * pre-process the source string before expanding the substitution |
| | 3909 | * parameters. |
| | 3910 | */ |
| | 3911 | generateMessage(orig) { return inherited(processOrig(orig)); } |
| | 3912 | |
| | 3913 | /* |
| | 3914 | * Pre-process a source string containing substitution parameters, |
| | 3915 | * before generating the expanded message from it. |
| | 3916 | * |
| | 3917 | * We use this hook to implement the special tense-switching syntax |
| | 3918 | * {<present>|<past>}. Although it superficially looks like an |
| | 3919 | * ordinary substitution parameter, we actually can't use the normal |
| | 3920 | * parameter substitution framework for that, because we want to |
| | 3921 | * allow the <present> and <past> substrings themselves to contain |
| | 3922 | * substitution parameters, and the normal framework doesn't allow |
| | 3923 | * for recursive substitution. |
| | 3924 | * |
| | 3925 | * We simply replace every sequence of the form {<present>|<past>} |
| | 3926 | * with either <present> or <past>, depending on the current |
| | 3927 | * narrative tense. We then substitute braces for square brackets in |
| | 3928 | * the resulting string. This allows treating every bracketed tag |
| | 3929 | * inside the tense-switching sequence as a regular substitution |
| | 3930 | * parameter. |
| | 3931 | * |
| | 3932 | * For example, the sequence "{take[s]|took}" appearing in the |
| | 3933 | * message string would be replaced with "take{s}" if the current |
| | 3934 | * narrative tense is present, and would be replaced with "took" if |
| | 3935 | * the current narrative tense is past. The string "take{s}", if |
| | 3936 | * selected, would in turn be expanded to either "take" or "takes", |
| | 3937 | * depending on the grammatical person of the subject, as per the |
| | 3938 | * regular substitution mechanism. |
| | 3939 | */ |
| | 3940 | processOrig(str) |
| | 3941 | { |
| | 3942 | local idx = 1; |
| | 3943 | local len; |
| | 3944 | local match; |
| | 3945 | local replStr; |
| | 3946 | |
| | 3947 | /* |
| | 3948 | * Keep searching the string until we run out of character |
| | 3949 | * sequences with a special meaning (specifically, we look for |
| | 3950 | * substrings enclosed in braces, and stuttered opening braces). |
| | 3951 | */ |
| | 3952 | for (;;) |
| | 3953 | { |
| | 3954 | /* |
| | 3955 | * Find the next special sequence. |
| | 3956 | */ |
| | 3957 | match = rexSearch(patSpecial, str, idx); |
| | 3958 | |
| | 3959 | /* |
| | 3960 | * If there are no more special sequence, we're done |
| | 3961 | * pre-processing the string. |
| | 3962 | */ |
| | 3963 | if (match == nil) break; |
| | 3964 | |
| | 3965 | /* |
| | 3966 | * Remember the starting index and length of the special |
| | 3967 | * sequence. |
| | 3968 | */ |
| | 3969 | idx = match[1]; |
| | 3970 | len = match[2]; |
| | 3971 | |
| | 3972 | /* |
| | 3973 | * Check if this special sequence matches our tense-switching |
| | 3974 | * syntax. |
| | 3975 | */ |
| | 3976 | if (nil == rexMatch(patTenseSwitching, str, idx)) |
| | 3977 | { |
| | 3978 | /* |
| | 3979 | * It doesn't, so forget about it and continue searching |
| | 3980 | * from the end of this special sequence. |
| | 3981 | */ |
| | 3982 | idx += len; |
| | 3983 | continue; |
| | 3984 | } |
| | 3985 | |
| | 3986 | /* |
| | 3987 | * Extract either the first or the second embedded string, |
| | 3988 | * depending on the current narrative tense. |
| | 3989 | */ |
| | 3990 | match = rexGroup(tSel(1, 2)); |
| | 3991 | replStr = match[3]; |
| | 3992 | |
| | 3993 | /* |
| | 3994 | * Convert all square brackets to braces in the extracted |
| | 3995 | * string. |
| | 3996 | */ |
| | 3997 | replStr = replStr.findReplace('[', '{', ReplaceAll); |
| | 3998 | replStr = replStr.findReplace(']', '}', ReplaceAll); |
| | 3999 | |
| | 4000 | /* |
| | 4001 | * In the original string, replace the tense-switching |
| | 4002 | * sequence with the extracted string. |
| | 4003 | */ |
| | 4004 | str = str.substr(1, idx - 1) + replStr + str.substr(idx + len); |
| | 4005 | |
| | 4006 | /* |
| | 4007 | * Move the index at the end of the substituted string. |
| | 4008 | */ |
| | 4009 | idx += match[2]; |
| | 4010 | } |
| | 4011 | |
| | 4012 | /* |
| | 4013 | * We're done - return the result. |
| | 4014 | */ |
| | 4015 | return str; |
| | 4016 | } |
| | 4017 | |
| | 4018 | /* |
| | 4019 | * Pre-compiled regular expression pattern matching any sequence with |
| | 4020 | * a special meaning in a message string. |
| | 4021 | * |
| | 4022 | * We match either a stuttered opening brace, or a single opening |
| | 4023 | * brace followed by any sequence of characters that doesn't contain |
| | 4024 | * a closing brace followed by a closing brace. |
| | 4025 | */ |
| | 4026 | patSpecial = static new RexPattern |
| | 4027 | ('<lbrace><lbrace>|<lbrace>(?!<lbrace>)((?:<^rbrace>)*)<rbrace>') |
| | 4028 | |
| | 4029 | /* |
| | 4030 | * Pre-compiled regular expression pattern matching our special |
| | 4031 | * tense-switching syntax. |
| | 4032 | * |
| | 4033 | * We match a single opening brace, followed by any sequence of |
| | 4034 | * characters that doesn't contain a closing brace or a vertical bar, |
| | 4035 | * followed by a vertical bar, followed by any sequence of characters |
| | 4036 | * that doesn't contain a closing brace or a vertical bar, followed |
| | 4037 | * by a closing brace. |
| | 4038 | */ |
| | 4039 | patTenseSwitching = static new RexPattern |
| | 4040 | ( |
| | 4041 | '<lbrace>(?!<lbrace>)((?:<^rbrace|vbar>)*)<vbar>' |
| | 4042 | + '((?:<^rbrace|vbar>)*)<rbrace>' |
| | 4043 | ) |
| | 4044 | |
| | 4045 | /* |
| | 4046 | * The most recent target object used in the nominative case. We |
| | 4047 | * note this so that we can supply reflexive mappings when the same |
| | 4048 | * object is re-used in the objective case. This allows us to map |
| | 4049 | * things like "you can't take you" to the better-sounding "you |
| | 4050 | * can't take yourself". |
| | 4051 | */ |
| | 4052 | lastSubject_ = nil |
| | 4053 | |
| | 4054 | /* the parameter name of the last subject ('dobj', 'actor', etc) */ |
| | 4055 | lastSubjectName_ = nil |
| | 4056 | |
| | 4057 | /* |
| | 4058 | * Get the target object property mapping. If the target object is |
| | 4059 | * the same as the most recent subject object (i.e., the last object |
| | 4060 | * used in the nominative case), and this parameter has a reflexive |
| | 4061 | * form property, we'll return the reflexive form property. |
| | 4062 | * Otherwise, we'll return the standard property mapping. |
| | 4063 | * |
| | 4064 | * Also, if there was an exclamation mark at the end of any word in |
| | 4065 | * the tag, we'll return a property returning a fixed-tense form of |
| | 4066 | * the property for the tag. |
| | 4067 | */ |
| | 4068 | getTargetProp(targetObj, paramObj, info) |
| | 4069 | { |
| | 4070 | local ret; |
| | 4071 | |
| | 4072 | /* |
| | 4073 | * If this target object matches the last subject, and we have a |
| | 4074 | * reflexive rendering, return the property for the reflexive |
| | 4075 | * rendering. |
| | 4076 | * |
| | 4077 | * Only use the reflexive rendering if the parameter name is |
| | 4078 | * different - if the parameter name is the same, then presumably |
| | 4079 | * the message will have been written with a reflexive pronoun or |
| | 4080 | * not, exactly as the author wants it. When the author knows |
| | 4081 | * going in that these two objects are structurally the same, |
| | 4082 | * they want the exact usage they wrote. |
| | 4083 | */ |
| | 4084 | if (targetObj == lastSubject_ |
| | 4085 | && paramObj != lastSubjectName_ |
| | 4086 | && info[4] != nil) |
| | 4087 | { |
| | 4088 | /* use the reflexive rendering */ |
| | 4089 | ret = info[4]; |
| | 4090 | } |
| | 4091 | else |
| | 4092 | { |
| | 4093 | /* no special handling; inherit the default handling */ |
| | 4094 | ret = inherited(targetObj, paramObj, info); |
| | 4095 | } |
| | 4096 | |
| | 4097 | /* if this is a nominative usage, note it as the last subject */ |
| | 4098 | if (info[5]) |
| | 4099 | { |
| | 4100 | lastSubject_ = targetObj; |
| | 4101 | lastSubjectName_ = paramObj; |
| | 4102 | } |
| | 4103 | |
| | 4104 | /* |
| | 4105 | * If there was an exclamation mark at the end of any word in the |
| | 4106 | * parameter string (which we remember via the fixedTenseProp_ |
| | 4107 | * property), store the original target property in |
| | 4108 | * fixedTenseProp_ and use &propWithPresentMessageBuilder_ as the |
| | 4109 | * target property instead. propWithPresentMessageBuilder_ acts |
| | 4110 | * as a wrapper for the original target property, which it |
| | 4111 | * invokes after temporarily switching to the present tense. |
| | 4112 | */ |
| | 4113 | if (fixedTenseProp_) |
| | 4114 | { |
| | 4115 | fixedTenseProp_ = ret; |
| | 4116 | ret = &propWithPresentMessageBuilder_; |
| | 4117 | } |
| | 4118 | |
| | 4119 | /* return the result */ |
| | 4120 | return ret; |
| | 4121 | } |
| | 4122 | |
| | 4123 | /* end-of-sentence match pattern */ |
| | 4124 | patEndOfSentence = static new RexPattern('[.;:!?]<^alphanum>') |
| | 4125 | |
| | 4126 | /* |
| | 4127 | * Process result text. |
| | 4128 | */ |
| | 4129 | processResult(txt) |
| | 4130 | { |
| | 4131 | /* |
| | 4132 | * If the text contains any sentence-ending punctuation, reset |
| | 4133 | * our internal memory of the subject of the sentence. We |
| | 4134 | * consider the sentence to end with a period, semicolon, colon, |
| | 4135 | * question mark, or exclamation point followed by anything |
| | 4136 | * other than an alpha-numeric. (We require the secondary |
| | 4137 | * character so that we don't confuse things like "3:00" or |
| | 4138 | * "7.5" to contain sentence-ending punctuation.) |
| | 4139 | */ |
| | 4140 | if (rexSearch(patEndOfSentence, txt) != nil) |
| | 4141 | { |
| | 4142 | /* |
| | 4143 | * we have a sentence ending in this run of text, so any |
| | 4144 | * saved subject object will no longer apply after this text |
| | 4145 | * - forget our subject object |
| | 4146 | */ |
| | 4147 | lastSubject_ = nil; |
| | 4148 | lastSubjectName_ = nil; |
| | 4149 | } |
| | 4150 | |
| | 4151 | /* return the inherited processing */ |
| | 4152 | return inherited(txt); |
| | 4153 | } |
| | 4154 | |
| | 4155 | /* some pre-compiled search patterns we use a lot */ |
| | 4156 | patIdObjSlashIdApostS = static new RexPattern( |
| | 4157 | '(<^space>+)(<space>+<^space>+)\'s(/<^space>+)$') |
| | 4158 | patIdObjApostS = static new RexPattern( |
| | 4159 | '(?!<^space>+\'s<space>)(<^space>+)(<space>+<^space>+)\'s$') |
| | 4160 | patParamWithExclam = static new RexPattern('.*(!)(?:<space>.*|/.*|$)') |
| | 4161 | patSSlashLetterEd = static new RexPattern( |
| | 4162 | 's/(<alpha>ed)$|(<alpha>ed)/s$') |
| | 4163 | |
| | 4164 | /* |
| | 4165 | * Rewrite a parameter string for a language-specific syntax |
| | 4166 | * extension. |
| | 4167 | * |
| | 4168 | * For English, we'll handle the possessive apostrophe-s suffix |
| | 4169 | * specially, by allowing the apostrophe-s to be appended to the |
| | 4170 | * target object name. If we find an apostrophe-s on the target |
| | 4171 | * object name, we'll move it to the preceding identifier name: |
| | 4172 | * |
| | 4173 | * the dobj's -> the's dobj |
| | 4174 | *. the dobj's/he -> the's dobj/he |
| | 4175 | *. he/the dobj's -> he/the's dobj |
| | 4176 | * |
| | 4177 | * We also use this method to check for the presence of an |
| | 4178 | * exclamation mark at the end of any word in the parameter string |
| | 4179 | * (triggering the fixed-tense handling), and to detect a parameter |
| | 4180 | * string matching the {s/?ed} syntax, where ? is any letter, and |
| | 4181 | * rewrite it literally as 's/?ed' literally. |
| | 4182 | */ |
| | 4183 | langRewriteParam(paramStr) |
| | 4184 | { |
| | 4185 | /* |
| | 4186 | * Check for an exclamation mark at the end of any word in the |
| | 4187 | * parameter string, and remember the result of the test. |
| | 4188 | */ |
| | 4189 | local exclam = rexMatch(patParamWithExclam, paramStr); |
| | 4190 | fixedTenseProp_ = exclam; |
| | 4191 | |
| | 4192 | /* |
| | 4193 | * Remove the exclamation mark, if any. |
| | 4194 | */ |
| | 4195 | if (exclam) |
| | 4196 | { |
| | 4197 | local exclamInd = rexGroup(1)[1]; |
| | 4198 | paramStr = paramStr.substr(1, exclamInd - 1) |
| | 4199 | + paramStr.substr(exclamInd + 1); |
| | 4200 | } |
| | 4201 | |
| | 4202 | /* look for "id obj's" and "id1 obj's/id2" */ |
| | 4203 | if (rexMatch(patIdObjSlashIdApostS, paramStr) != nil) |
| | 4204 | { |
| | 4205 | /* rewrite with the "'s" moved to the preceding parameter name */ |
| | 4206 | paramStr = rexGroup(1)[3] + '\'s' |
| | 4207 | + rexGroup(2)[3] + rexGroup(3)[3]; |
| | 4208 | } |
| | 4209 | else if (rexMatch(patIdObjApostS, paramStr) != nil) |
| | 4210 | { |
| | 4211 | /* rewrite with the "'s" moved to the preceding parameter name */ |
| | 4212 | paramStr = rexGroup(1)[3] + '\'s' + rexGroup(2)[3]; |
| | 4213 | } |
| | 4214 | |
| | 4215 | /* |
| | 4216 | * Check if this parameter matches the {s/?ed} or {?ed/s} syntax. |
| | 4217 | */ |
| | 4218 | if (rexMatch(patSSlashLetterEd, paramStr)) |
| | 4219 | { |
| | 4220 | /* |
| | 4221 | * It does - remember the past verb ending, and rewrite the |
| | 4222 | * parameter literally as 's/?ed'. |
| | 4223 | */ |
| | 4224 | pastEnding_ = rexGroup(1)[3]; |
| | 4225 | paramStr = 's/?ed'; |
| | 4226 | } |
| | 4227 | |
| | 4228 | /* return our (possibly modified) result */ |
| | 4229 | return paramStr; |
| | 4230 | } |
| | 4231 | |
| | 4232 | /* |
| | 4233 | * This property is used to temporarily store the past-tense ending |
| | 4234 | * of a verb to be displayed by Thing.verbEndingSMessageBuilder_. |
| | 4235 | * It's for internal use only; game authors shouldn't have any reason |
| | 4236 | * to access it directly. |
| | 4237 | */ |
| | 4238 | pastEnding_ = nil |
| | 4239 | |
| | 4240 | /* |
| | 4241 | * This property is used to temporarily store either a boolean value |
| | 4242 | * indicating whether the last encountered parameter string had an |
| | 4243 | * exclamation mark at the end of any word, or a property to be |
| | 4244 | * invoked by Thing.propWithPresentMessageBuilder_. This field is |
| | 4245 | * for internal use only; authors shouldn't have any reason to access |
| | 4246 | * it directly. |
| | 4247 | */ |
| | 4248 | fixedTenseProp_ = nil |
| | 4249 | ; |
| | 4250 | |
| | 4251 | |
| | 4252 | /* ------------------------------------------------------------------------ */ |
| | 4253 | /* |
| | 4254 | * Temporarily override the current narrative tense and invoke a callback |
| | 4255 | * function. |
| | 4256 | */ |
| | 4257 | withTense(usePastTense, callback) |
| | 4258 | { |
| | 4259 | /* |
| | 4260 | * Remember the old value of the usePastTense flag. |
| | 4261 | */ |
| | 4262 | local oldUsePastTense = gameMain.usePastTense; |
| | 4263 | /* |
| | 4264 | * Set the new value. |
| | 4265 | */ |
| | 4266 | gameMain.usePastTense = usePastTense; |
| | 4267 | /* |
| | 4268 | * Invoke the callback (remembering the return value) and restore the |
| | 4269 | * usePastTense flag on our way out. |
| | 4270 | */ |
| | 4271 | local ret; |
| | 4272 | try { ret = callback(); } |
| | 4273 | finally { gameMain.usePastTense = oldUsePastTense; } |
| | 4274 | /* |
| | 4275 | * Return the result. |
| | 4276 | */ |
| | 4277 | return ret; |
| | 4278 | } |
| | 4279 | |
| | 4280 | |
| | 4281 | /* ------------------------------------------------------------------------ */ |
| | 4282 | /* |
| | 4283 | * Functions for spelling out numbers. These functions take a numeric |
| | 4284 | * value as input, and return a string with the number spelled out as |
| | 4285 | * words in English. For example, given the number 52, we'd return a |
| | 4286 | * string like 'fifty-two'. |
| | 4287 | * |
| | 4288 | * These functions obviously have language-specific implementations. |
| | 4289 | * Note also that even their interfaces might vary by language. Some |
| | 4290 | * languages might need additional information in the interface; for |
| | 4291 | * example, some languages might need to know the grammatical context |
| | 4292 | * (such as part of speech, case, or gender) of the result. |
| | 4293 | * |
| | 4294 | * Note that some of the spellIntXxx flags might not be meaningful in all |
| | 4295 | * languages, because most of the flags are by their very nature |
| | 4296 | * associated with language-specific idioms. Translations are free to |
| | 4297 | * ignore flags that indicate variations with no local equivalent, and to |
| | 4298 | * add their own language-specific flags as needed. |
| | 4299 | */ |
| | 4300 | |
| | 4301 | /* |
| | 4302 | * Spell out an integer number in words. Returns a string with the |
| | 4303 | * spelled-out number. |
| | 4304 | * |
| | 4305 | * Note that this simple version of the function uses the default |
| | 4306 | * options. If you want to specify non-default options with the |
| | 4307 | * SpellIntXxx flags, you can call spellIntExt(). |
| | 4308 | */ |
| | 4309 | spellInt(val) |
| | 4310 | { |
| | 4311 | return spellIntExt(val, 0); |
| | 4312 | } |
| | 4313 | |
| | 4314 | /* |
| | 4315 | * Spell out an integer number in words, but only if it's below the given |
| | 4316 | * threshold. It's often awkward in prose to spell out large numbers, |
| | 4317 | * but exactly what constitutes a large number depends on context, so |
| | 4318 | * this routine lets the caller specify the threshold. |
| | 4319 | * |
| | 4320 | * If the absolute value of val is less than (not equal to) the threshold |
| | 4321 | * value, we'll return a string with the number spelled out. If the |
| | 4322 | * absolute value is greater than or equal to the threshold value, we'll |
| | 4323 | * return a string representing the number in decimal digits. |
| | 4324 | */ |
| | 4325 | spellIntBelow(val, threshold) |
| | 4326 | { |
| | 4327 | return spellIntBelowExt(val, threshold, 0, 0); |
| | 4328 | } |
| | 4329 | |
| | 4330 | /* |
| | 4331 | * Spell out an integer number in words if it's below a threshold, using |
| | 4332 | * the spellIntXxx flags given in spellFlags to control the spelled-out |
| | 4333 | * format, and using the DigitFormatXxx flags in digitFlags to control |
| | 4334 | * the digit format. |
| | 4335 | */ |
| | 4336 | spellIntBelowExt(val, threshold, spellFlags, digitFlags) |
| | 4337 | { |
| | 4338 | local absval; |
| | 4339 | |
| | 4340 | /* compute the absolute value */ |
| | 4341 | absval = (val < 0 ? -val : val); |
| | 4342 | |
| | 4343 | /* check the value to see whether to spell it or write it as digits */ |
| | 4344 | if (absval < threshold) |
| | 4345 | { |
| | 4346 | /* it's below the threshold - spell it out in words */ |
| | 4347 | return spellIntExt(val, spellFlags); |
| | 4348 | } |
| | 4349 | else |
| | 4350 | { |
| | 4351 | /* it's not below the threshold - write it as digits */ |
| | 4352 | return intToDecimal(val, digitFlags); |
| | 4353 | } |
| | 4354 | } |
| | 4355 | |
| | 4356 | /* |
| | 4357 | * Format a number as a string of decimal digits. The DigitFormatXxx |
| | 4358 | * flags specify how the number is to be formatted.` |
| | 4359 | */ |
| | 4360 | intToDecimal(val, flags) |
| | 4361 | { |
| | 4362 | local str; |
| | 4363 | local sep; |
| | 4364 | |
| | 4365 | /* perform the basic conversion */ |
| | 4366 | str = toString(val); |
| | 4367 | |
| | 4368 | /* add group separators as needed */ |
| | 4369 | if ((flags & DigitFormatGroupComma) != 0) |
| | 4370 | { |
| | 4371 | /* explicitly use a comma as a separator */ |
| | 4372 | sep = ','; |
| | 4373 | } |
| | 4374 | else if ((flags & DigitFormatGroupPeriod) != 0) |
| | 4375 | { |
| | 4376 | /* explicitly use a period as a separator */ |
| | 4377 | sep = '.'; |
| | 4378 | } |
| | 4379 | else if ((flags & DigitFormatGroupSep) != 0) |
| | 4380 | { |
| | 4381 | /* use the current languageGlobals separator */ |
| | 4382 | sep = languageGlobals.digitGroupSeparator; |
| | 4383 | } |
| | 4384 | else |
| | 4385 | { |
| | 4386 | /* no separator */ |
| | 4387 | sep = nil; |
| | 4388 | } |
| | 4389 | |
| | 4390 | /* if there's a separator, add it in */ |
| | 4391 | if (sep != nil) |
| | 4392 | { |
| | 4393 | local i; |
| | 4394 | local len; |
| | 4395 | |
| | 4396 | /* |
| | 4397 | * Insert the separator before each group of three digits. |
| | 4398 | * Start at the right end of the string and work left: peel off |
| | 4399 | * the last three digits and insert a comma. Then, move back |
| | 4400 | * four characters through the string - another three-digit |
| | 4401 | * group, plus the comma we inserted - and repeat. Keep going |
| | 4402 | * until the amount we'd want to peel off the end is as long or |
| | 4403 | * longer than the entire remaining string. |
| | 4404 | */ |
| | 4405 | for (i = 3, len = str.length() ; len > i ; i += 4) |
| | 4406 | { |
| | 4407 | /* insert this comma */ |
| | 4408 | str = str.substr(1, len - i) + sep + str.substr(len - i + 1); |
| | 4409 | |
| | 4410 | /* note the new length */ |
| | 4411 | len = str.length(); |
| | 4412 | } |
| | 4413 | } |
| | 4414 | |
| | 4415 | /* return the result */ |
| | 4416 | return str; |
| | 4417 | } |
| | 4418 | |
| | 4419 | /* |
| | 4420 | * Spell out an integer number - "extended" interface with flags. The |
| | 4421 | * "flags" argument is a (bitwise-OR'd) combination of SpellIntXxx |
| | 4422 | * values, specifying the desired format of the result. |
| | 4423 | */ |
| | 4424 | spellIntExt(val, flags) |
| | 4425 | { |
| | 4426 | local str; |
| | 4427 | local trailingSpace; |
| | 4428 | local needAnd; |
| | 4429 | local powers = [1000000000, ' billion ', |
| | 4430 | 1000000, ' million ', |
| | 4431 | 1000, ' thousand ', |
| | 4432 | 100, ' hundred ']; |
| | 4433 | |
| | 4434 | /* start with an empty string */ |
| | 4435 | str = ''; |
| | 4436 | trailingSpace = nil; |
| | 4437 | needAnd = nil; |
| | 4438 | |
| | 4439 | /* if it's zero, it's a special case */ |
| | 4440 | if (val == 0) |
| | 4441 | return 'zero'; |
| | 4442 | |
| | 4443 | /* |
| | 4444 | * if the number is negative, note it in the string, and use the |
| | 4445 | * absolute value |
| | 4446 | */ |
| | 4447 | if (val < 0) |
| | 4448 | { |
| | 4449 | str = 'negative '; |
| | 4450 | val = -val; |
| | 4451 | } |
| | 4452 | |
| | 4453 | /* do each named power of ten */ |
| | 4454 | for (local i = 1 ; val >= 100 && i <= powers.length() ; i += 2) |
| | 4455 | { |
| | 4456 | /* |
| | 4457 | * if we're in teen-hundreds mode, do the teen-hundreds - this |
| | 4458 | * only works for values from 1,100 to 9,999, since a number like |
| | 4459 | * 12,000 doesn't work this way - 'one hundred twenty hundred' is |
| | 4460 | * no good |
| | 4461 | */ |
| | 4462 | if ((flags & SpellIntTeenHundreds) != 0 |
| | 4463 | && val >= 1100 && val < 10000) |
| | 4464 | { |
| | 4465 | /* if desired, add a comma if there was a prior power group */ |
| | 4466 | if (needAnd && (flags & SpellIntCommas) != 0) |
| | 4467 | str = str.substr(1, str.length() - 1) + ', '; |
| | 4468 | |
| | 4469 | /* spell it out as a number of hundreds */ |
| | 4470 | str += spellIntExt(val / 100, flags) + ' hundred '; |
| | 4471 | |
| | 4472 | /* take off the hundreds */ |
| | 4473 | val %= 100; |
| | 4474 | |
| | 4475 | /* note the trailing space */ |
| | 4476 | trailingSpace = true; |
| | 4477 | |
| | 4478 | /* we have something to put an 'and' after, if desired */ |
| | 4479 | needAnd = true; |
| | 4480 | |
| | 4481 | /* |
| | 4482 | * whatever's left is below 100 now, so there's no need to |
| | 4483 | * keep scanning the big powers of ten |
| | 4484 | */ |
| | 4485 | break; |
| | 4486 | } |
| | 4487 | |
| | 4488 | /* if we have something in this power range, apply it */ |
| | 4489 | if (val >= powers[i]) |
| | 4490 | { |
| | 4491 | /* if desired, add a comma if there was a prior power group */ |
| | 4492 | if (needAnd && (flags & SpellIntCommas) != 0) |
| | 4493 | str = str.substr(1, str.length() - 1) + ', '; |
| | 4494 | |
| | 4495 | /* add the number of multiples of this power and the power name */ |
| | 4496 | str += spellIntExt(val / powers[i], flags) + powers[i+1]; |
| | 4497 | |
| | 4498 | /* take it out of the remaining value */ |
| | 4499 | val %= powers[i]; |
| | 4500 | |
| | 4501 | /* |
| | 4502 | * note that we have a trailing space in the string (all of |
| | 4503 | * the power-of-ten names have a trailing space, to make it |
| | 4504 | * easy to tack on the remainder of the value) |
| | 4505 | */ |
| | 4506 | trailingSpace = true; |
| | 4507 | |
| | 4508 | /* we have something to put an 'and' after, if one is desired */ |
| | 4509 | needAnd = true; |
| | 4510 | } |
| | 4511 | } |
| | 4512 | |
| | 4513 | /* |
| | 4514 | * if we have anything left, and we have written something so far, |
| | 4515 | * and the caller wanted an 'and' before the tens part, add the |
| | 4516 | * 'and' |
| | 4517 | */ |
| | 4518 | if ((flags & SpellIntAndTens) != 0 |
| | 4519 | && needAnd |
| | 4520 | && val != 0) |
| | 4521 | { |
| | 4522 | /* add the 'and' */ |
| | 4523 | str += 'and '; |
| | 4524 | trailingSpace = true; |
| | 4525 | } |
| | 4526 | |
| | 4527 | /* do the tens */ |
| | 4528 | if (val >= 20) |
| | 4529 | { |
| | 4530 | /* anything above the teens is nice and regular */ |
| | 4531 | str += ['twenty', 'thirty', 'forty', 'fifty', 'sixty', |
| | 4532 | 'seventy', 'eighty', 'ninety'][val/10 - 1]; |
| | 4533 | val %= 10; |
| | 4534 | |
| | 4535 | /* if it's non-zero, we'll add the units, so add a hyphen */ |
| | 4536 | if (val != 0) |
| | 4537 | str += '-'; |
| | 4538 | |
| | 4539 | /* we no longer have a trailing space in the string */ |
| | 4540 | trailingSpace = nil; |
| | 4541 | } |
| | 4542 | else if (val >= 10) |
| | 4543 | { |
| | 4544 | /* we have a teen */ |
| | 4545 | str += ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', |
| | 4546 | 'fifteen', 'sixteen', 'seventeen', 'eighteen', |
| | 4547 | 'nineteen'][val - 9]; |
| | 4548 | |
| | 4549 | /* we've finished with the number */ |
| | 4550 | val = 0; |
| | 4551 | |
| | 4552 | /* there's no trailing space */ |
| | 4553 | trailingSpace = nil; |
| | 4554 | } |
| | 4555 | |
| | 4556 | /* if we have a units value, add it */ |
| | 4557 | if (val != 0) |
| | 4558 | { |
| | 4559 | /* add the units name */ |
| | 4560 | str += ['one', 'two', 'three', 'four', 'five', |
| | 4561 | 'six', 'seven', 'eight', 'nine'][val]; |
| | 4562 | |
| | 4563 | /* we have no trailing space now */ |
| | 4564 | trailingSpace = nil; |
| | 4565 | } |
| | 4566 | |
| | 4567 | /* if there's a trailing space, remove it */ |
| | 4568 | if (trailingSpace) |
| | 4569 | str = str.substr(1, str.length() - 1); |
| | 4570 | |
| | 4571 | /* return the string */ |
| | 4572 | return str; |
| | 4573 | } |
| | 4574 | |
| | 4575 | /* |
| | 4576 | * Return a string giving the numeric ordinal representation of a number: |
| | 4577 | * 1st, 2nd, 3rd, 4th, etc. |
| | 4578 | */ |
| | 4579 | intOrdinal(n) |
| | 4580 | { |
| | 4581 | local s; |
| | 4582 | |
| | 4583 | /* start by getting the string form of the number */ |
| | 4584 | s = toString(n); |
| | 4585 | |
| | 4586 | /* now add the appropriate suffix */ |
| | 4587 | if (n >= 10 && n <= 19) |
| | 4588 | { |
| | 4589 | /* the teens all end in 'th' */ |
| | 4590 | return s + 'th'; |
| | 4591 | } |
| | 4592 | else |
| | 4593 | { |
| | 4594 | /* |
| | 4595 | * for anything but a teen, a number whose last digit is 1 |
| | 4596 | * always has the suffix 'st' (for 'xxx-first', as in '141st'), |
| | 4597 | * a number whose last digit is 2 always ends in 'nd' (for |
| | 4598 | * 'xxx-second', as in '532nd'), a number whose last digit is 3 |
| | 4599 | * ends in 'rd' (for 'xxx-third', as in '53rd'), and anything |
| | 4600 | * else ends in 'th' |
| | 4601 | */ |
| | 4602 | switch(n % 10) |
| | 4603 | { |
| | 4604 | case 1: |
| | 4605 | return s + 'st'; |
| | 4606 | |
| | 4607 | case 2: |
| | 4608 | return s + 'nd'; |
| | 4609 | |
| | 4610 | case 3: |
| | 4611 | return s + 'rd'; |
| | 4612 | |
| | 4613 | default: |
| | 4614 | return s + 'th'; |
| | 4615 | } |
| | 4616 | } |
| | 4617 | } |
| | 4618 | |
| | 4619 | |
| | 4620 | /* |
| | 4621 | * Return a string giving a fully spelled-out ordinal form of a number: |
| | 4622 | * first, second, third, etc. |
| | 4623 | */ |
| | 4624 | spellIntOrdinal(n) |
| | 4625 | { |
| | 4626 | return spellIntOrdinalExt(n, 0); |
| | 4627 | } |
| | 4628 | |
| | 4629 | /* |
| | 4630 | * Return a string giving a fully spelled-out ordinal form of a number: |
| | 4631 | * first, second, third, etc. This form takes the same flag values as |
| | 4632 | * spellIntExt(). |
| | 4633 | */ |
| | 4634 | spellIntOrdinalExt(n, flags) |
| | 4635 | { |
| | 4636 | local s; |
| | 4637 | |
| | 4638 | /* get the spelled-out form of the number itself */ |
| | 4639 | s = spellIntExt(n, flags); |
| | 4640 | |
| | 4641 | /* |
| | 4642 | * If the number ends in 'one', change the ending to 'first'; 'two' |
| | 4643 | * becomes 'second'; 'three' becomes 'third'; 'five' becomes |
| | 4644 | * 'fifth'; 'eight' becomes 'eighth'; 'nine' becomes 'ninth'. If |
| | 4645 | * the number ends in 'y', change the 'y' to 'ieth'. 'Zero' becomes |
| | 4646 | * 'zeroeth'. For everything else, just add 'th' to the spelled-out |
| | 4647 | * name |
| | 4648 | */ |
| | 4649 | if (s == 'zero') |
| | 4650 | return 'zeroeth'; |
| | 4651 | if (s.endsWith('one')) |
| | 4652 | return s.substr(1, s.length() - 3) + 'first'; |
| | 4653 | else if (s.endsWith('two')) |
| | 4654 | return s.substr(1, s.length() - 3) + 'second'; |
| | 4655 | else if (s.endsWith('three')) |
| | 4656 | return s.substr(1, s.length() - 5) + 'third'; |
| | 4657 | else if (s.endsWith('five')) |
| | 4658 | return s.substr(1, s.length() - 4) + 'fifth'; |
| | 4659 | else if (s.endsWith('eight')) |
| | 4660 | return s.substr(1, s.length() - 5) + 'eighth'; |
| | 4661 | else if (s.endsWith('nine')) |
| | 4662 | return s.substr(1, s.length() - 4) + 'ninth'; |
| | 4663 | else if (s.endsWith('y')) |
| | 4664 | return s.substr(1, s.length() - 1) + 'ieth'; |
| | 4665 | else |
| | 4666 | return s + 'th'; |
| | 4667 | } |
| | 4668 | |
| | 4669 | /* ------------------------------------------------------------------------ */ |
| | 4670 | /* |
| | 4671 | * Parse a spelled-out number. This is essentially the reverse of |
| | 4672 | * spellInt() and related functions: we take a string that contains a |
| | 4673 | * spelled-out number and return the integer value. This uses the |
| | 4674 | * command parser's spelled-out number rules, so we can parse anything |
| | 4675 | * that would be recognized as a number in a command. |
| | 4676 | * |
| | 4677 | * If the string contains numerals, we'll treat it as a number in digit |
| | 4678 | * format: for example, if it contains '789', we'll return 789. |
| | 4679 | * |
| | 4680 | * If the string doesn't parse as a number, we return nil. |
| | 4681 | */ |
| | 4682 | parseInt(str) |
| | 4683 | { |
| | 4684 | try |
| | 4685 | { |
| | 4686 | /* tokenize the string */ |
| | 4687 | local toks = cmdTokenizer.tokenize(str); |
| | 4688 | |
| | 4689 | /* parse it */ |
| | 4690 | return parseIntTokens(toks); |
| | 4691 | } |
| | 4692 | catch (Exception exc) |
| | 4693 | { |
| | 4694 | /* |
| | 4695 | * on any exception, just return nil to indicate that we couldn't |
| | 4696 | * parse the string as a number |
| | 4697 | */ |
| | 4698 | return nil; |
| | 4699 | } |
| | 4700 | } |
| | 4701 | |
| | 4702 | /* |
| | 4703 | * Parse a spelled-out number that's given as a token list (as returned |
| | 4704 | * from Tokenizer.tokenize). If we can successfully parse the token list |
| | 4705 | * as a number, we'll return the integer value. If not, we'll return |
| | 4706 | * nil. |
| | 4707 | */ |
| | 4708 | parseIntTokens(toks) |
| | 4709 | { |
| | 4710 | try |
| | 4711 | { |
| | 4712 | /* |
| | 4713 | * if the first token contains digits, treat it as a numeric |
| | 4714 | * string value rather than a spelled-out number |
| | 4715 | */ |
| | 4716 | if (toks.length() != 0 |
| | 4717 | && rexMatch('<digit>+', getTokOrig(toks[1])) != nil) |
| | 4718 | return toInteger(getTokOrig(toks[1])); |
| | 4719 | |
| | 4720 | /* parse it using the spelledNumber production */ |
| | 4721 | local lst = spelledNumber.parseTokens(toks, cmdDict); |
| | 4722 | |
| | 4723 | /* |
| | 4724 | * if we got a match, return the integer value; if not, it's not |
| | 4725 | * parseable as a number, so return nil |
| | 4726 | */ |
| | 4727 | return (lst.length() != 0 ? lst[1].getval() : nil); |
| | 4728 | } |
| | 4729 | catch (Exception exc) |
| | 4730 | { |
| | 4731 | /* |
| | 4732 | * on any exception, just return nil to indicate that it's not |
| | 4733 | * parseable as a number |
| | 4734 | */ |
| | 4735 | return nil; |
| | 4736 | } |
| | 4737 | } |
| | 4738 | |
| | 4739 | |
| | 4740 | /* ------------------------------------------------------------------------ */ |
| | 4741 | /* |
| | 4742 | * Additional token types for US English. |
| | 4743 | */ |
| | 4744 | |
| | 4745 | /* special "apostrophe-s" token */ |
| | 4746 | enum token tokApostropheS; |
| | 4747 | |
| | 4748 | /* special abbreviation-period token */ |
| | 4749 | enum token tokAbbrPeriod; |
| | 4750 | |
| | 4751 | /* special "#nnn" numeric token */ |
| | 4752 | enum token tokPoundInt; |
| | 4753 | |
| | 4754 | /* |
| | 4755 | * Command tokenizer for US English. Other language modules should |
| | 4756 | * provide their own tokenizers to allow for differences in punctuation |
| | 4757 | * and other lexical elements. |
| | 4758 | */ |
| | 4759 | cmdTokenizer: Tokenizer |
| | 4760 | rules_ = static |
| | 4761 | [ |
| | 4762 | /* skip whitespace */ |
| | 4763 | ['whitespace', new RexPattern('<Space>+'), nil, &tokCvtSkip, nil], |
| | 4764 | |
| | 4765 | /* certain punctuation marks */ |
| | 4766 | ['punctuation', new RexPattern('[.,;:?!]'), tokPunct, nil, nil], |
| | 4767 | |
| | 4768 | /* |
| | 4769 | * We have a special rule for spelled-out numbers from 21 to 99: |
| | 4770 | * when we see a 'tens' word followed by a hyphen followed by a |
| | 4771 | * digits word, we'll pull out the tens word, the hyphen, and |
| | 4772 | * the digits word as separate tokens. |
| | 4773 | */ |
| | 4774 | ['spelled number', |
| | 4775 | new RexPattern('<NoCase>(twenty|thirty|forty|fifty|sixty|' |
| | 4776 | + 'seventy|eighty|ninety)-' |
| | 4777 | + '(one|two|three|four|five|six|seven|eight|nine)' |
| | 4778 | + '(?!<AlphaNum>)'), |
| | 4779 | tokWord, &tokCvtSpelledNumber, nil], |
| | 4780 | |
| | 4781 | |
| | 4782 | /* |
| | 4783 | * Initials. We'll look for strings of three or two initials, |
| | 4784 | * set off by periods but without spaces. We'll look for |
| | 4785 | * three-letter initials first ("G.H.W. Billfold"), then |
| | 4786 | * two-letter initials ("X.Y. Zed"), so that we find the longest |
| | 4787 | * sequence that's actually in the dictionary. Note that we |
| | 4788 | * don't have a separate rule for individual initials, since |
| | 4789 | * we'll pick that up with the regular abbreviated word rule |
| | 4790 | * below. |
| | 4791 | * |
| | 4792 | * Some games could conceivably extend this to allow strings of |
| | 4793 | * initials of four letters or longer, but in practice people |
| | 4794 | * tend to elide the periods in longer sets of initials, so that |
| | 4795 | * the initials become an acronym, and thus would fit the |
| | 4796 | * ordinary word token rule. |
| | 4797 | */ |
| | 4798 | ['three initials', |
| | 4799 | new RexPattern('<alpha><period><alpha><period><alpha><period>'), |
| | 4800 | tokWord, &tokCvtAbbr, &acceptAbbrTok], |
| | 4801 | |
| | 4802 | ['two initials', |
| | 4803 | new RexPattern('<alpha><period><alpha><period>'), |
| | 4804 | tokWord, &tokCvtAbbr, &acceptAbbrTok], |
| | 4805 | |
| | 4806 | /* |
| | 4807 | * Abbbreviated word - this is a word that ends in a period, |
| | 4808 | * such as "Mr.". This rule comes before the ordinary word rule |
| | 4809 | * because we will only consider the period to be part of the |
| | 4810 | * word (and not a separate token) if the entire string |
| | 4811 | * including the period is in the main vocabulary dictionary. |
| | 4812 | */ |
| | 4813 | ['abbreviation', |
| | 4814 | new RexPattern('<Alpha|-><AlphaNum|-|squote>*<period>'), |
| | 4815 | tokWord, &tokCvtAbbr, &acceptAbbrTok], |
| | 4816 | |
| | 4817 | /* |
| | 4818 | * A word ending in an apostrophe-s. We parse this as two |
| | 4819 | * separate tokens: one for the word and one for the |
| | 4820 | * apostrophe-s. |
| | 4821 | */ |
| | 4822 | ['apostrophe-s word', |
| | 4823 | new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*<squote>[sS]'), |
| | 4824 | tokWord, &tokCvtApostropheS, nil], |
| | 4825 | |
| | 4826 | /* |
| | 4827 | * Words - note that we convert everything to lower-case. A word |
| | 4828 | * must start with an alphabetic character, a hyphen, or an |
| | 4829 | * ampersand; after the initial character, a word can contain |
| | 4830 | * alphabetics, digits, hyphens, ampersands, and apostrophes. |
| | 4831 | */ |
| | 4832 | ['word', |
| | 4833 | new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*'), |
| | 4834 | tokWord, nil, nil], |
| | 4835 | |
| | 4836 | /* an abbreviation word starting with a number */ |
| | 4837 | ['abbreviation with initial digit', |
| | 4838 | new RexPattern('<Digit>(?=<AlphaNum|-|&|squote>*<Alpha|-|&|squote>)' |
| | 4839 | + '<AlphaNum|-|&|squote>*<period>'), |
| | 4840 | tokWord, &tokCvtAbbr, &acceptAbbrTok], |
| | 4841 | |
| | 4842 | /* |
| | 4843 | * A word can start with a number, as long as there's something |
| | 4844 | * other than numbers in the string - if it's all numbers, we |
| | 4845 | * want to treat it as a numeric token. |
| | 4846 | */ |
| | 4847 | ['word with initial digit', |
| | 4848 | new RexPattern('<Digit>(?=<AlphaNum|-|&|squote>*<Alpha|-|&|squote>)' |
| | 4849 | + '<AlphaNum|-|&|squote>*'), tokWord, nil, nil], |
| | 4850 | |
| | 4851 | /* strings with ASCII "straight" quotes */ |
| | 4852 | ['string ascii-quote', |
| | 4853 | new RexPattern('<min>([`\'"])(.*)%1(?!<AlphaNum>)'), |
| | 4854 | tokString, nil, nil], |
| | 4855 | |
| | 4856 | /* some people like to use single quotes like `this' */ |
| | 4857 | ['string back-quote', |
| | 4858 | new RexPattern('<min>`(.*)\'(?!<AlphaNum>)'), tokString, nil, nil], |
| | 4859 | |
| | 4860 | /* strings with Latin-1 curly quotes (single and double) */ |
| | 4861 | ['string curly single-quote', |
| | 4862 | new RexPattern('<min>\u2018(.*)\u2019'), tokString, nil, nil], |
| | 4863 | ['string curly double-quote', |
| | 4864 | new RexPattern('<min>\u201C(.*)\u201D'), tokString, nil, nil], |
| | 4865 | |
| | 4866 | /* |
| | 4867 | * unterminated string - if we didn't just match a terminated |
| | 4868 | * string, but we have what looks like the start of a string, |
| | 4869 | * match to the end of the line |
| | 4870 | */ |
| | 4871 | ['string unterminated', |
| | 4872 | new RexPattern('([`\'"\u2018\u201C](.*)'), tokString, nil, nil], |
| | 4873 | |
| | 4874 | /* integer numbers */ |
| | 4875 | ['integer', new RexPattern('[0-9]+'), tokInt, nil, nil], |
| | 4876 | |
| | 4877 | /* numbers with a '#' preceding */ |
| | 4878 | ['integer with #', |
| | 4879 | new RexPattern('#[0-9]+'), tokPoundInt, nil, nil] |
| | 4880 | ] |
| | 4881 | |
| | 4882 | /* |
| | 4883 | * Handle an apostrophe-s word. We'll return this as two separate |
| | 4884 | * tokens: one for the word preceding the apostrophe-s, and one for |
| | 4885 | * the apostrophe-s itself. |
| | 4886 | */ |
| | 4887 | tokCvtApostropheS(txt, typ, toks) |
| | 4888 | { |
| | 4889 | local w; |
| | 4890 | local s; |
| | 4891 | |
| | 4892 | /* |
| | 4893 | * pull out the part up to but not including the apostrophe, and |
| | 4894 | * pull out the apostrophe-s part |
| | 4895 | */ |
| | 4896 | w = txt.substr(1, txt.length() - 2); |
| | 4897 | s = txt.substr(txt.length() - 1); |
| | 4898 | |
| | 4899 | /* add the part before the apostrophe as the main token type */ |
| | 4900 | toks.append([w, typ, w]); |
| | 4901 | |
| | 4902 | /* add the apostrophe-s as a separate special token */ |
| | 4903 | toks.append([s, tokApostropheS, s]); |
| | 4904 | } |
| | 4905 | |
| | 4906 | /* |
| | 4907 | * Handle a spelled-out hyphenated number from 21 to 99. We'll |
| | 4908 | * return this as three separate tokens: a word for the tens name, a |
| | 4909 | * word for the hyphen, and a word for the units name. |
| | 4910 | */ |
| | 4911 | tokCvtSpelledNumber(txt, typ, toks) |
| | 4912 | { |
| | 4913 | /* parse the number into its three parts with a regular expression */ |
| | 4914 | rexMatch(patAlphaDashAlpha, txt); |
| | 4915 | |
| | 4916 | /* add the part before the hyphen */ |
| | 4917 | toks.append([rexGroup(1)[3], typ, rexGroup(1)[3]]); |
| | 4918 | |
| | 4919 | /* add the hyphen */ |
| | 4920 | toks.append(['-', typ, '-']); |
| | 4921 | |
| | 4922 | /* add the part after the hyphen */ |
| | 4923 | toks.append([rexGroup(2)[3], typ, rexGroup(2)[3]]); |
| | 4924 | } |
| | 4925 | patAlphaDashAlpha = static new RexPattern('(<alpha>+)-(<alpha>+)') |
| | 4926 | |
| | 4927 | /* |
| | 4928 | * Check to see if we want to accept an abbreviated token - this is |
| | 4929 | * a token that ends in a period, which we use for abbreviated words |
| | 4930 | * like "Mr." or "Ave." We'll accept the token only if it appears |
| | 4931 | * as given - including the period - in the dictionary. Note that |
| | 4932 | * we ignore truncated matches, since the only way we'll accept a |
| | 4933 | * period in a word token is as the last character; there is thus no |
| | 4934 | * way that a token ending in a period could be a truncation of any |
| | 4935 | * longer valid token. |
| | 4936 | */ |
| | 4937 | acceptAbbrTok(txt) |
| | 4938 | { |
| | 4939 | /* look up the word, filtering out truncated results */ |
| | 4940 | return cmdDict.isWordDefined( |
| | 4941 | txt, {result: (result & StrCompTrunc) == 0}); |
| | 4942 | } |
| | 4943 | |
| | 4944 | /* |
| | 4945 | * Process an abbreviated token. |
| | 4946 | * |
| | 4947 | * When we find an abbreviation, we'll enter it with the abbreviated |
| | 4948 | * word minus the trailing period, plus the period as a separate |
| | 4949 | * token. We'll mark the period as an "abbreviation period" so that |
| | 4950 | * grammar rules will be able to consider treating it as an |
| | 4951 | * abbreviation -- but since it's also a regular period, grammar |
| | 4952 | * rules that treat periods as regular punctuation will also be able |
| | 4953 | * to try to match the result. This will ensure that we try it both |
| | 4954 | * ways - as abbreviation and as a word with punctuation - and pick |
| | 4955 | * the one that gives us the best result. |
| | 4956 | */ |
| | 4957 | tokCvtAbbr(txt, typ, toks) |
| | 4958 | { |
| | 4959 | local w; |
| | 4960 | |
| | 4961 | /* add the part before the period as the ordinary token */ |
| | 4962 | w = txt.substr(1, txt.length() - 1); |
| | 4963 | toks.append([w, typ, w]); |
| | 4964 | |
| | 4965 | /* add the token for the "abbreviation period" */ |
| | 4966 | toks.append(['.', tokAbbrPeriod, '.']); |
| | 4967 | } |
| | 4968 | |
| | 4969 | /* |
| | 4970 | * Given a list of token strings, rebuild the original input string. |
| | 4971 | * We can't recover the exact input string, because the tokenization |
| | 4972 | * process throws away whitespace information, but we can at least |
| | 4973 | * come up with something that will display cleanly and produce the |
| | 4974 | * same results when run through the tokenizer. |
| | 4975 | */ |
| | 4976 | buildOrigText(toks) |
| | 4977 | { |
| | 4978 | local str; |
| | 4979 | |
| | 4980 | /* start with an empty string */ |
| | 4981 | str = ''; |
| | 4982 | |
| | 4983 | /* concatenate each token in the list */ |
| | 4984 | for (local i = 1, local len = toks.length() ; i <= len ; ++i) |
| | 4985 | { |
| | 4986 | /* add the current token to the string */ |
| | 4987 | str += getTokOrig(toks[i]); |
| | 4988 | |
| | 4989 | /* |
| | 4990 | * if this looks like a hyphenated number that we picked |
| | 4991 | * apart into two tokens, put it back together without |
| | 4992 | * spaces |
| | 4993 | */ |
| | 4994 | if (i + 2 <= len |
| | 4995 | && rexMatch(patSpelledTens, getTokVal(toks[i])) != nil |
| | 4996 | && getTokVal(toks[i+1]) == '-' |
| | 4997 | && rexMatch(patSpelledUnits, getTokVal(toks[i+2])) != nil) |
| | 4998 | { |
| | 4999 | /* |
| | 5000 | * it's a hyphenated number, all right - put the three |
| | 5001 | * tokens back together without any intervening spaces, |
| | 5002 | * so ['twenty', '-', 'one'] turns into 'twenty-one' |
| | 5003 | */ |
| | 5004 | str += getTokOrig(toks[i+1]) + getTokOrig(toks[i+2]); |
| | 5005 | |
| | 5006 | /* skip ahead by the two extra tokens we're adding */ |
| | 5007 | i += 2; |
| | 5008 | } |
| | 5009 | else if (i + 1 <= len |
| | 5010 | && getTokType(toks[i]) == tokWord |
| | 5011 | && getTokType(toks[i+1]) == tokApostropheS) |
| | 5012 | { |
| | 5013 | /* |
| | 5014 | * it's a word followed by an apostrophe-s token - these |
| | 5015 | * are appended together without any intervening spaces |
| | 5016 | */ |
| | 5017 | str += getTokOrig(toks[i+1]); |
| | 5018 | |
| | 5019 | /* skip the extra token we added */ |
| | 5020 | ++i; |
| | 5021 | } |
| | 5022 | |
| | 5023 | /* |
| | 5024 | * if another token follows, and the next token isn't a |
| | 5025 | * punctuation mark, add a space before the next token |
| | 5026 | */ |
| | 5027 | if (i != len && rexMatch(patPunct, getTokVal(toks[i+1])) == nil) |
| | 5028 | str += ' '; |
| | 5029 | } |
| | 5030 | |
| | 5031 | /* return the result string */ |
| | 5032 | return str; |
| | 5033 | } |
| | 5034 | |
| | 5035 | /* some pre-compiled regular expressions */ |
| | 5036 | patSpelledTens = static new RexPattern( |
| | 5037 | '<nocase>twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety') |
| | 5038 | patSpelledUnits = static new RexPattern( |
| | 5039 | '<nocase>one|two|three|four|five|six|seven|eight|nine') |
| | 5040 | patPunct = static new RexPattern('[.,;:?!]') |
| | 5041 | ; |
| | 5042 | |
| | 5043 | |
| | 5044 | /* ------------------------------------------------------------------------ */ |
| | 5045 | /* |
| | 5046 | * Grammar Rules |
| | 5047 | */ |
| | 5048 | |
| | 5049 | /* |
| | 5050 | * Command with explicit target actor. When a command starts with an |
| | 5051 | * actor's name followed by a comma followed by a verb, we take it as |
| | 5052 | * being directed to the actor. |
| | 5053 | */ |
| | 5054 | grammar firstCommandPhrase(withActor): |
| | 5055 | singleNounOnly->actor_ ',' commandPhrase->cmd_ |
| | 5056 | : FirstCommandProdWithActor |
| | 5057 | |
| | 5058 | /* "execute" the target actor phrase */ |
| | 5059 | execActorPhrase(issuingActor) |
| | 5060 | { |
| | 5061 | /* flag that the actor's being addressed in the second person */ |
| | 5062 | resolvedActor_.commandReferralPerson = SecondPerson; |
| | 5063 | } |
| | 5064 | ; |
| | 5065 | |
| | 5066 | grammar firstCommandPhrase(askTellActorTo): |
| | 5067 | ('ask' | 'tell' | 'a' | 't') singleNounOnly->actor_ |
| | 5068 | 'to' commandPhrase->cmd_ |
| | 5069 | : FirstCommandProdWithActor |
| | 5070 | |
| | 5071 | /* "execute" the target actor phrase */ |
| | 5072 | execActorPhrase(issuingActor) |
| | 5073 | { |
| | 5074 | /* |
| | 5075 | * Since our phrasing is TELL <ACTOR> TO <DO SOMETHING>, the |
| | 5076 | * actor clearly becomes the antecedent for a subsequent |
| | 5077 | * pronoun. For example, in TELL BOB TO READ HIS BOOK, the word |
| | 5078 | * HIS pretty clearly refers back to BOB. |
| | 5079 | */ |
| | 5080 | if (resolvedActor_ != nil) |
| | 5081 | { |
| | 5082 | /* set the possessive anaphor object to the actor */ |
| | 5083 | resolvedActor_.setPossAnaphorObj(resolvedActor_); |
| | 5084 | |
| | 5085 | /* flag that the actor's being addressed in the third person */ |
| | 5086 | resolvedActor_.commandReferralPerson = ThirdPerson; |
| | 5087 | |
| | 5088 | /* |
| | 5089 | * in subsequent commands carried out by the issuer, the |
| | 5090 | * target actor is now the pronoun antecedent (for example: |
| | 5091 | * after TELL BOB TO GO NORTH, the command FOLLOW HIM means |
| | 5092 | * to follow Bob) |
| | 5093 | */ |
| | 5094 | issuingActor.setPronounObj(resolvedActor_); |
| | 5095 | } |
| | 5096 | } |
| | 5097 | ; |
| | 5098 | |
| | 5099 | /* |
| | 5100 | * An actor-targeted command with a bad command phrase. This is used as |
| | 5101 | * a fallback if we fail to match anything on the first attempt at |
| | 5102 | * parsing the first command on a line. The point is to at least detect |
| | 5103 | * the target actor phrase, if that much is valid, so that we better |
| | 5104 | * customize error messages for the rest of the command. |
| | 5105 | */ |
| | 5106 | grammar actorBadCommandPhrase(main): |
| | 5107 | singleNounOnly->actor_ ',' miscWordList |
| | 5108 | | ('ask' | 'tell' | 'a' | 't') singleNounOnly->actor_ 'to' miscWordList |
| | 5109 | : FirstCommandProdWithActor |
| | 5110 | |
| | 5111 | /* to resolve nouns, we merely resolve the actor */ |
| | 5112 | resolveNouns(issuingActor, targetActor, results) |
| | 5113 | { |
| | 5114 | /* resolve the underlying actor phrase */ |
| | 5115 | return actor_.resolveNouns(getResolver(issuingActor), results); |
| | 5116 | } |
| | 5117 | ; |
| | 5118 | |
| | 5119 | |
| | 5120 | /* |
| | 5121 | * Command-only conjunctions. These words and groups of words can |
| | 5122 | * separate commands from one another, and can't be used to separate noun |
| | 5123 | * phrases in a noun list. |
| | 5124 | */ |
| | 5125 | grammar commandOnlyConjunction(sentenceEnding): |
| | 5126 | '.' |
| | 5127 | | '!' |
| | 5128 | : BasicProd |
| | 5129 | |
| | 5130 | /* these conjunctions end the sentence */ |
| | 5131 | isEndOfSentence() { return true; } |
| | 5132 | ; |
| | 5133 | |
| | 5134 | grammar commandOnlyConjunction(nonSentenceEnding): |
| | 5135 | 'then' |
| | 5136 | | 'and' 'then' |
| | 5137 | | ',' 'then' |
| | 5138 | | ',' 'and' 'then' |
| | 5139 | | ';' |
| | 5140 | : BasicProd |
| | 5141 | |
| | 5142 | /* these conjunctions do not end a sentence */ |
| | 5143 | isEndOfSentence() { return nil; } |
| | 5144 | ; |
| | 5145 | |
| | 5146 | |
| | 5147 | /* |
| | 5148 | * Command-or-noun conjunctions. These words and groups of words can be |
| | 5149 | * used to separate commands from one another, and can also be used to |
| | 5150 | * separate noun phrases in a noun list. |
| | 5151 | */ |
| | 5152 | grammar commandOrNounConjunction(main): |
| | 5153 | ',' |
| | 5154 | | 'and' |
| | 5155 | | ',' 'and' |
| | 5156 | : BasicProd |
| | 5157 | |
| | 5158 | /* these do not end a sentence */ |
| | 5159 | isEndOfSentence() { return nil; } |
| | 5160 | ; |
| | 5161 | |
| | 5162 | /* |
| | 5163 | * Noun conjunctions. These words and groups of words can be used to |
| | 5164 | * separate noun phrases from one another. Note that these do not need |
| | 5165 | * to be exclusive to noun phrases - these can occur as command |
| | 5166 | * conjunctions as well; this list is separated from |
| | 5167 | * commandOrNounConjunction in case there are conjunctions that can never |
| | 5168 | * be used as command conjunctions, since such conjunctions, which can |
| | 5169 | * appear here, would not appear in commandOrNounConjunctions. |
| | 5170 | */ |
| | 5171 | grammar nounConjunction(main): |
| | 5172 | ',' |
| | 5173 | | 'and' |
| | 5174 | | ',' 'and' |
| | 5175 | : BasicProd |
| | 5176 | |
| | 5177 | /* these conjunctions do not end a sentence */ |
| | 5178 | isEndOfSentence() { return nil; } |
| | 5179 | ; |
| | 5180 | |
| | 5181 | /* ------------------------------------------------------------------------ */ |
| | 5182 | /* |
| | 5183 | * Noun list: one or more noun phrases connected with conjunctions. This |
| | 5184 | * kind of noun list can end in a terminal noun phrase. |
| | 5185 | * |
| | 5186 | * Note that a single noun phrase is a valid noun list, since a list can |
| | 5187 | * simply be a list of one. The separate production nounMultiList can be |
| | 5188 | * used when multiple noun phrases are required. |
| | 5189 | */ |
| | 5190 | |
| | 5191 | /* |
| | 5192 | * a noun list can consist of a single terminal noun phrase |
| | 5193 | */ |
| | 5194 | grammar nounList(terminal): terminalNounPhrase->np_ : NounListProd |
| | 5195 | resolveNouns(resolver, results) |
| | 5196 | { |
| | 5197 | /* resolve the underlying noun phrase */ |
| | 5198 | return np_.resolveNouns(resolver, results); |
| | 5199 | } |
| | 5200 | ; |
| | 5201 | |
| | 5202 | /* |
| | 5203 | * a noun list can consist of a list of a single complete (non-terminal) |
| | 5204 | * noun phrase |
| | 5205 | */ |
| | 5206 | grammar nounList(nonTerminal): completeNounPhrase->np_ : NounListProd |
| | 5207 | resolveNouns(resolver, results) |
| | 5208 | { |
| | 5209 | /* resolve the underlying noun phrase */ |
| | 5210 | return np_.resolveNouns(resolver, results); |
| | 5211 | } |
| | 5212 | ; |
| | 5213 | |
| | 5214 | /* |
| | 5215 | * a noun list can consist of a list with two or more entries |
| | 5216 | */ |
| | 5217 | grammar nounList(list): nounMultiList->lst_ : NounListProd |
| | 5218 | resolveNouns(resolver, results) |
| | 5219 | { |
| | 5220 | /* resolve the underlying list */ |
| | 5221 | return lst_.resolveNouns(resolver, results); |
| | 5222 | } |
| | 5223 | ; |
| | 5224 | |
| | 5225 | /* |
| | 5226 | * An empty noun list is one with no words at all. This is matched when |
| | 5227 | * a command requires a noun list but the player doesn't include one; |
| | 5228 | * this construct has "badness" because we only want to match it when we |
| | 5229 | * have no choice. |
| | 5230 | */ |
| | 5231 | grammar nounList(empty): [badness 500] : EmptyNounPhraseProd |
| | 5232 | responseProd = nounList |
| | 5233 | ; |
| | 5234 | |
| | 5235 | /* ------------------------------------------------------------------------ */ |
| | 5236 | /* |
| | 5237 | * Noun Multi List: two or more noun phrases connected by conjunctions. |
| | 5238 | * This is almost the same as the basic nounList production, but this |
| | 5239 | * type of production requires at least two noun phrases, whereas the |
| | 5240 | * basic nounList production more generally defines its list as any |
| | 5241 | * number - including one - of noun phrases. |
| | 5242 | */ |
| | 5243 | |
| | 5244 | /* |
| | 5245 | * a multi list can consist of a noun multi- list plus a terminal noun |
| | 5246 | * phrase, separated by a conjunction |
| | 5247 | */ |
| | 5248 | grammar nounMultiList(multi): |
| | 5249 | nounMultiList->lst_ nounConjunction terminalNounPhrase->np_ |
| | 5250 | : NounListProd |
| | 5251 | resolveNouns(resolver, results) |
| | 5252 | { |
| | 5253 | /* return a list of all of the objects from both underlying lists */ |
| | 5254 | return np_.resolveNouns(resolver, results) |
| | 5255 | + lst_.resolveNouns(resolver, results); |
| | 5256 | } |
| | 5257 | ; |
| | 5258 | |
| | 5259 | /* |
| | 5260 | * a multi list can consist of a non-terminal multi list |
| | 5261 | */ |
| | 5262 | grammar nounMultiList(nonterminal): nonTerminalNounMultiList->lst_ |
| | 5263 | : NounListProd |
| | 5264 | resolveNouns(resolver, results) |
| | 5265 | { |
| | 5266 | /* resolve the underlying list */ |
| | 5267 | return lst_.resolveNouns(resolver, results); |
| | 5268 | } |
| | 5269 | ; |
| | 5270 | |
| | 5271 | /* ------------------------------------------------------------------------ */ |
| | 5272 | /* |
| | 5273 | * A non-terminal noun multi list is a noun list made up of at least two |
| | 5274 | * non-terminal noun phrases, connected by conjunctions. |
| | 5275 | * |
| | 5276 | * This is almost the same as the regular non-terminal noun list |
| | 5277 | * production, but this production requires two or more underlying noun |
| | 5278 | * phrases, whereas the basic non-terminal noun list matches any number |
| | 5279 | * of underlying phrases, including one. |
| | 5280 | */ |
| | 5281 | |
| | 5282 | /* |
| | 5283 | * a non-terminal multi-list can consist of a pair of complete noun |
| | 5284 | * phrases separated by a conjunction |
| | 5285 | */ |
| | 5286 | grammar nonTerminalNounMultiList(pair): |
| | 5287 | completeNounPhrase->np1_ nounConjunction completeNounPhrase->np2_ |
| | 5288 | : NounListProd |
| | 5289 | resolveNouns(resolver, results) |
| | 5290 | { |
| | 5291 | /* return the combination of the two underlying noun phrases */ |
| | 5292 | return np1_.resolveNouns(resolver, results) |
| | 5293 | + np2_.resolveNouns(resolver, results); |
| | 5294 | } |
| | 5295 | ; |
| | 5296 | |
| | 5297 | /* |
| | 5298 | * a non-terminal multi-list can consist of another non-terminal |
| | 5299 | * multi-list plus a complete noun phrase, connected by a conjunction |
| | 5300 | */ |
| | 5301 | grammar nonTerminalNounMultiList(multi): |
| | 5302 | nonTerminalNounMultiList->lst_ nounConjunction completeNounPhrase->np_ |
| | 5303 | : NounListProd |
| | 5304 | resolveNouns(resolver, results) |
| | 5305 | { |
| | 5306 | /* return the combination of the sublist and the noun phrase */ |
| | 5307 | return lst_.resolveNouns(resolver, results) |
| | 5308 | + np_.resolveNouns(resolver, results); |
| | 5309 | } |
| | 5310 | ; |
| | 5311 | |
| | 5312 | |
| | 5313 | /* ------------------------------------------------------------------------ */ |
| | 5314 | /* |
| | 5315 | * "Except" list. This is a noun list that can contain anything that's |
| | 5316 | * in a regular noun list plus some things that only make sense as |
| | 5317 | * exceptions, such as possessive nouns (e.g., "mine"). |
| | 5318 | */ |
| | 5319 | |
| | 5320 | grammar exceptList(single): exceptNounPhrase->np_ : ExceptListProd |
| | 5321 | resolveNouns(resolver, results) |
| | 5322 | { |
| | 5323 | return np_.resolveNouns(resolver, results); |
| | 5324 | } |
| | 5325 | ; |
| | 5326 | |
| | 5327 | grammar exceptList(list): |
| | 5328 | exceptNounPhrase->np_ nounConjunction exceptList->lst_ |
| | 5329 | : ExceptListProd |
| | 5330 | resolveNouns(resolver, results) |
| | 5331 | { |
| | 5332 | /* return a list consisting of all of our objects */ |
| | 5333 | return np_.resolveNouns(resolver, results) |
| | 5334 | + lst_.resolveNouns(resolver, results); |
| | 5335 | } |
| | 5336 | ; |
| | 5337 | |
| | 5338 | /* |
| | 5339 | * An "except" noun phrase is a normal "complete" noun phrase or a |
| | 5340 | * possessive noun phrase that doesn't explicitly qualify another phrase |
| | 5341 | * (for example, "all the coins but bob's" - the "bob's" is just a |
| | 5342 | * possessive noun phrase without another noun phrase attached, since it |
| | 5343 | * implicitly qualifies "the coins"). |
| | 5344 | */ |
| | 5345 | grammar exceptNounPhrase(singleComplete): completeNounPhraseWithoutAll->np_ |
| | 5346 | : ExceptListProd |
| | 5347 | resolveNouns(resolver, results) |
| | 5348 | { |
| | 5349 | return np_.resolveNouns(resolver, results); |
| | 5350 | } |
| | 5351 | ; |
| | 5352 | |
| | 5353 | grammar exceptNounPhrase(singlePossessive): possessiveNounPhrase->poss_ |
| | 5354 | : ButPossessiveProd |
| | 5355 | ; |
| | 5356 | |
| | 5357 | |
| | 5358 | /* ------------------------------------------------------------------------ */ |
| | 5359 | /* |
| | 5360 | * A single noun is sometimes required where, structurally, a list is not |
| | 5361 | * allowed. Single nouns should not be used to prohibit lists where |
| | 5362 | * there is no structural reason for the prohibition - these should be |
| | 5363 | * used only where it doesn't make sense to use a list structurally. |
| | 5364 | */ |
| | 5365 | grammar singleNoun(normal): singleNounOnly->np_ : LayeredNounPhraseProd |
| | 5366 | ; |
| | 5367 | |
| | 5368 | /* |
| | 5369 | * An empty single noun is one with no words at all. This is matched |
| | 5370 | * when a command requires a noun list but the player doesn't include |
| | 5371 | * one; this construct has "badness" because we only want to match it |
| | 5372 | * when we have no choice. |
| | 5373 | */ |
| | 5374 | grammar singleNoun(empty): [badness 500] : EmptyNounPhraseProd |
| | 5375 | responseProd = singleNoun |
| | 5376 | ; |
| | 5377 | |
| | 5378 | /* |
| | 5379 | * A user could attempt to use a noun list with more than one entry (a |
| | 5380 | * "multi list") where a single noun is required. This is not a |
| | 5381 | * grammatical error, so we accept it grammatically; however, for |
| | 5382 | * disambiguation purposes we score it lower than a singleNoun production |
| | 5383 | * with only one noun phrase, and if we try to resolve it, we'll fail |
| | 5384 | * with an error. |
| | 5385 | */ |
| | 5386 | grammar singleNoun(multiple): nounMultiList->np_ : SingleNounWithListProd |
| | 5387 | ; |
| | 5388 | |
| | 5389 | |
| | 5390 | /* |
| | 5391 | * A *structural* single noun phrase. This production is for use where a |
| | 5392 | * single noun phrase (not a list of nouns) is required grammatically. |
| | 5393 | */ |
| | 5394 | grammar singleNounOnly(main): |
| | 5395 | terminalNounPhrase->np_ |
| | 5396 | | completeNounPhrase->np_ |
| | 5397 | : SingleNounProd |
| | 5398 | ; |
| | 5399 | |
| | 5400 | /* ------------------------------------------------------------------------ */ |
| | 5401 | /* |
| | 5402 | * Prepositionally modified single noun phrases. These can be used in |
| | 5403 | * indirect object responses, so allow for interactions like this: |
| | 5404 | * |
| | 5405 | * >unlock door |
| | 5406 | *. What do you want to unlock it with? |
| | 5407 | * |
| | 5408 | * >with the key |
| | 5409 | * |
| | 5410 | * The entire notion of prepositionally qualified noun phrases in |
| | 5411 | * interactive indirect object responses is specific to English, so this |
| | 5412 | * is implemented in the English module only. However, the general |
| | 5413 | * notion of specialized responses to interactive indirect object queries |
| | 5414 | * is handled in the language-independent library in some cases, in such |
| | 5415 | * a way that the language-specific library can customize the behavior - |
| | 5416 | * see TIAction.askIobjResponseProd. |
| | 5417 | */ |
| | 5418 | class PrepSingleNounProd: SingleNounProd |
| | 5419 | resolveNouns(resolver, results) |
| | 5420 | { |
| | 5421 | return np_.resolveNouns(resolver, results); |
| | 5422 | } |
| | 5423 | ; |
| | 5424 | |
| | 5425 | /* |
| | 5426 | * Same thing for a Topic phrase |
| | 5427 | */ |
| | 5428 | class PrepSingleTopicProd: TopicProd |
| | 5429 | resolveNouns(resolver, results) |
| | 5430 | { |
| | 5431 | return np_.resolveNouns(resolver, results); |
| | 5432 | } |
| | 5433 | ; |
| | 5434 | |
| | 5435 | grammar inSingleNoun(main): |
| | 5436 | singleNoun->np_ | ('in' | 'into' | 'in' 'to') singleNoun->np_ |
| | 5437 | : PrepSingleNounProd |
| | 5438 | ; |
| | 5439 | |
| | 5440 | grammar forSingleNoun(main): |
| | 5441 | singleNoun->np_ | 'for' singleNoun->np_ : PrepSingleNounProd |
| | 5442 | ; |
| | 5443 | |
| | 5444 | grammar toSingleNoun(main): |
| | 5445 | singleNoun->np_ | 'to' singleNoun->np_ : PrepSingleNounProd |
| | 5446 | ; |
| | 5447 | |
| | 5448 | grammar throughSingleNoun(main): |
| | 5449 | singleNoun->np_ | ('through' | 'thru') singleNoun->np_ |
| | 5450 | : PrepSingleNounProd |
| | 5451 | ; |
| | 5452 | |
| | 5453 | grammar fromSingleNoun(main): |
| | 5454 | singleNoun->np_ | 'from' singleNoun->np_ : PrepSingleNounProd |
| | 5455 | ; |
| | 5456 | |
| | 5457 | grammar onSingleNoun(main): |
| | 5458 | singleNoun->np_ | ('on' | 'onto' | 'on' 'to') singleNoun->np_ |
| | 5459 | : PrepSingleNounProd |
| | 5460 | ; |
| | 5461 | |
| | 5462 | grammar withSingleNoun(main): |
| | 5463 | singleNoun->np_ | 'with' singleNoun->np_ : PrepSingleNounProd |
| | 5464 | ; |
| | 5465 | |
| | 5466 | grammar atSingleNoun(main): |
| | 5467 | singleNoun->np_ | 'at' singleNoun->np_ : PrepSingleNounProd |
| | 5468 | ; |
| | 5469 | |
| | 5470 | grammar outOfSingleNoun(main): |
| | 5471 | singleNoun->np_ | 'out' 'of' singleNoun->np_ : PrepSingleNounProd |
| | 5472 | ; |
| | 5473 | |
| | 5474 | grammar aboutTopicPhrase(main): |
| | 5475 | topicPhrase->np_ | 'about' topicPhrase->np_ |
| | 5476 | : PrepSingleTopicProd |
| | 5477 | ; |
| | 5478 | |
| | 5479 | /* ------------------------------------------------------------------------ */ |
| | 5480 | /* |
| | 5481 | * Complete noun phrase - this is a fully-qualified noun phrase that |
| | 5482 | * cannot be modified with articles, quantifiers, or anything else. This |
| | 5483 | * is the highest-level individual noun phrase. |
| | 5484 | */ |
| | 5485 | |
| | 5486 | grammar completeNounPhrase(main): |
| | 5487 | completeNounPhraseWithAll->np_ | completeNounPhraseWithoutAll->np_ |
| | 5488 | : LayeredNounPhraseProd |
| | 5489 | ; |
| | 5490 | |
| | 5491 | /* |
| | 5492 | * Slightly better than a purely miscellaneous word list is a pair of |
| | 5493 | * otherwise valid noun phrases connected by a preposition that's |
| | 5494 | * commonly used in command phrases. This will match commands where the |
| | 5495 | * user has assumed a command with a prepositional structure that doesn't |
| | 5496 | * exist among the defined commands. Since we have badness, we'll be |
| | 5497 | * ignored any time there's a valid command syntax with the same |
| | 5498 | * prepositional structure. |
| | 5499 | */ |
| | 5500 | grammar completeNounPhrase(miscPrep): |
| | 5501 | [badness 100] completeNounPhrase->np1_ |
| | 5502 | ('with' | 'into' | 'in' 'to' | 'through' | 'thru' | 'for' | 'to' |
| | 5503 | | 'onto' | 'on' 'to' | 'at' | 'under' | 'behind') |
| | 5504 | completeNounPhrase->np2_ |
| | 5505 | : NounPhraseProd |
| | 5506 | resolveNouns(resolver, results) |
| | 5507 | { |
| | 5508 | /* note that we have an invalid prepositional phrase structure */ |
| | 5509 | results.noteBadPrep(); |
| | 5510 | |
| | 5511 | /* resolve the underlying noun phrases, for scoring purposes */ |
| | 5512 | np1_.resolveNouns(resolver, results); |
| | 5513 | np2_.resolveNouns(resolver, results); |
| | 5514 | |
| | 5515 | /* return nothing */ |
| | 5516 | return []; |
| | 5517 | } |
| | 5518 | ; |
| | 5519 | |
| | 5520 | |
| | 5521 | /* |
| | 5522 | * A qualified noun phrase can, all by itself, be a full noun phrase |
| | 5523 | */ |
| | 5524 | grammar completeNounPhraseWithoutAll(qualified): qualifiedNounPhrase->np_ |
| | 5525 | : LayeredNounPhraseProd |
| | 5526 | ; |
| | 5527 | |
| | 5528 | /* |
| | 5529 | * Pronoun rules. A pronoun is a complete noun phrase; it does not allow |
| | 5530 | * further qualification. |
| | 5531 | */ |
| | 5532 | grammar completeNounPhraseWithoutAll(it): 'it' : ItProd; |
| | 5533 | grammar completeNounPhraseWithoutAll(them): 'them' : ThemProd; |
| | 5534 | grammar completeNounPhraseWithoutAll(him): 'him' : HimProd; |
| | 5535 | grammar completeNounPhraseWithoutAll(her): 'her' : HerProd; |
| | 5536 | |
| | 5537 | /* |
| | 5538 | * Reflexive second-person pronoun, for things like "bob, look at |
| | 5539 | * yourself" |
| | 5540 | */ |
| | 5541 | grammar completeNounPhraseWithoutAll(yourself): |
| | 5542 | 'yourself' | 'yourselves' | 'you' : YouProd |
| | 5543 | ; |
| | 5544 | |
| | 5545 | /* |
| | 5546 | * Reflexive third-person pronouns. We accept these in places such as |
| | 5547 | * the indirect object of a two-object verb. |
| | 5548 | */ |
| | 5549 | grammar completeNounPhraseWithoutAll(itself): 'itself' : ItselfProd |
| | 5550 | /* check agreement of our binding */ |
| | 5551 | checkAgreement(lst) |
| | 5552 | { |
| | 5553 | /* the result is required to be singular and ungendered */ |
| | 5554 | return (lst.length() == 1 && lst[1].obj_.canMatchIt); |
| | 5555 | } |
| | 5556 | ; |
| | 5557 | |
| | 5558 | grammar completeNounPhraseWithoutAll(themselves): |
| | 5559 | 'themself' | 'themselves' : ThemselvesProd |
| | 5560 | |
| | 5561 | /* check agreement of our binding */ |
| | 5562 | checkAgreement(lst) |
| | 5563 | { |
| | 5564 | /* |
| | 5565 | * For 'themselves', allow anything; we could balk at this |
| | 5566 | * matching a single object that isn't a mass noun, but that |
| | 5567 | * would be overly picky, and it would probably reject at least |
| | 5568 | * a few things that really ought to be acceptable. Besides, |
| | 5569 | * 'them' is the closest thing English has to a singular |
| | 5570 | * gender-neutral pronoun, and some people intentionally use it |
| | 5571 | * as such. |
| | 5572 | */ |
| | 5573 | return true; |
| | 5574 | } |
| | 5575 | ; |
| | 5576 | |
| | 5577 | grammar completeNounPhraseWithoutAll(himself): 'himself' : HimselfProd |
| | 5578 | /* check agreement of our binding */ |
| | 5579 | checkAgreement(lst) |
| | 5580 | { |
| | 5581 | /* the result is required to be singular and masculine */ |
| | 5582 | return (lst.length() == 1 && lst[1].obj_.canMatchHim); |
| | 5583 | } |
| | 5584 | ; |
| | 5585 | |
| | 5586 | grammar completeNounPhraseWithoutAll(herself): 'herself' : HerselfProd |
| | 5587 | /* check agreement of our binding */ |
| | 5588 | checkAgreement(lst) |
| | 5589 | { |
| | 5590 | /* the result is required to be singular and feminine */ |
| | 5591 | return (lst.length() == 1 && lst[1].obj_.canMatchHer); |
| | 5592 | } |
| | 5593 | ; |
| | 5594 | |
| | 5595 | /* |
| | 5596 | * First-person pronoun, for referring to the speaker: "bob, look at me" |
| | 5597 | */ |
| | 5598 | grammar completeNounPhraseWithoutAll(me): 'me' | 'myself' : MeProd; |
| | 5599 | |
| | 5600 | /* |
| | 5601 | * "All" and "all but". |
| | 5602 | * |
| | 5603 | * "All" is a "complete" noun phrase, because there's nothing else needed |
| | 5604 | * to make it a noun phrase. We make this a special kind of complete |
| | 5605 | * noun phrase because 'all' is not acceptable as a complete noun phrase |
| | 5606 | * in some contexts where any of the other complete noun phrases are |
| | 5607 | * acceptable. |
| | 5608 | * |
| | 5609 | * "All but" is a "terminal" noun phrase - this is a special kind of |
| | 5610 | * complete noun phrase that cannot be followed by another noun phrase |
| | 5611 | * with "and". "All but" is terminal because we want any and's that |
| | 5612 | * follow it to be part of the exception list, so that we interpret "take |
| | 5613 | * all but a and b" as excluding a and b, not as excluding a but then |
| | 5614 | * including b as a separate list. |
| | 5615 | */ |
| | 5616 | grammar completeNounPhraseWithAll(main): |
| | 5617 | 'all' | 'everything' |
| | 5618 | : EverythingProd |
| | 5619 | ; |
| | 5620 | |
| | 5621 | grammar terminalNounPhrase(allBut): |
| | 5622 | ('all' | 'everything') ('but' | 'except' | 'except' 'for') |
| | 5623 | exceptList->except_ |
| | 5624 | : EverythingButProd |
| | 5625 | ; |
| | 5626 | |
| | 5627 | /* |
| | 5628 | * Plural phrase with an exclusion list. This is a terminal noun phrase |
| | 5629 | * because it ends in an exclusion list. |
| | 5630 | */ |
| | 5631 | grammar terminalNounPhrase(pluralExcept): |
| | 5632 | (qualifiedPluralNounPhrase->np_ | detPluralNounPhrase->np_) |
| | 5633 | ('except' | 'except' 'for' | 'but' | 'but' 'not') exceptList->except_ |
| | 5634 | : ListButProd |
| | 5635 | ; |
| | 5636 | |
| | 5637 | /* |
| | 5638 | * Qualified singular with an exception |
| | 5639 | */ |
| | 5640 | grammar terminalNounPhrase(anyBut): |
| | 5641 | 'any' nounPhrase->np_ |
| | 5642 | ('but' | 'except' | 'except' 'for' | 'but' 'not') exceptList->except_ |
| | 5643 | : IndefiniteNounButProd |
| | 5644 | ; |
| | 5645 | |
| | 5646 | /* ------------------------------------------------------------------------ */ |
| | 5647 | /* |
| | 5648 | * A qualified noun phrase is a noun phrase with an optional set of |
| | 5649 | * qualifiers: a definite or indefinite article, a quantifier, words such |
| | 5650 | * as 'any' and 'all', possessives, and locational specifiers ("the box |
| | 5651 | * on the table"). |
| | 5652 | * |
| | 5653 | * Without qualification, a definite article is implicit, so we read |
| | 5654 | * "take box" as equivalent to "take the box." |
| | 5655 | * |
| | 5656 | * Grammar rule instantiations in language-specific modules should set |
| | 5657 | * property np_ to the underlying noun phrase match tree. |
| | 5658 | */ |
| | 5659 | |
| | 5660 | /* |
| | 5661 | * A qualified noun phrase can be either singular or plural. The number |
| | 5662 | * is a feature of the overall phrase; the phrase might consist of |
| | 5663 | * subphrases of different numbers (for example, "bob's coins" is plural |
| | 5664 | * even though it contains a singular subphrase, "bob"; and "one of the |
| | 5665 | * coins" is singular, even though its subphrase "coins" is plural). |
| | 5666 | */ |
| | 5667 | grammar qualifiedNounPhrase(main): |
| | 5668 | qualifiedSingularNounPhrase->np_ |
| | 5669 | | qualifiedPluralNounPhrase->np_ |
| | 5670 | : LayeredNounPhraseProd |
| | 5671 | ; |
| | 5672 | |
| | 5673 | /* ------------------------------------------------------------------------ */ |
| | 5674 | /* |
| | 5675 | * Singular qualified noun phrase. |
| | 5676 | */ |
| | 5677 | |
| | 5678 | /* |
| | 5679 | * A singular qualified noun phrase with an implicit or explicit definite |
| | 5680 | * article. If there is no article, a definite article is implied (we |
| | 5681 | * interpret "take box" as though it were "take the box"). |
| | 5682 | */ |
| | 5683 | grammar qualifiedSingularNounPhrase(definite): |
| | 5684 | ('the' | 'the' 'one' | 'the' '1' | ) indetSingularNounPhrase->np_ |
| | 5685 | : DefiniteNounProd |
| | 5686 | ; |
| | 5687 | |
| | 5688 | /* |
| | 5689 | * A singular qualified noun phrase with an explicit indefinite article. |
| | 5690 | */ |
| | 5691 | grammar qualifiedSingularNounPhrase(indefinite): |
| | 5692 | ('a' | 'an') indetSingularNounPhrase->np_ |
| | 5693 | : IndefiniteNounProd |
| | 5694 | ; |
| | 5695 | |
| | 5696 | /* |
| | 5697 | * A singular qualified noun phrase with an explicit arbitrary |
| | 5698 | * determiner. |
| | 5699 | */ |
| | 5700 | grammar qualifiedSingularNounPhrase(arbitrary): |
| | 5701 | ('any' | 'one' | '1' | 'any' ('one' | '1')) indetSingularNounPhrase->np_ |
| | 5702 | : ArbitraryNounProd |
| | 5703 | ; |
| | 5704 | |
| | 5705 | /* |
| | 5706 | * A singular qualified noun phrase with a possessive adjective. |
| | 5707 | */ |
| | 5708 | grammar qualifiedSingularNounPhrase(possessive): |
| | 5709 | possessiveAdjPhrase->poss_ indetSingularNounPhrase->np_ |
| | 5710 | : PossessiveNounProd |
| | 5711 | ; |
| | 5712 | |
| | 5713 | /* |
| | 5714 | * A singular qualified noun phrase that arbitrarily selects from a |
| | 5715 | * plural set. This is singular, even though the underlying noun phrase |
| | 5716 | * is plural, because we're explicitly selecting one item. |
| | 5717 | */ |
| | 5718 | grammar qualifiedSingularNounPhrase(anyPlural): |
| | 5719 | 'any' 'of' explicitDetPluralNounPhrase->np_ |
| | 5720 | : ArbitraryNounProd |
| | 5721 | ; |
| | 5722 | |
| | 5723 | /* |
| | 5724 | * A singular object specified only by its containment, with a definite |
| | 5725 | * article. |
| | 5726 | */ |
| | 5727 | grammar qualifiedSingularNounPhrase(theOneIn): |
| | 5728 | 'the' 'one' ('that' ('is' | 'was') | 'that' tokApostropheS | ) |
| | 5729 | ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') |
| | 5730 | completeNounPhraseWithoutAll->cont_ |
| | 5731 | : VagueContainerDefiniteNounPhraseProd |
| | 5732 | |
| | 5733 | /* |
| | 5734 | * our main phrase is simply 'one' (so disambiguation prompts will |
| | 5735 | * read "which one do you mean...") |
| | 5736 | */ |
| | 5737 | mainPhraseText = 'one' |
| | 5738 | ; |
| | 5739 | |
| | 5740 | /* |
| | 5741 | * A singular object specified only by its containment, with an |
| | 5742 | * indefinite article. |
| | 5743 | */ |
| | 5744 | grammar qualifiedSingularNounPhrase(anyOneIn): |
| | 5745 | ('anything' | 'one') ('that' ('is' | 'was') | 'that' tokApostropheS | ) |
| | 5746 | ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') |
| | 5747 | completeNounPhraseWithoutAll->cont_ |
| | 5748 | : VagueContainerIndefiniteNounPhraseProd |
| | 5749 | ; |
| | 5750 | |
| | 5751 | /* ------------------------------------------------------------------------ */ |
| | 5752 | /* |
| | 5753 | * An "indeterminate" singular noun phrase is a noun phrase without any |
| | 5754 | * determiner. A determiner is a phrase that specifies the phrase's |
| | 5755 | * number and indicates whether or not it refers to a specific object, |
| | 5756 | * and if so fixes which object it refers to; determiners include |
| | 5757 | * articles ("the", "a") and possessives. |
| | 5758 | * |
| | 5759 | * Note that an indeterminate phrase is NOT necessarily an indefinite |
| | 5760 | * phrase. In fact, in most cases, we assume a definite usage when the |
| | 5761 | * determiner is omitted: we take TAKE BOX as meaning TAKE THE BOX. This |
| | 5762 | * is more or less the natural way an English speaker would interpret |
| | 5763 | * this ill-formed phrasing, but even more than that, it's the |
| | 5764 | * Adventurese convention, taking into account that most players enter |
| | 5765 | * commands telegraphically and are accustomed to noun phrases being |
| | 5766 | * definite by default. |
| | 5767 | */ |
| | 5768 | |
| | 5769 | /* an indetermine noun phrase can be a simple noun phrase */ |
| | 5770 | grammar indetSingularNounPhrase(basic): |
| | 5771 | nounPhrase->np_ |
| | 5772 | : LayeredNounPhraseProd |
| | 5773 | ; |
| | 5774 | |
| | 5775 | /* |
| | 5776 | * An indetermine noun phrase can specify a location for the object(s). |
| | 5777 | * The location must be a singular noun phrase, but can itself be a fully |
| | 5778 | * qualified noun phrase (so it can have possessives, articles, and |
| | 5779 | * locational qualifiers of its own). |
| | 5780 | * |
| | 5781 | * Note that we take 'that are' even though the noun phrase is singular, |
| | 5782 | * because what we consider a singular noun phrase can have plural usage |
| | 5783 | * ("scissors", for example). |
| | 5784 | */ |
| | 5785 | grammar indetSingularNounPhrase(locational): |
| | 5786 | nounPhrase->np_ |
| | 5787 | ('that' ('is' | 'was') |
| | 5788 | | 'that' tokApostropheS |
| | 5789 | | 'that' ('are' | 'were') |
| | 5790 | | ) |
| | 5791 | ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') |
| | 5792 | completeNounPhraseWithoutAll->cont_ |
| | 5793 | : ContainerNounPhraseProd |
| | 5794 | ; |
| | 5795 | |
| | 5796 | /* ------------------------------------------------------------------------ */ |
| | 5797 | /* |
| | 5798 | * Plural qualified noun phrase. |
| | 5799 | */ |
| | 5800 | |
| | 5801 | /* |
| | 5802 | * A simple unqualified plural phrase with determiner. Since this form |
| | 5803 | * of plural phrase doesn't have any additional syntax that makes it an |
| | 5804 | * unambiguous plural, we can only accept an actual plural for the |
| | 5805 | * underlying phrase here - we can't accept an adjective phrase. |
| | 5806 | */ |
| | 5807 | grammar qualifiedPluralNounPhrase(determiner): |
| | 5808 | ('any' | ) detPluralOnlyNounPhrase->np_ |
| | 5809 | : LayeredNounPhraseProd |
| | 5810 | ; |
| | 5811 | |
| | 5812 | /* plural phrase qualified with a number and optional "any" */ |
| | 5813 | grammar qualifiedPluralNounPhrase(anyNum): |
| | 5814 | ('any' | ) numberPhrase->quant_ indetPluralNounPhrase->np_ |
| | 5815 | | ('any' | ) numberPhrase->quant_ 'of' explicitDetPluralNounPhrase->np_ |
| | 5816 | : QuantifiedPluralProd |
| | 5817 | ; |
| | 5818 | |
| | 5819 | /* plural phrase qualified with a number and "all" */ |
| | 5820 | grammar qualifiedPluralNounPhrase(allNum): |
| | 5821 | 'all' numberPhrase->quant_ indetPluralNounPhrase->np_ |
| | 5822 | | 'all' numberPhrase->quant_ 'of' explicitDetPluralNounPhrase->np_ |
| | 5823 | : ExactQuantifiedPluralProd |
| | 5824 | ; |
| | 5825 | |
| | 5826 | /* plural phrase qualified with "both" */ |
| | 5827 | grammar qualifiedPluralNounPhrase(both): |
| | 5828 | 'both' detPluralNounPhrase->np_ |
| | 5829 | | 'both' 'of' explicitDetPluralNounPhrase->np_ |
| | 5830 | : BothPluralProd |
| | 5831 | ; |
| | 5832 | |
| | 5833 | /* plural phrase qualified with "all" */ |
| | 5834 | grammar qualifiedPluralNounPhrase(all): |
| | 5835 | 'all' detPluralNounPhrase->np_ |
| | 5836 | | 'all' 'of' explicitDetPluralNounPhrase->np_ |
| | 5837 | : AllPluralProd |
| | 5838 | ; |
| | 5839 | |
| | 5840 | /* vague plural phrase with location specified */ |
| | 5841 | grammar qualifiedPluralNounPhrase(theOnesIn): |
| | 5842 | ('the' 'ones' ('that' ('are' | 'were') | ) |
| | 5843 | | ('everything' | 'all') |
| | 5844 | ('that' ('is' | 'was') | 'that' tokApostropheS | )) |
| | 5845 | ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') |
| | 5846 | completeNounPhraseWithoutAll->cont_ |
| | 5847 | : AllInContainerNounPhraseProd |
| | 5848 | ; |
| | 5849 | |
| | 5850 | /* ------------------------------------------------------------------------ */ |
| | 5851 | /* |
| | 5852 | * A plural noun phrase with a determiner. The determiner can be |
| | 5853 | * explicit (such as an article or possessive) or it can implied (the |
| | 5854 | * implied determiner is the definite article, so "take boxes" is |
| | 5855 | * understood as "take the boxes"). |
| | 5856 | */ |
| | 5857 | grammar detPluralNounPhrase(main): |
| | 5858 | indetPluralNounPhrase->np_ | explicitDetPluralNounPhrase->np_ |
| | 5859 | : LayeredNounPhraseProd |
| | 5860 | ; |
| | 5861 | |
| | 5862 | /* ------------------------------------------------------------------------ */ |
| | 5863 | /* |
| | 5864 | * A determiner plural phrase with an explicit underlying plural (i.e., |
| | 5865 | * excluding adjective phrases with no explicitly plural words). |
| | 5866 | */ |
| | 5867 | grammar detPluralOnlyNounPhrase(main): |
| | 5868 | implicitDetPluralOnlyNounPhrase->np_ |
| | 5869 | | explicitDetPluralOnlyNounPhrase->np_ |
| | 5870 | : LayeredNounPhraseProd |
| | 5871 | ; |
| | 5872 | |
| | 5873 | /* |
| | 5874 | * An implicit determiner plural phrase is an indeterminate plural phrase |
| | 5875 | * without any extra determiner - i.e., the determiner is implicit. |
| | 5876 | * We'll treat this the same way we do a plural explicitly determined |
| | 5877 | * with a definite article, since this is the most natural interpretation |
| | 5878 | * in English. |
| | 5879 | * |
| | 5880 | * (This might seem like a pointless extra layer in the grammar, but it's |
| | 5881 | * necessary for the resolution process to have a layer that explicitly |
| | 5882 | * declares the phrase to be determined, even though the determiner is |
| | 5883 | * implied in the structure. This extra layer is important because it |
| | 5884 | * explicitly calls results.noteMatches(), which is needed for rankings |
| | 5885 | * and the like.) |
| | 5886 | */ |
| | 5887 | grammar implicitDetPluralOnlyNounPhrase(main): |
| | 5888 | indetPluralOnlyNounPhrase->np_ |
| | 5889 | : DefinitePluralProd |
| | 5890 | ; |
| | 5891 | |
| | 5892 | /* ------------------------------------------------------------------------ */ |
| | 5893 | /* |
| | 5894 | * A plural noun phrase with an explicit determiner. |
| | 5895 | */ |
| | 5896 | |
| | 5897 | /* a plural noun phrase with a definite article */ |
| | 5898 | grammar explicitDetPluralNounPhrase(definite): |
| | 5899 | 'the' indetPluralNounPhrase->np_ |
| | 5900 | : DefinitePluralProd |
| | 5901 | ; |
| | 5902 | |
| | 5903 | /* a plural noun phrase with a definite article and a number */ |
| | 5904 | grammar explicitDetPluralNounPhrase(definiteNumber): |
| | 5905 | 'the' numberPhrase->quant_ indetPluralNounPhrase->np_ |
| | 5906 | : ExactQuantifiedPluralProd |
| | 5907 | ; |
| | 5908 | |
| | 5909 | /* a plural noun phrase with a possessive */ |
| | 5910 | grammar explicitDetPluralNounPhrase(possessive): |
| | 5911 | possessiveAdjPhrase->poss_ indetPluralNounPhrase->np_ |
| | 5912 | : PossessivePluralProd |
| | 5913 | ; |
| | 5914 | |
| | 5915 | /* a plural noun phrase with a possessive and a number */ |
| | 5916 | grammar explicitDetPluralNounPhrase(possessiveNumber): |
| | 5917 | possessiveAdjPhrase->poss_ numberPhrase->quant_ |
| | 5918 | indetPluralNounPhrase->np_ |
| | 5919 | : ExactQuantifiedPossessivePluralProd |
| | 5920 | ; |
| | 5921 | |
| | 5922 | /* ------------------------------------------------------------------------ */ |
| | 5923 | /* |
| | 5924 | * A plural noun phrase with an explicit determiner and only an |
| | 5925 | * explicitly plural underlying phrase. |
| | 5926 | */ |
| | 5927 | grammar explicitDetPluralOnlyNounPhrase(definite): |
| | 5928 | 'the' indetPluralOnlyNounPhrase->np_ |
| | 5929 | : AllPluralProd |
| | 5930 | ; |
| | 5931 | |
| | 5932 | grammar explicitDetPluralOnlyNounPhrase(definiteNumber): |
| | 5933 | 'the' numberPhrase->quant_ indetPluralNounPhrase->np_ |
| | 5934 | : ExactQuantifiedPluralProd |
| | 5935 | ; |
| | 5936 | |
| | 5937 | grammar explicitDetPluralOnlyNounPhrase(possessive): |
| | 5938 | possessiveAdjPhrase->poss_ indetPluralOnlyNounPhrase->np_ |
| | 5939 | : PossessivePluralProd |
| | 5940 | ; |
| | 5941 | |
| | 5942 | grammar explicitDetPluralOnlyNounPhrase(possessiveNumber): |
| | 5943 | possessiveAdjPhrase->poss_ numberPhrase->quant_ |
| | 5944 | indetPluralNounPhrase->np_ |
| | 5945 | : ExactQuantifiedPossessivePluralProd |
| | 5946 | ; |
| | 5947 | |
| | 5948 | |
| | 5949 | /* ------------------------------------------------------------------------ */ |
| | 5950 | /* |
| | 5951 | * An indeterminate plural noun phrase. |
| | 5952 | * |
| | 5953 | * For the basic indeterminate plural phrase, allow an adjective phrase |
| | 5954 | * anywhere a plural phrase is allowed; this makes possible the |
| | 5955 | * short-hand of omitting a plural word when the plural number is |
| | 5956 | * unambiguous from context. |
| | 5957 | */ |
| | 5958 | |
| | 5959 | /* a simple plural noun phrase */ |
| | 5960 | grammar indetPluralNounPhrase(basic): |
| | 5961 | pluralPhrase->np_ | adjPhrase->np_ |
| | 5962 | : LayeredNounPhraseProd |
| | 5963 | ; |
| | 5964 | |
| | 5965 | /* |
| | 5966 | * A plural noun phrase with a locational qualifier. Note that even |
| | 5967 | * though the overall phrase is plural (and so the main underlying noun |
| | 5968 | * phrase is plural), the location phrase itself must always be singular. |
| | 5969 | */ |
| | 5970 | grammar indetPluralNounPhrase(locational): |
| | 5971 | (pluralPhrase->np_ | adjPhrase->np_) ('that' ('are' | 'were') | ) |
| | 5972 | ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') |
| | 5973 | completeNounPhraseWithoutAll->cont_ |
| | 5974 | : ContainerNounPhraseProd |
| | 5975 | ; |
| | 5976 | |
| | 5977 | /* |
| | 5978 | * An indetermine plural noun phrase with only explicit plural phrases. |
| | 5979 | */ |
| | 5980 | grammar indetPluralOnlyNounPhrase(basic): |
| | 5981 | pluralPhrase->np_ |
| | 5982 | : LayeredNounPhraseProd |
| | 5983 | ; |
| | 5984 | |
| | 5985 | grammar indetPluralOnlyNounPhrase(locational): |
| | 5986 | pluralPhrase->np_ ('that' ('are' | 'were') | ) |
| | 5987 | ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') |
| | 5988 | completeNounPhraseWithoutAll->cont_ |
| | 5989 | : ContainerNounPhraseProd |
| | 5990 | ; |
| | 5991 | |
| | 5992 | /* ------------------------------------------------------------------------ */ |
| | 5993 | /* |
| | 5994 | * Noun Phrase. This is the basic noun phrase, which serves as a |
| | 5995 | * building block for complete noun phrases. This type of noun phrase |
| | 5996 | * can be qualified with articles, quantifiers, and possessives, and can |
| | 5997 | * be used to construct possessives via the addition of "'s" at the end |
| | 5998 | * of the phrase. |
| | 5999 | * |
| | 6000 | * In most cases, custom noun phrase rules should be added to this |
| | 6001 | * production, as long as qualification (with numbers, articles, and |
| | 6002 | * possessives) is allowed. For a custom noun phrase rule that cannot be |
| | 6003 | * qualified, a completeNounPhrase rule should be added instead. |
| | 6004 | */ |
| | 6005 | grammar nounPhrase(main): compoundNounPhrase->np_ |
| | 6006 | : LayeredNounPhraseProd |
| | 6007 | ; |
| | 6008 | |
| | 6009 | /* |
| | 6010 | * Plural phrase. This is the basic plural phrase, and corresponds to |
| | 6011 | * the basic nounPhrase for plural forms. |
| | 6012 | */ |
| | 6013 | grammar pluralPhrase(main): compoundPluralPhrase->np_ |
| | 6014 | : LayeredNounPhraseProd |
| | 6015 | ; |
| | 6016 | |
| | 6017 | /* ------------------------------------------------------------------------ */ |
| | 6018 | /* |
| | 6019 | * Compound noun phrase. This is one or more noun phrases connected with |
| | 6020 | * 'of', as in "piece of paper". The part after the 'of' is another |
| | 6021 | * compound noun phrase. |
| | 6022 | * |
| | 6023 | * Note that this general rule does not allow the noun phrase after the |
| | 6024 | * 'of' to be qualified with an article or number, except that we make an |
| | 6025 | * exception to allow a definite article. Other cases ("a piece of four |
| | 6026 | * papers") do not generally make sense, so we won't attempt to support |
| | 6027 | * them; instead, games can add as special cases new nounPhrase rules for |
| | 6028 | * specific literal sequences where more complex grammar is necessary. |
| | 6029 | */ |
| | 6030 | grammar compoundNounPhrase(simple): simpleNounPhrase->np_ |
| | 6031 | : NounPhraseWithVocab |
| | 6032 | getVocabMatchList(resolver, results, extraFlags) |
| | 6033 | { |
| | 6034 | return np_.getVocabMatchList(resolver, results, extraFlags); |
| | 6035 | } |
| | 6036 | getAdjustedTokens() |
| | 6037 | { |
| | 6038 | return np_.getAdjustedTokens(); |
| | 6039 | } |
| | 6040 | ; |
| | 6041 | |
| | 6042 | grammar compoundNounPhrase(of): |
| | 6043 | simpleNounPhrase->np1_ 'of'->of_ compoundNounPhrase->np2_ |
| | 6044 | | simpleNounPhrase->np1_ 'of'->of_ 'the'->the_ compoundNounPhrase->np2_ |
| | 6045 | : NounPhraseWithVocab |
| | 6046 | getVocabMatchList(resolver, results, extraFlags) |
| | 6047 | { |
| | 6048 | local lst1; |
| | 6049 | local lst2; |
| | 6050 | |
| | 6051 | /* resolve the two underlying lists */ |
| | 6052 | lst1 = np1_.getVocabMatchList(resolver, results, extraFlags); |
| | 6053 | lst2 = np2_.getVocabMatchList(resolver, results, extraFlags); |
| | 6054 | |
| | 6055 | /* |
| | 6056 | * the result is the intersection of the two lists, since we |
| | 6057 | * want the list of objects with all of the underlying |
| | 6058 | * vocabulary words |
| | 6059 | */ |
| | 6060 | return intersectNounLists(lst1, lst2); |
| | 6061 | } |
| | 6062 | getAdjustedTokens() |
| | 6063 | { |
| | 6064 | local ofLst; |
| | 6065 | |
| | 6066 | /* generate the 'of the' list from the original words */ |
| | 6067 | if (the_ == nil) |
| | 6068 | ofLst = [of_, &miscWord]; |
| | 6069 | else |
| | 6070 | ofLst = [of_, &miscWord, the_, &miscWord]; |
| | 6071 | |
| | 6072 | /* return the full list */ |
| | 6073 | return np1_.getAdjustedTokens() + ofLst + np2_.getAdjustedTokens(); |
| | 6074 | } |
| | 6075 | ; |
| | 6076 | |
| | 6077 | |
| | 6078 | /* ------------------------------------------------------------------------ */ |
| | 6079 | /* |
| | 6080 | * Compound plural phrase - same as a compound noun phrase, but involving |
| | 6081 | * a plural part before the 'of'. |
| | 6082 | */ |
| | 6083 | |
| | 6084 | /* |
| | 6085 | * just a single plural phrase |
| | 6086 | */ |
| | 6087 | grammar compoundPluralPhrase(simple): simplePluralPhrase->np_ |
| | 6088 | : NounPhraseWithVocab |
| | 6089 | getVocabMatchList(resolver, results, extraFlags) |
| | 6090 | { |
| | 6091 | return np_.getVocabMatchList(resolver, results, extraFlags); |
| | 6092 | } |
| | 6093 | getAdjustedTokens() |
| | 6094 | { |
| | 6095 | return np_.getAdjustedTokens(); |
| | 6096 | } |
| | 6097 | ; |
| | 6098 | |
| | 6099 | /* |
| | 6100 | * <plural-phrase> of <noun-phrase> |
| | 6101 | */ |
| | 6102 | grammar compoundPluralPhrase(of): |
| | 6103 | simplePluralPhrase->np1_ 'of'->of_ compoundNounPhrase->np2_ |
| | 6104 | | simplePluralPhrase->np1_ 'of'->of_ 'the'->the_ compoundNounPhrase->np2_ |
| | 6105 | : NounPhraseWithVocab |
| | 6106 | getVocabMatchList(resolver, results, extraFlags) |
| | 6107 | { |
| | 6108 | local lst1; |
| | 6109 | local lst2; |
| | 6110 | |
| | 6111 | /* resolve the two underlying lists */ |
| | 6112 | lst1 = np1_.getVocabMatchList(resolver, results, extraFlags); |
| | 6113 | lst2 = np2_.getVocabMatchList(resolver, results, extraFlags); |
| | 6114 | |
| | 6115 | /* |
| | 6116 | * the result is the intersection of the two lists, since we |
| | 6117 | * want the list of objects with all of the underlying |
| | 6118 | * vocabulary words |
| | 6119 | */ |
| | 6120 | return intersectNounLists(lst1, lst2); |
| | 6121 | } |
| | 6122 | getAdjustedTokens() |
| | 6123 | { |
| | 6124 | local ofLst; |
| | 6125 | |
| | 6126 | /* generate the 'of the' list from the original words */ |
| | 6127 | if (the_ == nil) |
| | 6128 | ofLst = [of_, &miscWord]; |
| | 6129 | else |
| | 6130 | ofLst = [of_, &miscWord, the_, &miscWord]; |
| | 6131 | |
| | 6132 | /* return the full list */ |
| | 6133 | return np1_.getAdjustedTokens() + ofLst + np2_.getAdjustedTokens(); |
| | 6134 | } |
| | 6135 | ; |
| | 6136 | |
| | 6137 | /* ------------------------------------------------------------------------ */ |
| | 6138 | /* |
| | 6139 | * Simple noun phrase. This is the most basic noun phrase, which is |
| | 6140 | * simply a noun, optionally preceded by one or more adjectives. |
| | 6141 | */ |
| | 6142 | |
| | 6143 | /* |
| | 6144 | * just a noun |
| | 6145 | */ |
| | 6146 | grammar simpleNounPhrase(noun): nounWord->noun_ : NounPhraseWithVocab |
| | 6147 | /* generate a list of my resolved objects */ |
| | 6148 | getVocabMatchList(resolver, results, extraFlags) |
| | 6149 | { |
| | 6150 | return noun_.getVocabMatchList(resolver, results, extraFlags); |
| | 6151 | } |
| | 6152 | getAdjustedTokens() |
| | 6153 | { |
| | 6154 | return noun_.getAdjustedTokens(); |
| | 6155 | } |
| | 6156 | ; |
| | 6157 | |
| | 6158 | /* |
| | 6159 | * <adjective> <simple-noun-phrase> (this allows any number of adjectives |
| | 6160 | * to be applied) |
| | 6161 | */ |
| | 6162 | grammar simpleNounPhrase(adjNP): adjWord->adj_ simpleNounPhrase->np_ |
| | 6163 | : NounPhraseWithVocab |
| | 6164 | |
| | 6165 | /* generate a list of my resolved objects */ |
| | 6166 | getVocabMatchList(resolver, results, extraFlags) |
| | 6167 | { |
| | 6168 | /* |
| | 6169 | * return the list of objects in scope matching our adjective |
| | 6170 | * plus the list from the underlying noun phrase |
| | 6171 | */ |
| | 6172 | return intersectNounLists( |
| | 6173 | adj_.getVocabMatchList(resolver, results, extraFlags), |
| | 6174 | np_.getVocabMatchList(resolver, results, extraFlags)); |
| | 6175 | } |
| | 6176 | getAdjustedTokens() |
| | 6177 | { |
| | 6178 | return adj_.getAdjustedTokens() + np_.getAdjustedTokens(); |
| | 6179 | } |
| | 6180 | ; |
| | 6181 | |
| | 6182 | /* |
| | 6183 | * A simple noun phrase can also include a number or a quoted string |
| | 6184 | * before or after a noun. A number can be spelled out or written with |
| | 6185 | * numerals; we consider both forms equivalent in meaning. |
| | 6186 | * |
| | 6187 | * A number in this type of usage is grammatically equivalent to an |
| | 6188 | * adjective - it's not meant to quantify the rest of the noun phrase, |
| | 6189 | * but rather is simply an adjective-like modifier. For example, an |
| | 6190 | * elevator's control panel might have a set of numbered buttons which we |
| | 6191 | * want to refer to as "button 1," "button 2," and so on. It is |
| | 6192 | * frequently the case that numeric adjectives are equally at home before |
| | 6193 | * or after their noun: "push 3 button" or "push button 3". In addition, |
| | 6194 | * we accept a number by itself as a lone adjective, as in "push 3". |
| | 6195 | */ |
| | 6196 | |
| | 6197 | /* |
| | 6198 | * just a numeric/string adjective (for things like "push 3", "push #3", |
| | 6199 | * 'push "G"') |
| | 6200 | */ |
| | 6201 | grammar simpleNounPhrase(number): literalAdjPhrase->adj_ |
| | 6202 | : NounPhraseWithVocab |
| | 6203 | getVocabMatchList(resolver, results, extraFlags) |
| | 6204 | { |
| | 6205 | /* |
| | 6206 | * note that this counts as an adjective-ending phrase, since we |
| | 6207 | * don't have a noun involved |
| | 6208 | */ |
| | 6209 | results.noteAdjEnding(); |
| | 6210 | |
| | 6211 | /* pass through to the underlying literal adjective phrase */ |
| | 6212 | local lst = adj_.getVocabMatchList(resolver, results, |
| | 6213 | extraFlags | EndsWithAdj); |
| | 6214 | |
| | 6215 | /* if in global scope, also try a noun interpretation */ |
| | 6216 | if (resolver.isGlobalScope) |
| | 6217 | lst = adj_.addNounMatchList(lst, resolver, results, extraFlags); |
| | 6218 | |
| | 6219 | /* return the result */ |
| | 6220 | return lst; |
| | 6221 | } |
| | 6222 | getAdjustedTokens() |
| | 6223 | { |
| | 6224 | /* pass through to the underlying literal adjective phrase */ |
| | 6225 | return adj_.getAdjustedTokens(); |
| | 6226 | } |
| | 6227 | ; |
| | 6228 | |
| | 6229 | /* |
| | 6230 | * <literal-adjective> <noun> (for things like "board 44 bus" or 'push |
| | 6231 | * "G" button') |
| | 6232 | */ |
| | 6233 | grammar simpleNounPhrase(numberAndNoun): |
| | 6234 | literalAdjPhrase->adj_ nounWord->noun_ |
| | 6235 | : NounPhraseWithVocab |
| | 6236 | getVocabMatchList(resolver, results, extraFlags) |
| | 6237 | { |
| | 6238 | local nounList; |
| | 6239 | local adjList; |
| | 6240 | |
| | 6241 | /* get the list of objects matching the rest of the noun phrase */ |
| | 6242 | nounList = noun_.getVocabMatchList(resolver, results, extraFlags); |
| | 6243 | |
| | 6244 | /* get the list of objects matching the literal adjective */ |
| | 6245 | adjList = adj_.getVocabMatchList(resolver, results, extraFlags); |
| | 6246 | |
| | 6247 | /* intersect the two lists and return the results */ |
| | 6248 | return intersectNounLists(nounList, adjList); |
| | 6249 | } |
| | 6250 | getAdjustedTokens() |
| | 6251 | { |
| | 6252 | return adj_.getAdjustedTokens() + noun_.getAdjustedTokens(); |
| | 6253 | } |
| | 6254 | ; |
| | 6255 | |
| | 6256 | /* |
| | 6257 | * <noun> <literal-adjective> (for things like "press button 3" or 'put |
| | 6258 | * tab "A" in slot "B"') |
| | 6259 | */ |
| | 6260 | grammar simpleNounPhrase(nounAndNumber): |
| | 6261 | nounWord->noun_ literalAdjPhrase->adj_ |
| | 6262 | : NounPhraseWithVocab |
| | 6263 | getVocabMatchList(resolver, results, extraFlags) |
| | 6264 | { |
| | 6265 | local nounList; |
| | 6266 | local adjList; |
| | 6267 | |
| | 6268 | /* get the list of objects matching the rest of the noun phrase */ |
| | 6269 | nounList = noun_.getVocabMatchList(resolver, results, extraFlags); |
| | 6270 | |
| | 6271 | /* get the literal adjective matches */ |
| | 6272 | adjList = adj_.getVocabMatchList(resolver, results, extraFlags); |
| | 6273 | |
| | 6274 | /* intersect the two lists and return the results */ |
| | 6275 | return intersectNounLists(nounList, adjList); |
| | 6276 | } |
| | 6277 | getAdjustedTokens() |
| | 6278 | { |
| | 6279 | return noun_.getAdjustedTokens() + adj_.getAdjustedTokens(); |
| | 6280 | } |
| | 6281 | ; |
| | 6282 | |
| | 6283 | /* |
| | 6284 | * A simple noun phrase can also end in an adjective, which allows |
| | 6285 | * players to refer to objects using only their unique adjectives rather |
| | 6286 | * than their full names, which is sometimes more convenient: just "take |
| | 6287 | * gold" rather than "take gold key." |
| | 6288 | * |
| | 6289 | * When a particular phrase can be interpreted as either ending in an |
| | 6290 | * adjective or ending in a noun, we will always take the noun-ending |
| | 6291 | * interpretation - in such cases, the adjective-ending interpretation is |
| | 6292 | * probably a weak binding. For example, "take pizza" almost certainly |
| | 6293 | * refers to the pizza itself when "pizza" and "pizza box" are both |
| | 6294 | * present, but it can also refer just to the box when no pizza is |
| | 6295 | * present. |
| | 6296 | * |
| | 6297 | * Equivalent to a noun phrase ending in an adjective is a noun phrase |
| | 6298 | * ending with an adjective followed by "one," as in "the red one." |
| | 6299 | */ |
| | 6300 | grammar simpleNounPhrase(adj): adjWord->adj_ : NounPhraseWithVocab |
| | 6301 | /* generate a list of my resolved objects */ |
| | 6302 | getVocabMatchList(resolver, results, extraFlags) |
| | 6303 | { |
| | 6304 | /* note in the results that we end in an adjective */ |
| | 6305 | results.noteAdjEnding(); |
| | 6306 | |
| | 6307 | /* generate a list of objects matching the adjective */ |
| | 6308 | local lst = adj_.getVocabMatchList( |
| | 6309 | resolver, results, extraFlags | EndsWithAdj); |
| | 6310 | |
| | 6311 | /* if in global scope, also try a noun interpretation */ |
| | 6312 | if (resolver.isGlobalScope) |
| | 6313 | lst = adj_.addNounMatchList(lst, resolver, results, extraFlags); |
| | 6314 | |
| | 6315 | /* return the result */ |
| | 6316 | return lst; |
| | 6317 | } |
| | 6318 | getAdjustedTokens() |
| | 6319 | { |
| | 6320 | /* return the adjusted token list for the adjective */ |
| | 6321 | return adj_.getAdjustedTokens(); |
| | 6322 | } |
| | 6323 | ; |
| | 6324 | |
| | 6325 | grammar simpleNounPhrase(adjAndOne): adjective->adj_ 'one' |
| | 6326 | : NounPhraseWithVocab |
| | 6327 | /* generate a list of my resolved objects */ |
| | 6328 | getVocabMatchList(resolver, results, extraFlags) |
| | 6329 | { |
| | 6330 | /* |
| | 6331 | * This isn't exactly an adjective ending, but consider it as |
| | 6332 | * such anyway, since we're not matching 'one' to a vocabulary |
| | 6333 | * word - we're just using it as a grammatical marker that we're |
| | 6334 | * not providing a real noun. If there's another match for |
| | 6335 | * which 'one' is a noun, that one is definitely preferred to |
| | 6336 | * this one; the adj-ending marking will ensure that we choose |
| | 6337 | * the other one. |
| | 6338 | */ |
| | 6339 | results.noteAdjEnding(); |
| | 6340 | |
| | 6341 | /* generate a list of objects matching the adjective */ |
| | 6342 | return getWordMatches(adj_, &adjective, resolver, |
| | 6343 | extraFlags | EndsWithAdj, VocabTruncated); |
| | 6344 | } |
| | 6345 | getAdjustedTokens() |
| | 6346 | { |
| | 6347 | return [adj_, &adjective]; |
| | 6348 | } |
| | 6349 | ; |
| | 6350 | |
| | 6351 | /* |
| | 6352 | * In the worst case, a simple noun phrase can be constructed from |
| | 6353 | * arbitrary words that don't appear in our dictionary. |
| | 6354 | */ |
| | 6355 | grammar simpleNounPhrase(misc): |
| | 6356 | [badness 200] miscWordList->lst_ : NounPhraseWithVocab |
| | 6357 | getVocabMatchList(resolver, results, extraFlags) |
| | 6358 | { |
| | 6359 | /* get the match list from the underlying list */ |
| | 6360 | local lst = lst_.getVocabMatchList(resolver, results, extraFlags); |
| | 6361 | |
| | 6362 | /* |
| | 6363 | * If there are no matches, note in the results that we have an |
| | 6364 | * arbitrary word list. Note that we do this only if there are |
| | 6365 | * no matches, because we might match non-dictionary words to an |
| | 6366 | * object with a wildcard in its vocabulary words, in which case |
| | 6367 | * this is a valid, matching phrase after all. |
| | 6368 | */ |
| | 6369 | if (lst == nil || lst.length() == 0) |
| | 6370 | results.noteMiscWordList(lst_.getOrigText()); |
| | 6371 | |
| | 6372 | /* return the match list */ |
| | 6373 | return lst; |
| | 6374 | } |
| | 6375 | getAdjustedTokens() |
| | 6376 | { |
| | 6377 | return lst_.getAdjustedTokens(); |
| | 6378 | } |
| | 6379 | ; |
| | 6380 | |
| | 6381 | /* |
| | 6382 | * If the command has qualifiers but omits everything else, we can have |
| | 6383 | * an empty simple noun phrase. |
| | 6384 | */ |
| | 6385 | grammar simpleNounPhrase(empty): [badness 600] : NounPhraseWithVocab |
| | 6386 | getVocabMatchList(resolver, results, extraFlags) |
| | 6387 | { |
| | 6388 | /* we have an empty noun phrase */ |
| | 6389 | return results.emptyNounPhrase(resolver); |
| | 6390 | } |
| | 6391 | getAdjustedTokens() |
| | 6392 | { |
| | 6393 | return []; |
| | 6394 | } |
| | 6395 | ; |
| | 6396 | |
| | 6397 | /* ------------------------------------------------------------------------ */ |
| | 6398 | /* |
| | 6399 | * An AdjPhraseWithVocab is an English-specific subclass of |
| | 6400 | * NounPhraseWithVocab, specifically for noun phrases that contain |
| | 6401 | * entirely adjectives. |
| | 6402 | */ |
| | 6403 | class AdjPhraseWithVocab: NounPhraseWithVocab |
| | 6404 | /* the property for the adjective literal - this is usually adj_ */ |
| | 6405 | adjVocabProp = &adj_ |
| | 6406 | |
| | 6407 | /* |
| | 6408 | * Add the vocabulary matches that we'd get if we were treating our |
| | 6409 | * adjective as a noun. This combines the noun interpretation with a |
| | 6410 | * list of matches we got for the adjective version. |
| | 6411 | */ |
| | 6412 | addNounMatchList(lst, resolver, results, extraFlags) |
| | 6413 | { |
| | 6414 | /* get the word matches with a noun interpretation of our adjective */ |
| | 6415 | local nLst = getWordMatches( |
| | 6416 | self.(adjVocabProp), &noun, resolver, extraFlags, VocabTruncated); |
| | 6417 | |
| | 6418 | /* combine the lists and return the result */ |
| | 6419 | return combineWordMatches(lst, nLst); |
| | 6420 | } |
| | 6421 | ; |
| | 6422 | |
| | 6423 | /* ------------------------------------------------------------------------ */ |
| | 6424 | /* |
| | 6425 | * A "literal adjective" phrase is a number or string used as an |
| | 6426 | * adjective. |
| | 6427 | */ |
| | 6428 | grammar literalAdjPhrase(number): |
| | 6429 | numberPhrase->num_ | poundNumberPhrase->num_ |
| | 6430 | : AdjPhraseWithVocab |
| | 6431 | |
| | 6432 | adj_ = (num_.getStrVal()) |
| | 6433 | getVocabMatchList(resolver, results, extraFlags) |
| | 6434 | { |
| | 6435 | local numList; |
| | 6436 | |
| | 6437 | /* |
| | 6438 | * get the list of objects matching the numeral form of the |
| | 6439 | * number as an adjective |
| | 6440 | */ |
| | 6441 | numList = getWordMatches(num_.getStrVal(), &adjective, |
| | 6442 | resolver, extraFlags, VocabTruncated); |
| | 6443 | |
| | 6444 | /* add the list of objects matching the special '#' wildcard */ |
| | 6445 | numList += getWordMatches('#', &adjective, resolver, |
| | 6446 | extraFlags, VocabTruncated); |
| | 6447 | |
| | 6448 | /* return the combined lists */ |
| | 6449 | return numList; |
| | 6450 | } |
| | 6451 | getAdjustedTokens() |
| | 6452 | { |
| | 6453 | return [num_.getStrVal(), &adjective]; |
| | 6454 | } |
| | 6455 | ; |
| | 6456 | |
| | 6457 | grammar literalAdjPhrase(string): quotedStringPhrase->str_ |
| | 6458 | : AdjPhraseWithVocab |
| | 6459 | |
| | 6460 | adj_ = (str_.getStringText().toLower()) |
| | 6461 | getVocabMatchList(resolver, results, extraFlags) |
| | 6462 | { |
| | 6463 | local strList; |
| | 6464 | local wLst; |
| | 6465 | |
| | 6466 | /* |
| | 6467 | * get the list of objects matching the string with the quotes |
| | 6468 | * removed |
| | 6469 | */ |
| | 6470 | strList = getWordMatches(str_.getStringText().toLower(), |
| | 6471 | &literalAdjective, |
| | 6472 | resolver, extraFlags, VocabTruncated); |
| | 6473 | |
| | 6474 | /* add the list of objects matching the literal-adjective wildcard */ |
| | 6475 | wLst = getWordMatches('\u0001', &literalAdjective, resolver, |
| | 6476 | extraFlags, VocabTruncated); |
| | 6477 | strList = combineWordMatches(strList, wLst); |
| | 6478 | |
| | 6479 | /* return the combined lists */ |
| | 6480 | return strList; |
| | 6481 | } |
| | 6482 | getAdjustedTokens() |
| | 6483 | { |
| | 6484 | return [str_.getStringText().toLower(), &adjective]; |
| | 6485 | } |
| | 6486 | ; |
| | 6487 | |
| | 6488 | /* |
| | 6489 | * In many cases, we might want to write what is semantically a literal |
| | 6490 | * string qualifier without the quotes. For example, we might want to |
| | 6491 | * refer to an elevator button that's labeled "G" as simply "button G", |
| | 6492 | * without any quotes around the "G". To accommodate these cases, we |
| | 6493 | * provide the literalAdjective part-of-speech. We'll match these parts |
| | 6494 | * of speech the same way we'd match them if they were quoted. |
| | 6495 | */ |
| | 6496 | grammar literalAdjPhrase(literalAdj): literalAdjective->adj_ |
| | 6497 | : AdjPhraseWithVocab |
| | 6498 | getVocabMatchList(resolver, results, extraFlags) |
| | 6499 | { |
| | 6500 | local lst; |
| | 6501 | |
| | 6502 | /* get a list of objects in scope matching our literal adjective */ |
| | 6503 | lst = getWordMatches(adj_, &literalAdjective, resolver, |
| | 6504 | extraFlags, VocabTruncated); |
| | 6505 | |
| | 6506 | /* if the scope is global, also include ordinary adjective matches */ |
| | 6507 | if (resolver.isGlobalScope) |
| | 6508 | { |
| | 6509 | /* get the ordinary adjective bindings */ |
| | 6510 | local aLst = getWordMatches(adj_, &adjective, resolver, |
| | 6511 | extraFlags, VocabTruncated); |
| | 6512 | |
| | 6513 | /* global scope - combine the lists */ |
| | 6514 | lst = combineWordMatches(lst, aLst); |
| | 6515 | } |
| | 6516 | |
| | 6517 | /* return the result */ |
| | 6518 | return lst; |
| | 6519 | } |
| | 6520 | getAdjustedTokens() |
| | 6521 | { |
| | 6522 | return [adj_, &literalAdjective]; |
| | 6523 | } |
| | 6524 | ; |
| | 6525 | |
| | 6526 | |
| | 6527 | /* ------------------------------------------------------------------------ */ |
| | 6528 | /* |
| | 6529 | * A noun word. This can be either a simple 'noun' vocabulary word, or |
| | 6530 | * it can be an abbreviated noun with a trailing abbreviation period. |
| | 6531 | */ |
| | 6532 | class NounWordProd: NounPhraseWithVocab |
| | 6533 | getVocabMatchList(resolver, results, extraFlags) |
| | 6534 | { |
| | 6535 | local w; |
| | 6536 | local nLst; |
| | 6537 | |
| | 6538 | /* get our word text */ |
| | 6539 | w = getNounText(); |
| | 6540 | |
| | 6541 | /* get the list of matches as nouns */ |
| | 6542 | nLst = getWordMatches(w, &noun, resolver, extraFlags, VocabTruncated); |
| | 6543 | |
| | 6544 | /* |
| | 6545 | * If the resolver indicates that we're in a "global" scope, |
| | 6546 | * *also* include any additional matches as adjectives. |
| | 6547 | * |
| | 6548 | * Normally, when we're operating in limited, local scopes, we |
| | 6549 | * use the structure of the phrasing to determine whether to |
| | 6550 | * match a noun or adjective; if we have a match for a given word |
| | 6551 | * as a noun, we'll treat it only as a noun. This allows us to |
| | 6552 | * take PIZZA to refer to the pizza (for which 'pizza' is defined |
| | 6553 | * as a noun) rather than to the PIZZA BOX (for which 'pizza' is |
| | 6554 | * a mere adjective) when both are in scope. It's obvious which |
| | 6555 | * the player means in such cases, so we can be smart about |
| | 6556 | * choosing the stronger match. |
| | 6557 | * |
| | 6558 | * In cases of global scope, though, it's much harder to guess |
| | 6559 | * about the player's intentions. When the player types PIZZA, |
| | 6560 | * they might be thinking of the box even though there's a pizza |
| | 6561 | * somewhere else in the game. Since the two objects might be in |
| | 6562 | * entirely different locations, both out of view, we can't |
| | 6563 | * assume that one or the other is more likely on the basis of |
| | 6564 | * which is closer to the player's senses. So, it's better to |
| | 6565 | * allow both to match for now, and decide later, based on the |
| | 6566 | * context of the command, which was actually meant. |
| | 6567 | */ |
| | 6568 | if (resolver.isGlobalScope) |
| | 6569 | { |
| | 6570 | /* get the list of matching adjectives */ |
| | 6571 | local aLst = getWordMatches(w, &adjective, resolver, |
| | 6572 | extraFlags, VocabTruncated); |
| | 6573 | |
| | 6574 | /* combine it with the noun list */ |
| | 6575 | nLst = combineWordMatches(nLst, aLst); |
| | 6576 | } |
| | 6577 | |
| | 6578 | /* return the match list */ |
| | 6579 | return nLst; |
| | 6580 | } |
| | 6581 | getAdjustedTokens() |
| | 6582 | { |
| | 6583 | /* the noun includes the period as part of the literal text */ |
| | 6584 | return [getNounText(), &noun]; |
| | 6585 | } |
| | 6586 | |
| | 6587 | /* the actual text of the noun to match to the dictionary */ |
| | 6588 | getNounText() { return noun_; } |
| | 6589 | ; |
| | 6590 | |
| | 6591 | grammar nounWord(noun): noun->noun_ : NounWordProd |
| | 6592 | ; |
| | 6593 | |
| | 6594 | grammar nounWord(nounAbbr): noun->noun_ tokAbbrPeriod->period_ |
| | 6595 | : NounWordProd |
| | 6596 | |
| | 6597 | /* |
| | 6598 | * for dictionary matching purposes, include the text of our noun |
| | 6599 | * with the period attached - the period is part of the dictionary |
| | 6600 | * entry for an abbreviated word |
| | 6601 | */ |
| | 6602 | getNounText() { return noun_ + period_; } |
| | 6603 | ; |
| | 6604 | |
| | 6605 | /* ------------------------------------------------------------------------ */ |
| | 6606 | /* |
| | 6607 | * An adjective word. This can be either a simple 'adjective' vocabulary |
| | 6608 | * word, or it can be an 'adjApostS' vocabulary word plus a 's token. |
| | 6609 | */ |
| | 6610 | grammar adjWord(adj): adjective->adj_ : AdjPhraseWithVocab |
| | 6611 | /* generate a list of resolved objects */ |
| | 6612 | getVocabMatchList(resolver, results, extraFlags) |
| | 6613 | { |
| | 6614 | /* return a list of objects in scope matching our adjective */ |
| | 6615 | return getWordMatches(adj_, &adjective, resolver, |
| | 6616 | extraFlags, VocabTruncated); |
| | 6617 | } |
| | 6618 | getAdjustedTokens() |
| | 6619 | { |
| | 6620 | return [adj_, &adjective]; |
| | 6621 | } |
| | 6622 | ; |
| | 6623 | |
| | 6624 | grammar adjWord(adjApostS): adjApostS->adj_ tokApostropheS->apost_ |
| | 6625 | : AdjPhraseWithVocab |
| | 6626 | /* generate a list of resolved objects */ |
| | 6627 | getVocabMatchList(resolver, results, extraFlags) |
| | 6628 | { |
| | 6629 | /* return a list of objects in scope matching our adjective */ |
| | 6630 | return getWordMatches(adj_, &adjApostS, resolver, |
| | 6631 | extraFlags, VocabTruncated); |
| | 6632 | } |
| | 6633 | getAdjustedTokens() |
| | 6634 | { |
| | 6635 | return [adj_, &adjApostS]; |
| | 6636 | } |
| | 6637 | ; |
| | 6638 | |
| | 6639 | grammar adjWord(adjAbbr): adjective->adj_ tokAbbrPeriod->period_ |
| | 6640 | : AdjPhraseWithVocab |
| | 6641 | getVocabMatchList(resolver, results, extraFlags) |
| | 6642 | { |
| | 6643 | /* |
| | 6644 | * return the list matching our adjective *with* the period |
| | 6645 | * attached; the period is part of the dictionary entry for an |
| | 6646 | * abbreviated word |
| | 6647 | */ |
| | 6648 | return getWordMatches(adj_ + period_, &adjective, resolver, |
| | 6649 | extraFlags, VocabTruncated); |
| | 6650 | } |
| | 6651 | getAdjustedTokens() |
| | 6652 | { |
| | 6653 | /* the adjective includes the period as part of the literal text */ |
| | 6654 | return [adj_ + period_, &adjective]; |
| | 6655 | } |
| | 6656 | ; |
| | 6657 | |
| | 6658 | /* ------------------------------------------------------------------------ */ |
| | 6659 | /* |
| | 6660 | * Possessive phrase. This is a noun phrase expressing ownership of |
| | 6661 | * another object. |
| | 6662 | * |
| | 6663 | * Note that all possessive phrases that can possibly be ambiguous must |
| | 6664 | * define getOrigMainText() to return the "main noun phrase" text. In |
| | 6665 | * English, this means that we must omit any "'s" suffix. This is needed |
| | 6666 | * only when the phrase can be ambiguous, so pronouns don't need it since |
| | 6667 | * they are inherently unambiguous. |
| | 6668 | */ |
| | 6669 | grammar possessiveAdjPhrase(its): 'its' : ItsAdjProd |
| | 6670 | /* we only agree with a singular ungendered noun */ |
| | 6671 | checkAnaphorAgreement(lst) |
| | 6672 | { return lst.length() == 1 && lst[1].obj_.canMatchIt; } |
| | 6673 | ; |
| | 6674 | grammar possessiveAdjPhrase(his): 'his' : HisAdjProd |
| | 6675 | /* we only agree with a singular masculine noun */ |
| | 6676 | checkAnaphorAgreement(lst) |
| | 6677 | { return lst.length() == 1 && lst[1].obj_.canMatchHim; } |
| | 6678 | ; |
| | 6679 | grammar possessiveAdjPhrase(her): 'her' : HerAdjProd |
| | 6680 | /* we only agree with a singular feminine noun */ |
| | 6681 | checkAnaphorAgreement(lst) |
| | 6682 | { return lst.length() == 1 && lst[1].obj_.canMatchHer; } |
| | 6683 | ; |
| | 6684 | grammar possessiveAdjPhrase(their): 'their' : TheirAdjProd |
| | 6685 | /* we only agree with a single noun that has plural usage */ |
| | 6686 | checkAnaphorAgreement(lst) |
| | 6687 | { return lst.length() == 1 && lst[1].obj_.isPlural; } |
| | 6688 | ; |
| | 6689 | grammar possessiveAdjPhrase(your): 'your' : YourAdjProd |
| | 6690 | /* we are non-anaphoric */ |
| | 6691 | checkAnaphorAgreement(lst) { return nil; } |
| | 6692 | ; |
| | 6693 | grammar possessiveAdjPhrase(my): 'my' : MyAdjProd |
| | 6694 | /* we are non-anaphoric */ |
| | 6695 | checkAnaphorAgreement(lst) { return nil; } |
| | 6696 | ; |
| | 6697 | |
| | 6698 | grammar possessiveAdjPhrase(npApostropheS): |
| | 6699 | nounPhrase->np_ tokApostropheS->apost_ : LayeredNounPhraseProd |
| | 6700 | |
| | 6701 | /* get the original text without the "'s" suffix */ |
| | 6702 | getOrigMainText() |
| | 6703 | { |
| | 6704 | /* return just the basic noun phrase part */ |
| | 6705 | return np_.getOrigText(); |
| | 6706 | } |
| | 6707 | ; |
| | 6708 | |
| | 6709 | grammar possessiveAdjPhrase(ppApostropheS): |
| | 6710 | pluralPhrase->np_ tokApostropheS->apost_ : LayeredNounPhraseProd |
| | 6711 | |
| | 6712 | /* get the original text without the "'s" suffix */ |
| | 6713 | getOrigMainText() |
| | 6714 | { |
| | 6715 | /* return just the basic noun phrase part */ |
| | 6716 | return np_.getOrigText(); |
| | 6717 | } |
| | 6718 | |
| | 6719 | resolveNouns(resolver, results) |
| | 6720 | { |
| | 6721 | /* note that we have a plural phrase, structurally speaking */ |
| | 6722 | results.notePlural(); |
| | 6723 | |
| | 6724 | /* inherit the default handling */ |
| | 6725 | return inherited(resolver, results); |
| | 6726 | } |
| | 6727 | |
| | 6728 | /* the possessive phrase is plural */ |
| | 6729 | isPluralPossessive = true |
| | 6730 | ; |
| | 6731 | |
| | 6732 | /* |
| | 6733 | * Possessive noun phrases. These are similar to possessive phrases, but |
| | 6734 | * are stand-alone phrases that can act as nouns rather than as |
| | 6735 | * qualifiers for other noun phrases. For example, for a first-person |
| | 6736 | * player character, "mine" would be a possessive noun phrase referring |
| | 6737 | * to an object owned by the player character. |
| | 6738 | * |
| | 6739 | * Note that many of the words used for possessive nouns are the same as |
| | 6740 | * for possessive adjectives - for example "his" is the same in either |
| | 6741 | * case, as are "'s" words. However, we make the distinction internally |
| | 6742 | * because the two have different grammatical uses, and some of the words |
| | 6743 | * do differ ("her" vs "hers", for example). |
| | 6744 | */ |
| | 6745 | grammar possessiveNounPhrase(its): 'its': ItsNounProd; |
| | 6746 | grammar possessiveNounPhrase(his): 'his': HisNounProd; |
| | 6747 | grammar possessiveNounPhrase(hers): 'hers': HersNounProd; |
| | 6748 | grammar possessiveNounPhrase(theirs): 'theirs': TheirsNounProd; |
| | 6749 | grammar possessiveNounPhrase(yours): 'yours' : YoursNounProd; |
| | 6750 | grammar possessiveNounPhrase(mine): 'mine' : MineNounProd; |
| | 6751 | |
| | 6752 | grammar possessiveNounPhrase(npApostropheS): |
| | 6753 | (nounPhrase->np_ | pluralPhrase->np_) tokApostropheS->apost_ |
| | 6754 | : LayeredNounPhraseProd |
| | 6755 | |
| | 6756 | /* get the original text without the "'s" suffix */ |
| | 6757 | getOrigMainText() |
| | 6758 | { |
| | 6759 | /* return just the basic noun phrase part */ |
| | 6760 | return np_.getOrigText(); |
| | 6761 | } |
| | 6762 | ; |
| | 6763 | |
| | 6764 | |
| | 6765 | /* ------------------------------------------------------------------------ */ |
| | 6766 | /* |
| | 6767 | * Simple plural phrase. This is the most basic plural phrase, which is |
| | 6768 | * simply a plural noun, optionally preceded by one or more adjectives. |
| | 6769 | * |
| | 6770 | * (English doesn't have any sort of adjective declension in number, so |
| | 6771 | * there's no need to distinguish between plural and singular adjectives; |
| | 6772 | * this equivalent rule in languages with adjective-noun agreement in |
| | 6773 | * number would use plural adjectives here as well as plural nouns.) |
| | 6774 | */ |
| | 6775 | grammar simplePluralPhrase(plural): plural->plural_ : NounPhraseWithVocab |
| | 6776 | /* generate a list of my resolved objects */ |
| | 6777 | getVocabMatchList(resolver, results, extraFlags) |
| | 6778 | { |
| | 6779 | local lst; |
| | 6780 | |
| | 6781 | /* get the list of matching plurals */ |
| | 6782 | lst = getWordMatches(plural_, &plural, resolver, |
| | 6783 | extraFlags, PluralTruncated); |
| | 6784 | |
| | 6785 | /* get the list of matching 'noun' definitions */ |
| | 6786 | local nLst = getWordMatches(plural_, &noun, resolver, |
| | 6787 | extraFlags, VocabTruncated); |
| | 6788 | |
| | 6789 | /* get the combined list */ |
| | 6790 | local comboLst = combineWordMatches(lst, nLst); |
| | 6791 | |
| | 6792 | /* |
| | 6793 | * If we're in global scope, add in the matches for just plain |
| | 6794 | * 'noun' properties as well. This is important because we'll |
| | 6795 | * sometimes want to define a word that's actually a plural |
| | 6796 | * usage (in terms of the real-world English) under the 'noun' |
| | 6797 | * property. This occurs particularly when a single game-world |
| | 6798 | * object represents a multiplicity of real-world objects. When |
| | 6799 | * the scope is global, it's hard to anticipate all of the |
| | 6800 | * possible interactions with vocabulary along these lines, so |
| | 6801 | * it's easiest just to include the 'noun' matches. |
| | 6802 | */ |
| | 6803 | if (resolver.isGlobalScope) |
| | 6804 | { |
| | 6805 | /* keep the combined list */ |
| | 6806 | lst = comboLst; |
| | 6807 | } |
| | 6808 | else if (comboLst.length() > lst.length()) |
| | 6809 | { |
| | 6810 | /* |
| | 6811 | * ordinary scope, so don't include the noun matches; but |
| | 6812 | * since we found extra items to add, at least mark the |
| | 6813 | * plural matches as potentially ambiguous |
| | 6814 | */ |
| | 6815 | lst.forEach({x: x.flags_ |= UnclearDisambig}); |
| | 6816 | } |
| | 6817 | |
| | 6818 | /* return the result list */ |
| | 6819 | return lst; |
| | 6820 | } |
| | 6821 | getAdjustedTokens() |
| | 6822 | { |
| | 6823 | return [plural_, &plural]; |
| | 6824 | } |
| | 6825 | ; |
| | 6826 | |
| | 6827 | grammar simplePluralPhrase(adj): adjWord->adj_ simplePluralPhrase->np_ : |
| | 6828 | NounPhraseWithVocab |
| | 6829 | |
| | 6830 | /* resolve my object list */ |
| | 6831 | getVocabMatchList(resolver, results, extraFlags) |
| | 6832 | { |
| | 6833 | /* |
| | 6834 | * return the list of objects in scope matching our adjective |
| | 6835 | * plus the list from the underlying noun phrase |
| | 6836 | */ |
| | 6837 | return intersectNounLists( |
| | 6838 | adj_.getVocabMatchList(resolver, results, extraFlags), |
| | 6839 | np_.getVocabMatchList(resolver, results, extraFlags)); |
| | 6840 | } |
| | 6841 | getAdjustedTokens() |
| | 6842 | { |
| | 6843 | return adj_.getAdjustedTokens() + np_.getAdjustedTokens(); |
| | 6844 | } |
| | 6845 | ; |
| | 6846 | |
| | 6847 | grammar simplePluralPhrase(poundNum): |
| | 6848 | poundNumberPhrase->num_ simplePluralPhrase->np_ |
| | 6849 | : NounPhraseWithVocab |
| | 6850 | |
| | 6851 | /* resolve my object list */ |
| | 6852 | getVocabMatchList(resolver, results, extraFlags) |
| | 6853 | { |
| | 6854 | local baseList; |
| | 6855 | local numList; |
| | 6856 | |
| | 6857 | /* get the base list for the rest of the phrase */ |
| | 6858 | baseList = np_.getVocabMatchList(resolver, results, extraFlags); |
| | 6859 | |
| | 6860 | /* get the numeric matches, including numeric wildcards */ |
| | 6861 | numList = getWordMatches(num_.getStrVal(), &adjective, |
| | 6862 | resolver, extraFlags, VocabTruncated) |
| | 6863 | + getWordMatches('#', &adjective, |
| | 6864 | resolver, extraFlags, VocabTruncated); |
| | 6865 | |
| | 6866 | /* return the intersection of the lists */ |
| | 6867 | return intersectNounLists(numList, baseList); |
| | 6868 | } |
| | 6869 | getAdjustedTokens() |
| | 6870 | { |
| | 6871 | return [num_.getStrVal(), &adjective] + np_.getAdjustedTokens(); |
| | 6872 | } |
| | 6873 | ; |
| | 6874 | |
| | 6875 | /* |
| | 6876 | * A simple plural phrase can end with an adjective and "ones," as in |
| | 6877 | * "the red ones." |
| | 6878 | */ |
| | 6879 | grammar simplePluralPhrase(adjAndOnes): adjective->adj_ 'ones' |
| | 6880 | : NounPhraseWithVocab |
| | 6881 | getVocabMatchList(resolver, results, extraFlags) |
| | 6882 | { |
| | 6883 | /* generate a list of objects matching the adjective */ |
| | 6884 | return getWordMatches(adj_, &adjective, resolver, |
| | 6885 | extraFlags | EndsWithAdj, VocabTruncated); |
| | 6886 | } |
| | 6887 | getAdjustedTokens() |
| | 6888 | { |
| | 6889 | return [adj_, &adjective]; |
| | 6890 | } |
| | 6891 | ; |
| | 6892 | |
| | 6893 | /* |
| | 6894 | * If the command has qualifiers that require a plural, but omits |
| | 6895 | * everything else, we can have an empty simple noun phrase. |
| | 6896 | */ |
| | 6897 | grammar simplePluralPhrase(empty): [badness 600] : NounPhraseWithVocab |
| | 6898 | getVocabMatchList(resolver, results, extraFlags) |
| | 6899 | { |
| | 6900 | /* we have an empty noun phrase */ |
| | 6901 | return results.emptyNounPhrase(resolver); |
| | 6902 | } |
| | 6903 | getAdjustedTokens() |
| | 6904 | { |
| | 6905 | return []; |
| | 6906 | } |
| | 6907 | ; |
| | 6908 | |
| | 6909 | /* |
| | 6910 | * A simple plural phrase can match unknown words as a last resort. |
| | 6911 | */ |
| | 6912 | grammar simplePluralPhrase(misc): |
| | 6913 | [badness 300] miscWordList->lst_ : NounPhraseWithVocab |
| | 6914 | getVocabMatchList(resolver, results, extraFlags) |
| | 6915 | { |
| | 6916 | /* get the match list from the underlying list */ |
| | 6917 | local lst = lst_.getVocabMatchList(resolver, results, extraFlags); |
| | 6918 | |
| | 6919 | /* |
| | 6920 | * if there are no matches, note in the results that we have an |
| | 6921 | * arbitrary word list that doesn't correspond to any object |
| | 6922 | */ |
| | 6923 | if (lst == nil || lst.length() == 0) |
| | 6924 | results.noteMiscWordList(lst_.getOrigText()); |
| | 6925 | |
| | 6926 | /* return the vocabulary match list */ |
| | 6927 | return lst; |
| | 6928 | } |
| | 6929 | getAdjustedTokens() |
| | 6930 | { |
| | 6931 | return lst_.getAdjustedTokens(); |
| | 6932 | } |
| | 6933 | ; |
| | 6934 | |
| | 6935 | |
| | 6936 | /* ------------------------------------------------------------------------ */ |
| | 6937 | /* |
| | 6938 | * An "adjective phrase" is a phrase made entirely of adjectives. |
| | 6939 | */ |
| | 6940 | grammar adjPhrase(adj): adjective->adj_ : AdjPhraseWithVocab |
| | 6941 | getVocabMatchList(resolver, results, extraFlags) |
| | 6942 | { |
| | 6943 | /* note the adjective ending */ |
| | 6944 | results.noteAdjEnding(); |
| | 6945 | |
| | 6946 | /* return the match list */ |
| | 6947 | local lst = getWordMatches(adj_, &adjective, resolver, |
| | 6948 | extraFlags | EndsWithAdj, VocabTruncated); |
| | 6949 | |
| | 6950 | /* if in global scope, also try a noun interpretation */ |
| | 6951 | if (resolver.isGlobalScope) |
| | 6952 | lst = addNounMatchList(lst, resolver, results, extraFlags); |
| | 6953 | |
| | 6954 | /* return the result */ |
| | 6955 | return lst; |
| | 6956 | } |
| | 6957 | |
| | 6958 | getAdjustedTokens() |
| | 6959 | { |
| | 6960 | return [adj_, &adjective]; |
| | 6961 | } |
| | 6962 | ; |
| | 6963 | |
| | 6964 | grammar adjPhrase(adjAdj): adjective->adj_ adjPhrase->ap_ |
| | 6965 | : NounPhraseWithVocab |
| | 6966 | /* generate a list of my resolved objects */ |
| | 6967 | getVocabMatchList(resolver, results, extraFlags) |
| | 6968 | { |
| | 6969 | /* |
| | 6970 | * return the list of objects in scope matching our adjective |
| | 6971 | * plus the list from the underlying adjective phrase |
| | 6972 | */ |
| | 6973 | return intersectWordMatches( |
| | 6974 | adj_, &adjective, resolver, extraFlags, VocabTruncated, |
| | 6975 | ap_.getVocabMatchList(resolver, results, extraFlags)); |
| | 6976 | } |
| | 6977 | getAdjustedTokens() |
| | 6978 | { |
| | 6979 | return [adj_, &adjective] + ap_.getAdjustedTokens(); |
| | 6980 | } |
| | 6981 | ; |
| | 6982 | |
| | 6983 | |
| | 6984 | /* ------------------------------------------------------------------------ */ |
| | 6985 | /* |
| | 6986 | * A "topic" is a special type of noun phrase used in commands like "ask |
| | 6987 | * <actor> about <topic>." We define a topic as simply an ordinary |
| | 6988 | * single-noun phrase. We distinguish this in the grammar to allow games |
| | 6989 | * to add special syntax for these. |
| | 6990 | */ |
| | 6991 | grammar topicPhrase(main): singleNoun->np_ : TopicProd |
| | 6992 | ; |
| | 6993 | |
| | 6994 | /* |
| | 6995 | * Explicitly match a miscellaneous word list as a topic. |
| | 6996 | * |
| | 6997 | * This might seem redundant with the ordinary topicPhrase that accepts a |
| | 6998 | * singleNoun, because singleNoun can match a miscellaneous word list. |
| | 6999 | * The difference is that singleNoun only matches a miscWordList with a |
| | 7000 | * "badness" value, whereas we match a miscWordList here without any |
| | 7001 | * badness. We want to be more tolerant of unrecognized input in topic |
| | 7002 | * phrases than in ordinary noun phrases, because it's in the nature of |
| | 7003 | * topic phrases to go outside of what's implemented directly in the |
| | 7004 | * simulation model. At a grammatical level, we don't want to treat |
| | 7005 | * topic phrases that we can resolve to the simulation model any |
| | 7006 | * differently than we treat those we can't resolve, so we must add this |
| | 7007 | * rule to eliminate the badness that singleNoun associated with a |
| | 7008 | * miscWordList match. |
| | 7009 | * |
| | 7010 | * Note that we do prefer resolvable noun phrase matches to miscWordList |
| | 7011 | * matches, but we handle this preference with the resolver's scoring |
| | 7012 | * mechanism rather than with badness. |
| | 7013 | */ |
| | 7014 | grammar topicPhrase(misc): miscWordList->np_ : TopicProd |
| | 7015 | resolveNouns(resolver, results) |
| | 7016 | { |
| | 7017 | /* note in the results that we have an arbitrary word list */ |
| | 7018 | results.noteMiscWordList(np_.getOrigText()); |
| | 7019 | |
| | 7020 | /* inherit the default TopicProd behavior */ |
| | 7021 | return inherited(resolver, results); |
| | 7022 | } |
| | 7023 | ; |
| | 7024 | |
| | 7025 | /* ------------------------------------------------------------------------ */ |
| | 7026 | /* |
| | 7027 | * A "quoted string" phrase is a literal enclosed in single or double |
| | 7028 | * quotes. |
| | 7029 | * |
| | 7030 | * Note that this is a separate production from literalPhrase. This |
| | 7031 | * production can be used when *only* a quoted string is allowed. The |
| | 7032 | * literalPhrase production allows both quoted and unquoted text. |
| | 7033 | */ |
| | 7034 | grammar quotedStringPhrase(main): tokString->str_ : LiteralProd |
| | 7035 | /* |
| | 7036 | * get my string, with the quotes trimmed off (so we return simply |
| | 7037 | * the contents of the string) |
| | 7038 | */ |
| | 7039 | getStringText() { return stripQuotesFrom(str_); } |
| | 7040 | ; |
| | 7041 | |
| | 7042 | /* |
| | 7043 | * Service routine: strip quotes from a *possibly* quoted string. If the |
| | 7044 | * string starts with a quote, we'll remove the open quote. If it starts |
| | 7045 | * with a quote and it ends with a corresponding close quote, we'll |
| | 7046 | * remove that as well. |
| | 7047 | */ |
| | 7048 | stripQuotesFrom(str) |
| | 7049 | { |
| | 7050 | local hasOpen; |
| | 7051 | local hasClose; |
| | 7052 | |
| | 7053 | /* presume we won't find open or close quotes */ |
| | 7054 | hasOpen = hasClose = nil; |
| | 7055 | |
| | 7056 | /* |
| | 7057 | * Check for quotes. We'll accept regular ASCII "straight" single |
| | 7058 | * or double quotes, as well as Latin-1 curly single or double |
| | 7059 | * quotes. The curly quotes must be used in their normal |
| | 7060 | */ |
| | 7061 | if (str.startsWith('\'') || str.startsWith('"')) |
| | 7062 | { |
| | 7063 | /* single or double quote - check for a matching close quote */ |
| | 7064 | hasOpen = true; |
| | 7065 | hasClose = (str.length() > 2 && str.endsWith(str.substr(1, 1))); |
| | 7066 | } |
| | 7067 | else if (str.startsWith('`')) |
| | 7068 | { |
| | 7069 | /* single in-slanted quote - check for either type of close */ |
| | 7070 | hasOpen = true; |
| | 7071 | hasClose = (str.length() > 2 |
| | 7072 | && (str.endsWith('`') || str.endsWith('\''))); |
| | 7073 | } |
| | 7074 | else if (str.startsWith('\u201C')) |
| | 7075 | { |
| | 7076 | /* it's a curly double quote */ |
| | 7077 | hasOpen = true; |
| | 7078 | hasClose = str.endsWith('\u201D'); |
| | 7079 | } |
| | 7080 | else if (str.startsWith('\u2018')) |
| | 7081 | { |
| | 7082 | /* it's a curly single quote */ |
| | 7083 | hasOpen = true; |
| | 7084 | hasClose = str.endsWith('\u2019'); |
| | 7085 | } |
| | 7086 | |
| | 7087 | /* trim off the quotes */ |
| | 7088 | if (hasOpen) |
| | 7089 | { |
| | 7090 | if (hasClose) |
| | 7091 | str = str.substr(2, str.length() - 2); |
| | 7092 | else |
| | 7093 | str = str.substr(2); |
| | 7094 | } |
| | 7095 | |
| | 7096 | /* return the modified text */ |
| | 7097 | return str; |
| | 7098 | } |
| | 7099 | |
| | 7100 | /* ------------------------------------------------------------------------ */ |
| | 7101 | /* |
| | 7102 | * A "literal" is essentially any phrase. This can include a quoted |
| | 7103 | * string, a number, or any set of word tokens. |
| | 7104 | */ |
| | 7105 | grammar literalPhrase(string): quotedStringPhrase->str_ : LiteralProd |
| | 7106 | getLiteralText(results, action, which) |
| | 7107 | { |
| | 7108 | /* get the text from our underlying quoted string */ |
| | 7109 | return str_.getStringText(); |
| | 7110 | } |
| | 7111 | |
| | 7112 | getTentativeLiteralText() |
| | 7113 | { |
| | 7114 | /* |
| | 7115 | * our result will never change, so our tentative text is the |
| | 7116 | * same as our regular literal text |
| | 7117 | */ |
| | 7118 | return str_.getStringText(); |
| | 7119 | } |
| | 7120 | |
| | 7121 | resolveLiteral(results) |
| | 7122 | { |
| | 7123 | /* flag the literal text */ |
| | 7124 | results.noteLiteral(str_.getOrigText()); |
| | 7125 | } |
| | 7126 | ; |
| | 7127 | |
| | 7128 | grammar literalPhrase(miscList): miscWordList->misc_ : LiteralProd |
| | 7129 | getLiteralText(results, action, which) |
| | 7130 | { |
| | 7131 | /* get my original text */ |
| | 7132 | local txt = misc_.getOrigText(); |
| | 7133 | |
| | 7134 | /* |
| | 7135 | * if our underlying miscWordList has only one token, strip |
| | 7136 | * quotes, in case that token is a quoted string token |
| | 7137 | */ |
| | 7138 | if (misc_.getOrigTokenList().length() == 1) |
| | 7139 | txt = stripQuotesFrom(txt); |
| | 7140 | |
| | 7141 | /* return the text */ |
| | 7142 | return txt; |
| | 7143 | } |
| | 7144 | |
| | 7145 | getTentativeLiteralText() |
| | 7146 | { |
| | 7147 | /* our regular text is permanent, so simply use it now */ |
| | 7148 | return misc_.getOrigText(); |
| | 7149 | } |
| | 7150 | |
| | 7151 | resolveLiteral(results) |
| | 7152 | { |
| | 7153 | /* |
| | 7154 | * note the length of our literal phrase - when we have a choice |
| | 7155 | * of interpretations, we prefer to choose shorter literal |
| | 7156 | * phrases, since this means that we'll have more of our tokens |
| | 7157 | * being fully interpreted rather than bunched into an |
| | 7158 | * uninterpreted literal |
| | 7159 | */ |
| | 7160 | results.noteLiteral(misc_.getOrigText()); |
| | 7161 | } |
| | 7162 | ; |
| | 7163 | |
| | 7164 | /* |
| | 7165 | * In case we have a verb grammar rule that calls for a literal phrase, |
| | 7166 | * but the player enters a command with nothing in that slot, match an |
| | 7167 | * empty token list as a last resort. Since this phrasing has a badness, |
| | 7168 | * we won't match it unless we don't have any better structural match. |
| | 7169 | */ |
| | 7170 | grammar literalPhrase(empty): [badness 400]: EmptyLiteralPhraseProd |
| | 7171 | resolveLiteral(results) { } |
| | 7172 | ; |
| | 7173 | |
| | 7174 | /* ------------------------------------------------------------------------ */ |
| | 7175 | /* |
| | 7176 | * An miscellaneous word list is a list of one or more words of any kind: |
| | 7177 | * any word, any integer, or any apostrophe-S token will do. Note that |
| | 7178 | * known and unknown words can be mixed in an unknown word list; we care |
| | 7179 | * only that the list is made up of tokWord, tokInt, tokApostropheS, |
| | 7180 | * and/or abbreviation-period tokens. |
| | 7181 | * |
| | 7182 | * Note that this kind of phrase is often used with a 'badness' value. |
| | 7183 | * However, we don't assign any badness here, because a miscellaneous |
| | 7184 | * word list might be perfectly valid in some contexts; instead, any |
| | 7185 | * productions that include a misc word list should specify badness as |
| | 7186 | * desired. |
| | 7187 | */ |
| | 7188 | grammar miscWordList(wordOrNumber): |
| | 7189 | tokWord->txt_ | tokInt->txt_ | tokApostropheS->txt_ |
| | 7190 | | tokPoundInt->txt_ | tokString->txt_ | tokAbbrPeriod->txt_ |
| | 7191 | : NounPhraseWithVocab |
| | 7192 | getVocabMatchList(resolver, results, extraFlags) |
| | 7193 | { |
| | 7194 | /* we don't match anything directly with our vocabulary */ |
| | 7195 | return []; |
| | 7196 | } |
| | 7197 | getAdjustedTokens() |
| | 7198 | { |
| | 7199 | /* our token type is the special miscellaneous word type */ |
| | 7200 | return [txt_, &miscWord]; |
| | 7201 | } |
| | 7202 | ; |
| | 7203 | |
| | 7204 | grammar miscWordList(list): |
| | 7205 | (tokWord->txt_ | tokInt->txt_ | tokApostropheS->tok_ | tokAbbrPeriod->txt_ |
| | 7206 | | tokPoundInt->txt_ | tokString->txt_) miscWordList->lst_ |
| | 7207 | : NounPhraseWithVocab |
| | 7208 | getVocabMatchList(resolver, results, extraFlags) |
| | 7209 | { |
| | 7210 | /* we don't match anything directly with our vocabulary */ |
| | 7211 | return []; |
| | 7212 | } |
| | 7213 | getAdjustedTokens() |
| | 7214 | { |
| | 7215 | /* our token type is the special miscellaneous word type */ |
| | 7216 | return [txt_, &miscWord] + lst_.getAdjustedTokens(); |
| | 7217 | } |
| | 7218 | ; |
| | 7219 | |
| | 7220 | /* ------------------------------------------------------------------------ */ |
| | 7221 | /* |
| | 7222 | * A main disambiguation phrase consists of a disambiguation phrase, |
| | 7223 | * optionally terminated with a period. |
| | 7224 | */ |
| | 7225 | grammar mainDisambigPhrase(main): |
| | 7226 | disambigPhrase->dp_ |
| | 7227 | | disambigPhrase->dp_ '.' |
| | 7228 | : BasicProd |
| | 7229 | resolveNouns(resolver, results) |
| | 7230 | { |
| | 7231 | return dp_.resolveNouns(resolver, results); |
| | 7232 | } |
| | 7233 | getResponseList() { return dp_.getResponseList(); } |
| | 7234 | ; |
| | 7235 | |
| | 7236 | /* |
| | 7237 | * A "disambiguation phrase" is a phrase that answers a disambiguation |
| | 7238 | * question ("which book do you mean..."). |
| | 7239 | * |
| | 7240 | * A disambiguation question can be answered with several types of |
| | 7241 | * syntax: |
| | 7242 | * |
| | 7243 | *. all/everything/all of them |
| | 7244 | *. both/both of them |
| | 7245 | *. any/any of them |
| | 7246 | *. <disambig list> |
| | 7247 | *. the <ordinal list> ones |
| | 7248 | *. the former/the latter |
| | 7249 | * |
| | 7250 | * Note that we assign non-zero badness to all of the ordinal |
| | 7251 | * interpretations, so that we will take an actual vocabulary |
| | 7252 | * interpretation instead of an ordinal interpretation whenever possible. |
| | 7253 | * For example, if an object's name is actually "the third button," this |
| | 7254 | * will give us greater affinity for using "third" as an adjective than |
| | 7255 | * as an ordinal in our own list. |
| | 7256 | */ |
| | 7257 | grammar disambigPhrase(all): |
| | 7258 | 'all' | 'everything' | 'all' 'of' 'them' : DisambigProd |
| | 7259 | resolveNouns(resolver, results) |
| | 7260 | { |
| | 7261 | /* they want everything we proposed - return the whole list */ |
| | 7262 | return removeAmbigFlags(resolver.getAll(self)); |
| | 7263 | } |
| | 7264 | |
| | 7265 | /* there's only me in the response list */ |
| | 7266 | getResponseList() { return [self]; } |
| | 7267 | ; |
| | 7268 | |
| | 7269 | grammar disambigPhrase(both): 'both' | 'both' 'of' 'them' : DisambigProd |
| | 7270 | resolveNouns(resolver, results) |
| | 7271 | { |
| | 7272 | /* |
| | 7273 | * they want two items - return the whole list (if it has more |
| | 7274 | * than two items, we'll simply act as though they wanted all of |
| | 7275 | * them) |
| | 7276 | */ |
| | 7277 | return removeAmbigFlags(resolver.getAll(self)); |
| | 7278 | } |
| | 7279 | |
| | 7280 | /* there's only me in the response list */ |
| | 7281 | getResponseList() { return [self]; } |
| | 7282 | ; |
| | 7283 | |
| | 7284 | grammar disambigPhrase(any): 'any' | 'any' 'of' 'them' : DisambigProd |
| | 7285 | resolveNouns(resolver, results) |
| | 7286 | { |
| | 7287 | local lst; |
| | 7288 | |
| | 7289 | /* they want any item - arbitrarily pick the first one */ |
| | 7290 | lst = resolver.matchList.sublist(1, 1); |
| | 7291 | |
| | 7292 | /* |
| | 7293 | * add the "unclear disambiguation" flag to the item we picked, |
| | 7294 | * to indicate that the selection was arbitrary |
| | 7295 | */ |
| | 7296 | if (lst.length() > 0) |
| | 7297 | lst[1].flags_ |= UnclearDisambig; |
| | 7298 | |
| | 7299 | /* return the result */ |
| | 7300 | return lst; |
| | 7301 | } |
| | 7302 | |
| | 7303 | /* there's only me in the response list */ |
| | 7304 | getResponseList() { return [self]; } |
| | 7305 | ; |
| | 7306 | |
| | 7307 | grammar disambigPhrase(list): disambigList->lst_ : DisambigProd |
| | 7308 | resolveNouns(resolver, results) |
| | 7309 | { |
| | 7310 | return removeAmbigFlags(lst_.resolveNouns(resolver, results)); |
| | 7311 | } |
| | 7312 | |
| | 7313 | /* there's only me in the response list */ |
| | 7314 | getResponseList() { return lst_.getResponseList(); } |
| | 7315 | ; |
| | 7316 | |
| | 7317 | grammar disambigPhrase(ordinalList): |
| | 7318 | disambigOrdinalList->lst_ 'ones' |
| | 7319 | | 'the' disambigOrdinalList->lst_ 'ones' |
| | 7320 | : DisambigProd |
| | 7321 | |
| | 7322 | resolveNouns(resolver, results) |
| | 7323 | { |
| | 7324 | /* return the list with the ambiguity flags removed */ |
| | 7325 | return removeAmbigFlags(lst_.resolveNouns(resolver, results)); |
| | 7326 | } |
| | 7327 | |
| | 7328 | /* the response list consists of my single ordinal list item */ |
| | 7329 | getResponseList() { return [lst_]; } |
| | 7330 | ; |
| | 7331 | |
| | 7332 | /* |
| | 7333 | * A disambig list consists of one or more disambig list items, connected |
| | 7334 | * by noun phrase conjunctions. |
| | 7335 | */ |
| | 7336 | grammar disambigList(single): disambigListItem->item_ : DisambigProd |
| | 7337 | resolveNouns(resolver, results) |
| | 7338 | { |
| | 7339 | return item_.resolveNouns(resolver, results); |
| | 7340 | } |
| | 7341 | |
| | 7342 | /* the response list consists of my single item */ |
| | 7343 | getResponseList() { return [item_]; } |
| | 7344 | ; |
| | 7345 | |
| | 7346 | grammar disambigList(list): |
| | 7347 | disambigListItem->item_ commandOrNounConjunction disambigList->lst_ |
| | 7348 | : DisambigProd |
| | 7349 | |
| | 7350 | resolveNouns(resolver, results) |
| | 7351 | { |
| | 7352 | return item_.resolveNouns(resolver, results) |
| | 7353 | + lst_.resolveNouns(resolver, results); |
| | 7354 | } |
| | 7355 | |
| | 7356 | /* my response list consists of each of our list items */ |
| | 7357 | getResponseList() { return [item_] + lst_.getResponseList(); } |
| | 7358 | ; |
| | 7359 | |
| | 7360 | /* |
| | 7361 | * Base class for ordinal disambiguation items |
| | 7362 | */ |
| | 7363 | class DisambigOrdProd: DisambigProd |
| | 7364 | resolveNouns(resolver, results) |
| | 7365 | { |
| | 7366 | /* note the ordinal match */ |
| | 7367 | results.noteDisambigOrdinal(); |
| | 7368 | |
| | 7369 | /* select the result by the ordinal */ |
| | 7370 | return selectByOrdinal(ord_, resolver, results); |
| | 7371 | } |
| | 7372 | |
| | 7373 | selectByOrdinal(ordTok, resolver, results) |
| | 7374 | { |
| | 7375 | local idx; |
| | 7376 | local matchList = resolver.ordinalMatchList; |
| | 7377 | |
| | 7378 | /* |
| | 7379 | * look up the meaning of the ordinal word (note that we assume |
| | 7380 | * that each ordinalWord is unique, since we only create one of |
| | 7381 | * each) |
| | 7382 | */ |
| | 7383 | idx = cmdDict.findWord(ordTok, &ordinalWord)[1].numval; |
| | 7384 | |
| | 7385 | /* |
| | 7386 | * if it's the special value -1, it indicates that we should |
| | 7387 | * select the *last* item in the list |
| | 7388 | */ |
| | 7389 | if (idx == -1) |
| | 7390 | idx = matchList.length(); |
| | 7391 | |
| | 7392 | /* if it's outside the limits of the match list, it's an error */ |
| | 7393 | if (idx > matchList.length()) |
| | 7394 | { |
| | 7395 | /* note the problem */ |
| | 7396 | results.noteOrdinalOutOfRange(ordTok); |
| | 7397 | |
| | 7398 | /* no results */ |
| | 7399 | return []; |
| | 7400 | } |
| | 7401 | |
| | 7402 | /* return the selected item as a one-item list */ |
| | 7403 | return matchList.sublist(idx, 1); |
| | 7404 | } |
| | 7405 | ; |
| | 7406 | |
| | 7407 | /* |
| | 7408 | * A disambig vocab production is the base class for disambiguation |
| | 7409 | * phrases that involve vocabulary words. |
| | 7410 | */ |
| | 7411 | class DisambigVocabProd: DisambigProd |
| | 7412 | ; |
| | 7413 | |
| | 7414 | /* |
| | 7415 | * A disambig list item consists of: |
| | 7416 | * |
| | 7417 | *. first/second/etc |
| | 7418 | *. the first/second/etc |
| | 7419 | *. first one/second one/etc |
| | 7420 | *. the first one/the second one/etc |
| | 7421 | *. <compound noun phrase> |
| | 7422 | *. possessive |
| | 7423 | */ |
| | 7424 | |
| | 7425 | grammar disambigListItem(ordinal): |
| | 7426 | ordinalWord->ord_ |
| | 7427 | | ordinalWord->ord_ 'one' |
| | 7428 | | 'the' ordinalWord->ord_ |
| | 7429 | | 'the' ordinalWord->ord_ 'one' |
| | 7430 | : DisambigOrdProd |
| | 7431 | ; |
| | 7432 | |
| | 7433 | grammar disambigListItem(noun): |
| | 7434 | completeNounPhraseWithoutAll->np_ |
| | 7435 | | terminalNounPhrase->np_ |
| | 7436 | : DisambigVocabProd |
| | 7437 | resolveNouns(resolver, results) |
| | 7438 | { |
| | 7439 | /* get the matches for the underlying noun phrase */ |
| | 7440 | local lst = np_.resolveNouns(resolver, results); |
| | 7441 | |
| | 7442 | /* note the matches */ |
| | 7443 | results.noteMatches(lst); |
| | 7444 | |
| | 7445 | /* return the match list */ |
| | 7446 | return lst; |
| | 7447 | } |
| | 7448 | ; |
| | 7449 | |
| | 7450 | grammar disambigListItem(plural): |
| | 7451 | pluralPhrase->np_ |
| | 7452 | : DisambigVocabProd |
| | 7453 | resolveNouns(resolver, results) |
| | 7454 | { |
| | 7455 | local lst; |
| | 7456 | |
| | 7457 | /* |
| | 7458 | * get the underlying match list; since we explicitly have a |
| | 7459 | * plural, the result doesn't need to be unique, so simply |
| | 7460 | * return everything we find |
| | 7461 | */ |
| | 7462 | lst = np_.resolveNouns(resolver, results); |
| | 7463 | |
| | 7464 | /* |
| | 7465 | * if we didn't get anything, it's an error; otherwise, take |
| | 7466 | * everything, since we explicitly wanted a plural usage |
| | 7467 | */ |
| | 7468 | if (lst.length() == 0) |
| | 7469 | results.noMatch(resolver.getAction(), np_.getOrigText()); |
| | 7470 | else |
| | 7471 | results.noteMatches(lst); |
| | 7472 | |
| | 7473 | /* return the list */ |
| | 7474 | return lst; |
| | 7475 | } |
| | 7476 | ; |
| | 7477 | |
| | 7478 | grammar disambigListItem(possessive): possessiveNounPhrase->poss_ |
| | 7479 | : DisambigPossessiveProd |
| | 7480 | ; |
| | 7481 | |
| | 7482 | /* |
| | 7483 | * A disambig ordinal list consists of two or more ordinal words |
| | 7484 | * separated by noun phrase conjunctions. Note that there is a minimum |
| | 7485 | * of two entries in the list. |
| | 7486 | */ |
| | 7487 | grammar disambigOrdinalList(tail): |
| | 7488 | ordinalWord->ord1_ ('and' | ',') ordinalWord->ord2_ : DisambigOrdProd |
| | 7489 | resolveNouns(resolver, results) |
| | 7490 | { |
| | 7491 | /* note the pair of ordinal matches */ |
| | 7492 | results.noteDisambigOrdinal(); |
| | 7493 | results.noteDisambigOrdinal(); |
| | 7494 | |
| | 7495 | /* combine the selections of our two ordinals */ |
| | 7496 | return selectByOrdinal(ord1_, resolver, results) |
| | 7497 | + selectByOrdinal(ord2_, resolver, results); |
| | 7498 | } |
| | 7499 | ; |
| | 7500 | |
| | 7501 | grammar disambigOrdinalList(head): |
| | 7502 | ordinalWord->ord_ ('and' | ',') disambigOrdinalList->lst_ |
| | 7503 | : DisambigOrdProd |
| | 7504 | resolveNouns(resolver, results) |
| | 7505 | { |
| | 7506 | /* note the ordinal match */ |
| | 7507 | results.noteDisambigOrdinal(); |
| | 7508 | |
| | 7509 | /* combine the selections of our ordinal and the sublist */ |
| | 7510 | return selectByOrdinal(ord_, resolver, results) |
| | 7511 | + lst_.resolveNouns(resolver, results); |
| | 7512 | } |
| | 7513 | ; |
| | 7514 | |
| | 7515 | |
| | 7516 | /* ------------------------------------------------------------------------ */ |
| | 7517 | /* |
| | 7518 | * Ordinal words. We define a limited set of these, since we only use |
| | 7519 | * them in a few special contexts where it would be unreasonable to need |
| | 7520 | * even as many as define here. |
| | 7521 | */ |
| | 7522 | #define defOrdinal(str, val) object ordinalWord=#@str numval=val |
| | 7523 | |
| | 7524 | defOrdinal(former, 1); |
| | 7525 | defOrdinal(first, 1); |
| | 7526 | defOrdinal(second, 2); |
| | 7527 | defOrdinal(third, 3); |
| | 7528 | defOrdinal(fourth, 4); |
| | 7529 | defOrdinal(fifth, 5); |
| | 7530 | defOrdinal(sixth, 6); |
| | 7531 | defOrdinal(seventh, 7); |
| | 7532 | defOrdinal(eighth, 8); |
| | 7533 | defOrdinal(ninth, 9); |
| | 7534 | defOrdinal(tenth, 10); |
| | 7535 | defOrdinal(eleventh, 11); |
| | 7536 | defOrdinal(twelfth, 12); |
| | 7537 | defOrdinal(thirteenth, 13); |
| | 7538 | defOrdinal(fourteenth, 14); |
| | 7539 | defOrdinal(fifteenth, 15); |
| | 7540 | defOrdinal(sixteenth, 16); |
| | 7541 | defOrdinal(seventeenth, 17); |
| | 7542 | defOrdinal(eighteenth, 18); |
| | 7543 | defOrdinal(nineteenth, 19); |
| | 7544 | defOrdinal(twentieth, 20); |
| | 7545 | defOrdinal(1st, 1); |
| | 7546 | defOrdinal(2nd, 2); |
| | 7547 | defOrdinal(3rd, 3); |
| | 7548 | defOrdinal(4th, 4); |
| | 7549 | defOrdinal(5th, 5); |
| | 7550 | defOrdinal(6th, 6); |
| | 7551 | defOrdinal(7th, 7); |
| | 7552 | defOrdinal(8th, 8); |
| | 7553 | defOrdinal(9th, 9); |
| | 7554 | defOrdinal(10th, 10); |
| | 7555 | defOrdinal(11th, 11); |
| | 7556 | defOrdinal(12th, 12); |
| | 7557 | defOrdinal(13th, 13); |
| | 7558 | defOrdinal(14th, 14); |
| | 7559 | defOrdinal(15th, 15); |
| | 7560 | defOrdinal(16th, 16); |
| | 7561 | defOrdinal(17th, 17); |
| | 7562 | defOrdinal(18th, 18); |
| | 7563 | defOrdinal(19th, 19); |
| | 7564 | defOrdinal(20th, 20); |
| | 7565 | |
| | 7566 | /* |
| | 7567 | * the special 'last' ordinal - the value -1 is special to indicate the |
| | 7568 | * last item in a list |
| | 7569 | */ |
| | 7570 | defOrdinal(last, -1); |
| | 7571 | defOrdinal(latter, -1); |
| | 7572 | |
| | 7573 | |
| | 7574 | /* ------------------------------------------------------------------------ */ |
| | 7575 | /* |
| | 7576 | * A numeric production. These can be either spelled-out numbers (such |
| | 7577 | * as "fifty-seven") or numbers entered in digit form (as in "57"). |
| | 7578 | */ |
| | 7579 | class NumberProd: BasicProd |
| | 7580 | /* get the numeric (integer) value */ |
| | 7581 | getval() { return 0; } |
| | 7582 | |
| | 7583 | /* |
| | 7584 | * Get the string version of the numeric value. This should return |
| | 7585 | * a string, but the string should be in digit form. If the |
| | 7586 | * original entry was in digit form, then the original entry should |
| | 7587 | * be returned; otherwise, a string should be constructed from the |
| | 7588 | * integer value. By default, we'll do the latter. |
| | 7589 | */ |
| | 7590 | getStrVal() { return toString(getval()); } |
| | 7591 | ; |
| | 7592 | |
| | 7593 | /* |
| | 7594 | * A quantifier is simply a number, entered with numerals or spelled out. |
| | 7595 | */ |
| | 7596 | grammar numberPhrase(digits): tokInt->num_ : NumberProd |
| | 7597 | /* get the numeric value */ |
| | 7598 | getval() { return toInteger(num_); } |
| | 7599 | |
| | 7600 | /* |
| | 7601 | * get the string version of the numeric value - since the token was |
| | 7602 | * an integer to start with, return the actual integer value |
| | 7603 | */ |
| | 7604 | getStrVal() { return num_; } |
| | 7605 | ; |
| | 7606 | |
| | 7607 | grammar numberPhrase(spelled): spelledNumber->num_ : NumberProd |
| | 7608 | /* get the numeric value */ |
| | 7609 | getval() { return num_.getval(); } |
| | 7610 | ; |
| | 7611 | |
| | 7612 | /* |
| | 7613 | * A number phrase preceded by a pound sign. We distinguish this kind of |
| | 7614 | * number phrase from plain numbers, since this kind has a somewhat more |
| | 7615 | * limited set of valid contexts. |
| | 7616 | */ |
| | 7617 | grammar poundNumberPhrase(main): tokPoundInt->num_ : NumberProd |
| | 7618 | /* |
| | 7619 | * get the numeric value - a tokPoundInt token has a pound sign |
| | 7620 | * followed by digits, so the numeric value is the value of the |
| | 7621 | * substring following the '#' sign |
| | 7622 | */ |
| | 7623 | getval() { return toInteger(num_.substr(2)); } |
| | 7624 | |
| | 7625 | /* |
| | 7626 | * get the string value - we have a number token following the '#', |
| | 7627 | * so simply return the part after the '#' |
| | 7628 | */ |
| | 7629 | getStrVal() { return num_.substr(2); } |
| | 7630 | ; |
| | 7631 | |
| | 7632 | |
| | 7633 | /* |
| | 7634 | * Number literals. We'll define a set of special objects for numbers: |
| | 7635 | * each object defines a number and a value for the number. |
| | 7636 | */ |
| | 7637 | #define defDigit(num, val) object digitWord=#@num numval=val |
| | 7638 | #define defTeen(num, val) object teenWord=#@num numval=val |
| | 7639 | #define defTens(num, val) object tensWord=#@num numval=val |
| | 7640 | |
| | 7641 | defDigit(one, 1); |
| | 7642 | defDigit(two, 2); |
| | 7643 | defDigit(three, 3); |
| | 7644 | defDigit(four, 4); |
| | 7645 | defDigit(five, 5); |
| | 7646 | defDigit(six, 6); |
| | 7647 | defDigit(seven, 7); |
| | 7648 | defDigit(eight, 8); |
| | 7649 | defDigit(nine, 9); |
| | 7650 | defTeen(ten, 10); |
| | 7651 | defTeen(eleven, 11); |
| | 7652 | defTeen(twelve, 12); |
| | 7653 | defTeen(thirteen, 13); |
| | 7654 | defTeen(fourteen, 14); |
| | 7655 | defTeen(fifteen, 15); |
| | 7656 | defTeen(sixteen, 16); |
| | 7657 | defTeen(seventeen, 17); |
| | 7658 | defTeen(eighteen, 18); |
| | 7659 | defTeen(nineteen, 19); |
| | 7660 | defTens(twenty, 20); |
| | 7661 | defTens(thirty, 30); |
| | 7662 | defTens(forty, 40); |
| | 7663 | defTens(fifty, 50); |
| | 7664 | defTens(sixty, 60); |
| | 7665 | defTens(seventy, 70); |
| | 7666 | defTens(eighty, 80); |
| | 7667 | defTens(ninety, 90); |
| | 7668 | |
| | 7669 | grammar spelledSmallNumber(digit): digitWord->num_ : NumberProd |
| | 7670 | getval() |
| | 7671 | { |
| | 7672 | /* |
| | 7673 | * Look up the units word - there should be only one in the |
| | 7674 | * dictionary, since these are our special words. Return the |
| | 7675 | * object's numeric value property 'numval', which gives the |
| | 7676 | * number for the name. |
| | 7677 | */ |
| | 7678 | return cmdDict.findWord(num_, &digitWord)[1].numval; |
| | 7679 | } |
| | 7680 | ; |
| | 7681 | |
| | 7682 | grammar spelledSmallNumber(teen): teenWord->num_ : NumberProd |
| | 7683 | getval() |
| | 7684 | { |
| | 7685 | /* look up the dictionary word for the number */ |
| | 7686 | return cmdDict.findWord(num_, &teenWord)[1].numval; |
| | 7687 | } |
| | 7688 | ; |
| | 7689 | |
| | 7690 | grammar spelledSmallNumber(tens): tensWord->num_ : NumberProd |
| | 7691 | getval() |
| | 7692 | { |
| | 7693 | /* look up the dictionary word for the number */ |
| | 7694 | return cmdDict.findWord(num_, &tensWord)[1].numval; |
| | 7695 | } |
| | 7696 | ; |
| | 7697 | |
| | 7698 | grammar spelledSmallNumber(tensAndUnits): |
| | 7699 | tensWord->tens_ '-'->sep_ digitWord->units_ |
| | 7700 | | tensWord->tens_ digitWord->units_ |
| | 7701 | : NumberProd |
| | 7702 | getval() |
| | 7703 | { |
| | 7704 | /* look up the words, and add up the values */ |
| | 7705 | return cmdDict.findWord(tens_, &tensWord)[1].numval |
| | 7706 | + cmdDict.findWord(units_, &digitWord)[1].numval; |
| | 7707 | } |
| | 7708 | ; |
| | 7709 | |
| | 7710 | grammar spelledSmallNumber(zero): 'zero' : NumberProd |
| | 7711 | getval() { return 0; } |
| | 7712 | ; |
| | 7713 | |
| | 7714 | grammar spelledHundred(small): spelledSmallNumber->num_ : NumberProd |
| | 7715 | getval() { return num_.getval(); } |
| | 7716 | ; |
| | 7717 | |
| | 7718 | grammar spelledHundred(hundreds): spelledSmallNumber->hun_ 'hundred' |
| | 7719 | : NumberProd |
| | 7720 | getval() { return hun_.getval() * 100; } |
| | 7721 | ; |
| | 7722 | |
| | 7723 | grammar spelledHundred(hundredsPlus): |
| | 7724 | spelledSmallNumber->hun_ 'hundred' spelledSmallNumber->num_ |
| | 7725 | | spelledSmallNumber->hun_ 'hundred' 'and'->and_ spelledSmallNumber->num_ |
| | 7726 | : NumberProd |
| | 7727 | getval() { return hun_.getval() * 100 + num_.getval(); } |
| | 7728 | ; |
| | 7729 | |
| | 7730 | grammar spelledHundred(aHundred): 'a' 'hundred' : NumberProd |
| | 7731 | getval() { return 100; } |
| | 7732 | ; |
| | 7733 | |
| | 7734 | grammar spelledHundred(aHundredPlus): |
| | 7735 | 'a' 'hundred' 'and' spelledSmallNumber->num_ |
| | 7736 | : NumberProd |
| | 7737 | getval() { return 100 + num_.getval(); } |
| | 7738 | ; |
| | 7739 | |
| | 7740 | grammar spelledThousand(thousands): spelledHundred->thou_ 'thousand' |
| | 7741 | : NumberProd |
| | 7742 | getval() { return thou_.getval() * 1000; } |
| | 7743 | ; |
| | 7744 | |
| | 7745 | grammar spelledThousand(thousandsPlus): |
| | 7746 | spelledHundred->thou_ 'thousand' spelledHundred->num_ |
| | 7747 | : NumberProd |
| | 7748 | getval() { return thou_.getval() * 1000 + num_.getval(); } |
| | 7749 | ; |
| | 7750 | |
| | 7751 | grammar spelledThousand(thousandsAndSmall): |
| | 7752 | spelledHundred->thou_ 'thousand' 'and' spelledSmallNumber->num_ |
| | 7753 | : NumberProd |
| | 7754 | getval() { return thou_.getval() * 1000 + num_.getval(); } |
| | 7755 | ; |
| | 7756 | |
| | 7757 | grammar spelledThousand(aThousand): 'a' 'thousand' : NumberProd |
| | 7758 | getval() { return 1000; } |
| | 7759 | ; |
| | 7760 | |
| | 7761 | grammar spelledThousand(aThousandAndSmall): |
| | 7762 | 'a' 'thousand' 'and' spelledSmallNumber->num_ |
| | 7763 | : NumberProd |
| | 7764 | getval() { return 1000 + num_.getval(); } |
| | 7765 | ; |
| | 7766 | |
| | 7767 | grammar spelledMillion(millions): spelledHundred->mil_ 'million': NumberProd |
| | 7768 | getval() { return mil_.getval() * 1000000; } |
| | 7769 | ; |
| | 7770 | |
| | 7771 | grammar spelledMillion(millionsPlus): |
| | 7772 | spelledHundred->mil_ 'million' |
| | 7773 | (spelledThousand->nxt_ | spelledHundred->nxt_) |
| | 7774 | : NumberProd |
| | 7775 | getval() { return mil_.getval() * 1000000 + nxt_.getval(); } |
| | 7776 | ; |
| | 7777 | |
| | 7778 | grammar spelledMillion(aMillion): 'a' 'million' : NumberProd |
| | 7779 | getval() { return 1000000; } |
| | 7780 | ; |
| | 7781 | |
| | 7782 | grammar spelledMillion(aMillionAndSmall): |
| | 7783 | 'a' 'million' 'and' spelledSmallNumber->num_ |
| | 7784 | : NumberProd |
| | 7785 | getval() { return 1000000 + num_.getval(); } |
| | 7786 | ; |
| | 7787 | |
| | 7788 | grammar spelledMillion(millionsAndSmall): |
| | 7789 | spelledHundred->mil_ 'million' 'and' spelledSmallNumber->num_ |
| | 7790 | : NumberProd |
| | 7791 | getval() { return mil_.getval() * 1000000 + num_.getval(); } |
| | 7792 | ; |
| | 7793 | |
| | 7794 | grammar spelledNumber(main): |
| | 7795 | spelledHundred->num_ |
| | 7796 | | spelledThousand->num_ |
| | 7797 | | spelledMillion->num_ |
| | 7798 | : NumberProd |
| | 7799 | getval() { return num_.getval(); } |
| | 7800 | ; |
| | 7801 | |
| | 7802 | |
| | 7803 | /* ------------------------------------------------------------------------ */ |
| | 7804 | /* |
| | 7805 | * "OOPS" command syntax |
| | 7806 | */ |
| | 7807 | grammar oopsCommand(main): |
| | 7808 | oopsPhrase->oops_ | oopsPhrase->oops_ '.' : BasicProd |
| | 7809 | getNewTokens() { return oops_.getNewTokens(); } |
| | 7810 | ; |
| | 7811 | |
| | 7812 | grammar oopsPhrase(main): |
| | 7813 | 'oops' miscWordList->lst_ |
| | 7814 | | 'oops' ',' miscWordList->lst_ |
| | 7815 | | 'o' miscWordList->lst_ |
| | 7816 | | 'o' ',' miscWordList->lst_ |
| | 7817 | : BasicProd |
| | 7818 | getNewTokens() { return lst_.getOrigTokenList(); } |
| | 7819 | ; |
| | 7820 | |
| | 7821 | grammar oopsPhrase(missing): |
| | 7822 | 'oops' | 'o' |
| | 7823 | : BasicProd |
| | 7824 | getNewTokens() { return nil; } |
| | 7825 | ; |
| | 7826 | |
| | 7827 | /* ------------------------------------------------------------------------ */ |
| | 7828 | /* |
| | 7829 | * finishGame options. We provide descriptions and keywords for the |
| | 7830 | * option objects here, because these are inherently language-specific. |
| | 7831 | * |
| | 7832 | * Note that we provide hyperlinks for our descriptions when possible. |
| | 7833 | * When we're in plain text mode, we can't show links, so we'll instead |
| | 7834 | * show an alternate form with the single-letter response highlighted in |
| | 7835 | * the text. We don't highlight the single-letter response in the |
| | 7836 | * hyperlinked version because (a) if the user wants a shortcut, they can |
| | 7837 | * simply click the hyperlink, and (b) most UI's that show hyperlinks |
| | 7838 | * show a distinctive appearance for the hyperlink itself, so adding even |
| | 7839 | * more highlighting within the hyperlink starts to look awfully busy. |
| | 7840 | */ |
| | 7841 | modify finishOptionQuit |
| | 7842 | desc = "<<aHrefAlt('quit', 'QUIT', '<b>Q</b>UIT', 'Leave the story')>>" |
| | 7843 | responseKeyword = 'quit' |
| | 7844 | responseChar = 'q' |
| | 7845 | ; |
| | 7846 | |
| | 7847 | modify finishOptionRestore |
| | 7848 | desc = "<<aHrefAlt('restore', 'RESTORE', '<b>R</b>ESTORE', |
| | 7849 | 'Restore a saved position')>> a saved position" |
| | 7850 | responseKeyword = 'restore' |
| | 7851 | responseChar = 'r' |
| | 7852 | ; |
| | 7853 | |
| | 7854 | modify finishOptionRestart |
| | 7855 | desc = "<<aHrefAlt('restart', 'RESTART', 'RE<b>S</b>TART', |
| | 7856 | 'Start the story over from the beginning')>> the story" |
| | 7857 | responseKeyword = 'restart' |
| | 7858 | responseChar = 's' |
| | 7859 | ; |
| | 7860 | |
| | 7861 | modify finishOptionUndo |
| | 7862 | desc = "<<aHrefAlt('undo', 'UNDO', '<b>U</b>NDO', |
| | 7863 | 'Undo the last move')>> the last move" |
| | 7864 | responseKeyword = 'undo' |
| | 7865 | responseChar = 'u' |
| | 7866 | ; |
| | 7867 | |
| | 7868 | modify finishOptionCredits |
| | 7869 | desc = "see the <<aHrefAlt('credits', 'CREDITS', '<b>C</b>REDITS', |
| | 7870 | 'Show credits')>>" |
| | 7871 | responseKeyword = 'credits' |
| | 7872 | responseChar = 'c' |
| | 7873 | ; |
| | 7874 | |
| | 7875 | modify finishOptionFullScore |
| | 7876 | desc = "see your <<aHrefAlt('full score', 'FULL SCORE', |
| | 7877 | '<b>F</b>ULL SCORE', 'Show full score')>>" |
| | 7878 | responseKeyword = 'full score' |
| | 7879 | responseChar = 'f' |
| | 7880 | ; |
| | 7881 | |
| | 7882 | modify finishOptionAmusing |
| | 7883 | desc = "see some <<aHrefAlt('amusing', 'AMUSING', '<b>A</b>MUSING', |
| | 7884 | 'Show some amusing things to try')>> things to try" |
| | 7885 | responseKeyword = 'amusing' |
| | 7886 | responseChar = 'a' |
| | 7887 | ; |
| | 7888 | |
| | 7889 | modify restoreOptionStartOver |
| | 7890 | desc = "<<aHrefAlt('start', 'START', '<b>S</b>TART', |
| | 7891 | 'Start from the beginning')>> the game from the beginning" |
| | 7892 | responseKeyword = 'start' |
| | 7893 | responseChar = 's' |
| | 7894 | ; |
| | 7895 | |
| | 7896 | modify restoreOptionRestoreAnother |
| | 7897 | desc = "<<aHrefAlt('restore', 'RESTORE', '<b>R</b>ESTORE', |
| | 7898 | 'Restore a saved position')>> a different saved position" |
| | 7899 | ; |
| | 7900 | |
| | 7901 | /* ------------------------------------------------------------------------ */ |
| | 7902 | /* |
| | 7903 | * Context for Action.getVerbPhrase(). This keeps track of pronoun |
| | 7904 | * antecedents in cases where we're stringing together a series of verb |
| | 7905 | * phrases. |
| | 7906 | */ |
| | 7907 | class GetVerbPhraseContext: object |
| | 7908 | /* get the objective form of an object, using a pronoun as appropriate */ |
| | 7909 | objNameObj(obj) |
| | 7910 | { |
| | 7911 | /* |
| | 7912 | * if it's the pronoun antecedent, use the pronoun form; |
| | 7913 | * otherwise, use the full name |
| | 7914 | */ |
| | 7915 | if (obj == pronounObj) |
| | 7916 | return obj.itObj; |
| | 7917 | else |
| | 7918 | return obj.theNameObj; |
| | 7919 | } |
| | 7920 | |
| | 7921 | /* are we showing the given object pronomially? */ |
| | 7922 | isObjPronoun(obj) { return (obj == pronounObj); } |
| | 7923 | |
| | 7924 | /* set the pronoun antecedent */ |
| | 7925 | setPronounObj(obj) { pronounObj = obj; } |
| | 7926 | |
| | 7927 | /* the pronoun antecedent */ |
| | 7928 | pronounObj = nil |
| | 7929 | ; |
| | 7930 | |
| | 7931 | /* |
| | 7932 | * Default getVerbPhrase context. This can be used when no other context |
| | 7933 | * is needed. This context instance has no state - it doesn't track any |
| | 7934 | * antecedents. |
| | 7935 | */ |
| | 7936 | defaultGetVerbPhraseContext: GetVerbPhraseContext |
| | 7937 | /* we don't remember any antecedents */ |
| | 7938 | setPronounObj(obj) { } |
| | 7939 | ; |
| | 7940 | |
| | 7941 | /* ------------------------------------------------------------------------ */ |
| | 7942 | /* |
| | 7943 | * Implicit action context. This is passed to the message methods that |
| | 7944 | * generate implicit action announcements, to indicate the context in |
| | 7945 | * which the message is to be used. |
| | 7946 | */ |
| | 7947 | class ImplicitAnnouncementContext: object |
| | 7948 | /* |
| | 7949 | * Should we use the infinitive form of the verb, or the participle |
| | 7950 | * form for generating the announcement? By default, use use the |
| | 7951 | * participle form: "(first OPENING THE BOX)". |
| | 7952 | */ |
| | 7953 | useInfPhrase = nil |
| | 7954 | |
| | 7955 | /* is this message going in a list? */ |
| | 7956 | isInList = nil |
| | 7957 | |
| | 7958 | /* |
| | 7959 | * Are we in a sublist of 'just trying' or 'just asking' messages? |
| | 7960 | * (We can only have sublist groupings one level deep, so we don't |
| | 7961 | * need to worry about what kind of sublist we're in.) |
| | 7962 | */ |
| | 7963 | isInSublist = nil |
| | 7964 | |
| | 7965 | /* our getVerbPhrase context - by default, don't use one */ |
| | 7966 | getVerbCtx = nil |
| | 7967 | |
| | 7968 | /* generate the announcement message given the action description */ |
| | 7969 | buildImplicitAnnouncement(txt) |
| | 7970 | { |
| | 7971 | /* if we're not in a list, make it a full, stand-alone message */ |
| | 7972 | if (!isInList) |
| | 7973 | txt = '<./p0>\n<.assume>first ' + txt + '<./assume>\n'; |
| | 7974 | |
| | 7975 | /* return the result */ |
| | 7976 | return txt; |
| | 7977 | } |
| | 7978 | ; |
| | 7979 | |
| | 7980 | /* the standard implicit action announcement context */ |
| | 7981 | standardImpCtx: ImplicitAnnouncementContext; |
| | 7982 | |
| | 7983 | /* the "just trying" implicit action announcement context */ |
| | 7984 | tryingImpCtx: ImplicitAnnouncementContext |
| | 7985 | /* |
| | 7986 | * The action was merely attempted, so use the infinitive phrase in |
| | 7987 | * the announcement: "(first trying to OPEN THE BOX)". |
| | 7988 | */ |
| | 7989 | useInfPhrase = true |
| | 7990 | |
| | 7991 | /* build the announcement */ |
| | 7992 | buildImplicitAnnouncement(txt) |
| | 7993 | { |
| | 7994 | /* |
| | 7995 | * If we're not in a list of 'trying' messages, add the 'trying' |
| | 7996 | * prefix message to the action description. This isn't |
| | 7997 | * necessary if we're in a 'trying' list, since the list itself |
| | 7998 | * will have the 'trying' part. |
| | 7999 | */ |
| | 8000 | if (!isInSublist) |
| | 8001 | txt = 'trying to ' + txt; |
| | 8002 | |
| | 8003 | /* now build the message into the full text as usual */ |
| | 8004 | return inherited(txt); |
| | 8005 | } |
| | 8006 | ; |
| | 8007 | |
| | 8008 | /* |
| | 8009 | * The "asking question" implicit action announcement context. By |
| | 8010 | * default, we generate the message exactly the same way we do for the |
| | 8011 | * 'trying' case. |
| | 8012 | */ |
| | 8013 | askingImpCtx: tryingImpCtx; |
| | 8014 | |
| | 8015 | /* |
| | 8016 | * A class for messages appearing in a list. Within a list, we want to |
| | 8017 | * keep track of the last direct object, so that we can refer to it with |
| | 8018 | * a pronoun later in the list. |
| | 8019 | */ |
| | 8020 | class ListImpCtx: ImplicitAnnouncementContext, GetVerbPhraseContext |
| | 8021 | /* |
| | 8022 | * Set the appropriate base context for the given implicit action |
| | 8023 | * announcement report (an ImplicitActionAnnouncement object). |
| | 8024 | */ |
| | 8025 | setBaseCtx(ctx) |
| | 8026 | { |
| | 8027 | /* |
| | 8028 | * if this is a failed attempt, use a 'trying' context; |
| | 8029 | * otherwise, use a standard context |
| | 8030 | */ |
| | 8031 | if (ctx.justTrying) |
| | 8032 | baseCtx = tryingImpCtx; |
| | 8033 | else if (ctx.justAsking) |
| | 8034 | baseCtx = askingImpCtx; |
| | 8035 | else |
| | 8036 | baseCtx = standardImpCtx; |
| | 8037 | } |
| | 8038 | |
| | 8039 | /* we're in a list */ |
| | 8040 | isInList = true |
| | 8041 | |
| | 8042 | /* we are our own getVerbPhrase context */ |
| | 8043 | getVerbCtx = (self) |
| | 8044 | |
| | 8045 | /* delegate the phrase format to our underlying announcement context */ |
| | 8046 | useInfPhrase = (delegated baseCtx) |
| | 8047 | |
| | 8048 | /* build the announcement using our underlying context */ |
| | 8049 | buildImplicitAnnouncement(txt) { return delegated baseCtx(txt); } |
| | 8050 | |
| | 8051 | /* our base context - we delegate some unoverridden behavior to this */ |
| | 8052 | baseCtx = nil |
| | 8053 | ; |
| | 8054 | |
| | 8055 | /* ------------------------------------------------------------------------ */ |
| | 8056 | /* |
| | 8057 | * Language-specific Action modifications. |
| | 8058 | */ |
| | 8059 | modify Action |
| | 8060 | /* |
| | 8061 | * In the English grammar, all 'predicate' grammar definitions |
| | 8062 | * (which are usually made via the VerbRule macro) are associated |
| | 8063 | * with Action match tree objects; in fact, each 'predicate' grammar |
| | 8064 | * match tree is the specific Action subclass associated with the |
| | 8065 | * grammar for the predicate. This means that the Action associated |
| | 8066 | * with a grammar match is simply the grammar match object itself. |
| | 8067 | * Hence, we can resolve the action for a 'predicate' match simply |
| | 8068 | * by returning the match itself: it is the Action as well as the |
| | 8069 | * grammar match. |
| | 8070 | * |
| | 8071 | * This approach ('grammar predicate' matches are based on Action |
| | 8072 | * subclasses) works well for languages like English that encode the |
| | 8073 | * role of each phrase in the word order of the sentence. |
| | 8074 | * |
| | 8075 | * Languages that encode phrase roles using case markers or other |
| | 8076 | * devices tend to be freer with word order. As a result, |
| | 8077 | * 'predicate' grammars for such languages should generally not |
| | 8078 | * attempt to capture all possible word orderings for a given |
| | 8079 | * action, but should instead take the complementary approach of |
| | 8080 | * capturing the possible overall sentence structures independently |
| | 8081 | * of verb phrases, and plug in a verb phrase as a component, just |
| | 8082 | * like noun phrases plug into the English grammar. In these cases, |
| | 8083 | * the match objects will NOT be Action subclasses; the Action |
| | 8084 | * objects will instead be buried down deeper in the match tree. |
| | 8085 | * Hence, resolveAction() must be defined on whatever class is used |
| | 8086 | * to construct 'predicate' grammar matches, instead of on Action, |
| | 8087 | * since Action will not be a 'predicate' match. |
| | 8088 | */ |
| | 8089 | resolveAction(issuingActor, targetActor) { return self; } |
| | 8090 | |
| | 8091 | /* |
| | 8092 | * Return the interrogative pronoun for a missing object in one of |
| | 8093 | * our object roles. In most cases, this is simply "what", but for |
| | 8094 | * some actions, "whom" is more appropriate (for example, the direct |
| | 8095 | * object of "ask" is implicitly a person, so "whom" is most |
| | 8096 | * appropriate for this role). |
| | 8097 | */ |
| | 8098 | whatObj(which) |
| | 8099 | { |
| | 8100 | /* intransitive verbs have no objects, so there's nothing to show */ |
| | 8101 | } |
| | 8102 | |
| | 8103 | /* |
| | 8104 | * Translate an interrogative word for whatObj. If the word is |
| | 8105 | * 'whom', translate to the library message for 'whom'; this allows |
| | 8106 | * authors to use 'who' rather than 'whom' as the objective form of |
| | 8107 | * 'who', which sounds less stuffy to many people. |
| | 8108 | */ |
| | 8109 | whatTranslate(txt) |
| | 8110 | { |
| | 8111 | /* |
| | 8112 | * if it's 'whom', translate to the library message for 'whom'; |
| | 8113 | * otherwise, just show the word as is |
| | 8114 | */ |
| | 8115 | return (txt == 'whom' ? gLibMessages.whomPronoun : txt); |
| | 8116 | } |
| | 8117 | |
| | 8118 | /* |
| | 8119 | * Return a string with the appropriate pronoun (objective form) for |
| | 8120 | * a list of object matches, with the given resolved cardinality. |
| | 8121 | * This list is a list of ResolveInfo objects. |
| | 8122 | */ |
| | 8123 | objListPronoun(objList) |
| | 8124 | { |
| | 8125 | local himCnt, herCnt, themCnt; |
| | 8126 | local FirstPersonCnt, SecondPersonCnt; |
| | 8127 | local resolvedNumber; |
| | 8128 | |
| | 8129 | /* if there's no object list at all, just use 'it' */ |
| | 8130 | if (objList == nil || objList == []) |
| | 8131 | return 'it'; |
| | 8132 | |
| | 8133 | /* note the number of objects in the resolved list */ |
| | 8134 | resolvedNumber = objList.length(); |
| | 8135 | |
| | 8136 | /* |
| | 8137 | * In the tentatively resolved object list, we might have hidden |
| | 8138 | * away ambiguous matches. Expand those back into the list so |
| | 8139 | * we have the full list of in-scope matches. |
| | 8140 | */ |
| | 8141 | foreach (local cur in objList) |
| | 8142 | { |
| | 8143 | /* |
| | 8144 | * if this one has hidden ambiguous objects, add the hidden |
| | 8145 | * objects back into our list |
| | 8146 | */ |
| | 8147 | if (cur.extraObjects != nil) |
| | 8148 | objList += cur.extraObjects; |
| | 8149 | } |
| | 8150 | |
| | 8151 | /* |
| | 8152 | * if the desired cardinality is plural and the object list has |
| | 8153 | * more than one object, simply say 'them' |
| | 8154 | */ |
| | 8155 | if (objList.length() > 1 && resolvedNumber > 1) |
| | 8156 | return 'them'; |
| | 8157 | |
| | 8158 | /* |
| | 8159 | * singular cardinality - count masculine and feminine objects, |
| | 8160 | * and count the referral persons |
| | 8161 | */ |
| | 8162 | himCnt = herCnt = themCnt = 0; |
| | 8163 | FirstPersonCnt = SecondPersonCnt = 0; |
| | 8164 | foreach (local cur in objList) |
| | 8165 | { |
| | 8166 | /* if it's masculine, count it */ |
| | 8167 | if (cur.obj_.isHim) |
| | 8168 | ++himCnt; |
| | 8169 | |
| | 8170 | /* if it's feminine, count it */ |
| | 8171 | if (cur.obj_.isHer) |
| | 8172 | ++herCnt; |
| | 8173 | |
| | 8174 | /* if it has plural usage, count it */ |
| | 8175 | if (cur.obj_.isPlural) |
| | 8176 | ++themCnt; |
| | 8177 | |
| | 8178 | /* if it's first person usage, count it */ |
| | 8179 | if (cur.obj_.referralPerson == FirstPerson) |
| | 8180 | ++FirstPersonCnt; |
| | 8181 | |
| | 8182 | /* if it's second person usage, count it */ |
| | 8183 | if (cur.obj_.referralPerson == SecondPerson) |
| | 8184 | ++SecondPersonCnt; |
| | 8185 | } |
| | 8186 | |
| | 8187 | /* |
| | 8188 | * if they all have plural usage, show "them"; if they're all of |
| | 8189 | * one gender, show "him" or "her" as appropriate; if they're |
| | 8190 | * all neuter, show "it"; otherwise, show "them" |
| | 8191 | */ |
| | 8192 | if (themCnt == objList.length()) |
| | 8193 | return 'them'; |
| | 8194 | else if (FirstPersonCnt == objList.length()) |
| | 8195 | return 'myself'; |
| | 8196 | else if (SecondPersonCnt == objList.length()) |
| | 8197 | return 'yourself'; |
| | 8198 | else if (himCnt == objList.length() && herCnt == 0) |
| | 8199 | return 'him'; |
| | 8200 | else if (herCnt == objList.length() && himCnt == 0) |
| | 8201 | return 'her'; |
| | 8202 | else if (herCnt == 0 && himCnt == 0) |
| | 8203 | return 'it'; |
| | 8204 | else |
| | 8205 | return 'them'; |
| | 8206 | } |
| | 8207 | |
| | 8208 | /* |
| | 8209 | * Announce a default object used with this action. |
| | 8210 | * |
| | 8211 | * 'resolvedAllObjects' indicates where we are in the command |
| | 8212 | * processing: this is true if we've already resolved all of the |
| | 8213 | * other objects in the command, nil if not. We use this |
| | 8214 | * information to get the phrasing right according to the situation. |
| | 8215 | */ |
| | 8216 | announceDefaultObject(obj, whichObj, resolvedAllObjects) |
| | 8217 | { |
| | 8218 | /* |
| | 8219 | * the basic action class takes no objects, so there can be no |
| | 8220 | * default announcement |
| | 8221 | */ |
| | 8222 | return ''; |
| | 8223 | } |
| | 8224 | |
| | 8225 | /* |
| | 8226 | * Announce all defaulted objects in the action. By default, we |
| | 8227 | * show nothing. |
| | 8228 | */ |
| | 8229 | announceAllDefaultObjects(allResolved) { } |
| | 8230 | |
| | 8231 | /* |
| | 8232 | * Return a phrase describing the action performed implicitly, as a |
| | 8233 | * participle phrase. 'ctx' is an ImplicitAnnouncementContext object |
| | 8234 | * describing the context in which we're generating the phrase. |
| | 8235 | * |
| | 8236 | * This comes in two forms: if the context indicates we're only |
| | 8237 | * attempting the action, we'll return an infinitive phrase ("open |
| | 8238 | * the box") for use in a larger participle phrase describing the |
| | 8239 | * attempt ("trying to..."). Otherwise, we'll be describing the |
| | 8240 | * action as actually having been performed, so we'll return a |
| | 8241 | * present participle phrase ("opening the box"). |
| | 8242 | */ |
| | 8243 | getImplicitPhrase(ctx) |
| | 8244 | { |
| | 8245 | /* |
| | 8246 | * Get the phrase. Use the infinitive or participle form, as |
| | 8247 | * indicated in the context. |
| | 8248 | */ |
| | 8249 | return getVerbPhrase(ctx.useInfPhrase, ctx.getVerbCtx); |
| | 8250 | } |
| | 8251 | |
| | 8252 | /* |
| | 8253 | * Get the infinitive form of the action. We are NOT to include the |
| | 8254 | * infinitive complementizer (i.e., "to") as part of the result, |
| | 8255 | * since the complementizer isn't used in all contexts in which we |
| | 8256 | * might want to use the infinitive; for example, we don't want a |
| | 8257 | * "to" in phrases involving an auxiliary verb, such as "he can open |
| | 8258 | * the box." |
| | 8259 | */ |
| | 8260 | getInfPhrase() |
| | 8261 | { |
| | 8262 | /* return the verb phrase in infinitive form */ |
| | 8263 | return getVerbPhrase(true, nil); |
| | 8264 | } |
| | 8265 | |
| | 8266 | /* |
| | 8267 | * Get the root infinitive form of our verb phrase as part of a |
| | 8268 | * question in which one of the verb's objects is the "unknown" of |
| | 8269 | * the interrogative. 'which' is one of the role markers |
| | 8270 | * (DirectObject, IndirectObject, etc), indicating which object is |
| | 8271 | * the subject of the interrogative. |
| | 8272 | * |
| | 8273 | * For example, for the verb UNLOCK <dobj> WITH <iobj>, if the |
| | 8274 | * unknown is the direct object, the phrase we'd return would be |
| | 8275 | * "unlock": this would plug into contexts such as "what do you want |
| | 8276 | * to unlock." If the indirect object is the unknown for the same |
| | 8277 | * verb, the phrase would be "unlock it with", which would plug in as |
| | 8278 | * "what do you want to unlock it with". |
| | 8279 | * |
| | 8280 | * Note that we are NOT to include the infinitive complementizer |
| | 8281 | * (i.e., "to") as part of the phrase we generate, since the |
| | 8282 | * complementizer isn't used in some contexts where the infinitive |
| | 8283 | * conjugation is needed (for example, "what should I <infinitive>"). |
| | 8284 | */ |
| | 8285 | getQuestionInf(which) |
| | 8286 | { |
| | 8287 | /* |
| | 8288 | * for a verb without objects, this is the same as the basic |
| | 8289 | * infinitive |
| | 8290 | */ |
| | 8291 | return getInfPhrase(); |
| | 8292 | } |
| | 8293 | |
| | 8294 | /* |
| | 8295 | * Get a string describing the full action in present participle |
| | 8296 | * form, using the current command objects: "taking the watch", |
| | 8297 | * "putting the book on the shelf" |
| | 8298 | */ |
| | 8299 | getParticiplePhrase() |
| | 8300 | { |
| | 8301 | /* return the verb phrase in participle form */ |
| | 8302 | return getVerbPhrase(nil, nil); |
| | 8303 | } |
| | 8304 | |
| | 8305 | /* |
| | 8306 | * Get the full verb phrase in either infinitive or participle |
| | 8307 | * format. This is a common handler for getInfinitivePhrase() and |
| | 8308 | * getParticiplePhrase(). |
| | 8309 | * |
| | 8310 | * 'ctx' is a GetVerbPhraseContext object, which lets us keep track |
| | 8311 | * of antecedents when we're stringing together multiple verb |
| | 8312 | * phrases. 'ctx' can be nil if the verb phrase is being used in |
| | 8313 | * isolation. |
| | 8314 | */ |
| | 8315 | getVerbPhrase(inf, ctx) |
| | 8316 | { |
| | 8317 | /* |
| | 8318 | * parse the verbPhrase into the parts before and after the |
| | 8319 | * slash, and any additional text following the slash part |
| | 8320 | */ |
| | 8321 | rexMatch('(.*)/(<alphanum|-|squote>+)(.*)', verbPhrase); |
| | 8322 | |
| | 8323 | /* return the appropriate parts */ |
| | 8324 | if (inf) |
| | 8325 | { |
| | 8326 | /* |
| | 8327 | * infinitive - we want the part before the slash, plus the |
| | 8328 | * extra prepositions (or whatever) after the switched part |
| | 8329 | */ |
| | 8330 | return rexGroup(1)[3] + rexGroup(3)[3]; |
| | 8331 | } |
| | 8332 | else |
| | 8333 | { |
| | 8334 | /* participle - it's the part after the slash */ |
| | 8335 | return rexGroup(2)[3] + rexGroup(3)[3]; |
| | 8336 | } |
| | 8337 | } |
| | 8338 | |
| | 8339 | /* |
| | 8340 | * Show the "noMatch" library message. For most verbs, we use the |
| | 8341 | * basic "you can't see that here". Verbs that are mostly used with |
| | 8342 | * intangible objects, such as LISTEN TO and SMELL, might want to |
| | 8343 | * override this to use a less visually-oriented message. |
| | 8344 | */ |
| | 8345 | noMatch(msgObj, actor, txt) { msgObj.noMatchCannotSee(actor, txt); } |
| | 8346 | |
| | 8347 | /* |
| | 8348 | * Verb flags - these are used to control certain aspects of verb |
| | 8349 | * formatting. By default, we have no special flags. |
| | 8350 | */ |
| | 8351 | verbFlags = 0 |
| | 8352 | |
| | 8353 | /* add a space prefix/suffix to a string if the string is non-empty */ |
| | 8354 | spPrefix(str) { return (str == '' ? str : ' ' + str); } |
| | 8355 | spSuffix(str) { return (str == '' ? str : str + ' '); } |
| | 8356 | ; |
| | 8357 | |
| | 8358 | /* |
| | 8359 | * English-specific additions for single-object verbs. |
| | 8360 | */ |
| | 8361 | modify TAction |
| | 8362 | /* return an interrogative word for an object of the action */ |
| | 8363 | whatObj(which) |
| | 8364 | { |
| | 8365 | /* |
| | 8366 | * Show the interrogative for our direct object - this is the |
| | 8367 | * last word enclosed in parentheses in our verbPhrase string. |
| | 8368 | */ |
| | 8369 | rexSearch('<lparen>.*?(<alpha>+)<rparen>', verbPhrase); |
| | 8370 | return whatTranslate(rexGroup(1)[3]); |
| | 8371 | } |
| | 8372 | |
| | 8373 | /* announce a default object used with this action */ |
| | 8374 | announceDefaultObject(obj, whichObj, resolvedAllObjects) |
| | 8375 | { |
| | 8376 | local prep; |
| | 8377 | local nm = obj.getAnnouncementDistinguisher().theName(obj); |
| | 8378 | |
| | 8379 | /* |
| | 8380 | * get any direct object preposition - this is the part inside |
| | 8381 | * the "(what)" specifier parens, excluding the last word |
| | 8382 | */ |
| | 8383 | rexSearch('<lparen>(.*<space>+)?<alpha>+<rparen>', verbPhrase); |
| | 8384 | prep = (rexGroup(1) == nil ? '' : rexGroup(1)[3]); |
| | 8385 | |
| | 8386 | /* do any verb-specific adjustment of the preposition */ |
| | 8387 | if (prep != nil) |
| | 8388 | prep = adjustDefaultObjectPrep(prep, obj); |
| | 8389 | |
| | 8390 | /* show the preposition (if any) and the object */ |
| | 8391 | return (prep == '' ? nm : prep + nm); |
| | 8392 | } |
| | 8393 | |
| | 8394 | /* |
| | 8395 | * Adjust the preposition. In some cases, the verb will want to vary |
| | 8396 | * the preposition according to the object. This method can return a |
| | 8397 | * custom preposition in place of the one in the verbPhrase. By |
| | 8398 | * default, we just use the fixed preposition from the verbPhrase, |
| | 8399 | * which is passed in to us in 'prep'. |
| | 8400 | */ |
| | 8401 | adjustDefaultObjectPrep(prep, obj) { return prep; } |
| | 8402 | |
| | 8403 | /* announce all defaulted objects */ |
| | 8404 | announceAllDefaultObjects(allResolved) |
| | 8405 | { |
| | 8406 | /* announce a defaulted direct object if appropriate */ |
| | 8407 | maybeAnnounceDefaultObject(dobjList_, DirectObject, allResolved); |
| | 8408 | } |
| | 8409 | |
| | 8410 | /* show the verb's basic infinitive form for an interrogative */ |
| | 8411 | getQuestionInf(which) |
| | 8412 | { |
| | 8413 | /* |
| | 8414 | * Show the present-tense verb form (removing the participle |
| | 8415 | * part - the "/xxxing" part). Include any prepositions |
| | 8416 | * attached to the verb itself or to the direct object (inside |
| | 8417 | * the "(what)" parens). |
| | 8418 | */ |
| | 8419 | rexSearch('(.*)/<alphanum|-|squote>+(.*?)<space>+' |
| | 8420 | + '<lparen>(.*?)<space>*?<alpha>+<rparen>', |
| | 8421 | verbPhrase); |
| | 8422 | return rexGroup(1)[3] + spPrefix(rexGroup(2)[3]) |
| | 8423 | + spPrefix(rexGroup(3)[3]); |
| | 8424 | } |
| | 8425 | |
| | 8426 | /* get the verb phrase in infinitive or participle form */ |
| | 8427 | getVerbPhrase(inf, ctx) |
| | 8428 | { |
| | 8429 | local dobj; |
| | 8430 | local dobjText; |
| | 8431 | local dobjIsPronoun; |
| | 8432 | local ret; |
| | 8433 | |
| | 8434 | /* use the default pronoun context if one wasn't supplied */ |
| | 8435 | if (ctx == nil) |
| | 8436 | ctx = defaultGetVerbPhraseContext; |
| | 8437 | |
| | 8438 | /* get the direct object */ |
| | 8439 | dobj = getDobj(); |
| | 8440 | |
| | 8441 | /* note if it's a pronoun */ |
| | 8442 | dobjIsPronoun = ctx.isObjPronoun(dobj); |
| | 8443 | |
| | 8444 | /* get the direct object name */ |
| | 8445 | dobjText = ctx.objNameObj(dobj); |
| | 8446 | |
| | 8447 | /* get the phrasing */ |
| | 8448 | ret = getVerbPhrase1(inf, verbPhrase, dobjText, dobjIsPronoun); |
| | 8449 | |
| | 8450 | /* set the pronoun antecedent to my direct object */ |
| | 8451 | ctx.setPronounObj(dobj); |
| | 8452 | |
| | 8453 | /* return the result */ |
| | 8454 | return ret; |
| | 8455 | } |
| | 8456 | |
| | 8457 | /* |
| | 8458 | * Given the text of the direct object phrase, build the verb phrase |
| | 8459 | * for a one-object verb. This is a class method that can be used by |
| | 8460 | * other kinds of verbs (i.e., non-TActions) that use phrasing like a |
| | 8461 | * single object. |
| | 8462 | * |
| | 8463 | * 'inf' is a flag indicating whether to use the infinitive form |
| | 8464 | * (true) or the present participle form (nil); 'vp' is the |
| | 8465 | * verbPhrase string; 'dobjText' is the direct object phrase's text; |
| | 8466 | * and 'dobjIsPronoun' is true if the dobj text is rendered as a |
| | 8467 | * pronoun. |
| | 8468 | */ |
| | 8469 | getVerbPhrase1(inf, vp, dobjText, dobjIsPronoun) |
| | 8470 | { |
| | 8471 | local ret; |
| | 8472 | local dprep; |
| | 8473 | local vcomp; |
| | 8474 | |
| | 8475 | /* |
| | 8476 | * parse the verbPhrase: pick out the 'infinitive/participle' |
| | 8477 | * part, the complementizer part up to the '(what)' direct |
| | 8478 | * object placeholder, and any preposition within the '(what)' |
| | 8479 | * specifier |
| | 8480 | */ |
| | 8481 | rexMatch('(.*)/(<alphanum|-|squote>+)(.*) ' |
| | 8482 | + '<lparen>(.*?)<space>*?<alpha>+<rparen>(.*)', |
| | 8483 | vp); |
| | 8484 | |
| | 8485 | /* start off with the infinitive or participle, as desired */ |
| | 8486 | if (inf) |
| | 8487 | ret = rexGroup(1)[3]; |
| | 8488 | else |
| | 8489 | ret = rexGroup(2)[3]; |
| | 8490 | |
| | 8491 | /* get the prepositional complementizer */ |
| | 8492 | vcomp = rexGroup(3)[3]; |
| | 8493 | |
| | 8494 | /* get the direct object preposition */ |
| | 8495 | dprep = rexGroup(4)[3]; |
| | 8496 | |
| | 8497 | /* do any verb-specific adjustment of the preposition */ |
| | 8498 | if (dprep != nil) |
| | 8499 | dprep = adjustDefaultObjectPrep(dprep, getDobj()); |
| | 8500 | |
| | 8501 | /* |
| | 8502 | * if the direct object is not a pronoun, put the complementizer |
| | 8503 | * BEFORE the direct object (the 'up' in "PICKING UP THE BOX") |
| | 8504 | */ |
| | 8505 | if (!dobjIsPronoun) |
| | 8506 | ret += spPrefix(vcomp); |
| | 8507 | |
| | 8508 | /* add the direct object preposition */ |
| | 8509 | ret += spPrefix(dprep); |
| | 8510 | |
| | 8511 | /* add the direct object, using the pronoun form if applicable */ |
| | 8512 | ret += ' ' + dobjText; |
| | 8513 | |
| | 8514 | /* |
| | 8515 | * if the direct object is a pronoun, put the complementizer |
| | 8516 | * AFTER the direct object (the 'up' in "PICKING IT UP") |
| | 8517 | */ |
| | 8518 | if (dobjIsPronoun) |
| | 8519 | ret += spPrefix(vcomp); |
| | 8520 | |
| | 8521 | /* |
| | 8522 | * if there's any suffix following the direct object |
| | 8523 | * placeholder, add it at the end of the phrase |
| | 8524 | */ |
| | 8525 | ret += rexGroup(5)[3]; |
| | 8526 | |
| | 8527 | /* return the complete phrase string */ |
| | 8528 | return ret; |
| | 8529 | } |
| | 8530 | ; |
| | 8531 | |
| | 8532 | /* |
| | 8533 | * English-specific additions for two-object verbs. |
| | 8534 | */ |
| | 8535 | modify TIAction |
| | 8536 | /* |
| | 8537 | * Flag: omit the indirect object in a query for a missing direct |
| | 8538 | * object. For many verbs, if we already know the indirect object |
| | 8539 | * and we need to ask for the direct object, the query sounds best |
| | 8540 | * when it includes the indirect object: "what do you want to put in |
| | 8541 | * it?" or "what do you want to take from it?". This is the |
| | 8542 | * default phrasing. |
| | 8543 | * |
| | 8544 | * However, the corresponding query for some verbs sounds weird: |
| | 8545 | * "what do you want to dig in with it?" or "whom do you want to ask |
| | 8546 | * about it?". For such actions, this property should be set to |
| | 8547 | * true to indicate that the indirect object should be omitted from |
| | 8548 | * the queries, which will change the phrasing to "what do you want |
| | 8549 | * to dig in", "whom do you want to ask", and so on. |
| | 8550 | */ |
| | 8551 | omitIobjInDobjQuery = nil |
| | 8552 | |
| | 8553 | /* |
| | 8554 | * For VerbRules: does this verb rule have a prepositional or |
| | 8555 | * structural phrasing of the direct and indirect object slots? That |
| | 8556 | * is, are the object slots determined by a prepositional marker, or |
| | 8557 | * purely by word order? For most English verbs with two objects, |
| | 8558 | * the indirect object is marked by a preposition: GIVE BOOK TO BOB, |
| | 8559 | * PUT BOOK IN BOX. There are a few English verbs that don't include |
| | 8560 | * any prespositional markers for the objects, though, and assign the |
| | 8561 | * noun phrase roles purely by the word order: GIVE BOB BOOK, SHOW |
| | 8562 | * BOB BOOK, THROW BOB BOOK. We define these phrasings with separate |
| | 8563 | * verb rules, which we mark with this property. |
| | 8564 | * |
| | 8565 | * We use this in ranking verb matches. Non-prepositional verb |
| | 8566 | * structures are especially prone to matching where they shouldn't, |
| | 8567 | * because we can often find a way to pick out words to fill the |
| | 8568 | * slots in the absence of any marker words. For example, GIVE GREEN |
| | 8569 | * BOOK could be interpreted as GIVE BOOK TO GREEN, where GREEN is |
| | 8570 | * assumed to be an adjective-ending noun phrase; but the player |
| | 8571 | * probably means to give the green book to someone who they assumed |
| | 8572 | * would be filled in as a default. So, whenever we find an |
| | 8573 | * interpretation that involves a non-prespositional phrasing, we'll |
| | 8574 | * use this flag to know we should be suspicious of it and try |
| | 8575 | * alternative phrasing first. |
| | 8576 | * |
| | 8577 | * Most two-object verbs in English use prepositional markers, so |
| | 8578 | * we'll set this as the default. Individual VerbRules that use |
| | 8579 | * purely structural phrasing should override this. |
| | 8580 | */ |
| | 8581 | isPrepositionalPhrasing = true |
| | 8582 | |
| | 8583 | /* resolve noun phrases */ |
| | 8584 | resolveNouns(issuingActor, targetActor, results) |
| | 8585 | { |
| | 8586 | /* |
| | 8587 | * If we're a non-prepositional phrasing, it means that we have |
| | 8588 | * the VERB IOBJ DOBJ word ordering (as in GIVE BOB BOX or THROW |
| | 8589 | * BOB COIN). For grammar match ranking purposes, give these |
| | 8590 | * phrasings a lower match probability when the dobj phrase |
| | 8591 | * doesn't have a clear qualifier. If the dobj phrase starts |
| | 8592 | * with 'the' or a qualifier word like that (GIVE BOB THE BOX), |
| | 8593 | * then it's pretty clear that the structural phrasing is right |
| | 8594 | * after all; but if there's no qualifier, we could reading too |
| | 8595 | * much into the word order. We could have something like GIVE |
| | 8596 | * GREEN BOX, where we *could* treat this as two objects, but we |
| | 8597 | * could just as well have a missing indirect object phrase. |
| | 8598 | */ |
| | 8599 | if (!isPrepositionalPhrasing) |
| | 8600 | { |
| | 8601 | /* |
| | 8602 | * If the direct object phrase starts with 'a', 'an', 'the', |
| | 8603 | * 'some', or 'any', the grammar is pretty clearly a good |
| | 8604 | * match for the non-prepositional phrasing. Otherwise, it's |
| | 8605 | * suspect, so rank it accordingly. |
| | 8606 | */ |
| | 8607 | if (rexMatch('(a|an|the|some|any)<space>', |
| | 8608 | dobjMatch.getOrigText()) == nil) |
| | 8609 | { |
| | 8610 | /* note this as weak phrasing level 100 */ |
| | 8611 | results.noteWeakPhrasing(100); |
| | 8612 | } |
| | 8613 | } |
| | 8614 | |
| | 8615 | /* inherit the base handling */ |
| | 8616 | inherited(issuingActor, targetActor, results); |
| | 8617 | } |
| | 8618 | |
| | 8619 | /* get the interrogative for one of our objects */ |
| | 8620 | whatObj(which) |
| | 8621 | { |
| | 8622 | switch (which) |
| | 8623 | { |
| | 8624 | case DirectObject: |
| | 8625 | /* |
| | 8626 | * the direct object interrogative is the first word in |
| | 8627 | * parentheses in our verbPhrase string |
| | 8628 | */ |
| | 8629 | rexSearch('<lparen>.*?(<alpha>+)<rparen>', verbPhrase); |
| | 8630 | break; |
| | 8631 | |
| | 8632 | case IndirectObject: |
| | 8633 | /* |
| | 8634 | * the indirect object interrogative is the second |
| | 8635 | * parenthesized word in our verbPhrase string |
| | 8636 | */ |
| | 8637 | rexSearch('<rparen>.*<lparen>.*?(<alpha>+)<rparen>', verbPhrase); |
| | 8638 | break; |
| | 8639 | } |
| | 8640 | |
| | 8641 | /* show the group match */ |
| | 8642 | return whatTranslate(rexGroup(1)[3]); |
| | 8643 | } |
| | 8644 | |
| | 8645 | /* announce a default object used with this action */ |
| | 8646 | announceDefaultObject(obj, whichObj, resolvedAllObjects) |
| | 8647 | { |
| | 8648 | local verb; |
| | 8649 | local prep; |
| | 8650 | |
| | 8651 | /* presume we won't have a verb or preposition */ |
| | 8652 | verb = ''; |
| | 8653 | prep = ''; |
| | 8654 | |
| | 8655 | /* |
| | 8656 | * Check the full phrasing - if we're showing the direct object, |
| | 8657 | * but an indirect object was supplied, use the verb's |
| | 8658 | * participle form ("asking bob") in the default string, since |
| | 8659 | * we must clarify that we're not tagging the default string on |
| | 8660 | * to the command line. Don't include the participle form if we |
| | 8661 | * don't know all the objects yet, since in this case we are in |
| | 8662 | * fact tagging the default string onto the command so far, as |
| | 8663 | * there's nothing else in the command to get in the way. |
| | 8664 | */ |
| | 8665 | if (whichObj == DirectObject && resolvedAllObjects) |
| | 8666 | { |
| | 8667 | /* |
| | 8668 | * extract the verb's participle form (including any |
| | 8669 | * complementizer phrase) |
| | 8670 | */ |
| | 8671 | rexSearch('/(<^lparen>+) <lparen>', verbPhrase); |
| | 8672 | verb = rexGroup(1)[3] + ' '; |
| | 8673 | } |
| | 8674 | |
| | 8675 | /* get the preposition to use, if any */ |
| | 8676 | switch(whichObj) |
| | 8677 | { |
| | 8678 | case DirectObject: |
| | 8679 | /* use the preposition in the first "(what)" phrase */ |
| | 8680 | rexSearch('<lparen>(.*?)<space>*<alpha>+<rparen>', verbPhrase); |
| | 8681 | prep = rexGroup(1)[3]; |
| | 8682 | break; |
| | 8683 | |
| | 8684 | case IndirectObject: |
| | 8685 | /* use the preposition in the second "(what)" phrase */ |
| | 8686 | rexSearch('<rparen>.*<lparen>(.*?)<space>*<alpha>+<rparen>', |
| | 8687 | verbPhrase); |
| | 8688 | prep = rexGroup(1)[3]; |
| | 8689 | break; |
| | 8690 | } |
| | 8691 | |
| | 8692 | /* build and return the complete phrase */ |
| | 8693 | return spSuffix(verb) + spSuffix(prep) |
| | 8694 | + obj.getAnnouncementDistinguisher().theName(obj); |
| | 8695 | } |
| | 8696 | |
| | 8697 | /* announce all defaulted objects */ |
| | 8698 | announceAllDefaultObjects(allResolved) |
| | 8699 | { |
| | 8700 | /* announce a defaulted direct object if appropriate */ |
| | 8701 | maybeAnnounceDefaultObject(dobjList_, DirectObject, allResolved); |
| | 8702 | |
| | 8703 | /* announce a defaulted indirect object if appropriate */ |
| | 8704 | maybeAnnounceDefaultObject(iobjList_, IndirectObject, allResolved); |
| | 8705 | } |
| | 8706 | |
| | 8707 | /* show the verb's basic infinitive form for an interrogative */ |
| | 8708 | getQuestionInf(which) |
| | 8709 | { |
| | 8710 | local ret; |
| | 8711 | local vcomp; |
| | 8712 | local dprep; |
| | 8713 | local iprep; |
| | 8714 | local pro; |
| | 8715 | |
| | 8716 | /* |
| | 8717 | * Our verb phrase can one of three formats, depending on which |
| | 8718 | * object role we're asking about (the part in <angle brackets> |
| | 8719 | * is the part we're responsible for generating). In these |
| | 8720 | * formats, 'verb' is the verb infinitive; 'comp' is the |
| | 8721 | * complementizer, if any (e.g., the 'up' in 'pick up'); 'dprep' |
| | 8722 | * is the direct object preposition (the 'in' in 'dig in x with |
| | 8723 | * y'); and 'iprep' is the indirect object preposition (the |
| | 8724 | * 'with' in 'dig in x with y'). |
| | 8725 | * |
| | 8726 | * asking for dobj: verb vcomp dprep iprep it ('what do you want |
| | 8727 | * to <open with it>?', '<dig in with it>', '<look up in it>'). |
| | 8728 | * |
| | 8729 | * asking for dobj, but suppressing the iobj part: verb vcomp |
| | 8730 | * dprep ('what do you want to <turn>?', '<look up>?', '<dig |
| | 8731 | * in>') |
| | 8732 | * |
| | 8733 | * asking for iobj: verb dprep it vcomp iprep ('what do you want |
| | 8734 | * to <open it with>', '<dig in it with>', '<look it up in>' |
| | 8735 | */ |
| | 8736 | |
| | 8737 | /* parse the verbPhrase into its component parts */ |
| | 8738 | rexMatch('(.*)/<alphanum|-|squote>+(?:<space>+(<^lparen>*))?' |
| | 8739 | + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>' |
| | 8740 | + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>', |
| | 8741 | verbPhrase); |
| | 8742 | |
| | 8743 | /* pull out the verb */ |
| | 8744 | ret = rexGroup(1)[3]; |
| | 8745 | |
| | 8746 | /* pull out the verb complementizer */ |
| | 8747 | vcomp = (rexGroup(2) == nil ? '' : rexGroup(2)[3]); |
| | 8748 | |
| | 8749 | /* pull out the direct and indirect object prepositions */ |
| | 8750 | dprep = rexGroup(3)[3]; |
| | 8751 | iprep = rexGroup(4)[3]; |
| | 8752 | |
| | 8753 | /* get the pronoun for the other object phrase */ |
| | 8754 | pro = getOtherMessageObjectPronoun(which); |
| | 8755 | |
| | 8756 | /* check what we're asking about */ |
| | 8757 | if (which == DirectObject) |
| | 8758 | { |
| | 8759 | /* add the <vcomp dprep> part in all cases */ |
| | 8760 | ret += spPrefix(vcomp) + spPrefix(dprep); |
| | 8761 | |
| | 8762 | /* add the <iprep it> part if we want the indirect object part */ |
| | 8763 | if (!omitIobjInDobjQuery && pro != nil) |
| | 8764 | ret += spPrefix(iprep) + ' ' + pro; |
| | 8765 | } |
| | 8766 | else |
| | 8767 | { |
| | 8768 | /* add the <dprep it> part if appropriate */ |
| | 8769 | if (pro != nil) |
| | 8770 | ret += spPrefix(dprep) + ' ' + pro; |
| | 8771 | |
| | 8772 | /* add the <vcomp iprep> part */ |
| | 8773 | ret += spPrefix(vcomp) + spPrefix(iprep); |
| | 8774 | } |
| | 8775 | |
| | 8776 | /* return the result */ |
| | 8777 | return ret; |
| | 8778 | } |
| | 8779 | |
| | 8780 | /* |
| | 8781 | * Get the pronoun for the message object in the given role. |
| | 8782 | */ |
| | 8783 | getOtherMessageObjectPronoun(which) |
| | 8784 | { |
| | 8785 | local lst; |
| | 8786 | |
| | 8787 | /* |
| | 8788 | * Get the resolution list (or tentative resolution list) for the |
| | 8789 | * *other* object, since we want to show a pronoun representing |
| | 8790 | * the other object. If we don't have a fully-resolved list for |
| | 8791 | * the other object, use the tentative resolution list, which |
| | 8792 | * we're guaranteed to have by the time we start resolving |
| | 8793 | * anything (and thus by the time we need to ask for objects). |
| | 8794 | */ |
| | 8795 | lst = (which == DirectObject ? iobjList_ : dobjList_); |
| | 8796 | if (lst == nil || lst == []) |
| | 8797 | lst = (which == DirectObject |
| | 8798 | ? tentativeIobj_ : tentativeDobj_); |
| | 8799 | |
| | 8800 | /* if we found an object list, use the pronoun for the list */ |
| | 8801 | if (lst != nil && lst != []) |
| | 8802 | { |
| | 8803 | /* we got a list - return a suitable pronoun for this list */ |
| | 8804 | return objListPronoun(lst); |
| | 8805 | } |
| | 8806 | else |
| | 8807 | { |
| | 8808 | /* there's no object list, so there's no pronoun */ |
| | 8809 | return nil; |
| | 8810 | } |
| | 8811 | } |
| | 8812 | |
| | 8813 | /* get the verb phrase in infinitive or participle form */ |
| | 8814 | getVerbPhrase(inf, ctx) |
| | 8815 | { |
| | 8816 | local dobj, dobjText, dobjIsPronoun; |
| | 8817 | local iobj, iobjText; |
| | 8818 | local ret; |
| | 8819 | |
| | 8820 | /* use the default context if one wasn't supplied */ |
| | 8821 | if (ctx == nil) |
| | 8822 | ctx = defaultGetVerbPhraseContext; |
| | 8823 | |
| | 8824 | /* get the direct object information */ |
| | 8825 | dobj = getDobj(); |
| | 8826 | dobjText = ctx.objNameObj(dobj); |
| | 8827 | dobjIsPronoun = ctx.isObjPronoun(dobj); |
| | 8828 | |
| | 8829 | /* get the indirect object information */ |
| | 8830 | iobj = getIobj(); |
| | 8831 | iobjText = (iobj != nil ? ctx.objNameObj(iobj) : nil); |
| | 8832 | |
| | 8833 | /* get the phrasing */ |
| | 8834 | ret = getVerbPhrase2(inf, verbPhrase, |
| | 8835 | dobjText, dobjIsPronoun, iobjText); |
| | 8836 | |
| | 8837 | /* |
| | 8838 | * Set the antecedent for the next verb phrase. Our direct |
| | 8839 | * object is normally the antecedent; however, if the indirect |
| | 8840 | * object matches the current antecedent, keep the current |
| | 8841 | * antecedent, so that 'it' (or whatever) remains the same for |
| | 8842 | * the next verb phrase. |
| | 8843 | */ |
| | 8844 | if (ctx.pronounObj != iobj) |
| | 8845 | ctx.setPronounObj(dobj); |
| | 8846 | |
| | 8847 | /* return the result */ |
| | 8848 | return ret; |
| | 8849 | } |
| | 8850 | |
| | 8851 | /* |
| | 8852 | * Get the verb phrase for a two-object (dobj + iobj) phrasing. This |
| | 8853 | * is a class method, so that it can be reused by unrelated (i.e., |
| | 8854 | * non-TIAction) classes that also use two-object syntax but with |
| | 8855 | * other internal structures. This is the two-object equivalent of |
| | 8856 | * TAction.getVerbPhrase1(). |
| | 8857 | */ |
| | 8858 | getVerbPhrase2(inf, vp, dobjText, dobjIsPronoun, iobjText) |
| | 8859 | { |
| | 8860 | local ret; |
| | 8861 | local vcomp; |
| | 8862 | local dprep, iprep; |
| | 8863 | |
| | 8864 | /* parse the verbPhrase into its component parts */ |
| | 8865 | rexMatch('(.*)/(<alphanum|-|squote>+)(?:<space>+(<^lparen>*))?' |
| | 8866 | + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>' |
| | 8867 | + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>', |
| | 8868 | vp); |
| | 8869 | |
| | 8870 | /* start off with the infinitive or participle, as desired */ |
| | 8871 | if (inf) |
| | 8872 | ret = rexGroup(1)[3]; |
| | 8873 | else |
| | 8874 | ret = rexGroup(2)[3]; |
| | 8875 | |
| | 8876 | /* get the complementizer */ |
| | 8877 | vcomp = (rexGroup(3) == nil ? '' : rexGroup(3)[3]); |
| | 8878 | |
| | 8879 | /* get the direct and indirect object prepositions */ |
| | 8880 | dprep = rexGroup(4)[3]; |
| | 8881 | iprep = rexGroup(5)[3]; |
| | 8882 | |
| | 8883 | /* |
| | 8884 | * add the complementizer BEFORE the direct object, if the |
| | 8885 | * direct object is being shown as a full name ("PICK UP BOX") |
| | 8886 | */ |
| | 8887 | if (!dobjIsPronoun) |
| | 8888 | ret += spPrefix(vcomp); |
| | 8889 | |
| | 8890 | /* |
| | 8891 | * add the direct object and its preposition, using a pronoun if |
| | 8892 | * applicable |
| | 8893 | */ |
| | 8894 | ret += spPrefix(dprep) + ' ' + dobjText; |
| | 8895 | |
| | 8896 | /* |
| | 8897 | * add the complementizer AFTER the direct object, if the direct |
| | 8898 | * object is shown as a pronoun ("PICK IT UP") |
| | 8899 | */ |
| | 8900 | if (dobjIsPronoun) |
| | 8901 | ret += spPrefix(vcomp); |
| | 8902 | |
| | 8903 | /* if we have an indirect object, add it with its preposition */ |
| | 8904 | if (iobjText != nil) |
| | 8905 | ret += spPrefix(iprep) + ' ' + iobjText; |
| | 8906 | |
| | 8907 | /* return the result phrase */ |
| | 8908 | return ret; |
| | 8909 | } |
| | 8910 | ; |
| | 8911 | |
| | 8912 | /* |
| | 8913 | * English-specific additions for verbs taking a literal phrase as the |
| | 8914 | * sole object. |
| | 8915 | */ |
| | 8916 | modify LiteralAction |
| | 8917 | /* provide a base verbPhrase, in case an instance leaves it out */ |
| | 8918 | verbPhrase = 'verb/verbing (what)' |
| | 8919 | |
| | 8920 | /* get an interrogative word for an object of the action */ |
| | 8921 | whatObj(which) |
| | 8922 | { |
| | 8923 | /* use the same processing as TAction */ |
| | 8924 | return delegated TAction(which); |
| | 8925 | } |
| | 8926 | |
| | 8927 | getVerbPhrase(inf, ctx) |
| | 8928 | { |
| | 8929 | /* handle this as though the literal were a direct object phrase */ |
| | 8930 | return TAction.getVerbPhrase1(inf, verbPhrase, gLiteral, nil); |
| | 8931 | } |
| | 8932 | |
| | 8933 | getQuestionInf(which) |
| | 8934 | { |
| | 8935 | /* use the same handling as for a regular one-object action */ |
| | 8936 | return delegated TAction(which); |
| | 8937 | } |
| | 8938 | |
| | 8939 | ; |
| | 8940 | |
| | 8941 | /* |
| | 8942 | * English-specific additions for verbs of a direct object and a literal |
| | 8943 | * phrase. |
| | 8944 | */ |
| | 8945 | modify LiteralTAction |
| | 8946 | announceDefaultObject(obj, whichObj, resolvedAllObjects) |
| | 8947 | { |
| | 8948 | /* |
| | 8949 | * Use the same handling as for a regular two-object action. We |
| | 8950 | * can only default the actual object in this kind of verb; the |
| | 8951 | * actual object always fills the DirectObject slot, but in |
| | 8952 | * message generation it might use a different slot, so use the |
| | 8953 | * message generation slot here. |
| | 8954 | */ |
| | 8955 | return delegated TIAction(obj, whichMessageObject, |
| | 8956 | resolvedAllObjects); |
| | 8957 | } |
| | 8958 | |
| | 8959 | whatObj(which) |
| | 8960 | { |
| | 8961 | /* use the same handling we use for a regular two-object action */ |
| | 8962 | return delegated TIAction(which); |
| | 8963 | } |
| | 8964 | |
| | 8965 | getQuestionInf(which) |
| | 8966 | { |
| | 8967 | /* |
| | 8968 | * use the same handling as for a two-object action (but note |
| | 8969 | * that we override getMessageObjectPronoun(), which will affect |
| | 8970 | * the way we present the verb infinitive in some cases) |
| | 8971 | */ |
| | 8972 | return delegated TIAction(which); |
| | 8973 | } |
| | 8974 | |
| | 8975 | /* |
| | 8976 | * When we want to show a verb infinitive phrase that involves a |
| | 8977 | * pronoun for the literal phrase, refer to the literal as 'that' |
| | 8978 | * rather than 'it' or anything else. |
| | 8979 | */ |
| | 8980 | getOtherMessageObjectPronoun(which) |
| | 8981 | { |
| | 8982 | /* |
| | 8983 | * If we're asking about the literal phrase, then the other |
| | 8984 | * pronoun is for the resolved object: so, return the pronoun |
| | 8985 | * for the direct object phrase, because we *always* store the |
| | 8986 | * non-literal in the direct object slot, regardless of the |
| | 8987 | * actual phrasing of the action. |
| | 8988 | * |
| | 8989 | * If we're asking about the resolved object (i.e., not the |
| | 8990 | * literal phrase), then return 'that' as the pronoun for the |
| | 8991 | * literal phrase. |
| | 8992 | */ |
| | 8993 | if (which == whichMessageLiteral) |
| | 8994 | { |
| | 8995 | /* |
| | 8996 | * we're asking about the literal, so the other pronoun is |
| | 8997 | * for the resolved object, which is always in the direct |
| | 8998 | * object slot (so the 'other' slot is effectively the |
| | 8999 | * indirect object) |
| | 9000 | */ |
| | 9001 | return delegated TIAction(IndirectObject); |
| | 9002 | } |
| | 9003 | else |
| | 9004 | { |
| | 9005 | /* |
| | 9006 | * We're asking about the resolved object, so the other |
| | 9007 | * pronoun is for the literal phrase: always use 'that' to |
| | 9008 | * refer to the literal phrase. |
| | 9009 | */ |
| | 9010 | return 'that'; |
| | 9011 | } |
| | 9012 | } |
| | 9013 | |
| | 9014 | getVerbPhrase(inf, ctx) |
| | 9015 | { |
| | 9016 | local dobj, dobjText, dobjIsPronoun; |
| | 9017 | local litText; |
| | 9018 | local ret; |
| | 9019 | |
| | 9020 | /* use the default context if one wasn't supplied */ |
| | 9021 | if (ctx == nil) |
| | 9022 | ctx = defaultGetVerbPhraseContext; |
| | 9023 | |
| | 9024 | /* get the direct object information */ |
| | 9025 | dobj = getDobj(); |
| | 9026 | dobjText = ctx.objNameObj(dobj); |
| | 9027 | dobjIsPronoun = ctx.isObjPronoun(dobj); |
| | 9028 | |
| | 9029 | /* get our literal text */ |
| | 9030 | litText = gLiteral; |
| | 9031 | |
| | 9032 | /* |
| | 9033 | * Use the standard two-object phrasing. The order of the |
| | 9034 | * phrasing depends on whether our literal phrase is in the |
| | 9035 | * direct or indirect object slot. |
| | 9036 | */ |
| | 9037 | if (whichMessageLiteral == DirectObject) |
| | 9038 | ret = TIAction.getVerbPhrase2(inf, verbPhrase, |
| | 9039 | litText, nil, dobjText); |
| | 9040 | else |
| | 9041 | ret = TIAction.getVerbPhrase2(inf, verbPhrase, |
| | 9042 | dobjText, dobjIsPronoun, litText); |
| | 9043 | |
| | 9044 | /* use the direct object as the antecedent for the next phrase */ |
| | 9045 | ctx.setPronounObj(dobj); |
| | 9046 | |
| | 9047 | /* return the result */ |
| | 9048 | return ret; |
| | 9049 | } |
| | 9050 | ; |
| | 9051 | |
| | 9052 | /* |
| | 9053 | * English-specific additions for verbs taking a topic phrase as the sole |
| | 9054 | * object. |
| | 9055 | */ |
| | 9056 | modify TopicAction |
| | 9057 | /* get an interrogative word for an object of the action */ |
| | 9058 | whatObj(which) |
| | 9059 | { |
| | 9060 | /* use the same processing as TAction */ |
| | 9061 | return delegated TAction(which); |
| | 9062 | } |
| | 9063 | |
| | 9064 | getVerbPhrase(inf, ctx) |
| | 9065 | { |
| | 9066 | /* handle this as though the topic text were a direct object phrase */ |
| | 9067 | return TAction.getVerbPhrase1( |
| | 9068 | inf, verbPhrase, getTopic().getTopicText().toLower(), nil); |
| | 9069 | } |
| | 9070 | |
| | 9071 | getQuestionInf(which) |
| | 9072 | { |
| | 9073 | /* use the same handling as for a regular one-object action */ |
| | 9074 | return delegated TAction(which); |
| | 9075 | } |
| | 9076 | |
| | 9077 | ; |
| | 9078 | |
| | 9079 | /* |
| | 9080 | * English-specific additions for verbs with topic phrases. |
| | 9081 | */ |
| | 9082 | modify TopicTAction |
| | 9083 | announceDefaultObject(obj, whichObj, resolvedAllObjects) |
| | 9084 | { |
| | 9085 | /* |
| | 9086 | * Use the same handling as for a regular two-object action. We |
| | 9087 | * can only default the actual object in this kind of verb; the |
| | 9088 | * actual object always fills the DirectObject slot, but in |
| | 9089 | * message generation it might use a different slot, so use the |
| | 9090 | * message generation slot here. |
| | 9091 | */ |
| | 9092 | return delegated TIAction(obj, whichMessageObject, |
| | 9093 | resolvedAllObjects); |
| | 9094 | } |
| | 9095 | |
| | 9096 | whatObj(which) |
| | 9097 | { |
| | 9098 | /* use the same handling we use for a regular two-object action */ |
| | 9099 | return delegated TIAction(which); |
| | 9100 | } |
| | 9101 | |
| | 9102 | getQuestionInf(which) |
| | 9103 | { |
| | 9104 | /* use the same handling as for a regular two-object action */ |
| | 9105 | return delegated TIAction(which); |
| | 9106 | } |
| | 9107 | |
| | 9108 | getOtherMessageObjectPronoun(which) |
| | 9109 | { |
| | 9110 | /* |
| | 9111 | * If we're asking about the topic, then the other pronoun is |
| | 9112 | * for the resolved object, which is always in the direct object |
| | 9113 | * slot. If we're asking about the resolved object, then return |
| | 9114 | * a pronoun for the topic. |
| | 9115 | */ |
| | 9116 | if (which == whichMessageTopic) |
| | 9117 | { |
| | 9118 | /* |
| | 9119 | * we want the pronoun for the resolved object, which is |
| | 9120 | * always in the direct object slot (so the 'other' slot is |
| | 9121 | * effectively the indirect object) |
| | 9122 | */ |
| | 9123 | return delegated TIAction(IndirectObject); |
| | 9124 | } |
| | 9125 | else |
| | 9126 | { |
| | 9127 | /* return a generic pronoun for the topic */ |
| | 9128 | return 'that'; |
| | 9129 | } |
| | 9130 | } |
| | 9131 | |
| | 9132 | getVerbPhrase(inf, ctx) |
| | 9133 | { |
| | 9134 | local dobj, dobjText, dobjIsPronoun; |
| | 9135 | local topicText; |
| | 9136 | local ret; |
| | 9137 | |
| | 9138 | /* use the default context if one wasn't supplied */ |
| | 9139 | if (ctx == nil) |
| | 9140 | ctx = defaultGetVerbPhraseContext; |
| | 9141 | |
| | 9142 | /* get the direct object information */ |
| | 9143 | dobj = getDobj(); |
| | 9144 | dobjText = ctx.objNameObj(dobj); |
| | 9145 | dobjIsPronoun = ctx.isObjPronoun(dobj); |
| | 9146 | |
| | 9147 | /* get our topic phrase */ |
| | 9148 | topicText = getTopic().getTopicText().toLower(); |
| | 9149 | |
| | 9150 | /* |
| | 9151 | * Use the standard two-object phrasing. The order of the |
| | 9152 | * phrasing depends on whether our topic phrase is in the direct |
| | 9153 | * or indirect object slot. |
| | 9154 | */ |
| | 9155 | if (whichMessageTopic == DirectObject) |
| | 9156 | ret = TIAction.getVerbPhrase2(inf, verbPhrase, |
| | 9157 | topicText, nil, dobjText); |
| | 9158 | else |
| | 9159 | ret = TIAction.getVerbPhrase2(inf, verbPhrase, |
| | 9160 | dobjText, dobjIsPronoun, topicText); |
| | 9161 | |
| | 9162 | /* use the direct object as the antecedent for the next phrase */ |
| | 9163 | ctx.setPronounObj(dobj); |
| | 9164 | |
| | 9165 | /* return the result */ |
| | 9166 | return ret; |
| | 9167 | } |
| | 9168 | ; |
| | 9169 | |
| | 9170 | /* ------------------------------------------------------------------------ */ |
| | 9171 | /* |
| | 9172 | * Verbs. |
| | 9173 | * |
| | 9174 | * The actual body of each of our verbs is defined in the main |
| | 9175 | * language-independent part of the library. We only define the |
| | 9176 | * language-specific grammar rules here. |
| | 9177 | */ |
| | 9178 | |
| | 9179 | VerbRule(Take) |
| | 9180 | ('take' | 'pick' 'up' | 'get') dobjList |
| | 9181 | | 'pick' dobjList 'up' |
| | 9182 | : TakeAction |
| | 9183 | verbPhrase = 'take/taking (what)' |
| | 9184 | ; |
| | 9185 | |
| | 9186 | VerbRule(TakeFrom) |
| | 9187 | ('take' | 'get') dobjList |
| | 9188 | ('from' | 'out' 'of' | 'off' | 'off' 'of') singleIobj |
| | 9189 | | 'remove' dobjList 'from' singleIobj |
| | 9190 | : TakeFromAction |
| | 9191 | verbPhrase = 'take/taking (what) (from what)' |
| | 9192 | ; |
| | 9193 | |
| | 9194 | VerbRule(Remove) |
| | 9195 | 'remove' dobjList |
| | 9196 | : RemoveAction |
| | 9197 | verbPhrase = 'remove/removing (what)' |
| | 9198 | ; |
| | 9199 | |
| | 9200 | VerbRule(Drop) |
| | 9201 | ('drop' | 'put' 'down' | 'set' 'down') dobjList |
| | 9202 | | ('put' | 'set') dobjList 'down' |
| | 9203 | : DropAction |
| | 9204 | verbPhrase = 'drop/dropping (what)' |
| | 9205 | ; |
| | 9206 | |
| | 9207 | VerbRule(Examine) |
| | 9208 | ('examine' | 'inspect' | 'x' | 'look' 'at' | 'l' 'at') dobjList |
| | 9209 | : ExamineAction |
| | 9210 | verbPhrase = 'examine/examining (what)' |
| | 9211 | ; |
| | 9212 | |
| | 9213 | VerbRule(Read) |
| | 9214 | 'read' dobjList |
| | 9215 | : ReadAction |
| | 9216 | verbPhrase = 'read/reading (what)' |
| | 9217 | ; |
| | 9218 | |
| | 9219 | VerbRule(LookIn) |
| | 9220 | ('look' | 'l') ('in' | 'inside') dobjList |
| | 9221 | : LookInAction |
| | 9222 | verbPhrase = 'look/looking (in what)' |
| | 9223 | ; |
| | 9224 | |
| | 9225 | VerbRule(Search) |
| | 9226 | 'search' dobjList |
| | 9227 | : SearchAction |
| | 9228 | verbPhrase = 'search/searching (what)' |
| | 9229 | ; |
| | 9230 | |
| | 9231 | VerbRule(LookThrough) |
| | 9232 | ('look' | 'l') ('through' | 'thru' | 'out') dobjList |
| | 9233 | : LookThroughAction |
| | 9234 | verbPhrase = 'look/looking (through what)' |
| | 9235 | ; |
| | 9236 | |
| | 9237 | VerbRule(LookUnder) |
| | 9238 | ('look' | 'l') 'under' dobjList |
| | 9239 | : LookUnderAction |
| | 9240 | verbPhrase = 'look/looking (under what)' |
| | 9241 | ; |
| | 9242 | |
| | 9243 | VerbRule(LookBehind) |
| | 9244 | ('look' | 'l') 'behind' dobjList |
| | 9245 | : LookBehindAction |
| | 9246 | verbPhrase = 'look/looking (behind what)' |
| | 9247 | ; |
| | 9248 | |
| | 9249 | VerbRule(Feel) |
| | 9250 | ('feel' | 'touch') dobjList |
| | 9251 | : FeelAction |
| | 9252 | verbPhrase = 'touch/touching (what)' |
| | 9253 | ; |
| | 9254 | |
| | 9255 | VerbRule(Taste) |
| | 9256 | 'taste' dobjList |
| | 9257 | : TasteAction |
| | 9258 | verbPhrase = 'taste/tasting (what)' |
| | 9259 | ; |
| | 9260 | |
| | 9261 | VerbRule(Smell) |
| | 9262 | ('smell' | 'sniff') dobjList |
| | 9263 | : SmellAction |
| | 9264 | verbPhrase = 'smell/smelling (what)' |
| | 9265 | |
| | 9266 | /* |
| | 9267 | * use the "not aware" version of the no-match message - the object |
| | 9268 | * of SMELL is often intangible, so the default "you can't see that" |
| | 9269 | * message is often incongruous for this verb |
| | 9270 | */ |
| | 9271 | noMatch(msgObj, actor, txt) { msgObj.noMatchNotAware(actor, txt); } |
| | 9272 | ; |
| | 9273 | |
| | 9274 | VerbRule(SmellImplicit) |
| | 9275 | 'smell' | 'sniff' |
| | 9276 | : SmellImplicitAction |
| | 9277 | verbPhrase = 'smell/smelling' |
| | 9278 | ; |
| | 9279 | |
| | 9280 | VerbRule(ListenTo) |
| | 9281 | ('hear' | 'listen' 'to' ) dobjList |
| | 9282 | : ListenToAction |
| | 9283 | verbPhrase = 'listen/listening (to what)' |
| | 9284 | |
| | 9285 | /* |
| | 9286 | * use the "not aware" version of the no-match message - the object |
| | 9287 | * of LISTEN TO is often intangible, so the default "you can't see |
| | 9288 | * that" message is often incongruous for this verb |
| | 9289 | */ |
| | 9290 | noMatch(msgObj, actor, txt) { msgObj.noMatchNotAware(actor, txt); } |
| | 9291 | ; |
| | 9292 | |
| | 9293 | VerbRule(ListenImplicit) |
| | 9294 | 'listen' | 'hear' |
| | 9295 | : ListenImplicitAction |
| | 9296 | verbPhrase = 'listen/listening' |
| | 9297 | ; |
| | 9298 | |
| | 9299 | VerbRule(PutIn) |
| | 9300 | ('put' | 'place' | 'set') dobjList |
| | 9301 | ('in' | 'into' | 'in' 'to' | 'inside' | 'inside' 'of') singleIobj |
| | 9302 | : PutInAction |
| | 9303 | verbPhrase = 'put/putting (what) (in what)' |
| | 9304 | askIobjResponseProd = inSingleNoun |
| | 9305 | ; |
| | 9306 | |
| | 9307 | VerbRule(PutOn) |
| | 9308 | ('put' | 'place' | 'drop' | 'set') dobjList |
| | 9309 | ('on' | 'onto' | 'on' 'to' | 'upon') singleIobj |
| | 9310 | | 'put' dobjList 'down' 'on' singleIobj |
| | 9311 | : PutOnAction |
| | 9312 | verbPhrase = 'put/putting (what) (on what)' |
| | 9313 | askIobjResponseProd = onSingleNoun |
| | 9314 | ; |
| | 9315 | |
| | 9316 | VerbRule(PutUnder) |
| | 9317 | ('put' | 'place' | 'set') dobjList 'under' singleIobj |
| | 9318 | : PutUnderAction |
| | 9319 | verbPhrase = 'put/putting (what) (under what)' |
| | 9320 | ; |
| | 9321 | |
| | 9322 | VerbRule(PutBehind) |
| | 9323 | ('put' | 'place' | 'set') dobjList 'behind' singleIobj |
| | 9324 | : PutBehindAction |
| | 9325 | verbPhrase = 'put/putting (what) (behind what)' |
| | 9326 | ; |
| | 9327 | |
| | 9328 | VerbRule(PutInWhat) |
| | 9329 | [badness 500] ('put' | 'place') dobjList |
| | 9330 | : PutInAction |
| | 9331 | verbPhrase = 'put/putting (what) (in what)' |
| | 9332 | construct() |
| | 9333 | { |
| | 9334 | /* set up the empty indirect object phrase */ |
| | 9335 | iobjMatch = new EmptyNounPhraseProd(); |
| | 9336 | iobjMatch.responseProd = inSingleNoun; |
| | 9337 | } |
| | 9338 | ; |
| | 9339 | |
| | 9340 | VerbRule(Wear) |
| | 9341 | ('wear' | 'don' | 'put' 'on') dobjList |
| | 9342 | | 'put' dobjList 'on' |
| | 9343 | : WearAction |
| | 9344 | verbPhrase = 'wear/wearing (what)' |
| | 9345 | ; |
| | 9346 | |
| | 9347 | VerbRule(Doff) |
| | 9348 | ('doff' | 'take' 'off') dobjList |
| | 9349 | | 'take' dobjList 'off' |
| | 9350 | : DoffAction |
| | 9351 | verbPhrase = 'take/taking off (what)' |
| | 9352 | ; |
| | 9353 | |
| | 9354 | VerbRule(Kiss) |
| | 9355 | 'kiss' singleDobj |
| | 9356 | : KissAction |
| | 9357 | verbPhrase = 'kiss/kissing (whom)' |
| | 9358 | ; |
| | 9359 | |
| | 9360 | VerbRule(AskFor) |
| | 9361 | ('ask' | 'a') singleDobj 'for' singleTopic |
| | 9362 | | ('ask' | 'a') 'for' singleTopic 'from' singleDobj |
| | 9363 | : AskForAction |
| | 9364 | verbPhrase = 'ask/asking (whom) (for what)' |
| | 9365 | omitIobjInDobjQuery = true |
| | 9366 | askDobjResponseProd = singleNoun |
| | 9367 | askIobjResponseProd = forSingleNoun |
| | 9368 | ; |
| | 9369 | |
| | 9370 | VerbRule(AskWhomFor) |
| | 9371 | ('ask' | 'a') 'for' singleTopic |
| | 9372 | : AskForAction |
| | 9373 | verbPhrase = 'ask/asking (whom) (for what)' |
| | 9374 | omitIobjInDobjQuery = true |
| | 9375 | construct() |
| | 9376 | { |
| | 9377 | /* set up the empty direct object phrase */ |
| | 9378 | dobjMatch = new EmptyNounPhraseProd(); |
| | 9379 | dobjMatch.responseProd = singleNoun; |
| | 9380 | } |
| | 9381 | ; |
| | 9382 | |
| | 9383 | VerbRule(AskAbout) |
| | 9384 | ('ask' | 'a') singleDobj 'about' singleTopic |
| | 9385 | : AskAboutAction |
| | 9386 | verbPhrase = 'ask/asking (whom) (about what)' |
| | 9387 | omitIobjInDobjQuery = true |
| | 9388 | askDobjResponseProd = singleNoun |
| | 9389 | ; |
| | 9390 | |
| | 9391 | VerbRule(AskAboutImplicit) |
| | 9392 | 'a' singleTopic |
| | 9393 | : AskAboutAction |
| | 9394 | verbPhrase = 'ask/asking (whom) (about what)' |
| | 9395 | omitIobjInDobjQuery = true |
| | 9396 | construct() |
| | 9397 | { |
| | 9398 | /* set up the empty direct object phrase */ |
| | 9399 | dobjMatch = new EmptyNounPhraseProd(); |
| | 9400 | dobjMatch.responseProd = singleNoun; |
| | 9401 | } |
| | 9402 | ; |
| | 9403 | |
| | 9404 | VerbRule(AskAboutWhat) |
| | 9405 | [badness 500] 'ask' singleDobj |
| | 9406 | : AskAboutAction |
| | 9407 | verbPhrase = 'ask/asking (whom) (about what)' |
| | 9408 | askDobjResponseProd = singleNoun |
| | 9409 | omitIobjInDobjQuery = true |
| | 9410 | construct() |
| | 9411 | { |
| | 9412 | /* set up the empty topic phrase */ |
| | 9413 | topicMatch = new EmptyNounPhraseProd(); |
| | 9414 | topicMatch.responseProd = aboutTopicPhrase; |
| | 9415 | } |
| | 9416 | ; |
| | 9417 | |
| | 9418 | |
| | 9419 | VerbRule(TellAbout) |
| | 9420 | ('tell' | 't') singleDobj 'about' singleTopic |
| | 9421 | : TellAboutAction |
| | 9422 | verbPhrase = 'tell/telling (whom) (about what)' |
| | 9423 | askDobjResponseProd = singleNoun |
| | 9424 | omitIobjInDobjQuery = true |
| | 9425 | ; |
| | 9426 | |
| | 9427 | VerbRule(TellAboutImplicit) |
| | 9428 | 't' singleTopic |
| | 9429 | : TellAboutAction |
| | 9430 | verbPhrase = 'tell/telling (whom) (about what)' |
| | 9431 | omitIobjInDobjQuery = true |
| | 9432 | construct() |
| | 9433 | { |
| | 9434 | /* set up the empty direct object phrase */ |
| | 9435 | dobjMatch = new EmptyNounPhraseProd(); |
| | 9436 | dobjMatch.responseProd = singleNoun; |
| | 9437 | } |
| | 9438 | ; |
| | 9439 | |
| | 9440 | VerbRule(TellAboutWhat) |
| | 9441 | [badness 500] 'tell' singleDobj |
| | 9442 | : TellAboutAction |
| | 9443 | verbPhrase = 'tell/telling (whom) (about what)' |
| | 9444 | askDobjResponseProd = singleNoun |
| | 9445 | omitIobjInDobjQuery = true |
| | 9446 | construct() |
| | 9447 | { |
| | 9448 | /* set up the empty topic phrase */ |
| | 9449 | topicMatch = new EmptyNounPhraseProd(); |
| | 9450 | topicMatch.responseProd = aboutTopicPhrase; |
| | 9451 | } |
| | 9452 | ; |
| | 9453 | |
| | 9454 | VerbRule(AskVague) |
| | 9455 | [badness 500] 'ask' singleDobj singleTopic |
| | 9456 | : AskVagueAction |
| | 9457 | verbPhrase = 'ask/asking (whom)' |
| | 9458 | ; |
| | 9459 | |
| | 9460 | VerbRule(TellVague) |
| | 9461 | [badness 500] 'tell' singleDobj singleTopic |
| | 9462 | : AskVagueAction |
| | 9463 | verbPhrase = 'ask/asking (whom)' |
| | 9464 | ; |
| | 9465 | |
| | 9466 | VerbRule(TalkTo) |
| | 9467 | ('greet' | 'say' 'hello' 'to' | 'talk' 'to') singleDobj |
| | 9468 | : TalkToAction |
| | 9469 | verbPhrase = 'talk/talking (to whom)' |
| | 9470 | askDobjResponseProd = singleNoun |
| | 9471 | ; |
| | 9472 | |
| | 9473 | VerbRule(TalkToWhat) |
| | 9474 | [badness 500] 'talk' |
| | 9475 | : TalkToAction |
| | 9476 | verbPhrase = 'talk/talking (to whom)' |
| | 9477 | askDobjResponseProd = singleNoun |
| | 9478 | |
| | 9479 | construct() |
| | 9480 | { |
| | 9481 | /* set up the empty direct object phrase */ |
| | 9482 | dobjMatch = new EmptyNounPhraseProd(); |
| | 9483 | dobjMatch.responseProd = onSingleNoun; |
| | 9484 | } |
| | 9485 | ; |
| | 9486 | |
| | 9487 | VerbRule(Topics) |
| | 9488 | 'topics' |
| | 9489 | : TopicsAction |
| | 9490 | verbPhrase = 'show/showing topics' |
| | 9491 | ; |
| | 9492 | |
| | 9493 | VerbRule(Hello) |
| | 9494 | ('say' | ) ('hello' | 'hallo' | 'hi') |
| | 9495 | : HelloAction |
| | 9496 | verbPhrase = 'say/saying hello' |
| | 9497 | ; |
| | 9498 | |
| | 9499 | VerbRule(Goodbye) |
| | 9500 | ('say' | ()) ('goodbye' | 'good-bye' | 'good' 'bye' | 'bye') |
| | 9501 | : GoodbyeAction |
| | 9502 | verbPhrase = 'say/saying goodbye' |
| | 9503 | ; |
| | 9504 | |
| | 9505 | VerbRule(Yes) |
| | 9506 | 'yes' | 'affirmative' | 'say' 'yes' |
| | 9507 | : YesAction |
| | 9508 | verbPhrase = 'say/saying yes' |
| | 9509 | ; |
| | 9510 | |
| | 9511 | VerbRule(No) |
| | 9512 | 'no' | 'negative' | 'say' 'no' |
| | 9513 | : NoAction |
| | 9514 | verbPhrase = 'say/saying no' |
| | 9515 | ; |
| | 9516 | |
| | 9517 | VerbRule(Yell) |
| | 9518 | 'yell' | 'scream' | 'shout' | 'holler' |
| | 9519 | : YellAction |
| | 9520 | verbPhrase = 'yell/yelling' |
| | 9521 | ; |
| | 9522 | |
| | 9523 | VerbRule(GiveTo) |
| | 9524 | ('give' | 'offer') dobjList 'to' singleIobj |
| | 9525 | : GiveToAction |
| | 9526 | verbPhrase = 'give/giving (what) (to whom)' |
| | 9527 | askIobjResponseProd = toSingleNoun |
| | 9528 | ; |
| | 9529 | |
| | 9530 | VerbRule(GiveToType2) |
| | 9531 | ('give' | 'offer') singleIobj dobjList |
| | 9532 | : GiveToAction |
| | 9533 | verbPhrase = 'give/giving (what) (to whom)' |
| | 9534 | askIobjResponseProd = toSingleNoun |
| | 9535 | |
| | 9536 | /* this is a non-prepositional phrasing */ |
| | 9537 | isPrepositionalPhrasing = nil |
| | 9538 | ; |
| | 9539 | |
| | 9540 | VerbRule(GiveToWhom) |
| | 9541 | ('give' | 'offer') dobjList |
| | 9542 | : GiveToAction |
| | 9543 | verbPhrase = 'give/giving (what) (to whom)' |
| | 9544 | construct() |
| | 9545 | { |
| | 9546 | /* set up the empty indirect object phrase */ |
| | 9547 | iobjMatch = new ImpliedActorNounPhraseProd(); |
| | 9548 | iobjMatch.responseProd = toSingleNoun; |
| | 9549 | } |
| | 9550 | ; |
| | 9551 | |
| | 9552 | VerbRule(ShowTo) |
| | 9553 | 'show' dobjList 'to' singleIobj |
| | 9554 | : ShowToAction |
| | 9555 | verbPhrase = 'show/showing (what) (to whom)' |
| | 9556 | askIobjResponseProd = toSingleNoun |
| | 9557 | ; |
| | 9558 | |
| | 9559 | VerbRule(ShowToType2) |
| | 9560 | 'show' singleIobj dobjList |
| | 9561 | : ShowToAction |
| | 9562 | verbPhrase = 'show/showing (what) (to whom)' |
| | 9563 | askIobjResponseProd = toSingleNoun |
| | 9564 | |
| | 9565 | /* this is a non-prepositional phrasing */ |
| | 9566 | isPrepositionalPhrasing = nil |
| | 9567 | ; |
| | 9568 | |
| | 9569 | VerbRule(ShowToWhom) |
| | 9570 | 'show' dobjList |
| | 9571 | : ShowToAction |
| | 9572 | verbPhrase = 'show/showing (what) (to whom)' |
| | 9573 | construct() |
| | 9574 | { |
| | 9575 | /* set up the empty indirect object phrase */ |
| | 9576 | iobjMatch = new ImpliedActorNounPhraseProd(); |
| | 9577 | iobjMatch.responseProd = toSingleNoun; |
| | 9578 | } |
| | 9579 | ; |
| | 9580 | |
| | 9581 | VerbRule(Throw) |
| | 9582 | ('throw' | 'toss') dobjList |
| | 9583 | : ThrowAction |
| | 9584 | verbPhrase = 'throw/throwing (what)' |
| | 9585 | ; |
| | 9586 | |
| | 9587 | VerbRule(ThrowAt) |
| | 9588 | ('throw' | 'toss') dobjList 'at' singleIobj |
| | 9589 | : ThrowAtAction |
| | 9590 | verbPhrase = 'throw/throwing (what) (at what)' |
| | 9591 | askIobjResponseProd = atSingleNoun |
| | 9592 | ; |
| | 9593 | |
| | 9594 | VerbRule(ThrowTo) |
| | 9595 | ('throw' | 'toss') dobjList 'to' singleIobj |
| | 9596 | : ThrowToAction |
| | 9597 | verbPhrase = 'throw/throwing (what) (to whom)' |
| | 9598 | askIobjResponseProd = toSingleNoun |
| | 9599 | ; |
| | 9600 | |
| | 9601 | VerbRule(ThrowToType2) |
| | 9602 | 'throw' singleIobj dobjList |
| | 9603 | : ThrowToAction |
| | 9604 | verbPhrase = 'throw/throwing (what) (to whom)' |
| | 9605 | askIobjResponseProd = toSingleNoun |
| | 9606 | |
| | 9607 | /* this is a non-prepositional phrasing */ |
| | 9608 | isPrepositionalPhrasing = nil |
| | 9609 | ; |
| | 9610 | |
| | 9611 | VerbRule(ThrowDir) |
| | 9612 | ('throw' | 'toss') dobjList ('to' ('the' | ) | ) singleDir |
| | 9613 | : ThrowDirAction |
| | 9614 | verbPhrase = ('throw/throwing (what) ' + dirMatch.dir.name) |
| | 9615 | ; |
| | 9616 | |
| | 9617 | /* a special rule for THROW DOWN <dobj> */ |
| | 9618 | VerbRule(ThrowDirDown) |
| | 9619 | 'throw' ('down' | 'd') dobjList |
| | 9620 | : ThrowDirAction |
| | 9621 | verbPhrase = ('throw/throwing (what) down') |
| | 9622 | |
| | 9623 | /* the direction is fixed as 'down' for this phrasing */ |
| | 9624 | getDirection() { return downDirection; } |
| | 9625 | ; |
| | 9626 | |
| | 9627 | VerbRule(Follow) |
| | 9628 | 'follow' singleDobj |
| | 9629 | : FollowAction |
| | 9630 | verbPhrase = 'follow/following (whom)' |
| | 9631 | askDobjResponseProd = singleNoun |
| | 9632 | ; |
| | 9633 | |
| | 9634 | VerbRule(Attack) |
| | 9635 | ('attack' | 'kill' | 'hit' | 'kick' | 'punch') singleDobj |
| | 9636 | : AttackAction |
| | 9637 | verbPhrase = 'attack/attacking (whom)' |
| | 9638 | askDobjResponseProd = singleNoun |
| | 9639 | ; |
| | 9640 | |
| | 9641 | VerbRule(AttackWith) |
| | 9642 | ('attack' | 'kill' | 'hit' | 'kick' | 'punch' | 'strike') |
| | 9643 | singleDobj |
| | 9644 | 'with' singleIobj |
| | 9645 | : AttackWithAction |
| | 9646 | verbPhrase = 'attack/attacking (whom) (with what)' |
| | 9647 | askDobjResponseProd = singleNoun |
| | 9648 | askIobjResponseProd = withSingleNoun |
| | 9649 | ; |
| | 9650 | |
| | 9651 | VerbRule(Inventory) |
| | 9652 | 'i' | 'inventory' | 'take' 'inventory' |
| | 9653 | : InventoryAction |
| | 9654 | verbPhrase = 'take/taking inventory' |
| | 9655 | ; |
| | 9656 | |
| | 9657 | VerbRule(InventoryTall) |
| | 9658 | 'i' 'tall' | 'inventory' 'tall' |
| | 9659 | : InventoryTallAction |
| | 9660 | verbPhrase = 'take/taking "tall" inventory' |
| | 9661 | ; |
| | 9662 | |
| | 9663 | VerbRule(InventoryWide) |
| | 9664 | 'i' 'wide' | 'inventory' 'wide' |
| | 9665 | : InventoryWideAction |
| | 9666 | verbPhrase = 'take/taking "wide" inventory' |
| | 9667 | ; |
| | 9668 | |
| | 9669 | VerbRule(Wait) |
| | 9670 | 'z' | 'wait' |
| | 9671 | : WaitAction |
| | 9672 | verbPhrase = 'wait/waiting' |
| | 9673 | ; |
| | 9674 | |
| | 9675 | VerbRule(Look) |
| | 9676 | 'look' | 'look' 'around' | 'l' | 'l' 'around' |
| | 9677 | : LookAction |
| | 9678 | verbPhrase = 'look/looking around' |
| | 9679 | ; |
| | 9680 | |
| | 9681 | VerbRule(Quit) |
| | 9682 | 'quit' | 'q' |
| | 9683 | : QuitAction |
| | 9684 | verbPhrase = 'quit/quitting' |
| | 9685 | ; |
| | 9686 | |
| | 9687 | VerbRule(Again) |
| | 9688 | 'again' | 'g' |
| | 9689 | : AgainAction |
| | 9690 | verbPhrase = 'repeat/repeating the last command' |
| | 9691 | ; |
| | 9692 | |
| | 9693 | VerbRule(Footnote) |
| | 9694 | ('footnote' | 'note') singleNumber |
| | 9695 | : FootnoteAction |
| | 9696 | verbPhrase = 'show/showing a footnote' |
| | 9697 | ; |
| | 9698 | |
| | 9699 | VerbRule(FootnotesFull) |
| | 9700 | 'footnotes' 'full' |
| | 9701 | : FootnotesFullAction |
| | 9702 | verbPhrase = 'enable/enabling all footnotes' |
| | 9703 | ; |
| | 9704 | |
| | 9705 | VerbRule(FootnotesMedium) |
| | 9706 | 'footnotes' 'medium' |
| | 9707 | : FootnotesMediumAction |
| | 9708 | verbPhrase = 'enable/enabling new footnotes' |
| | 9709 | ; |
| | 9710 | |
| | 9711 | VerbRule(FootnotesOff) |
| | 9712 | 'footnotes' 'off' |
| | 9713 | : FootnotesOffAction |
| | 9714 | verbPhrase = 'hide/hiding footnotes' |
| | 9715 | ; |
| | 9716 | |
| | 9717 | VerbRule(FootnotesStatus) |
| | 9718 | 'footnotes' |
| | 9719 | : FootnotesStatusAction |
| | 9720 | verbPhrase = 'show/showing footnote status' |
| | 9721 | ; |
| | 9722 | |
| | 9723 | VerbRule(TipsOn) |
| | 9724 | ('tips' | 'tip') 'on' |
| | 9725 | : TipModeAction |
| | 9726 | |
| | 9727 | stat_ = true |
| | 9728 | |
| | 9729 | verbPhrase = 'turn/turning tips on' |
| | 9730 | ; |
| | 9731 | |
| | 9732 | VerbRule(TipsOff) |
| | 9733 | ('tips' | 'tip') 'off' |
| | 9734 | : TipModeAction |
| | 9735 | |
| | 9736 | stat_ = nil |
| | 9737 | |
| | 9738 | verbPhrase = 'turn/turning tips off' |
| | 9739 | ; |
| | 9740 | |
| | 9741 | VerbRule(Verbose) |
| | 9742 | 'verbose' |
| | 9743 | : VerboseAction |
| | 9744 | verbPhrase = 'enter/entering VERBOSE mode' |
| | 9745 | ; |
| | 9746 | |
| | 9747 | VerbRule(Terse) |
| | 9748 | 'terse' | 'brief' |
| | 9749 | : TerseAction |
| | 9750 | verbPhrase = 'enter/entering BRIEF mode' |
| | 9751 | ; |
| | 9752 | |
| | 9753 | VerbRule(Score) |
| | 9754 | 'score' | 'status' |
| | 9755 | : ScoreAction |
| | 9756 | verbPhrase = 'show/showing score' |
| | 9757 | ; |
| | 9758 | |
| | 9759 | VerbRule(FullScore) |
| | 9760 | 'full' 'score' | 'fullscore' | 'full' |
| | 9761 | : FullScoreAction |
| | 9762 | verbPhrase = 'show/showing full score' |
| | 9763 | ; |
| | 9764 | |
| | 9765 | VerbRule(Notify) |
| | 9766 | 'notify' |
| | 9767 | : NotifyAction |
| | 9768 | verbPhrase = 'show/showing notification status' |
| | 9769 | ; |
| | 9770 | |
| | 9771 | VerbRule(NotifyOn) |
| | 9772 | 'notify' 'on' |
| | 9773 | : NotifyOnAction |
| | 9774 | verbPhrase = 'turn/turning on score notification' |
| | 9775 | ; |
| | 9776 | |
| | 9777 | VerbRule(NotifyOff) |
| | 9778 | 'notify' 'off' |
| | 9779 | : NotifyOffAction |
| | 9780 | verbPhrase = 'turn/turning off score notification' |
| | 9781 | ; |
| | 9782 | |
| | 9783 | VerbRule(Save) |
| | 9784 | 'save' |
| | 9785 | : SaveAction |
| | 9786 | verbPhrase = 'save/saving' |
| | 9787 | ; |
| | 9788 | |
| | 9789 | VerbRule(SaveString) |
| | 9790 | 'save' quotedStringPhrase->fname_ |
| | 9791 | : SaveStringAction |
| | 9792 | verbPhrase = 'save/saving' |
| | 9793 | ; |
| | 9794 | |
| | 9795 | VerbRule(Restore) |
| | 9796 | 'restore' |
| | 9797 | : RestoreAction |
| | 9798 | verbPhrase = 'restore/restoring' |
| | 9799 | ; |
| | 9800 | |
| | 9801 | VerbRule(RestoreString) |
| | 9802 | 'restore' quotedStringPhrase->fname_ |
| | 9803 | : RestoreStringAction |
| | 9804 | verbPhrase = 'restore/restoring' |
| | 9805 | ; |
| | 9806 | |
| | 9807 | VerbRule(SaveDefaults) |
| | 9808 | 'save' 'defaults' |
| | 9809 | : SaveDefaultsAction |
| | 9810 | verbPhrase = 'save/saving defaults' |
| | 9811 | ; |
| | 9812 | |
| | 9813 | VerbRule(RestoreDefaults) |
| | 9814 | 'restore' 'defaults' |
| | 9815 | : RestoreDefaultsAction |
| | 9816 | verbPhrase = 'restore/restoring defaults' |
| | 9817 | ; |
| | 9818 | |
| | 9819 | VerbRule(Restart) |
| | 9820 | 'restart' |
| | 9821 | : RestartAction |
| | 9822 | verbPhrase = 'restart/restarting' |
| | 9823 | ; |
| | 9824 | |
| | 9825 | VerbRule(Pause) |
| | 9826 | 'pause' |
| | 9827 | : PauseAction |
| | 9828 | verbPhrase = 'pause/pausing' |
| | 9829 | ; |
| | 9830 | |
| | 9831 | VerbRule(Undo) |
| | 9832 | 'undo' |
| | 9833 | : UndoAction |
| | 9834 | verbPhrase = 'undo/undoing' |
| | 9835 | ; |
| | 9836 | |
| | 9837 | VerbRule(Version) |
| | 9838 | 'version' |
| | 9839 | : VersionAction |
| | 9840 | verbPhrase = 'show/showing version' |
| | 9841 | ; |
| | 9842 | |
| | 9843 | VerbRule(Credits) |
| | 9844 | 'credits' |
| | 9845 | : CreditsAction |
| | 9846 | verbPhrase = 'show/showing credits' |
| | 9847 | ; |
| | 9848 | |
| | 9849 | VerbRule(About) |
| | 9850 | 'about' |
| | 9851 | : AboutAction |
| | 9852 | verbPhrase = 'show/showing story information' |
| | 9853 | ; |
| | 9854 | |
| | 9855 | VerbRule(Script) |
| | 9856 | 'script' | 'script' 'on' |
| | 9857 | : ScriptAction |
| | 9858 | verbPhrase = 'start/starting scripting' |
| | 9859 | ; |
| | 9860 | |
| | 9861 | VerbRule(ScriptString) |
| | 9862 | 'script' quotedStringPhrase->fname_ |
| | 9863 | : ScriptStringAction |
| | 9864 | verbPhrase = 'start/starting scripting' |
| | 9865 | ; |
| | 9866 | |
| | 9867 | VerbRule(ScriptOff) |
| | 9868 | 'script' 'off' | 'unscript' |
| | 9869 | : ScriptOffAction |
| | 9870 | verbPhrase = 'end/ending scripting' |
| | 9871 | ; |
| | 9872 | |
| | 9873 | VerbRule(Record) |
| | 9874 | 'record' | 'record' 'on' |
| | 9875 | : RecordAction |
| | 9876 | verbPhrase = 'start/starting command recording' |
| | 9877 | ; |
| | 9878 | |
| | 9879 | VerbRule(RecordString) |
| | 9880 | 'record' quotedStringPhrase->fname_ |
| | 9881 | : RecordStringAction |
| | 9882 | verbPhrase = 'start/starting command recording' |
| | 9883 | ; |
| | 9884 | |
| | 9885 | VerbRule(RecordEvents) |
| | 9886 | 'record' 'events' | 'record' 'events' 'on' |
| | 9887 | : RecordEventsAction |
| | 9888 | verbPhrase = 'start/starting event recording' |
| | 9889 | ; |
| | 9890 | |
| | 9891 | VerbRule(RecordEventsString) |
| | 9892 | 'record' 'events' quotedStringPhrase->fname_ |
| | 9893 | : RecordEventsStringAction |
| | 9894 | verbPhrase = 'start/starting command recording' |
| | 9895 | ; |
| | 9896 | |
| | 9897 | VerbRule(RecordOff) |
| | 9898 | 'record' 'off' |
| | 9899 | : RecordOffAction |
| | 9900 | verbPhrase = 'end/ending command recording' |
| | 9901 | ; |
| | 9902 | |
| | 9903 | VerbRule(ReplayString) |
| | 9904 | 'replay' ('quiet'->quiet_ | 'nonstop'->nonstop_ | ) |
| | 9905 | (quotedStringPhrase->fname_ | ) |
| | 9906 | : ReplayStringAction |
| | 9907 | verbPhrase = 'replay/replaying command recording' |
| | 9908 | |
| | 9909 | /* set the appropriate option flags */ |
| | 9910 | scriptOptionFlags = ((quiet_ != nil ? ScriptFileQuiet : 0) |
| | 9911 | | (nonstop_ != nil ? ScriptFileNonstop : 0)) |
| | 9912 | ; |
| | 9913 | VerbRule(ReplayQuiet) |
| | 9914 | 'rq' (quotedStringPhrase->fname_ | ) |
| | 9915 | : ReplayStringAction |
| | 9916 | |
| | 9917 | scriptOptionFlags = ScriptFileQuiet |
| | 9918 | ; |
| | 9919 | |
| | 9920 | VerbRule(VagueTravel) 'go' | 'walk' : VagueTravelAction |
| | 9921 | verbPhrase = 'go' |
| | 9922 | ; |
| | 9923 | |
| | 9924 | VerbRule(Travel) |
| | 9925 | 'go' singleDir | singleDir |
| | 9926 | : TravelAction |
| | 9927 | verbPhrase = ('go/going ' + dirMatch.dir.name) |
| | 9928 | ; |
| | 9929 | |
| | 9930 | /* |
| | 9931 | * Create a TravelVia subclass merely so we can supply a verbPhrase. |
| | 9932 | * (The parser looks for subclasses of each specific Action class to find |
| | 9933 | * its verb phrase, since the language-specific Action definitions are |
| | 9934 | * always in the language module's 'grammar' subclasses. We don't need |
| | 9935 | * an actual grammar rule, since this isn't an input-able verb, so we |
| | 9936 | * merely need to create a regular subclass in order for the verbPhrase |
| | 9937 | * to get found.) |
| | 9938 | */ |
| | 9939 | class EnTravelVia: TravelViaAction |
| | 9940 | verbPhrase = 'use/using (what)' |
| | 9941 | ; |
| | 9942 | |
| | 9943 | VerbRule(Port) |
| | 9944 | 'go' 'to' ('port' | 'p') |
| | 9945 | : PortAction |
| | 9946 | dirMatch: DirectionProd { dir = portDirection } |
| | 9947 | verbPhrase = 'go/going to port' |
| | 9948 | ; |
| | 9949 | |
| | 9950 | VerbRule(Starboard) |
| | 9951 | 'go' 'to' ('starboard' | 'sb') |
| | 9952 | : StarboardAction |
| | 9953 | dirMatch: DirectionProd { dir = starboardDirection } |
| | 9954 | verbPhrase = 'go/going to starboard' |
| | 9955 | ; |
| | 9956 | |
| | 9957 | VerbRule(In) |
| | 9958 | 'enter' |
| | 9959 | : InAction |
| | 9960 | dirMatch: DirectionProd { dir = inDirection } |
| | 9961 | verbPhrase = 'enter/entering' |
| | 9962 | ; |
| | 9963 | |
| | 9964 | VerbRule(Out) |
| | 9965 | 'exit' | 'leave' |
| | 9966 | : OutAction |
| | 9967 | dirMatch: DirectionProd { dir = outDirection } |
| | 9968 | verbPhrase = 'exit/exiting' |
| | 9969 | ; |
| | 9970 | |
| | 9971 | VerbRule(GoThrough) |
| | 9972 | ('walk' | 'go' ) ('through' | 'thru') |
| | 9973 | singleDobj |
| | 9974 | : GoThroughAction |
| | 9975 | verbPhrase = 'go/going (through what)' |
| | 9976 | askDobjResponseProd = singleNoun |
| | 9977 | ; |
| | 9978 | |
| | 9979 | VerbRule(Enter) |
| | 9980 | ('enter' | 'in' | 'into' | 'in' 'to' |
| | 9981 | | ('walk' | 'go') ('to' | 'in' | 'in' 'to' | 'into')) |
| | 9982 | singleDobj |
| | 9983 | : EnterAction |
| | 9984 | verbPhrase = 'enter/entering (what)' |
| | 9985 | askDobjResponseProd = singleNoun |
| | 9986 | ; |
| | 9987 | |
| | 9988 | VerbRule(GoBack) |
| | 9989 | 'back' | 'go' 'back' | 'return' |
| | 9990 | : GoBackAction |
| | 9991 | verbPhrase = 'go/going back' |
| | 9992 | ; |
| | 9993 | |
| | 9994 | VerbRule(Dig) |
| | 9995 | ('dig' | 'dig' 'in') singleDobj |
| | 9996 | : DigAction |
| | 9997 | verbPhrase = 'dig/digging (in what)' |
| | 9998 | askDobjResponseProd = inSingleNoun |
| | 9999 | ; |
| | 10000 | |
| | 10001 | VerbRule(DigWith) |
| | 10002 | ('dig' | 'dig' 'in') singleDobj 'with' singleIobj |
| | 10003 | : DigWithAction |
| | 10004 | verbPhrase = 'dig/digging (in what) (with what)' |
| | 10005 | omitIobjInDobjQuery = true |
| | 10006 | askDobjResponseProd = inSingleNoun |
| | 10007 | askIobjResponseProd = withSingleNoun |
| | 10008 | ; |
| | 10009 | |
| | 10010 | VerbRule(Jump) |
| | 10011 | 'jump' |
| | 10012 | : JumpAction |
| | 10013 | verbPhrase = 'jump/jumping' |
| | 10014 | ; |
| | 10015 | |
| | 10016 | VerbRule(JumpOffI) |
| | 10017 | 'jump' 'off' |
| | 10018 | : JumpOffIAction |
| | 10019 | verbPhrase = 'jump/jumping off' |
| | 10020 | ; |
| | 10021 | |
| | 10022 | VerbRule(JumpOff) |
| | 10023 | 'jump' 'off' singleDobj |
| | 10024 | : JumpOffAction |
| | 10025 | verbPhrase = 'jump/jumping (off what)' |
| | 10026 | askDobjResponseProd = singleNoun |
| | 10027 | ; |
| | 10028 | |
| | 10029 | VerbRule(JumpOver) |
| | 10030 | ('jump' | 'jump' 'over') singleDobj |
| | 10031 | : JumpOverAction |
| | 10032 | verbPhrase = 'jump/jumping (over what)' |
| | 10033 | askDobjResponseProd = singleNoun |
| | 10034 | ; |
| | 10035 | |
| | 10036 | VerbRule(Push) |
| | 10037 | ('push' | 'press') dobjList |
| | 10038 | : PushAction |
| | 10039 | verbPhrase = 'push/pushing (what)' |
| | 10040 | ; |
| | 10041 | |
| | 10042 | VerbRule(Pull) |
| | 10043 | 'pull' dobjList |
| | 10044 | : PullAction |
| | 10045 | verbPhrase = 'pull/pulling (what)' |
| | 10046 | ; |
| | 10047 | |
| | 10048 | VerbRule(Move) |
| | 10049 | 'move' dobjList |
| | 10050 | : MoveAction |
| | 10051 | verbPhrase = 'move/moving (what)' |
| | 10052 | ; |
| | 10053 | |
| | 10054 | VerbRule(MoveTo) |
| | 10055 | ('push' | 'move') dobjList ('to' | 'under') singleIobj |
| | 10056 | : MoveToAction |
| | 10057 | verbPhrase = 'move/moving (what) (to what)' |
| | 10058 | askIobjResponseProd = toSingleNoun |
| | 10059 | omitIobjInDobjQuery = true |
| | 10060 | ; |
| | 10061 | |
| | 10062 | VerbRule(MoveWith) |
| | 10063 | 'move' singleDobj 'with' singleIobj |
| | 10064 | : MoveWithAction |
| | 10065 | verbPhrase = 'move/moving (what) (with what)' |
| | 10066 | askDobjResponseProd = singleNoun |
| | 10067 | askIobjResponseProd = withSingleNoun |
| | 10068 | omitIobjInDobjQuery = true |
| | 10069 | ; |
| | 10070 | |
| | 10071 | VerbRule(Turn) |
| | 10072 | ('turn' | 'twist' | 'rotate') dobjList |
| | 10073 | : TurnAction |
| | 10074 | verbPhrase = 'turn/turning (what)' |
| | 10075 | ; |
| | 10076 | |
| | 10077 | VerbRule(TurnWith) |
| | 10078 | ('turn' | 'twist' | 'rotate') singleDobj 'with' singleIobj |
| | 10079 | : TurnWithAction |
| | 10080 | verbPhrase = 'turn/turning (what) (with what)' |
| | 10081 | askDobjResponseProd = singleNoun |
| | 10082 | askIobjResponseProd = withSingleNoun |
| | 10083 | ; |
| | 10084 | |
| | 10085 | VerbRule(TurnTo) |
| | 10086 | ('turn' | 'twist' | 'rotate') singleDobj |
| | 10087 | 'to' singleLiteral |
| | 10088 | : TurnToAction |
| | 10089 | verbPhrase = 'turn/turning (what) (to what)' |
| | 10090 | askDobjResponseProd = singleNoun |
| | 10091 | omitIobjInDobjQuery = true |
| | 10092 | ; |
| | 10093 | |
| | 10094 | VerbRule(Set) |
| | 10095 | 'set' dobjList |
| | 10096 | : SetAction |
| | 10097 | verbPhrase = 'set/setting (what)' |
| | 10098 | ; |
| | 10099 | |
| | 10100 | VerbRule(SetTo) |
| | 10101 | 'set' singleDobj 'to' singleLiteral |
| | 10102 | : SetToAction |
| | 10103 | verbPhrase = 'set/setting (what) (to what)' |
| | 10104 | askDobjResponseProd = singleNoun |
| | 10105 | omitIobjInDobjQuery = true |
| | 10106 | ; |
| | 10107 | |
| | 10108 | VerbRule(TypeOn) |
| | 10109 | 'type' 'on' singleDobj |
| | 10110 | : TypeOnAction |
| | 10111 | verbPhrase = 'type/typing (on what)' |
| | 10112 | ; |
| | 10113 | |
| | 10114 | VerbRule(TypeLiteralOn) |
| | 10115 | 'type' singleLiteral 'on' singleDobj |
| | 10116 | : TypeLiteralOnAction |
| | 10117 | verbPhrase = 'type/typing (what) (on what)' |
| | 10118 | askDobjResponseProd = singleNoun |
| | 10119 | ; |
| | 10120 | |
| | 10121 | VerbRule(TypeLiteralOnWhat) |
| | 10122 | [badness 500] 'type' singleLiteral |
| | 10123 | : TypeLiteralOnAction |
| | 10124 | verbPhrase = 'type/typing (what) (on what)' |
| | 10125 | construct() |
| | 10126 | { |
| | 10127 | /* set up the empty direct object phrase */ |
| | 10128 | dobjMatch = new EmptyNounPhraseProd(); |
| | 10129 | dobjMatch.responseProd = onSingleNoun; |
| | 10130 | } |
| | 10131 | ; |
| | 10132 | |
| | 10133 | VerbRule(EnterOn) |
| | 10134 | 'enter' singleLiteral |
| | 10135 | ('on' | 'in' | 'in' 'to' | 'into' | 'with') singleDobj |
| | 10136 | : EnterOnAction |
| | 10137 | verbPhrase = 'enter/entering (what) (on what)' |
| | 10138 | askDobjResponseProd = singleNoun |
| | 10139 | ; |
| | 10140 | |
| | 10141 | VerbRule(EnterOnWhat) |
| | 10142 | 'enter' singleLiteral |
| | 10143 | : EnterOnAction |
| | 10144 | verbPhrase = 'enter/entering (what) (on what)' |
| | 10145 | construct() |
| | 10146 | { |
| | 10147 | /* |
| | 10148 | * ENTER <text> is a little special, because it could mean ENTER |
| | 10149 | * <text> ON <keypad>, or it could mean GO INTO <object>. It's |
| | 10150 | * hard to tell which based on the grammar alone, so we have to |
| | 10151 | * do some semantic analysis to make a good decision about it. |
| | 10152 | * |
| | 10153 | * We'll start by assuming it's the ENTER <text> ON <iobj> form |
| | 10154 | * of the command, and we'll look for a suitable default object |
| | 10155 | * to serve as the iobj. If we can't find a suitable default, we |
| | 10156 | * won't prompt for the missing object as we usually would. |
| | 10157 | * Instead, we'll try re-parsing the command as GO INTO. To do |
| | 10158 | * this, use our custom "asker" - this won't actually prompt for |
| | 10159 | * the missing object, but will instead retry the command as a GO |
| | 10160 | * INTO command. |
| | 10161 | */ |
| | 10162 | dobjMatch = new EmptyNounPhraseProd(); |
| | 10163 | dobjMatch.setPrompt(onSingleNoun, enterOnWhatAsker); |
| | 10164 | } |
| | 10165 | ; |
| | 10166 | |
| | 10167 | /* our custom "asker" for the missing iobj in an "ENTER <text>" command */ |
| | 10168 | enterOnWhatAsker: ResolveAsker |
| | 10169 | askMissingObject(targetActor, action, which) |
| | 10170 | { |
| | 10171 | /* |
| | 10172 | * This method is called when the resolver has failed to find a |
| | 10173 | * suitable default for the missing indirect object of ENTER |
| | 10174 | * <text> ON <iobj>. |
| | 10175 | * |
| | 10176 | * Instead of issuing the prompt that we'd normally issue under |
| | 10177 | * these circumstances, assume that we're totally wrong about the |
| | 10178 | * way we've been interpreting the command: assume that it's not |
| | 10179 | * meant as ENTER <text> ON <iobj> after all, but was actually |
| | 10180 | * meant as GO IN <object>. So, rephrase the command as such and |
| | 10181 | * start over with the new phrasing. |
| | 10182 | */ |
| | 10183 | throw new ReplacementCommandStringException( |
| | 10184 | 'get in ' + action.getLiteral(), gIssuingActor, gActor); |
| | 10185 | } |
| | 10186 | ; |
| | 10187 | |
| | 10188 | VerbRule(Consult) |
| | 10189 | 'consult' singleDobj : ConsultAction |
| | 10190 | verbPhrase = 'consult/consulting (what)' |
| | 10191 | askDobjResponseProd = singleNoun |
| | 10192 | ; |
| | 10193 | |
| | 10194 | VerbRule(ConsultAbout) |
| | 10195 | 'consult' singleDobj ('on' | 'about') singleTopic |
| | 10196 | | 'search' singleDobj 'for' singleTopic |
| | 10197 | | (('look' | 'l') ('up' | 'for') |
| | 10198 | | 'find' |
| | 10199 | | 'search' 'for' |
| | 10200 | | 'read' 'about') |
| | 10201 | singleTopic 'in' singleDobj |
| | 10202 | | ('look' | 'l') singleTopic 'up' 'in' singleDobj |
| | 10203 | : ConsultAboutAction |
| | 10204 | verbPhrase = 'consult/consulting (what) (about what)' |
| | 10205 | omitIobjInDobjQuery = true |
| | 10206 | askDobjResponseProd = singleNoun |
| | 10207 | ; |
| | 10208 | |
| | 10209 | VerbRule(ConsultWhatAbout) |
| | 10210 | (('look' | 'l') ('up' | 'for') |
| | 10211 | | 'find' |
| | 10212 | | 'search' 'for' |
| | 10213 | | 'read' 'about') |
| | 10214 | singleTopic |
| | 10215 | | ('look' | 'l') singleTopic 'up' |
| | 10216 | : ConsultAboutAction |
| | 10217 | verbPhrase = 'look/looking up (what) (in what)' |
| | 10218 | whichMessageTopic = DirectObject |
| | 10219 | construct() |
| | 10220 | { |
| | 10221 | /* set up the empty direct object phrase */ |
| | 10222 | dobjMatch = new EmptyNounPhraseProd(); |
| | 10223 | dobjMatch.responseProd = inSingleNoun; |
| | 10224 | } |
| | 10225 | ; |
| | 10226 | |
| | 10227 | VerbRule(Switch) |
| | 10228 | 'switch' dobjList |
| | 10229 | : SwitchAction |
| | 10230 | verbPhrase = 'switch/switching (what)' |
| | 10231 | ; |
| | 10232 | |
| | 10233 | VerbRule(Flip) |
| | 10234 | 'flip' dobjList |
| | 10235 | : FlipAction |
| | 10236 | verbPhrase = 'flip/flipping (what)' |
| | 10237 | ; |
| | 10238 | |
| | 10239 | VerbRule(TurnOn) |
| | 10240 | ('activate' | ('turn' | 'switch') 'on') dobjList |
| | 10241 | | ('turn' | 'switch') dobjList 'on' |
| | 10242 | : TurnOnAction |
| | 10243 | verbPhrase = 'turn/turning on (what)' |
| | 10244 | ; |
| | 10245 | |
| | 10246 | VerbRule(TurnOff) |
| | 10247 | ('deactivate' | ('turn' | 'switch') 'off') dobjList |
| | 10248 | | ('turn' | 'switch') dobjList 'off' |
| | 10249 | : TurnOffAction |
| | 10250 | verbPhrase = 'turn/turning off (what)' |
| | 10251 | ; |
| | 10252 | |
| | 10253 | VerbRule(Light) |
| | 10254 | 'light' dobjList |
| | 10255 | : LightAction |
| | 10256 | verbPhrase = 'light/lighting (what)' |
| | 10257 | ; |
| | 10258 | |
| | 10259 | DefineTAction(Strike); |
| | 10260 | VerbRule(Strike) |
| | 10261 | 'strike' dobjList |
| | 10262 | : StrikeAction |
| | 10263 | verbPhrase = 'strike/striking (what)' |
| | 10264 | ; |
| | 10265 | |
| | 10266 | VerbRule(Burn) |
| | 10267 | ('burn' | 'ignite' | 'set' 'fire' 'to') dobjList |
| | 10268 | : BurnAction |
| | 10269 | verbPhrase = 'light/lighting (what)' |
| | 10270 | ; |
| | 10271 | |
| | 10272 | VerbRule(BurnWith) |
| | 10273 | ('light' | 'burn' | 'ignite' | 'set' 'fire' 'to') singleDobj |
| | 10274 | 'with' singleIobj |
| | 10275 | : BurnWithAction |
| | 10276 | verbPhrase = 'light/lighting (what) (with what)' |
| | 10277 | omitIobjInDobjQuery = true |
| | 10278 | askDobjResponseProd = singleNoun |
| | 10279 | askIobjResponseProd = withSingleNoun |
| | 10280 | ; |
| | 10281 | |
| | 10282 | VerbRule(Extinguish) |
| | 10283 | ('extinguish' | 'douse' | 'put' 'out' | 'blow' 'out') dobjList |
| | 10284 | | ('blow' | 'put') dobjList 'out' |
| | 10285 | : ExtinguishAction |
| | 10286 | verbPhrase = 'extinguish/extinguishing (what)' |
| | 10287 | ; |
| | 10288 | |
| | 10289 | VerbRule(Break) |
| | 10290 | ('break' | 'ruin' | 'destroy' | 'wreck') dobjList |
| | 10291 | : BreakAction |
| | 10292 | verbPhrase = 'break/breaking (what)' |
| | 10293 | ; |
| | 10294 | |
| | 10295 | VerbRule(CutWithWhat) |
| | 10296 | [badness 500] 'cut' singleDobj |
| | 10297 | : CutWithAction |
| | 10298 | verbPhrase = 'cut/cutting (what) (with what)' |
| | 10299 | construct() |
| | 10300 | { |
| | 10301 | /* set up the empty indirect object phrase */ |
| | 10302 | iobjMatch = new EmptyNounPhraseProd(); |
| | 10303 | iobjMatch.responseProd = withSingleNoun; |
| | 10304 | } |
| | 10305 | ; |
| | 10306 | |
| | 10307 | VerbRule(CutWith) |
| | 10308 | 'cut' singleDobj 'with' singleIobj |
| | 10309 | : CutWithAction |
| | 10310 | verbPhrase = 'cut/cutting (what) (with what)' |
| | 10311 | askDobjResponseProd = singleNoun |
| | 10312 | askIobjResponseProd = withSingleNoun |
| | 10313 | ; |
| | 10314 | |
| | 10315 | VerbRule(Eat) |
| | 10316 | ('eat' | 'consume') dobjList |
| | 10317 | : EatAction |
| | 10318 | verbPhrase = 'eat/eating (what)' |
| | 10319 | ; |
| | 10320 | |
| | 10321 | VerbRule(Drink) |
| | 10322 | ('drink' | 'quaff' | 'imbibe') dobjList |
| | 10323 | : DrinkAction |
| | 10324 | verbPhrase = 'drink/drinking (what)' |
| | 10325 | ; |
| | 10326 | |
| | 10327 | VerbRule(Pour) |
| | 10328 | 'pour' dobjList |
| | 10329 | : PourAction |
| | 10330 | verbPhrase = 'pour/pouring (what)' |
| | 10331 | ; |
| | 10332 | |
| | 10333 | VerbRule(PourInto) |
| | 10334 | 'pour' dobjList ('in' | 'into' | 'in' 'to') singleIobj |
| | 10335 | : PourIntoAction |
| | 10336 | verbPhrase = 'pour/pouring (what) (into what)' |
| | 10337 | askIobjResponseProd = inSingleNoun |
| | 10338 | ; |
| | 10339 | |
| | 10340 | VerbRule(PourOnto) |
| | 10341 | 'pour' dobjList ('on' | 'onto' | 'on' 'to') singleIobj |
| | 10342 | : PourOntoAction |
| | 10343 | verbPhrase = 'pour/pouring (what) (onto what)' |
| | 10344 | askIobjResponseProd = onSingleNoun |
| | 10345 | ; |
| | 10346 | |
| | 10347 | VerbRule(Climb) |
| | 10348 | 'climb' singleDobj |
| | 10349 | : ClimbAction |
| | 10350 | verbPhrase = 'climb/climbing (what)' |
| | 10351 | askDobjResponseProd = singleNoun |
| | 10352 | ; |
| | 10353 | |
| | 10354 | VerbRule(ClimbUp) |
| | 10355 | ('climb' | 'go' | 'walk') 'up' singleDobj |
| | 10356 | : ClimbUpAction |
| | 10357 | verbPhrase = 'climb/climbing (up what)' |
| | 10358 | askDobjResponseProd = singleNoun |
| | 10359 | ; |
| | 10360 | |
| | 10361 | VerbRule(ClimbUpWhat) |
| | 10362 | [badness 200] ('climb' | 'go' | 'walk') 'up' |
| | 10363 | : ClimbUpAction |
| | 10364 | verbPhrase = 'climb/climbing (up what)' |
| | 10365 | askDobjResponseProd = singleNoun |
| | 10366 | construct() |
| | 10367 | { |
| | 10368 | dobjMatch = new EmptyNounPhraseProd(); |
| | 10369 | dobjMatch.responseProd = onSingleNoun; |
| | 10370 | } |
| | 10371 | ; |
| | 10372 | |
| | 10373 | VerbRule(ClimbDown) |
| | 10374 | ('climb' | 'go' | 'walk') 'down' singleDobj |
| | 10375 | : ClimbDownAction |
| | 10376 | verbPhrase = 'climb/climbing (down what)' |
| | 10377 | askDobjResponseProd = singleNoun |
| | 10378 | ; |
| | 10379 | |
| | 10380 | VerbRule(ClimbDownWhat) |
| | 10381 | [badness 200] ('climb' | 'go' | 'walk') 'down' |
| | 10382 | : ClimbDownAction |
| | 10383 | verbPhrase = 'climb/climbing (down what)' |
| | 10384 | askDobjResponseProd = singleNoun |
| | 10385 | construct() |
| | 10386 | { |
| | 10387 | dobjMatch = new EmptyNounPhraseProd(); |
| | 10388 | dobjMatch.responseProd = onSingleNoun; |
| | 10389 | } |
| | 10390 | ; |
| | 10391 | |
| | 10392 | VerbRule(Clean) |
| | 10393 | 'clean' dobjList |
| | 10394 | : CleanAction |
| | 10395 | verbPhrase = 'clean/cleaning (what)' |
| | 10396 | ; |
| | 10397 | |
| | 10398 | VerbRule(CleanWith) |
| | 10399 | 'clean' dobjList 'with' singleIobj |
| | 10400 | : CleanWithAction |
| | 10401 | verbPhrase = 'clean/cleaning (what) (with what)' |
| | 10402 | askIobjResponseProd = withSingleNoun |
| | 10403 | omitIobjInDobjQuery = true |
| | 10404 | ; |
| | 10405 | |
| | 10406 | VerbRule(AttachTo) |
| | 10407 | ('attach' | 'connect') dobjList 'to' singleIobj |
| | 10408 | : AttachToAction |
| | 10409 | askIobjResponseProd = toSingleNoun |
| | 10410 | verbPhrase = 'attach/attaching (what) (to what)' |
| | 10411 | ; |
| | 10412 | |
| | 10413 | VerbRule(AttachToWhat) |
| | 10414 | [badness 500] ('attach' | 'connect') dobjList |
| | 10415 | : AttachToAction |
| | 10416 | verbPhrase = 'attach/attaching (what) (to what)' |
| | 10417 | construct() |
| | 10418 | { |
| | 10419 | /* set up the empty indirect object phrase */ |
| | 10420 | iobjMatch = new EmptyNounPhraseProd(); |
| | 10421 | iobjMatch.responseProd = toSingleNoun; |
| | 10422 | } |
| | 10423 | ; |
| | 10424 | |
| | 10425 | VerbRule(DetachFrom) |
| | 10426 | ('detach' | 'disconnect') dobjList 'from' singleIobj |
| | 10427 | : DetachFromAction |
| | 10428 | verbPhrase = 'detach/detaching (what) (from what)' |
| | 10429 | askIobjResponseProd = fromSingleNoun |
| | 10430 | ; |
| | 10431 | |
| | 10432 | VerbRule(Detach) |
| | 10433 | ('detach' | 'disconnect') dobjList |
| | 10434 | : DetachAction |
| | 10435 | verbPhrase = 'detach/detaching (what)' |
| | 10436 | ; |
| | 10437 | |
| | 10438 | VerbRule(Open) |
| | 10439 | 'open' dobjList |
| | 10440 | : OpenAction |
| | 10441 | verbPhrase = 'open/opening (what)' |
| | 10442 | ; |
| | 10443 | |
| | 10444 | VerbRule(Close) |
| | 10445 | ('close' | 'shut') dobjList |
| | 10446 | : CloseAction |
| | 10447 | verbPhrase = 'close/closing (what)' |
| | 10448 | ; |
| | 10449 | |
| | 10450 | VerbRule(Lock) |
| | 10451 | 'lock' dobjList |
| | 10452 | : LockAction |
| | 10453 | verbPhrase = 'lock/locking (what)' |
| | 10454 | ; |
| | 10455 | |
| | 10456 | VerbRule(Unlock) |
| | 10457 | 'unlock' dobjList |
| | 10458 | : UnlockAction |
| | 10459 | verbPhrase = 'unlock/unlocking (what)' |
| | 10460 | ; |
| | 10461 | |
| | 10462 | VerbRule(LockWith) |
| | 10463 | 'lock' singleDobj 'with' singleIobj |
| | 10464 | : LockWithAction |
| | 10465 | verbPhrase = 'lock/locking (what) (with what)' |
| | 10466 | omitIobjInDobjQuery = true |
| | 10467 | askDobjResponseProd = singleNoun |
| | 10468 | askIobjResponseProd = withSingleNoun |
| | 10469 | ; |
| | 10470 | |
| | 10471 | VerbRule(UnlockWith) |
| | 10472 | 'unlock' singleDobj 'with' singleIobj |
| | 10473 | : UnlockWithAction |
| | 10474 | verbPhrase = 'unlock/unlocking (what) (with what)' |
| | 10475 | omitIobjInDobjQuery = true |
| | 10476 | askDobjResponseProd = singleNoun |
| | 10477 | askIobjResponseProd = withSingleNoun |
| | 10478 | ; |
| | 10479 | |
| | 10480 | VerbRule(SitOn) |
| | 10481 | 'sit' ('on' | 'in' | 'down' 'on' | 'down' 'in') |
| | 10482 | singleDobj |
| | 10483 | : SitOnAction |
| | 10484 | verbPhrase = 'sit/sitting (on what)' |
| | 10485 | askDobjResponseProd = singleNoun |
| | 10486 | |
| | 10487 | /* use the actorInPrep, if there's a direct object available */ |
| | 10488 | adjustDefaultObjectPrep(prep, obj) |
| | 10489 | { return (obj != nil ? obj.actorInPrep + ' ' : prep); } |
| | 10490 | ; |
| | 10491 | |
| | 10492 | VerbRule(Sit) |
| | 10493 | 'sit' ( | 'down') : SitAction |
| | 10494 | verbPhrase = 'sit/sitting down' |
| | 10495 | ; |
| | 10496 | |
| | 10497 | VerbRule(LieOn) |
| | 10498 | 'lie' ('on' | 'in' | 'down' 'on' | 'down' 'in') |
| | 10499 | singleDobj |
| | 10500 | : LieOnAction |
| | 10501 | verbPhrase = 'lie/lying (on what)' |
| | 10502 | askDobjResponseProd = singleNoun |
| | 10503 | |
| | 10504 | /* use the actorInPrep, if there's a direct object available */ |
| | 10505 | adjustDefaultObjectPrep(prep, obj) |
| | 10506 | { return (obj != nil ? obj.actorInPrep + ' ' : prep); } |
| | 10507 | ; |
| | 10508 | |
| | 10509 | VerbRule(Lie) |
| | 10510 | 'lie' ( | 'down') : LieAction |
| | 10511 | verbPhrase = 'lie/lying down' |
| | 10512 | ; |
| | 10513 | |
| | 10514 | VerbRule(StandOn) |
| | 10515 | ('stand' ('on' | 'in' | 'onto' | 'on' 'to' | 'into' | 'in' 'to') |
| | 10516 | | 'climb' ('on' | 'onto' | 'on' 'to')) |
| | 10517 | singleDobj |
| | 10518 | : StandOnAction |
| | 10519 | verbPhrase = 'stand/standing (on what)' |
| | 10520 | askDobjResponseProd = singleNoun |
| | 10521 | |
| | 10522 | /* use the actorInPrep, if there's a direct object available */ |
| | 10523 | adjustDefaultObjectPrep(prep, obj) |
| | 10524 | { return (obj != nil ? obj.actorInPrep + ' ' : prep); } |
| | 10525 | ; |
| | 10526 | |
| | 10527 | VerbRule(Stand) |
| | 10528 | 'stand' | 'stand' 'up' | 'get' 'up' |
| | 10529 | : StandAction |
| | 10530 | verbPhrase = 'stand/standing up' |
| | 10531 | ; |
| | 10532 | |
| | 10533 | VerbRule(GetOutOf) |
| | 10534 | ('out' 'of' | 'get' 'out' 'of' | 'climb' 'out' 'of' | 'leave' | 'exit') |
| | 10535 | singleDobj |
| | 10536 | : GetOutOfAction |
| | 10537 | verbPhrase = 'get/getting (out of what)' |
| | 10538 | askDobjResponseProd = singleNoun |
| | 10539 | |
| | 10540 | /* use the actorOutOfPrep, if there's a direct object available */ |
| | 10541 | adjustDefaultObjectPrep(prep, obj) |
| | 10542 | { return (obj != nil ? obj.actorOutOfPrep + ' ' : prep); } |
| | 10543 | ; |
| | 10544 | |
| | 10545 | VerbRule(GetOffOf) |
| | 10546 | 'get' ('off' | 'off' 'of' | 'down' 'from') singleDobj |
| | 10547 | : GetOffOfAction |
| | 10548 | verbPhrase = 'get/getting (off of what)' |
| | 10549 | askDobjResponseProd = singleNoun |
| | 10550 | |
| | 10551 | /* use the actorOutOfPrep, if there's a direct object available */ |
| | 10552 | adjustDefaultObjectPrep(prep, obj) |
| | 10553 | { return (obj != nil ? obj.actorOutOfPrep + ' ' : prep); } |
| | 10554 | ; |
| | 10555 | |
| | 10556 | VerbRule(GetOut) |
| | 10557 | 'get' 'out' |
| | 10558 | | 'get' 'off' |
| | 10559 | | 'get' 'down' |
| | 10560 | | 'disembark' |
| | 10561 | | 'climb' 'out' |
| | 10562 | : GetOutAction |
| | 10563 | verbPhrase = 'get/getting out' |
| | 10564 | ; |
| | 10565 | |
| | 10566 | VerbRule(Board) |
| | 10567 | ('board' |
| | 10568 | | ('get' ('in' | 'into' | 'in' 'to' | 'on' | 'onto' | 'on' 'to')) |
| | 10569 | | ('climb' ('in' | 'into' | 'in' 'to'))) |
| | 10570 | singleDobj |
| | 10571 | : BoardAction |
| | 10572 | verbPhrase = 'get/getting (in what)' |
| | 10573 | askDobjResponseProd = singleNoun |
| | 10574 | ; |
| | 10575 | |
| | 10576 | VerbRule(Sleep) |
| | 10577 | 'sleep' |
| | 10578 | : SleepAction |
| | 10579 | verbPhrase = 'sleep/sleeping' |
| | 10580 | ; |
| | 10581 | |
| | 10582 | VerbRule(Fasten) |
| | 10583 | ('fasten' | 'buckle' | 'buckle' 'up') dobjList |
| | 10584 | : FastenAction |
| | 10585 | verbPhrase = 'fasten/fastening (what)' |
| | 10586 | ; |
| | 10587 | |
| | 10588 | VerbRule(FastenTo) |
| | 10589 | ('fasten' | 'buckle') dobjList 'to' singleIobj |
| | 10590 | : FastenToAction |
| | 10591 | verbPhrase = 'fasten/fastening (what) (to what)' |
| | 10592 | askIobjResponseProd = toSingleNoun |
| | 10593 | ; |
| | 10594 | |
| | 10595 | VerbRule(Unfasten) |
| | 10596 | ('unfasten' | 'unbuckle') dobjList |
| | 10597 | : UnfastenAction |
| | 10598 | verbPhrase = 'unfasten/unfastening (what)' |
| | 10599 | ; |
| | 10600 | |
| | 10601 | VerbRule(UnfastenFrom) |
| | 10602 | ('unfasten' | 'unbuckle') dobjList 'from' singleIobj |
| | 10603 | : UnfastenFromAction |
| | 10604 | verbPhrase = 'unfasten/unfastening (what) (from what)' |
| | 10605 | askIobjResponseProd = fromSingleNoun |
| | 10606 | ; |
| | 10607 | |
| | 10608 | VerbRule(PlugInto) |
| | 10609 | 'plug' dobjList ('in' | 'into' | 'in' 'to') singleIobj |
| | 10610 | : PlugIntoAction |
| | 10611 | verbPhrase = 'plug/plugging (what) (into what)' |
| | 10612 | askIobjResponseProd = inSingleNoun |
| | 10613 | ; |
| | 10614 | |
| | 10615 | VerbRule(PlugIntoWhat) |
| | 10616 | [badness 500] 'plug' dobjList |
| | 10617 | : PlugIntoAction |
| | 10618 | verbPhrase = 'plug/plugging (what) (into what)' |
| | 10619 | construct() |
| | 10620 | { |
| | 10621 | /* set up the empty indirect object phrase */ |
| | 10622 | iobjMatch = new EmptyNounPhraseProd(); |
| | 10623 | iobjMatch.responseProd = inSingleNoun; |
| | 10624 | } |
| | 10625 | ; |
| | 10626 | |
| | 10627 | VerbRule(PlugIn) |
| | 10628 | 'plug' dobjList 'in' |
| | 10629 | | 'plug' 'in' dobjList |
| | 10630 | : PlugInAction |
| | 10631 | verbPhrase = 'plug/plugging (what)' |
| | 10632 | ; |
| | 10633 | |
| | 10634 | VerbRule(UnplugFrom) |
| | 10635 | 'unplug' dobjList 'from' singleIobj |
| | 10636 | : UnplugFromAction |
| | 10637 | verbPhrase = 'unplug/unplugging (what) (from what)' |
| | 10638 | askIobjResponseProd = fromSingleNoun |
| | 10639 | ; |
| | 10640 | |
| | 10641 | VerbRule(Unplug) |
| | 10642 | 'unplug' dobjList |
| | 10643 | : UnplugAction |
| | 10644 | verbPhrase = 'unplug/unplugging (what)' |
| | 10645 | ; |
| | 10646 | |
| | 10647 | VerbRule(Screw) |
| | 10648 | 'screw' dobjList |
| | 10649 | : ScrewAction |
| | 10650 | verbPhrase = 'screw/screwing (what)' |
| | 10651 | ; |
| | 10652 | |
| | 10653 | VerbRule(ScrewWith) |
| | 10654 | 'screw' dobjList 'with' singleIobj |
| | 10655 | : ScrewWithAction |
| | 10656 | verbPhrase = 'screw/screwing (what) (with what)' |
| | 10657 | omitIobjInDobjQuery = true |
| | 10658 | askIobjResponseProd = withSingleNoun |
| | 10659 | ; |
| | 10660 | |
| | 10661 | VerbRule(Unscrew) |
| | 10662 | 'unscrew' dobjList |
| | 10663 | : UnscrewAction |
| | 10664 | verbPhrase = 'unscrew/unscrewing (what)' |
| | 10665 | ; |
| | 10666 | |
| | 10667 | VerbRule(UnscrewWith) |
| | 10668 | 'unscrew' dobjList 'with' singleIobj |
| | 10669 | : UnscrewWithAction |
| | 10670 | verbPhrase = 'unscrew/unscrewing (what) (with what)' |
| | 10671 | omitIobjInDobjQuery = true |
| | 10672 | askIobjResponseProd = withSingleNoun |
| | 10673 | ; |
| | 10674 | |
| | 10675 | VerbRule(PushTravelDir) |
| | 10676 | ('push' | 'pull' | 'drag' | 'move') singleDobj singleDir |
| | 10677 | : PushTravelDirAction |
| | 10678 | verbPhrase = ('push/pushing (what) ' + dirMatch.dir.name) |
| | 10679 | ; |
| | 10680 | |
| | 10681 | VerbRule(PushTravelThrough) |
| | 10682 | ('push' | 'pull' | 'drag' | 'move') singleDobj |
| | 10683 | ('through' | 'thru') singleIobj |
| | 10684 | : PushTravelThroughAction |
| | 10685 | verbPhrase = 'push/pushing (what) (through what)' |
| | 10686 | ; |
| | 10687 | |
| | 10688 | VerbRule(PushTravelEnter) |
| | 10689 | ('push' | 'pull' | 'drag' | 'move') singleDobj |
| | 10690 | ('in' | 'into' | 'in' 'to') singleIobj |
| | 10691 | : PushTravelEnterAction |
| | 10692 | verbPhrase = 'push/pushing (what) (into what)' |
| | 10693 | ; |
| | 10694 | |
| | 10695 | VerbRule(PushTravelGetOutOf) |
| | 10696 | ('push' | 'pull' | 'drag' | 'move') singleDobj |
| | 10697 | 'out' ('of' | ) singleIobj |
| | 10698 | : PushTravelGetOutOfAction |
| | 10699 | verbPhrase = 'push/pushing (what) (out of what)' |
| | 10700 | ; |
| | 10701 | |
| | 10702 | |
| | 10703 | VerbRule(PushTravelClimbUp) |
| | 10704 | ('push' | 'pull' | 'drag' | 'move') singleDobj |
| | 10705 | 'up' singleIobj |
| | 10706 | : PushTravelClimbUpAction |
| | 10707 | verbPhrase = 'push/pushing (what) (up what)' |
| | 10708 | omitIobjInDobjQuery = true |
| | 10709 | ; |
| | 10710 | |
| | 10711 | VerbRule(PushTravelClimbDown) |
| | 10712 | ('push' | 'pull' | 'drag' | 'move') singleDobj |
| | 10713 | 'down' singleIobj |
| | 10714 | : PushTravelClimbDownAction |
| | 10715 | verbPhrase = 'push/pushing (what) (down what)' |
| | 10716 | ; |
| | 10717 | |
| | 10718 | VerbRule(Exits) |
| | 10719 | 'exits' |
| | 10720 | : ExitsAction |
| | 10721 | verbPhrase = 'exits/showing exits' |
| | 10722 | ; |
| | 10723 | |
| | 10724 | VerbRule(ExitsMode) |
| | 10725 | 'exits' ('on'->on_ | 'all'->on_ |
| | 10726 | | 'off'->off_ | 'none'->off_ |
| | 10727 | | ('status' ('line' | ) | 'statusline') 'look'->on_ |
| | 10728 | | 'look'->on_ ('status' ('line' | ) | 'statusline') |
| | 10729 | | 'status'->stat_ ('line' | ) | 'statusline'->stat_ |
| | 10730 | | 'look'->look_) |
| | 10731 | : ExitsModeAction |
| | 10732 | verbPhrase = 'turn/turning off exits display' |
| | 10733 | ; |
| | 10734 | |
| | 10735 | VerbRule(HintsOff) |
| | 10736 | 'hints' 'off' |
| | 10737 | : HintsOffAction |
| | 10738 | verbPhrase = 'disable/disabling hints' |
| | 10739 | ; |
| | 10740 | |
| | 10741 | VerbRule(Hint) |
| | 10742 | 'hint' | 'hints' |
| | 10743 | : HintAction |
| | 10744 | verbPhrase = 'show/showing hints' |
| | 10745 | ; |
| | 10746 | |
| | 10747 | VerbRule(Oops) |
| | 10748 | ('oops' | 'o') singleLiteral |
| | 10749 | : OopsAction |
| | 10750 | verbPhrase = 'oops/correcting (what)' |
| | 10751 | ; |
| | 10752 | |
| | 10753 | VerbRule(OopsOnly) |
| | 10754 | ('oops' | 'o') |
| | 10755 | : OopsIAction |
| | 10756 | verbPhrase = 'oops/correcting' |
| | 10757 | ; |
| | 10758 | |
| | 10759 | /* ------------------------------------------------------------------------ */ |
| | 10760 | /* |
| | 10761 | * "debug" verb - special verb to break into the debugger. We'll only |
| | 10762 | * compile this into the game if we're compiling a debug version to begin |
| | 10763 | * with, since a non-debug version can't be run under the debugger. |
| | 10764 | */ |
| | 10765 | #ifdef __DEBUG |
| | 10766 | |
| | 10767 | VerbRule(Debug) |
| | 10768 | 'debug' |
| | 10769 | : DebugAction |
| | 10770 | verbPhrase = 'debug/debugging' |
| | 10771 | ; |
| | 10772 | |
| | 10773 | #endif /* __DEBUG */ |
| | 10774 | |