en_us.t | documentation |
#charset "us-ascii" /* * Copyright 2000, 2006 Michael J. Roberts. All Rights Reserved. *. Past-tense extensions written by Michel Nizette, and incorporated by * permission. * * TADS 3 Library - English (United States variant) implementation * * This defines the parts of the TADS 3 library that are specific to the * English language as spoken (and written) in the United States. * * We have attempted to isolate here the parts of the library that are * language-specific, so that translations to other languages or dialects * can be created by replacing this module, without changing the rest of * the library. * * In addition to this module, a separate set of US English messages are * defined in the various msg_xxx.t modules. Those modules define * messages in English for different stylistic variations. For a given * game, the author must select one of the message modules - but only * one, since they all define variations of the same messages. To * translate the library, a translator must create at least one module * defining those messages as well; only one message module is required * per language. * * The past-tense system was contributed by Michel Nizette. * *. ----- * * "Watch an immigrant struggling with a second language or a stroke * patient with a first one, or deconstruct a snatch of baby talk, or try * to program a computer to understand English, and ordinary speech * begins to look different." * *. Stephen Pinker, "The Language Instinct" */ #include "tads.h" #include "tok.h" #include "adv3.h" #include "en_us.h" #include <vector.h> #include <dict.h> #include <gramprod.h> #include <strcomp.h> /* ------------------------------------------------------------------------ */ /* * Fill in the default language for the GameInfo metadata class. */ modify GameInfoModuleID languageCode = 'en-US' ; /* ------------------------------------------------------------------------ */ /* * Simple yes/no confirmation. The caller must display a prompt; we'll * read a command line response, then return true if it's an affirmative * response, nil if not. */ yesOrNo() { /* switch to no-command mode for the interactive input */ "<.commandnone>"; /* * Read a line of input. Do not allow real-time event processing; * this type of prompt is used in the middle of a command, so we * don't want any interruptions. Note that the caller must display * any desired prompt, and since we don't allow interruptions, we * won't need to redisplay the prompt, so we pass nil for the prompt * callback. */ local str = inputManager.getInputLine(nil, nil); /* switch back to mid-command mode */ "<.commandmid>"; /* * If they answered with something starting with 'Y', it's * affirmative, otherwise it's negative. In reading the response, * ignore any leading whitespace. */ return rexMatch('<space>*[yY]', str) != nil; } /* ------------------------------------------------------------------------ */ /* * During start-up, install a case-insensitive truncating comparator in * the main dictionary. */ PreinitObject execute() { /* set up the main dictionary's comparator */ languageGlobals.setStringComparator( new StringComparator(gameMain.parserTruncLength, nil, [])); } /* * Make sure we run BEFORE the main library preinitializer, so that * we install the comparator in the dictionary before we add the * vocabulary words to the dictionary. This doesn't make any * difference in terms of the correctness of the dictionary, since * the dictionary will automatically rebuild itself whenever we * install a new comparator, but it makes the preinitialization run * a tiny bit faster by avoiding that rebuild step. */ execAfterMe = [adv3LibPreinit] ; /* ------------------------------------------------------------------------ */ /* * Language-specific globals */ languageGlobals: object /* * Set the StringComparator object for the parser. This sets the * comparator that's used in the main command parser dictionary. */ setStringComparator(sc) { /* remember it globally, and set it in the main dictionary */ dictComparator = sc; cmdDict.setComparator(sc); } /* * The character to use to separate groups of digits in large * numbers. US English uses commas; most Europeans use periods. * * Note that this setting does not affect system-level BigNumber * formatting, but this information can be passed when calling * BigNumber formatting routines. */ digitGroupSeparator = ',' /* * The decimal point to display in floating-point numbers. US * English uses a period; most Europeans use a comma. * * Note that this setting doesn't affect system-level BigNumber * formatting, but this information can be passed when calling * BigNumber formatting routines. */ decimalPointCharacter = '.' /* the main dictionary's string comparator */ dictComparator = nil ; /* ------------------------------------------------------------------------ */ /* * Language-specific extension of the default gameMain object * implementation. */ modify GameMainDef /* * Option setting: the parser's truncation length for player input. * As a convenience to the player, we can allow the player to * truncate long words, entering only the first, say, 6 characters. * For example, rather than typing "x flashlight", we could allow the * player to simply type "x flashl" - truncating "flashlight" to six * letters. * * We use a default truncation length of 6, but games can change this * by overriding this property in gameMain. We use a default of 6 * mostly because that's what the old Infocom games did - many * long-time IF players are accustomed to six-letter truncation from * those games. Shorter lengths are superficially more convenient * for the player, obviously, but there's a trade-off, which is that * shorter truncation lengths create more potential for ambiguity. * For some games, a longer length might actually be better for the * player, because it would reduce spurious ambiguity due to the * parser matching short input against long vocabulary words. * * If you don't want to allow the player to truncate long words at * all, set this to nil. This will require the player to type every * word in its entirety. * * Note that changing this property dynamicaly will have no effect. * The library only looks at it once, during library initialization * at the very start of the game. If you want to change the * truncation length dynamically, you must instead create a new * StringComparator object with the new truncation setting, and call * languageGlobals.setStringComparator() to select the new object. */ parserTruncLength = 6 /* * Option: are we currently using a past tense narrative? By * default, we aren't. * * This property can be reset at any time during the game in order to * switch between the past and present tenses. The macro * setPastTense can be used for this purpose: it just provides a * shorthand for setting gameMain.usePastTense directly. * * Authors who want their game to start in the past tense can achieve * this by overriding this property on their gameMain object and * giving it a value of true. */ usePastTense = nil ; /* ------------------------------------------------------------------------ */ /* * Language-specific modifications for ThingState. */ modify ThingState /* * Our state-specific tokens. This is a list of vocabulary words * that are state-specific: that is, if a word is in this list, the * word can ONLY refer to this object if the object is in a state * with that word in its list. * * The idea is that you set up the object's "static" vocabulary with * the *complete* list of words for all of its possible states. For * example: * *. + Matchstick 'lit unlit match'; * * Then, you define the states: in the "lit" state, the word 'lit' is * in the stateTokens list; in the "unlit" state, the word 'unlit' is * in the list. By putting the words in the state lists, you * "reserve" the words to their respective states. When the player * enters a command, the parser will limit object matches so that the * reserved state-specific words can only refer to objects in the * corresponding states. Hence, if the player refers to a "lit * match", the word 'lit' will only match an object in the "lit" * state, because 'lit' is a reserved state-specific word associated * with the "lit" state. * * You can re-use a word in multiple states. For example, you could * have a "red painted" state and a "blue painted" state, along with * an "unpainted" state. */ stateTokens = [] /* * Match the name of an object in this state. We'll check the token * list for any words that apply only to *other* states the object * can assume; if we find any, we'll reject the match, since the * phrase must be referring to an object in a different state. */ matchName(obj, origTokens, adjustedTokens, states) { /* scan each word in our adjusted token list */ for (local i = 1, local len = adjustedTokens.length() ; i <= len ; i += 2) { /* get the current token */ local cur = adjustedTokens[i]; /* * If this token is in our own state-specific token list, * it's acceptable as a match to this object. (It doesn't * matter whether or not it's in any other state's token list * if it's in our own, because its presence in our own makes * it an acceptable matching word when we're in this state.) */ if (stateTokens.indexWhich({t: t == cur}) != nil) continue; /* * It's not in our own state-specific token list. Check to * see if the word appears in ANOTHER state's token list: if * it does, then this word CAN'T match an object in this * state, because the token is special to that other state * and thus can't refer to an object in a state without the * token. */ if (states.indexWhich( {s: s.stateTokens.indexOf(cur) != nil}) != nil) return nil; } /* we didn't find any objection, so we can match this phrase */ return obj; } /* * Check a token list for any tokens matching any of our * state-specific words. Returns true if we find any such words, * nil if not. * * 'toks' is the *adjusted* token list used in matchName(). */ findStateToken(toks) { /* * Scan the token list for a match to any of our state-specific * words. Since we're using the adjusted token list, every * other entry is a part of speech, so work through the list in * pairs. */ for (local i = 1, local len = toks.length() ; i <= len ; i += 2) { /* * if this token matches any of our state tokens, indicate * that we found a match */ if (stateTokens.indexWhich({x: x == toks[i]}) != nil) return true; } /* we didn't find a match */ return nil; } /* get our name */ listName(lst) { return listName_; } /* * our list name setting - we define this so that we can be easily * initialized with a template (we can't initialize listName() * directly in this manner because it's a method, but we define the * listName() method to simply return this property value, which we * can initialize with a template) */ listName_ = nil ; /* ------------------------------------------------------------------------ */ /* * Language-specific modifications for VocabObject. */ modify VocabObject /* * The vocabulary initializer string for the object - this string * can be initialized (most conveniently via a template) to a string * of this format: * * 'adj adj adj noun/noun/noun*plural plural plural' * * The noun part of the string can be a hyphen, '-', in which case * it means that the string doesn't specify a noun or plural at all. * This can be useful when nouns and plurals are all inherited from * base classes, and only adjectives are to be specified. (In fact, * any word that consists of a single hyphen will be ignored, but * this is generally only useful for the adjective-only case.) * * During preinitialization, we'll parse this string and generate * dictionary entries and individual vocabulary properties for the * parts of speech we find. * * Note that the format described above is specific to the English * version of the library. Non-English versions will probably want * to use different formats to conveniently encode appropriate * language-specific information in the initializer string. See the * comments for initializeVocabWith() for more details. * * You can use the special wildcard # to match any numeric * adjective. This only works as a wildcard when it stands alone, * so a string like "7#" is matched as that literal string, not as a * wildcard. If you want to use a pound sign as a literal * adjective, just put it in double quotes. * * You can use the special wildcard "\u0001" (include the double * quotes within the string) to match any literal adjective. This * is the literal adjective equivalent of the pound sign. We use * this funny character value because it is unlikely ever to be * interesting in user input. * * If you want to match any string for a noun and/or adjective, you * can't do it with this property. Instead, just add the property * value noun='*' to the object. */ vocabWords = '' /* * On dynamic construction, initialize our vocabulary words and add * them to the dictionary. */ construct() { /* initialize our vocabulary words from vocabWords */ initializeVocab(); /* add our vocabulary words to the dictionary */ addToDictionary(&noun); addToDictionary(&adjective); addToDictionary(&plural); addToDictionary(&adjApostS); addToDictionary(&literalAdjective); } /* add the words from a dictionary property to the global dictionary */ addToDictionary(prop) { /* if we have any words defined, add them to the dictionary */ if (self.(prop) != nil) cmdDict.addWord(self, self.(prop), prop); } /* initialize the vocabulary from vocabWords */ initializeVocab() { /* inherit vocabulary from this class and its superclasses */ inheritVocab(self, new Vector(10)); } /* * Inherit vocabulary from this class and its superclasses, adding * the words to the given target object. 'target' is the object to * which we add our vocabulary words, and 'done' is a vector of * classes that have been visited so far. * * Since a class can be inherited more than once in an inheritance * tree (for example, a class can have multiple superclasses, each * of which have a common base class), we keep a vector of all of * the classes we've visited. If we're already in the vector, we'll * skip adding vocabulary for this class or its superclasses, since * we must have already traversed this branch of the tree from * another subclass. */ inheritVocab(target, done) { /* * if we're in the list of classes handled already, don't bother * visiting me again */ if (done.indexOf(self) != nil) return; /* add myself to the list of classes handled already */ done.append(self); /* * add words from our own vocabWords to the target object (but * only if it's our own - not if it's only inherited, as we'll * pick up the inherited ones explicitly in a bit) */ if (propDefined(&vocabWords, PropDefDirectly)) target.initializeVocabWith(vocabWords); /* add vocabulary from each of our superclasses */ foreach (local sc in getSuperclassList()) sc.inheritVocab(target, done); } /* * Initialize our vocabulary from the given string. This parses the * given vocabulary initializer string and adds the words defined in * the string to the dictionary. * * Note that this parsing is intentionally located in the * English-specific part of the library, because it is expected that * other languages will want to define their own vocabulary * initialization string formats. For example, a language with * gendered nouns might want to use gendered articles in the * initializer string as an author-friendly way of defining noun * gender; languages with inflected (declined) nouns and/or * adjectives might want to encode inflected forms in the * initializer. Non-English language implementations are free to * completely redefine the format - there's no need to follow the * conventions of the English format in other languages where * different formats would be more convenient. */ initializeVocabWith(str) { local sectPart; local modList = []; /* start off in the adjective section */ sectPart = &adjective; /* scan the string until we run out of text */ while (str != '') { local len; local cur; /* * if it starts with a quote, find the close quote; * otherwise, find the end of the current token by seeking * the next delimiter */ if (str.startsWith('"')) { /* find the close quote */ len = str.find('"', 2); } else { /* no quotes - find the next delimiter */ len = rexMatch('<^space|star|/>*', str); } /* if there's no match, use the whole rest of the string */ if (len == nil) len = str.length(); /* if there's anything before the delimiter, extract it */ if (len != 0) { /* extract the part up to but not including the delimiter */ cur = str.substr(1, len); /* * if we're in the adjectives, and either this is the * last token or the next delimiter is not a space, this * is implicitly a noun */ if (sectPart == &adjective && (len == str.length() || str.substr(len + 1, 1) != ' ')) { /* move to the noun section */ sectPart = &noun; } /* * if the word isn't a single hyphen (in which case it's * a null word placeholder, not an actual vocabulary * word), add it to our own appropriate part-of-speech * property and to the dictionary */ if (cur != '-') { /* * by default, use the part of speech of the current * string section as the part of speech for this * word */ local wordPart = sectPart; /* * Check for parentheses, which indicate that the * token is "weak." This doesn't affect anything * about the token or its part of speech except that * we must include the token in our list of weak * tokens. */ if (cur.startsWith('(') && cur.endsWith(')')) { /* it's a weak token - remove the parens */ cur = cur.substr(2, cur.length() - 2); /* * if we don't have a weak token list yet, * create the list */ if (weakTokens == nil) weakTokens = []; /* add the token to the weak list */ weakTokens += cur; } /* * Check for special formats: quoted strings, * apostrophe-S words. These formats are mutually * exclusive. */ if (cur.startsWith('"')) { /* * It's a quoted string, so it's a literal * adjective. */ /* remove the quote(s) */ if (cur.endsWith('"')) cur = cur.substr(2, cur.length() - 2); else cur = cur.substr(2); /* change the part of speech to 'literal adjective' */ wordPart = &literalAdjective; } else if (cur.endsWith('\'s')) { /* * It's an apostrophe-s word. Remove the "'s" * suffix and add the root word using adjApostS * as the part of speech. The grammar rules are * defined to allow this part of speech to be * used exclusively with "'s" suffixes in input. * Since the tokenizer always pulls the "'s" * suffix off of a word in the input, we have to * store any vocabulary words with "'s" suffixes * the same way, with the "'s" suffixes removed. */ /* change the part of speech to adjApostS */ wordPart = &adjApostS; /* remove the "'s" suffix from the string */ cur = cur.substr(1, cur.length() - 2); } /* add the word to our own list for this part of speech */ if (self.(wordPart) == nil) self.(wordPart) = [cur]; else self.(wordPart) += cur; /* add it to the dictionary */ cmdDict.addWord(self, cur, wordPart); if (cur.endsWith('.')) { local abbr; /* * It ends with a period, so this is an * abbreviated word. Enter the abbreviation * both with and without the period. The normal * handling will enter it with the period, so we * only need to enter it specifically without. */ abbr = cur.substr(1, cur.length() - 1); self.(wordPart) += abbr; cmdDict.addWord(self, abbr, wordPart); } /* note that we added to this list */ if (modList.indexOf(wordPart) == nil) modList += wordPart; } } /* if we have a delimiter, see what we have */ if (len + 1 < str.length()) { /* check the delimiter */ switch(str.substr(len + 1, 1)) { case ' ': /* stick with the current part */ break; case '*': /* start plurals */ sectPart = &plural; break; case '/': /* start alternative nouns */ sectPart = &noun; break; } /* remove the part up to and including the delimiter */ str = str.substr(len + 2); /* skip any additional spaces following the delimiter */ if ((len = rexMatch('<space>+', str)) != nil) str = str.substr(len + 1); } else { /* we've exhausted the string - we're done */ break; } } /* uniquify each word list we updated */ foreach (local p in modList) self.(p) = self.(p).getUnique(); } ; /* ------------------------------------------------------------------------ */ /* * Language-specific modifications for Thing. This class contains the * methods and properties of Thing that need to be replaced when the * library is translated to another language. * * The properties and methods defined here should generally never be used * by language-independent library code, because everything defined here * is specific to English. Translators are thus free to change the * entire scheme defined here. For example, the notions of number and * gender are confined to the English part of the library; other language * implementations can completely replace these attributes, so they're * not constrained to emulate their own number and gender systems with * the English system. */ modify Thing /* * Flag that this object's name is rendered as a plural (this * applies to both a singular noun with plural usage, such as * "pants" or "scissors," and an object used in the world model to * represent a collection of real-world objects, such as "shrubs"). */ isPlural = nil /* * Flag that this is object's name is a "mass noun" - that is, a * noun denoting a continuous (effectively infinitely divisible) * substance or material, such as water, wood, or popcorn; and * certain abstract concepts, such as knowledge or beauty. Mass * nouns are never rendered in the plural, and use different * determiners than ordinary ("count") nouns: "some popcorn" vs "a * kernel", for example. */ isMassNoun = nil /* * Flags indicating that the object should be referred to with * gendered pronouns (such as 'he' or 'she' rather than 'it'). * * Note that these flags aren't mutually exclusive, so it's legal * for the object to have both masculine and feminine usage. This * can be useful when creating collective objects that represent * more than one individual, for example. */ isHim = nil isHer = nil /* * Flag indicating that the object can be referred to with a neuter * pronoun ('it'). By default, this is true if the object has * neither masculine nor feminine gender, but it can be overridden * so that an object has both gendered and ungendered usage. This * can be useful for collective objects, as well as for cases where * gendered usage varies by speaker or situation, such as animals. */ isIt { /* by default, we're an 'it' if we're not a 'him' or a 'her' */ return !(isHim || isHer); } /* * Test to see if we can match the pronouns 'him', 'her', 'it', and * 'them'. By default, these simply test the corresponding isXxx * flags (except 'canMatchThem', which tests 'isPlural' to see if the * name has plural usage). */ canMatchHim = (isHim) canMatchHer = (isHer) canMatchIt = (isIt) canMatchThem = (isPlural) /* can we match the given PronounXxx pronoun type specifier? */ canMatchPronounType(typ) { /* check the type, and return the appropriate indicator property */ switch (typ) { case PronounHim: return canMatchHim; case PronounHer: return canMatchHer; case PronounIt: return canMatchIt; case PronounThem: return canMatchThem; default: return nil; } } /* * The grammatical cardinality of this item when it appears in a * list. This is used to ensure verb agreement when mentioning the * item in a list of items. ("Cardinality" is a fancy word for "how * many items does this look like"). * * English only distinguishes two degrees of cardinality in its * grammar: one, or many. That is, when constructing a sentence, the * only thing the grammar cares about is whether an object is * singular or plural: IT IS on the table, THEY ARE on the table. * Since English only distinguishes these two degrees, two is the * same as a hundred is the same as a million for grammatical * purposes, so we'll consider our cardinality to be 2 if we're * plural, 1 otherwise. * * Some languages don't express cardinality at all in their grammar, * and others distinguish cardinality in greater detail than just * singular-vs-plural, which is why this method has to be in the * language-specific part of the library. */ listCardinality(lister) { return isPlural ? 2 : 1; } /* * Proper name flag. This indicates that the 'name' property is the * name of a person or place. We consider proper names to be fully * qualified, so we don't add articles for variations on the name * such as 'theName'. */ isProperName = nil /* * Qualified name flag. This indicates that the object name, as * given by the 'name' property, is already fully qualified, so * doesn't need qualification by an article like "the" or "a" when * it appears in a sentence. By default, a name is considered * qualified if it's a proper name, but this can be overridden to * mark a non-proper name as qualified when needed. */ isQualifiedName = (isProperName) /* * The name of the object - this is a string giving the object's * short description, for constructing sentences that refer to the * object by name. Each instance should override this to define the * name of the object. This string should not contain any articles; * we use this string as the root to generate various forms of the * object's name for use in different places in sentences. */ name = '' /* * The name of the object, for the purposes of disambiguation * prompts. This should almost always be the object's ordinary * name, so we return self.name by default. * * In rare cases, it might be desirable to override this. In * particular, if a game has two objects that are NOT defined as * basic equivalents of one another (which means that the parser * will always ask for disambiguation when the two are ambiguous * with one another), but the two nonetheless have identical 'name' * properties, this property should be overridden for one or both * objects to give them different names. This will ensure that we * avoid asking questions of the form "which do you mean, the coin, * or the coin?". In most cases, non-equivalent objects will have * distinct 'name' properties to begin with, so this is not usually * an issue. * * When overriding this method, take care to override * theDisambigName, aDisambigName, countDisambigName, and/or * pluralDisambigName as needed. Those routines must be overridden * only when the default algorithms for determining articles and * plurals fail to work properly for the disambigName (for example, * the indefinite article algorithm fails with silent-h words like * "hour", so if disambigName is "hour", aDisambigName must be * overridden). In most cases, the automatic algorithms will * produce acceptable results, so the default implementations of * these other routines can be used without customization. */ disambigName = (name) /* * The "equivalence key" is the value we use to group equivalent * objects. Note that we can only treat objects as equivalent when * they're explicitly marked with isEquivalent=true, so the * equivalence key is irrelevant for objects not so marked. * * Since the main point of equivalence is to allow creation of groups * of like-named objects that are interchangeable in listings and in * command input, we use the basic disambiguation name as the * equivalence key. */ equivalenceKey = (disambigName) /* * The definite-article name for disambiguation prompts. * * By default, if the disambiguation name is identical to the * regular name (i.e, the string returned by self.disambigName is * the same as the string returned by self.name), then we simply * return self.theName. Since the base name is the same in either * case, presumably the definite article names should be the same as * well. This way, if the object overrides theName to do something * special, then we'll use the same definite-article name for * disambiguation prompts. * * If the disambigName isn't the same as the regular name, then * we'll apply the same algorithm to the base disambigName that we * normally do to the regular name to produce the theName. This * way, if the disambigName is overridden, we'll use the overridden * disambigName to produce the definite-article version, using the * standard definite-article algorithm. * * Note that there's an aspect of this conditional approach that * might not be obvious. It might look as though the test is * redundant: if name == disambigName, after all, and the default * theName returns theNameFrom(name), then this ought to be * identical to returning theNameFrom(disambigName). The subtlety * is that theName could be overridden to produce a custom result, * in which case returning theNameFrom(disambigName) would return * something different, which probably wouldn't be correct: the * whole reason theName would be overridden is that the algorithmic * determination (theNameFrom) gets it wrong. So, by calling * theName directly when disambigName is the same as name, we are * assured that we pick up any override in theName. * * Note that in rare cases, neither of these default approaches will * produce the right result; this will happen if the object uses a * custom disambigName, but that name doesn't fit the normal * algorithmic pattern for applying a definite article. In these * cases, the object should simply override this method to specify * the custom name. */ theDisambigName = (name == disambigName ? theName : theNameFrom(disambigName)) /* * The indefinite-article name for disambiguation prompts. We use * the same logic here as in theDisambigName. */ aDisambigName = (name == disambigName ? aName : aNameFrom(disambigName)) /* * The counted name for disambiguation prompts. We use the same * logic here as in theDisambigName. */ countDisambigName(cnt) { return (name == disambigName && pluralName == pluralDisambigName ? countName(cnt) : countNameFrom(cnt, disambigName, pluralDisambigName)); } /* * The plural name for disambiguation prompts. We use the same * logic here as in theDisambigName. */ pluralDisambigName = (name == disambigName ? pluralName : pluralNameFrom(disambigName)) /* * The name of the object, for the purposes of disambiguation prompts * to disambiguation among this object and basic equivalents of this * object (i.e., objects of the same class marked with * isEquivalent=true). * * This is used in disambiguation prompts in place of the actual text * typed by the user. For example, suppose the user types ">take * coin", then we ask for help disambiguating, and the player types * ">gold". This narrows things down to, say, three gold coins, but * they're in different locations so we need to ask for further * disambiguation. Normally, we ask "which gold do you mean", * because the player typed "gold" in the input. Once we're down to * equivalents, we don't have to rely on the input text any more, * which is good because the input text could be fragmentary (as in * our present example). Since we have only equivalents, we can use * the actual name of the objects (they're all the same, after all). * This property gives the name we use. * * For English, this is simply the object's ordinary disambiguation * name. This property is separate from 'name' and 'disambigName' * for the sake of languages that need to use an inflected form in * this context. */ disambigEquivName = (disambigName) /* * Single-item listing description. This is used to display the * item when it appears as a single (non-grouped) item in a list. * By default, we just show the indefinite article description. */ listName = (aName) /* * Return a string giving the "counted name" of the object - that is, * a phrase describing the given number of the object. For example, * for a red book, and a count of 5, we might return "five red * books". By default, we use countNameFrom() to construct a phrase * from the count and either our regular (singular) 'name' property * or our 'pluralName' property, according to whether count is 1 or * more than 1. */ countName(count) { return countNameFrom(count, name, pluralName); } /* * Returns a string giving a count applied to the name string. The * name must be given in both singular and plural forms. */ countNameFrom(count, singularStr, pluralStr) { /* if the count is one, use 'one' plus the singular name */ if (count == 1) return 'one ' + singularStr; /* * Get the number followed by a space - spell out numbers below * 100, but use numerals to denote larger numbers. Append the * plural name to the number and return the result. */ return spellIntBelowExt(count, 100, 0, DigitFormatGroupSep) + ' ' + pluralStr; } /* * Get the 'pronoun selector' for the various pronoun methods. This * returns: * *. - singular neuter = 1 *. - singular masculine = 2 *. - singular feminine = 3 *. - plural = 4 */ pronounSelector = (isPlural ? 4 : isHer ? 3 : isHim ? 2 : 1) /* * get a string with the appropriate pronoun for the object for the * nominative case, objective case, possessive adjective, possessive * noun */ itNom { return ['it', 'he', 'she', 'they'][pronounSelector]; } itObj { return ['it', 'him', 'her', 'them'][pronounSelector]; } itPossAdj { return ['its', 'his', 'her', 'their'][pronounSelector]; } itPossNoun { return ['its', 'his', 'hers', 'theirs'][pronounSelector]; } /* get the object reflexive pronoun (itself, etc) */ itReflexive { return ['itself', 'himself', 'herself', 'themselves'] [pronounSelector]; } /* demonstrative pronouns ('that' or 'those') */ thatNom { return ['that', 'he', 'she', 'those'][pronounSelector]; } thatIsContraction { return thatNom + tSel(isPlural ? ' are' : '’s', ' ' + verbToBe); } thatObj { return ['that', 'him', 'her', 'those'][pronounSelector]; } /* * get a string with the appropriate pronoun for the object plus the * correct conjugation of 'to be' */ itIs { return itNom + ' ' + verbToBe; } /* get a pronoun plus a 'to be' contraction */ itIsContraction { return itNom + tSel(isPlural ? '’re' : '’s', ' ' + verbToBe); } /* * get a string with the appropriate pronoun for the object plus the * correct conjugation of the given regular verb for the appropriate * person */ itVerb(verb) { return itNom + ' ' + conjugateRegularVerb(verb); } /* * Conjugate a regular verb in the present or past tense for our * person and number. * * In the present tense, this is pretty easy: we add an 's' for the * third person singular, and leave the verb unchanged for plural (it * asks, they ask). The only complication is that we must check some * special cases to add the -s suffix: -y -> -ies (it carries), -o -> * -oes (it goes). * * In the past tense, we can equally easily figure out when to use * -d, -ed, or -ied. However, we have a more serious problem: for * some verbs, the last consonant of the verb stem should be repeated * (as in deter -> deterred), and for others it shouldn't (as in * gather -> gathered). To figure out which rule applies, we would * sometimes need to know whether the last syllable is stressed, and * unfortunately there is no easy way to determine that * programmatically. * * Therefore, we do *not* handle the case where the last consonant is * repeated in the past tense. You shouldn't use this method for * this case; instead, treat it as you would handle an irregular * verb, by explicitly specifying the correct past tense form via the * tSel macro. For example, to generate the properly conjugated form * of the verb "deter" for an object named "thing", you could use an * expression such as: * * 'deter' + tSel(thing.verbEndingS, 'red') * * This would correctly generate "deter", "deters", or "deterred" * depending on the number of the object named "thing" and on the * current narrative tense. */ conjugateRegularVerb(verb) { /* * Which tense are we currently using? */ if (gameMain.usePastTense) { /* * We want the past tense form. * * If the last letter is 'e', simply add 'd'. */ if (verb.endsWith('e')) return verb + 'd'; /* * Otherwise, if the verb ending would become 'ies' in the * third-person singular present, then it becomes 'ied' in * the past. */ else if (rexMatch(iesEndingPat, verb)) return verb.substr(1, verb.length() - 1) + 'ied'; /* * Otherwise, use 'ed' as the ending. Don't try to determine * if the last consonant should be repeated: that's too * complicated. We'll just ignore the possibility. */ else return verb + 'ed'; } else { /* * We want the present tense form. * * Check our number and person. */ if (isPlural) { /* * We're plural, so simply use the base verb form ("they * ask"). */ return verb; } else { /* * Third-person singular, so we must add the -s suffix. * Check for special spelling cases: * * '-y' changes to '-ies', unless the 'y' is preceded by * a vowel * * '-sh', '-ch', and '-o' endings add suffix '-es' */ if (rexMatch(iesEndingPat, verb)) return verb.substr(1, verb.length() - 1) + 'ies'; else if (rexMatch(esEndingPat, verb)) return verb + 'es'; else return verb + 's'; } } } /* verb-ending patterns for figuring out which '-s' ending to add */ iesEndingPat = static new RexPattern('.*[^aeiou]y$') esEndingPat = static new RexPattern('.*(o|ch|sh)$') /* * Get the name with a definite article ("the box"). By default, we * use our standard definite article algorithm to apply an article * to self.name. * * The name returned must be in the nominative case (which makes no * difference unless the name is a pronoun, since in English * ordinary nouns don't vary according to how they're used in a * sentence). */ theName = (theNameFrom(name)) /* * theName in objective case. In most cases, this is identical to * the normal theName, so we use that by default. This must be * overridden if theName is a pronoun (which is usually only the * case for player character actors; see our language-specific Actor * modifications for information on that case). */ theNameObj { return theName; } /* * Generate the definite-article name from the given name string. * If my name is already qualified, don't add an article; otherwise, * add a 'the' as the prefixed definite article. */ theNameFrom(str) { return (isQualifiedName ? '' : 'the ') + str; } /* * theName as a possessive adjective (Bob's book, your book). If the * name's usage is singular (i.e., isPlural is nil), we'll simply add * an apostrophe-S. If the name is plural, and it ends in an "s", * we'll just add an apostrophe (no S). If it's plural and doesn't * end in "s", we'll add an apostrophe-S. * * Note that some people disagree about the proper usage for * singular-usage words (especially proper names) that end in 's'. * Some people like to use a bare apostrophe for any name that ends * in 's' (so Chris -> Chris'); other people use apostrophe-s for * singular words that end in an "s" sound and a bare apostrophe for * words that end in an "s" that sounds like a "z" (so Charles * Dickens -> Charles Dickens'). However, most usage experts agree * that proper names take an apostrophe-S in almost all cases, even * when ending with an "s": "Chris's", "Charles Dickens's". That's * what we do here. * * Note that this algorithm doesn't catch all of the special * exceptions in conventional English usage. For example, Greek * names ending with "-es" are usually written with the bare * apostrophe, but we don't have a property that tells us whether the * name is Greek or not, so we can't catch this case. Likewise, some * authors like to possessive-ize words that end with an "s" sound * with a bare apostrophe, as in "for appearance' sake", and we don't * attempt to catch these either. For any of these exceptions, you * must override this method for the individual object. */ theNamePossAdj { /* add apostrophe-S, unless it's a plural ending with 's' */ return theName + (isPlural && theName.endsWith('s') ? '’' : '’s'); } /* * TheName as a possessive noun (that is Bob's, that is yours). We * simply return the possessive adjective name, since the two forms * are usually identical in English (except for pronouns, where they * sometimes differ: "her" for the adjective vs "hers" for the noun). */ theNamePossNoun = (theNamePossAdj) /* * theName with my nominal owner explicitly stated, if we have a * nominal owner: "your backpack," "Bob's flashlight." If we have * no nominal owner, this is simply my theName. */ theNameWithOwner() { local owner; /* * if we have a nominal owner, show with our owner name; * otherwise, just show our regular theName */ if ((owner = getNominalOwner()) != nil) return owner.theNamePossAdj + ' ' + name; else return theName; } /* * Default preposition to use when an object is in/on this object. * By default, we use 'in' as the preposition; subclasses can * override to use others (such as 'on' for a surface). */ objInPrep = 'in' /* * Default preposition to use when an actor is in/on this object (as * a nested location), and full prepositional phrase, with no article * and with an indefinite article. By default, we use the objInPrep * for actors as well. */ actorInPrep = (objInPrep) /* preposition to use when an actor is being removed from this location */ actorOutOfPrep = 'out of' /* preposition to use when an actor is being moved into this location */ actorIntoPrep { if (actorInPrep is in ('in', 'on')) return actorInPrep + 'to'; else return actorInPrep; } /* * describe an actor as being in/being removed from/being moved into * this location */ actorInName = (actorInPrep + ' ' + theNameObj) actorInAName = (actorInPrep + ' ' + aNameObj) actorOutOfName = (actorOutOfPrep + ' ' + theNameObj) actorIntoName = (actorIntoPrep + ' ' + theNameObj) /* * A prepositional phrase that can be used to describe things that * are in this room as seen from a remote point of view. This * should be something along the lines of "in the airlock", "at the * end of the alley", or "on the lawn". * * 'pov' is the point of view from which we're seeing this room; * this might be * * We use this phrase in cases where we need to describe things in * this room when viewed from a point of view outside of the room * (i.e., in a different top-level room). By default, we'll use our * actorInName. */ inRoomName(pov) { return actorInName; } /* * Provide the prepositional phrase for an object being put into me. * For a container, for example, this would say "into the box"; for * a surface, it would say "onto the table." By default, we return * our library message given by our putDestMessage property; this * default is suitable for most cases, but individual objects can * customize as needed. When customizing this, be sure to make the * phrase suitable for use in sentences like "You put the book * <<putInName>>" and "The book falls <<putInName>>" - the phrase * should be suitable for a verb indicating active motion by the * object being received. */ putInName() { return gLibMessages.(putDestMessage)(self); } /* * Get a description of an object within this object, describing the * object's location as this object. By default, we'll append "in * <theName>" to the given object name. */ childInName(childName) { return childInNameGen(childName, theName); } /* * Get a description of an object within this object, showing the * owner of this object. This is similar to childInName, but * explicitly shows the owner of the containing object, if any: "the * flashlight in bob's backpack". */ childInNameWithOwner(childName) { return childInNameGen(childName, theNameWithOwner); } /* * get a description of an object within this object, as seen from a * remote location */ childInRemoteName(childName, pov) { return childInNameGen(childName, inRoomName(pov)); } /* * Base routine for generating childInName and related names. Takes * the name to use for the child and the name to use for me, and * combines them appropriately. * * In most cases, this is the only one of the various childInName * methods that needs to be overridden per subclass, since the others * are defined in terms of this one. Note also that if the only * thing you need to do is change the preposition from 'in' to * something else, you can just override objInPrep instead. */ childInNameGen(childName, myName) { return childName + ' ' + objInPrep + ' ' + myName; } /* * Get my name (in various forms) distinguished by my owner or * location. * * If the object has an owner, and either we're giving priority to * the owner or our immediate location is the same as the owner, * we'll show using a possessive form with the owner ("bob's * flashlight"). Otherwise, we'll show the name distinguished by * our immediate container ("the flashlight in the backpack"). * * These are used by the ownership and location distinguishers to * list objects according to owners in disambiguation lists. The * ownership distinguisher gives priority to naming by ownership, * regardless of the containment relationship between owner and * self; the location distinguisher gives priority to naming by * location, showing the owner only if the owner is the same as the * location. * * We will presume that objects with proper names are never * indistinguishable from other objects with proper names, so we * won't worry about cases like "Bob's Bill". This leaves us free * to use appropriate articles in all cases. */ aNameOwnerLoc(ownerPriority) { local owner; /* show in owner or location format, as appropriate */ if ((owner = getNominalOwner()) != nil && (ownerPriority || isDirectlyIn(owner))) { local ret; /* * we have an owner - show as "one of Bob's items" (or just * "Bob's items" if this is a mass noun or a proper name) */ ret = owner.theNamePossAdj + ' ' + pluralName; if (!isMassNoun && !isPlural) ret = 'one of ' + ret; /* return the result */ return ret; } else { /* we have no owner - show as "an item in the location" */ return location.childInNameWithOwner(aName); } } theNameOwnerLoc(ownerPriority) { local owner; /* show in owner or location format, as appropriate */ if ((owner = getNominalOwner()) != nil && (ownerPriority || isDirectlyIn(owner))) { /* we have an owner - show as "Bob's item" */ return owner.theNamePossAdj + ' ' + name; } else { /* we have no owner - show as "the item in the location" */ return location.childInNameWithOwner(theName); } } countNameOwnerLoc(cnt, ownerPriority) { local owner; /* show in owner or location format, as appropriate */ if ((owner = getNominalOwner()) != nil && (ownerPriority || isDirectlyIn(owner))) { /* we have an owner - show as "Bob's five items" */ return owner.theNamePossAdj + ' ' + countName(cnt); } else { /* we have no owner - show as "the five items in the location" */ return location.childInNameWithOwner('the ' + countName(cnt)); } } /* * Note that I'm being used in a disambiguation prompt by * owner/location. If we're showing the owner, we'll set the * antecedent for the owner's pronoun, if the owner is a 'him' or * 'her'; this allows the player to refer back to our prompt text * with appropriate pronouns. */ notePromptByOwnerLoc(ownerPriority) { local owner; /* show in owner or location format, as appropriate */ if ((owner = getNominalOwner()) != nil && (ownerPriority || isDirectlyIn(owner))) { /* we are showing by owner - let the owner know about it */ owner.notePromptByPossAdj(); } } /* * Note that we're being used in a prompt question with our * possessive adjective. If we're a 'him' or a 'her', set our * pronoun antecedent so that the player's response to the prompt * question can refer back to the prompt text by pronoun. */ notePromptByPossAdj() { if (isHim) gPlayerChar.setHim(self); if (isHer) gPlayerChar.setHer(self); } /* * My name with an indefinite article. By default, we figure out * which article to use (a, an, some) automatically. * * In rare cases, the automatic determination might get it wrong, * since some English spellings defy all of the standard * orthographic rules and must simply be handled as special cases; * for example, the algorithmic determination doesn't know about * silent-h words like "hour". When the automatic determination * gets it wrong, simply override this routine to specify the * correct article explicitly. */ aName = (aNameFrom(name)) /* the indefinite-article name in the objective case */ aNameObj { return aName; } /* * Apply an indefinite article ("a box", "an orange", "some lint") * to the given name. We'll try to figure out which indefinite * article to use based on what kind of noun phrase we use for our * name (singular, plural, or a "mass noun" like "lint"), and our * spelling. * * By default, we'll use the article "a" if the name starts with a * consonant, or "an" if it starts with a vowel. * * If the name starts with a "y", we'll look at the second letter; * if it's a consonant, we'll use "an", otherwise "a" (hence "an * yttrium block" but "a yellow brick"). * * If the object is marked as having plural usage, we will use * "some" as the article ("some pants" or "some shrubs"). * * Some objects will want to override the default behavior, because * the lexical rules about when to use "a" and "an" are not without * exception. For example, silent-"h" words ("honor") are written * with "an", and "h" words with a pronounced but weakly stressed * initial "h" are sometimes used with "an" ("an historian"). Also, * some 'y' words might not follow the generic 'y' rule. * * 'U' words are especially likely not to follow any lexical rule - * any 'u' word that sounds like it starts with 'y' should use 'a' * rather than 'an', but there's no good way to figure that out just * looking at the spelling (consider "a universal symbol" and "an * unimportant word", or "a unanimous decision" and "an unassuming * man"). We simply always use 'an' for a word starting with 'u', * but this will have to be overridden when the 'u' sounds like 'y'. */ aNameFrom(str) { /* remember the original source string */ local inStr = str; /* * The complete list of unaccented, accented, and ligaturized * Latin vowels from the Unicode character set. (The Unicode * database doesn't classify characters as vowels or the like, * so it seems the only way we can come up with this list is * simply to enumerate the vowels.) * * These are all lower-case letters; all of these are either * exclusively lower-case or have upper-case equivalents that * map to these lower-case letters. * * (Note an implementation detail: the compiler will append all * of these strings together at compile time, so we don't have * to perform all of this concatenation work each time we * execute this method.) * * Note that we consider any word starting with an '8' to start * with a vowel, since 'eight' and 'eighty' both take 'an'. */ local vowels = '8aeiou\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6' + '\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF' + '\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8\u00F9\u00FA' + '\u00FB\u00FC\u0101\u0103\u0105\u0113\u0115\u0117' + '\u0119\u011B\u0129\u012B\u012D\u012F\u014D\u014F' + '\u0151\u0169\u016B\u016D\u016F\u0171\u0173\u01A1' + '\u01A3\u01B0\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8' + '\u01DA\u01DC\u01DF\u01E1\u01E3\u01EB\u01ED\u01FB' + '\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B' + '\u020D\u020F\u0215\u0217\u0254\u025B\u0268\u0289' + '\u1E01\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E2D\u1E2F' + '\u1E4D\u1E4F\u1E51\u1E53\u1E73\u1E75\u1E77\u1E79' + '\u1E7B\u1E9A\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB' + '\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB' + '\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB' + '\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB' + '\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB' + '\u1EED\u1EEF\u1EF1\uFF41\uFF4F\uFF55'; /* * A few upper-case vowels in unicode don't have lower-case * mappings - consider them separately. */ local vowelsUpperOnly = '\u0130\u019f'; /* * the various accented forms of the letter 'y' - these are all * lower-case versions; the upper-case versions all map to these */ local ys = 'y\u00FD\u00FF\u0177\u01B4\u1E8F\u1E99\u1EF3' + '\u1EF5\u1EF7\u1EF9\u24B4\uFF59'; /* if the name is already qualified, don't add an article at all */ if (isQualifiedName) return str; /* if it's plural or a mass noun, use "some" as the article */ if (isPlural || isMassNoun) { /* use "some" as the article */ return 'some ' + str; } else { local firstChar; local firstCharLower; /* if it's empty, just use "a" */ if (inStr == '') return 'a'; /* get the first character of the name */ firstChar = inStr.substr(1, 1); /* skip any leading HTML tags */ if (rexMatch(patTagOrQuoteChar, firstChar) != nil) { /* * Scan for tags. Note that this pattern isn't quite * perfect, as it won't properly ignore close-brackets * that are inside quoted material, but it should be good * enough for nearly all cases in practice. In cases too * complex for this pattern, the object will simply have * to override aDesc. */ local len = rexMatch(patLeadingTagOrQuote, inStr); /* if we got a match, strip out the leading tags */ if (len != nil) { /* strip off the leading tags */ inStr = inStr.substr(len + 1); /* re-fetch the first character */ firstChar = inStr.substr(1, 1); } } /* get the lower-case version of the first character */ firstCharLower = firstChar.toLower(); /* * if the first word of the name is only one letter long, * treat it specially */ if (rexMatch(patOneLetterWord, inStr) != nil) { /* * We have a one-letter first word, such as "I-beam" or * "M-ray sensor", or just "A". Choose the article based * on the pronunciation of the letter as a letter. */ return (rexMatch(patOneLetterAnWord, inStr) != nil ? 'an ' : 'a ') + str; } /* * look for the first character in the lower-case and * upper-case-only vowel lists - if we find it, it takes * 'an' */ if (vowels.find(firstCharLower) != nil || vowelsUpperOnly.find(firstChar) != nil) { /* it starts with a vowel */ return 'an ' + str; } else if (ys.find(firstCharLower) != nil) { local secondChar; /* get the second character, if there is one */ secondChar = inStr.substr(2, 1); /* * It starts with 'y' - if the second letter is a * consonant, assume the 'y' is a vowel sound, hence we * should use 'an'; otherwise assume the 'y' is a * diphthong 'ei' sound, which means we should use 'a'. * If there's no second character at all, or the second * character isn't alphabetic, use 'a' - "a Y" or "a * Y-connector". */ if (secondChar == '' || rexMatch(patIsAlpha, secondChar) == nil || vowels.find(secondChar.toLower()) != nil || vowelsUpperOnly.find(secondChar) != nil) { /* * it's just one character, or the second character * is non-alphabetic, or the second character is a * vowel - in any of these cases, use 'a' */ return 'a ' + str; } else { /* the second character is a consonant - use 'an' */ return 'an ' + str; } } else if (rexMatch(patElevenEighteen, inStr) != nil) { /* * it starts with '11' or '18', so it takes 'an' ('an * 11th-hour appeal', 'an 18-hole golf course') */ return 'an ' + str; } else { /* it starts with a consonant */ return 'a ' + str; } } } /* pre-compile some regular expressions for aName */ patTagOrQuoteChar = static new RexPattern('[<"\']') patLeadingTagOrQuote = static new RexPattern( '(<langle><^rangle>+<rangle>|"|\')+') patOneLetterWord = static new RexPattern('<alpha>(<^alpha>|$)') patOneLetterAnWord = static new RexPattern('<nocase>[aefhilmnorsx]') patIsAlpha = static new RexPattern('<alpha>') patElevenEighteen = static new RexPattern('1[18](<^digit>|$)') /* * Get the default plural name. By default, we'll use the * algorithmic plural determination, which is based on the spelling * of the name. * * The algorithm won't always get it right, since some English * plurals are irregular ("men", "children", "Attorneys General"). * When the name doesn't fit the regular spelling patterns for * plurals, the object should simply override this routine to return * the correct plural name string. */ pluralName = (pluralNameFrom(name)) /* * Get the plural form of the given name string. If the name ends in * anything other than 'y', we'll add an 's'; otherwise we'll replace * the 'y' with 'ies'. We also handle abbreviations and individual * letters specially. * * This can only deal with simple adjective-noun forms. For more * complicated forms, particularly for compound words, it must be * overridden (e.g., "Attorney General" -> "Attorneys General", * "man-of-war" -> "men-of-war"). Likewise, names with irregular * plurals ('child' -> 'children', 'man' -> 'men') must be handled * with overrides. */ pluralNameFrom(str) { local len; local lastChar; local lastPair; /* * if it's marked as having plural usage, just use the ordinary * name, since it's already plural */ if (isPlural) return str; /* check for a 'phrase of phrase' format */ if (rexMatch(patOfPhrase, str) != nil) { local ofSuffix; /* * Pull out the two parts - the part up to the 'of' is the * part we'll actually pluralize, and the rest is a suffix * we'll stick on the end of the pluralized part. */ str = rexGroup(1)[3]; ofSuffix = rexGroup(2)[3]; /* * now pluralize the part up to the 'of' using the normal * rules, then add the rest back in at the end */ return pluralNameFrom(str) + ofSuffix; } /* if there's no short description, return an empty string */ len = str.length(); if (len == 0) return ''; /* * If it's only one character long, handle it specially. If it's * a lower-case letter, add an apostrophe-S. If it's a capital * A, E, I, M, U, or V, we'll add apostrophe-S (because these * could be confused with words or common abbreviations if we * just added "s": As, Es, Is, Ms, Us, Vs). If it's anything * else (any other capital letter, or any non-letter character), * we'll just add an "s". */ if (len == 1) { if (rexMatch(patSingleApostropheS, str) != nil) return str + '’s'; else return str + 's'; } /* get the last character of the name, and the last pair of chars */ lastChar = str.substr(len, 1); lastPair = (len == 1 ? lastChar : str.substr(len - 1, 2)); /* * If the last letter is a capital letter, assume it's an * abbreviation without embedded periods (CPA, PC), in which case * we just add an "s" (CPAs, PCs). Likewise, if it's a number, * just add "s": "the 1940s", "the low 20s". */ if (rexMatch(patUpperOrDigit, lastChar) != nil) return str + 's'; /* * If the last character is a period, it must be an abbreviation * with embedded periods (B.A., B.S., Ph.D.). In these cases, * add an apostrophe-S. */ if (lastChar == '.') return str + '’s'; /* * If it ends in a non-vowel followed by 'y', change -y to -ies. * (This doesn't apply if a vowel precedes a terminal 'y'; in * such cases, we'll use the normal '-s' ending instead: "survey" * -> "surveys", "essay" -> "essays", "day" -> "days".) */ if (rexMatch(patVowelY, lastPair) != nil) return str.substr(1, len - 1) + 'ies'; /* if it ends in s, x, z, or h, add -es */ if ('sxzh'.find(lastChar) != nil) return str + 'es'; /* for anything else, just add -s */ return str + 's'; } /* some pre-compiled patterns for pluralName */ patSingleApostropheS = static new RexPattern('<case><lower|A|E|I|M|U|V>') patUpperOrDigit = static new RexPattern('<upper|digit>') patVowelY = static new RexPattern('[^aeoiu]y') patOfPhrase = static new RexPattern( '<nocase>(.+?)(<space>+of<space>+.+)') /* get my name plus a being verb ("the box is") */ nameIs { return theName + ' ' + verbToBe; } /* get my name plus a negative being verb ("the box isn't") */ nameIsnt { return nameIs + 'n’t'; } /* * My name with the given regular verb in agreement: in the present * tense, if my name has singular usage, we'll add 's' to the verb, * otherwise we won't. In the past tense, we'll add 'd' (or 'ed'). * This can't be used with irregular verbs, or with regular verbs * that have the last consonant repeated before the past -ed ending, * such as "deter". */ nameVerb(verb) { return theName + ' ' + conjugateRegularVerb(verb); } /* being verb agreeing with this object as subject */ verbToBe { return tSel(isPlural ? 'are' : 'is', isPlural ? 'were' : 'was'); } /* past tense being verb agreeing with object as subject */ verbWas { return tSel(isPlural ? 'were' : 'was', 'had been'); } /* 'have' verb agreeing with this object as subject */ verbToHave { return tSel(isPlural ? 'have' : 'has', 'had'); } /* * A few common irregular verbs and name-plus-verb constructs, * defined for convenience. */ verbToDo = (tSel('do' + verbEndingEs, 'did')) nameDoes = (theName + ' ' + verbToDo) verbToGo = (tSel('go' + verbEndingEs, 'went')) verbToCome = (tSel('come' + verbEndingS, 'came')) verbToLeave = (tSel('leave' + verbEndingS, 'left')) verbToSee = (tSel('see' + verbEndingS, 'saw')) nameSees = (theName + ' ' + verbToSee) verbToSay = (tSel('say' + verbEndingS, 'said')) nameSays = (theName + ' ' + verbToSay) verbMust = (tSel('must', 'had to')) verbCan = (tSel('can', 'could')) verbCannot = (tSel('cannot', 'could not')) verbCant = (tSel('can’t', 'couldn’t')) verbWill = (tSel('will', 'would')) verbWont = (tSel('won’t', 'wouldn’t')) /* * Verb endings for regular '-s' verbs, agreeing with this object as * the subject. We define several methods each of which handles the * past tense differently. * * verbEndingS doesn't try to handle the past tense at all - use it * only in places where you know for certain that you'll never need * the past tense form, or in expressions constructed with the tSel * macro: use verbEndingS as the macro's first argument, and specify * the past tense ending explicitly as the second argument. For * example, you could generate the correctly conjugated form of the * verb "to fit" for an object named "key" with an expression such * as: * * 'fit' + tSel(key.verbEndingS, 'ted') * * This would generate 'fit', 'fits', or 'fitted' according to number * and tense. * * verbEndingSD and verbEndingSEd return 'd' and 'ed' respectively in * the past tense. * * verbEndingSMessageBuilder_ is for internal use only: it assumes * that the correct ending to be displayed in the past tense is * stored in langMessageBuilder.pastEnding_. It is used as part of * the string parameter substitution mechanism. */ verbEndingS { return isPlural ? '' : 's'; } verbEndingSD = (tSel(verbEndingS, 'd')) verbEndingSEd = (tSel(verbEndingS, 'ed')) verbEndingSMessageBuilder_ = (tSel(verbEndingS, langMessageBuilder.pastEnding_)) /* * Verb endings (present or past) for regular '-es/-ed' and * '-y/-ies/-ied' verbs, agreeing with this object as the subject. */ verbEndingEs { return tSel(isPlural ? '' : 'es', 'ed'); } verbEndingIes { return tSel(isPlural ? 'y' : 'ies', 'ied'); } /* * Dummy name - this simply displays nothing; it's used for cases * where messageBuilder substitutions want to refer to an object (for * internal bookkeeping) without actually showing the name of the * object in the output text. This should always simply return an * empty string. */ dummyName = '' /* * Invoke a property (with an optional argument list) on this object * while temporarily switching to the present tense, and return the * result. */ propWithPresent(prop, [args]) { return withPresent({: self.(prop)(args...)}); } /* * Method for internal use only: invoke on this object the property * stored in langMessageBuilder.fixedTenseProp_ while temporarily * switching to the present tense, and return the result. This is * used as part of the string parameter substitution mechanism. */ propWithPresentMessageBuilder_ { return propWithPresent(langMessageBuilder.fixedTenseProp_); } /* * For the most part, "strike" has the same meaning as "hit", so * define this as a synonym for "attack" most objects. There are a * few English idioms where "strike" means something different, as * in "strike match" or "strike tent." */ dobjFor(Strike) asDobjFor(Attack) ; /* ------------------------------------------------------------------------ */ /* * An object that uses the same name as another object. This maps all of * the properties involved in supplying the object's name, number, and * other usage information from this object to a given target object, so * that all messages involving this object use the same name as the * target object. This is a mix-in class that can be used with any other * class. * * Note that we map only the *reported* name for the object. We do NOT * give this object any vocabulary from the other object; in other words, * we don't enter this object into the dictionary with the other object's * vocabulary words. */ class NameAsOther: object /* the target object - we'll use the same name as this object */ targetObj = nil /* map our naming and usage properties to the target object */ isPlural = (targetObj.isPlural) isMassNoun = (targetObj.isMassNoun) isHim = (targetObj.isHim) isHer = (targetObj.isHer) isIt = (targetObj.isIt) isProperName = (targetObj.isProperName) isQualifiedName = (targetObj.isQualifiedName) name = (targetObj.name) /* map the derived name properties as well, in case any are overridden */ disambigName = (targetObj.disambigName) theDisambigName = (targetObj.theDisambigName) aDisambigName = (targetObj.aDisambigName) countDisambigName(cnt) { return targetObj.countDisambigName(cnt); } disambigEquivName = (targetObj.disambigEquivName) listName = (targetObj.listName) countName(cnt) { return targetObj.countName(cnt); } /* map the pronoun properites, in case any are overridden */ itNom = (targetObj.itNom) itObj = (targetObj.itObj) itPossAdj = (targetObj.itPossAdj) itPossNoun = (targetObj.itPossNoun) itReflexive = (targetObj.itReflexive) thatNom = (targetObj.thatNom) thatObj = (targetObj.thatObj) thatIsContraction = (targetObj.thatIsContraction) itIs = (targetObj.itIs) itIsContraction = (targetObj.itIsContraction) itVerb(verb) { return targetObj.itVerb(verb); } conjugateRegularVerb(verb) { return targetObj.conjugateRegularVerb(verb); } theName = (targetObj.theName) theNameObj = (targetObj.theNameObj) theNamePossAdj = (targetObj.theNamePossAdj) theNamePossNoun = (targetObj.theNamePossNoun) theNameWithOwner = (targetObj.theNameWithOwner) aNameOwnerLoc(ownerPri) { return targetObj.aNameOwnerLoc(ownerPri); } theNameOwnerLoc(ownerPri) { return targetObj.theNameOwnerLoc(ownerPri); } countNameOwnerLoc(cnt, ownerPri) { return targetObj.countNameOwnerLoc(cnt, ownerPri); } notePromptByOwnerLoc(ownerPri) { targetObj.notePromptByOwnerLoc(ownerPri); } notePromptByPossAdj() { targetObj.notePromptByPossAdj(); } aName = (targetObj.aName) aNameObj = (targetObj.aNameObj) pluralName = (targetObj.pluralName) nameIs = (targetObj.nameIs) nameIsnt = (targetObj.nameIsnt) nameVerb(verb) { return targetObj.nameVerb(verb); } verbToBe = (targetObj.verbToBe) verbWas = (targetObj.verbWas) verbToHave = (targetObj.verbToHave) verbToDo = (targetObj.verbToDo) nameDoes = (targetObj.nameDoes) verbToGo = (targetObj.verbToGo) verbToCome = (targetObj.verbToCome) verbToLeave = (targetObj.verbToLeave) verbToSee = (targetObj.verbToSee) nameSees = (targetObj.nameSees) verbToSay = (targetObj.verbToSay) nameSays = (targetObj.nameSays) verbMust = (targetObj.verbMust) verbCan = (targetObj.verbCan) verbCannot = (targetObj.verbCannot) verbCant = (targetObj.verbCant) verbWill = (targetObj.verbWill) verbWont = (targetObj.verbWont) verbEndingS = (targetObj.verbEndingS) verbEndingSD = (targetObj.verbEndingSD) verbEndingSEd = (targetObj.verbEndingSEd) verbEndingEs = (targetObj.verbEndingEs) verbEndingIes = (targetObj.verbEndingIes) ; /* * Name as Parent - this is a special case of NameAsOther that uses the * lexical parent of a nested object as the target object. (The lexical * parent is the enclosing object in a nested object definition; in other * words, it's the object in which the nested object is embedded.) */ class NameAsParent: NameAsOther targetObj = (lexicalParent) ; /* * ChildNameAsOther is a mix-in class that can be used with NameAsOther * to add the various childInXxx naming to the mapped properties. The * childInXxx names are the names generated when another object is * described as located within this object; by mapping these properties * to our target object, we ensure that we use exactly the same phrasing * as we would if the contained object were actually contained by our * target rather than by us. * * Note that this should always be used in combination with NameAsOther: * * myObj: NameAsOther, ChildNameAsOther, Thing ... * * You can also use it the same way in combination with a subclass of * NameAsOther, such as NameAsParent. */ class ChildNameAsOther: object objInPrep = (targetObj.objInPrep) actorInPrep = (targetObj.actorInPrep) actorOutOfPrep = (targetObj.actorOutOfPrep) actorIntoPrep = (targetObj.actorIntoPrep) childInName(childName) { return targetObj.childInName(childName); } childInNameWithOwner(childName) { return targetObj.childInNameWithOwner(childName); } childInNameGen(childName, myName) { return targetObj.childInNameGen(childName, myName); } actorInName = (targetObj.actorInName) actorOutOfName = (targetObj.actorOutOfName) actorIntoName = (targetObj.actorIntoName) actorInAName = (targetObj.actorInAName) ; /* ------------------------------------------------------------------------ */ /* * Language modifications for the specialized container types */ modify Surface /* * objects contained in a Surface are described as being on the * Surface */ objInPrep = 'on' actorInPrep = 'on' actorOutOfPrep = 'off of' ; modify Underside objInPrep = 'under' actorInPrep = 'under' actorOutOfPrep = 'from under' ; modify RearContainer objInPrep = 'behind' actorInPrep = 'behind' actorOutOfPrep = 'from behind' ; /* ------------------------------------------------------------------------ */ /* * Language modifications for Actor. * * An Actor has a "referral person" setting, which determines how we * refer to the actor; this is almost exclusively for the use of the * player character. The typical convention is that we refer to the * player character in the second person, but a game can override this on * an actor-by-actor basis. */ modify Actor /* by default, use my pronoun for my name */ name = (itNom) /* * Pronoun selector. This returns an index for selecting pronouns * or other words based on number and gender, taking into account * person, number, and gender. The value returned is the sum of the * following components: * * number/gender: *. - singular neuter = 1 *. - singular masculine = 2 *. - singular feminine = 3 *. - plural = 4 * * person: *. - first person = 0 *. - second person = 4 *. - third person = 8 * * The result can be used as a list selector as follows (1=first * person, etc; s=singular, p=plural; n=neuter, m=masculine, * f=feminine): * * [1/s/n, 1/s/m, 1/s/f, 1/p, 2/s/n, 2/s/m, 2/s/f, 2/p, *. 3/s/n, 3/s/m, 3/s/f, 3/p] */ pronounSelector { return ((referralPerson - FirstPerson)*4 + (isPlural ? 4 : isHim ? 2 : isHer ? 3 : 1)); } /* * get the verb form selector index for the person and number: * * [1/s, 2/s, 3/s, 1/p, 2/p, 3/p] */ conjugationSelector { return (referralPerson + (isPlural ? 3 : 0)); } /* * get an appropriate pronoun for the object in the appropriate * person for the nominative case, objective case, possessive * adjective, possessive noun, and objective reflexive */ itNom { return ['I', 'I', 'I', 'we', 'you', 'you', 'you', 'you', 'it', 'he', 'she', 'they'][pronounSelector]; } itObj { return ['me', 'me', 'me', 'us', 'you', 'you', 'you', 'you', 'it', 'him', 'her', 'them'][pronounSelector]; } itPossAdj { return ['my', 'my', 'my', 'our', 'your', 'your', 'your', 'your', 'its', 'his', 'her', 'their'][pronounSelector]; } itPossNoun { return ['mine', 'mine', 'mine', 'ours', 'yours', 'yours', 'yours', 'yours', 'its', 'his', 'hers', 'theirs'][pronounSelector]; } itReflexive { return ['myself', 'myself', 'myself', 'ourselves', 'yourself', 'yourself', 'yourself', 'yourselves', 'itself', 'himself', 'herself', 'themselves'][pronounSelector]; } /* * Demonstrative pronoun, nominative case. We'll use personal a * personal pronoun if we have a gender or we're in the first or * second person, otherwise we'll use 'that' or 'those' as we would * for an inanimate object. */ thatNom { return ['I', 'I', 'I', 'we', 'you', 'you', 'you', 'you', 'that', 'he', 'she', 'those'][pronounSelector]; } /* demonstrative pronoun, objective case */ thatObj { return ['me', 'me', 'me', 'us', 'you', 'you', 'you', 'you', 'that', 'him', 'her', 'those'][pronounSelector]; } /* demonstrative pronoun, nominative case with 'is' contraction */ thatIsContraction { return thatNom + tSel(['’m', '’re', '’s', '’re', '’re', ' are'][conjugationSelector], ' ' + verbToBe); } /* * We don't need to override itIs: the base class handling works for * actors too. */ /* get my pronoun with a being verb contraction ("the box's") */ itIsContraction { return itNom + tSel( '’' + ['m', 're', 's', 're', 're', 're'][conjugationSelector], ' ' + verbToBe); } /* * Conjugate a regular verb in the present or past tense for our * person and number. * * In the present tense, this is pretty easy: we add an 's' for the * third person singular, and leave the verb unchanged for every * other case. The only complication is that we must check some * special cases to add the -s suffix: -y -> -ies, -o -> -oes. * * In the past tense, we use the inherited handling since the past * tense ending doesn't vary with person. */ conjugateRegularVerb(verb) { /* * If we're in the third person or if we use the past tense, * inherit the default handling; otherwise, use the base verb * form regardless of number (regular verbs use the same * conjugated forms for every case but third person singular: I * ask, you ask, we ask, they ask). */ if (referralPerson != ThirdPerson && !gameMain.usePastTense) { /* * we're not using the third-person or the past tense, so the * conjugation is the same as the base verb form */ return verb; } else { /* * we're using the third person or the past tense, so inherit * the base class handling, which conjugates these forms */ return inherited(verb); } } /* * Get the name with a definite article ("the box"). If the * narrator refers to us in the first or second person, use a * pronoun rather than the short description. */ theName { return (referralPerson == ThirdPerson ? inherited : itNom); } /* theName in objective case */ theNameObj { return (referralPerson == ThirdPerson ? inherited : itObj); } /* theName as a possessive adjective */ theNamePossAdj { return (referralPerson == ThirdPerson ? inherited : itPossAdj); } /* theName as a possessive noun */ theNamePossNoun { return (referralPerson == ThirdPerson ? inherited : itPossNoun); } /* * Get the name with an indefinite article. Use the same rules of * referral person as for definite articles. */ aName { return (referralPerson == ThirdPerson ? inherited : itNom); } /* aName in objective case */ aNameObj { return (referralPerson == ThirdPerson ? inherited : itObj); } /* being verb agreeing with this object as subject */ verbToBe { return tSel(['am', 'are', 'is', 'are', 'are', 'are'], ['was', 'were', 'was', 'were', 'were', 'were']) [conjugationSelector]; } /* past tense being verb agreeing with this object as subject */ verbWas { return tSel(['was', 'were', 'was', 'were', 'were', 'were'] [conjugationSelector], 'had been'); } /* 'have' verb agreeing with this object as subject */ verbToHave { return tSel(['have', 'have', 'has', 'have', 'have', 'have'] [conjugationSelector], 'had'); } /* * verb endings for regular '-s' and '-es' verbs, agreeing with this * object as the subject */ verbEndingS { return ['', '', 's', '', '', ''][conjugationSelector]; } verbEndingEs { return tSel(['', '', 'es', '', '', ''][conjugationSelector], 'ed'); } verbEndingIes { return tSel(['y', 'y', 'ies', 'y', 'y', 'y'][conjugationSelector], 'ied'); } /* "I'm not" doesn't fit the regular "+n't" rule */ nameIsnt { return conjugationSelector == 1 && !gameMain.usePastTense ? 'I’m not' : inherited; } /* * Show my name for an arrival/departure message. If we've been seen * before by the player character, we'll show our definite name, * otherwise our indefinite name. */ travelerName(arriving) { say(gPlayerChar.hasSeen(self) ? theName : aName); } /* * Test to see if we can match the third-person pronouns. We'll * match these if our inherited test says we match them AND we can * be referred to in the third person. */ canMatchHim = (inherited && canMatch3rdPerson) canMatchHer = (inherited && canMatch3rdPerson) canMatchIt = (inherited && canMatch3rdPerson) canMatchThem = (inherited && canMatch3rdPerson) /* * Test to see if we can match a third-person pronoun ('it', 'him', * 'her', 'them'). We can unless we're the player character and the * player character is referred to in the first or second person. */ canMatch3rdPerson = (!isPlayerChar || referralPerson == ThirdPerson) /* * Set a pronoun antecedent to the given list of ResolveInfo objects. * Pronoun handling is language-specific, so this implementation is * part of the English library, not the generic library. * * If only one object is present, we'll set the object to be the * antecedent of 'it', 'him', or 'her', according to the object's * gender. We'll also set the object as the single antecedent for * 'them'. * * If we have multiple objects present, we'll set the list to be the * antecedent of 'them', and we'll forget about any antecedent for * 'it'. * * Note that the input is a list of ResolveInfo objects, so we must * pull out the underlying game objects when setting the antecedents. */ setPronoun(lst) { /* if the list is empty, ignore it */ if (lst == []) return; /* * if we have multiple objects, the entire list is the antecedent * for 'them'; otherwise, it's a singular antecedent which * depends on its gender */ if (lst.length() > 1) { local objs = lst.mapAll({x: x.obj_}); /* it's 'them' */ setThem(objs); /* forget any 'it' */ setIt(nil); } else if (lst.length() == 1) { /* * We have only one object, so set it as an antecedent * according to its gender. */ setPronounObj(lst[1].obj_); } } /* * Set a pronoun to refer to multiple potential antecedents. This is * used when the verb has multiple noun slots - UNLOCK DOOR WITH KEY. * For verbs like this, we have no way of knowing in advance whether * a future pronoun will refer back to the direct object or the * indirect object (etc) - we could just assume that 'it' will refer * to the direct object, but this won't always be what the player * intended. In natural English, pronoun antecedents must often be * inferred from context at the time of use - so we use the same * approach. * * Pass an argument list consisting of ResolveInfo lists - that is, * pass one argument per noun slot in the verb, and make each * argument a list of ResolveInfo objects. In other words, you call * this just as you would setPronoun(), except you can pass more than * one list argument. * * We'll store the multiple objects as antecedents. When we need to * resolve a future singular pronoun, we'll figure out which of the * multiple antecedents is the most logical choice in the context of * the pronoun's usage. */ setPronounMulti([args]) { local lst, subLst; local gotThem; /* * If there's a plural list, it's 'them'. Arbitrarily use only * the first plural list if there's more than one. */ if ((lst = args.valWhich({x: x.length() > 1})) != nil) { /* set 'them' to the plural list */ setPronoun(lst); /* note that we had a clear 'them' */ gotThem = true; } /* from now on, consider only the sublists with exactly one item */ args = args.subset({x: x.length() == 1}); /* get a list of the singular items from the lists */ lst = args.mapAll({x: x[1].obj_}); /* * Set 'it' to all of the items that can match 'it'; do likewise * with 'him' and 'her'. If there are no objects that can match * a given pronoun, leave that pronoun unchanged. */ if ((subLst = lst.subset({x: x.canMatchIt})).length() > 0) setIt(subLst); if ((subLst = lst.subset({x: x.canMatchHim})).length() > 0) setHim(subLst); if ((subLst = lst.subset({x: x.canMatchHer})).length() > 0) setHer(subLst); /* * set 'them' to the potential 'them' matches, if we didn't * already find a clear plural list */ if (!gotThem && (subLst = lst.subset({x: x.canMatchThem})).length() > 0) setThem(subLst); } /* * Set a pronoun antecedent to the given ResolveInfo list, for the * specified type of pronoun. We don't have to worry about setting * other types of pronouns to this antecedent - we specifically want * to set the given pronoun type. This is language-dependent * because we still have to figure out the number (i.e. singular or * plural) of the pronoun type. */ setPronounByType(typ, lst) { /* check for singular or plural pronouns */ if (typ == PronounThem) { /* it's plural - set a list antecedent */ setPronounAntecedent(typ, lst.mapAll({x: x.obj_})); } else { /* it's singular - set an individual antecedent */ setPronounAntecedent(typ, lst[1].obj_); } } /* * Set a pronoun antecedent to the given simulation object (usually * an object descended from Thing). */ setPronounObj(obj) { /* * Actually use the object's "identity object" as the antecedent * rather than the object itself. In some cases, we use multiple * program objects to represent what appears to be a single * object in the game; in these cases, the internal program * objects all point to the "real" object as their identity * object. Whenever we're manipulating one of these internal * program objects, we want to make sure that its the * player-visible object - the identity object - that appears as * the antecedent for subsequent references. */ obj = obj.getIdentityObject(); /* * Set the appropriate pronoun antecedent, depending on the * object's gender. * * Note that we'll set an object to be the antecedent for both * 'him' and 'her' if the object has both masculine and feminine * usage. */ /* check for masculine usage */ if (obj.canMatchHim) setHim(obj); /* check for feminine usage */ if (obj.canMatchHer) setHer(obj); /* check for neuter usage */ if (obj.canMatchIt) setIt(obj); /* check for third-person plural usage */ if (obj.canMatchThem) setThem([obj]); } /* set a possessive anaphor */ setPossAnaphorObj(obj) { /* check for each type of usage */ if (obj.canMatchHim) possAnaphorTable[PronounHim] = obj; if (obj.canMatchHer) possAnaphorTable[PronounHer] = obj; if (obj.canMatchIt) possAnaphorTable[PronounIt] = obj; if (obj.canMatchThem) possAnaphorTable[PronounThem] = [obj]; } ; /* ------------------------------------------------------------------------ */ /* * Give the postures some additional attributes */ modify Posture /* * Intransitive and transitive forms of the verb, for use in library * messages. Each of these methods simply calls one of the two * corresponding fixed-tense properties, depending on the current * tense. */ msgVerbI = (tSel(msgVerbIPresent, msgVerbIPast)) msgVerbT = (tSel(msgVerbTPresent, msgVerbTPast)) /* * Fixed-tense versions of the above properties, to be defined * individually by each instance of the Posture class. */ /* our present-tense intransitive form ("he stands up") */ // msgVerbIPresent = 'stand{s} up' /* our past-tense intransitive form ("he stood up") */ // msgVerbIPast = 'stood up' /* our present-tense transitive form ("he stands on the chair") */ // msgVerbTPresent = 'stand{s}' /* our past-tense transitive form ("he stood on the chair") */ // msgVerbTPast = 'stood' /* our participle form */ // participle = 'standing' ; modify standing msgVerbIPresent = 'stand{s} up' msgVerbIPast = 'stood up' msgVerbTPresent = 'stand{s}' msgVerbTPast = 'stood' participle = 'standing' ; modify sitting msgVerbIPresent = 'sit{s} down' msgVerbIPast = 'sat down' msgVerbTPresent = 'sit{s}' msgVerbTPast = 'sat' participle = 'sitting' ; modify lying msgVerbIPresent = 'lie{s} down' msgVerbIPast = 'lay down' msgVerbTPresent = 'lie{s}' msgVerbTPast = 'lay' participle = 'lying' ; /* ------------------------------------------------------------------------ */ /* * For our various topic suggestion types, we can infer the full name * from the short name fairly easily. */ modify SuggestedAskTopic fullName = ('ask {it targetActor/him} about ' + name) ; modify SuggestedTellTopic fullName = ('tell {it targetActor/him} about ' + name) ; modify SuggestedAskForTopic fullName = ('ask {it targetActor/him} for ' + name) ; modify SuggestedGiveTopic fullName = ('give {it targetActor/him} ' + name) ; modify SuggestedShowTopic fullName = ('show {it targetActor/him} ' + name) ; modify SuggestedYesTopic name = 'yes' fullName = 'say yes' ; modify SuggestedNoTopic name = 'no' fullName = 'say no' ; /* ------------------------------------------------------------------------ */ /* * Provide custom processing of the player input for matching * SpecialTopic patterns. When we're trying to match a player's command * to a set of active special topics, we'll run the input through this * processing to produce the string that we actually match against the * special topics. * * First, we'll remove any punctuation marks. This ensures that we'll * still match a special topic, for example, if the player puts a period * or a question mark at the end of the command. * * Second, if the user's input starts with "A" or "T" (the super-short * forms of the ASK ABOUT and TELL ABOUT commands), remove the "A" or "T" * and keep the rest of the input. Some users might think that special * topic suggestions are meant as ask/tell topics, so they might * instinctively try these as A/T commands. * * Users *probably* won't be tempted to do the same thing with the full * forms of the commands (e.g., ASK BOB ABOUT APOLOGIZE, TELL BOB ABOUT * EXPLAIN). It's more a matter of habit of using A or T for interaction * that would tempt a user to phrase a special topic this way; once * you're typing out the full form of the command, it generally won't be * grammatical, as special topics generally contain the sense of a verb * in their phrasing. */ modify specialTopicPreParser processInputStr(str) { /* * remove most punctuation from the string - we generally want to * ignore these, as we mostly just want to match keywords */ str = rexReplace(punctPat, str, '', ReplaceAll); /* if it starts with "A" or "T", strip off the leading verb */ if (rexMatch(aOrTPat, str) != nil) str = rexGroup(1)[3]; /* return the processed result */ return str; } /* pattern for string starting with "A" or "T" verbs */ aOrTPat = static new RexPattern( '<nocase><space>*[at]<space>+(<^space>.*)$') /* pattern to eliminate punctuation marks from the string */ punctPat = static new RexPattern('[.?!,;:]'); ; /* * For SpecialTopic matches, treat some strings as "weak": if the user's * input consists of just one of these weak strings and nothing else, * don't match the topic. */ modify SpecialTopic matchPreParse(str, procStr) { /* if it's one of our 'weak' strings, don't match */ if (rexMatch(weakPat, str) != nil) return nil; /* it's not a weak string, so match as usual */ return inherited(str, procStr); } /* * Our "weak" strings - 'i', 'l', 'look': these are weak because a * user typing one of these strings by itself is probably actually * trying to enter the command of the same name, rather than entering * a special topic. These come up in cases where the special topic * is something like "say I don't know" or "tell him you'll look into * it". */ weakPat = static new RexPattern('<nocase><space>*(i|l|look)<space>*$') ; /* ------------------------------------------------------------------------ */ /* * English-specific Traveler changes */ modify Traveler /* * Get my location's name, from the PC's perspective, for describing * my arrival to or departure from my current location. We'll * simply return our location's destName, or "the area" if it * doesn't have one. */ travelerLocName() { /* get our location's name from the PC's perspective */ local nm = location.getDestName(gPlayerChar, gPlayerChar.location); /* if there's a name, return it; otherwise, use "the area" */ return (nm != nil ? nm : 'the area'); } /* * Get my "remote" location name, from the PC's perspective. This * returns my location name, but only if my location is remote from * the PC's perspective - that is, my location has to be outside of * the PC's top-level room. If we're within the PC's top-level * room, we'll simply return an empty string. */ travelerRemoteLocName() { /* * if my location is outside of the PC's outermost room, we're * remote, so return my location name; otherwise, we're local, * so we don't need a remote name at all */ if (isIn(gPlayerChar.getOutermostRoom())) return ''; else return travelerLocName; } ; /* ------------------------------------------------------------------------ */ /* * English-specific Vehicle changes */ modify Vehicle /* * Display the name of the traveler, for use in an arrival or * departure message. */ travelerName(arriving) { /* * By default, start with the indefinite name if we're arriving, * or the definite name if we're leaving. * * If we're leaving, presumably they've seen us before, since we * were already in the room to start with. Since we've been * seen before, the definite is appropriate. * * If we're arriving, even if we're not being seen for the first * time, we haven't been seen yet in this place around this * time, so the indefinite is appropriate. */ say(arriving ? aName : theName); /* show the list of actors aboard */ aboardVehicleListerObj.showList( libGlobal.playerChar, nil, allContents(), 0, 0, libGlobal.playerChar.visibleInfoTable(), nil); } ; /* ------------------------------------------------------------------------ */ /* * English-specific PushTraveler changes */ modify PushTraveler /* * When an actor is pushing an object from one room to another, show * its name with an additional clause indicating the object being * moved along with us. */ travelerName(arriving) { "<<gPlayerChar.hasSeen(self) ? theName : aName>>, pushing <<obj_.theNameObj>>,"; } ; /* ------------------------------------------------------------------------ */ /* * English-specific travel connector changes */ modify PathPassage /* treat "take path" the same as "enter path" or "go through path" */ dobjFor(Take) maybeRemapTo( gAction.getEnteredVerbPhrase() == 'take (dobj)', TravelVia, self) dobjFor(Enter) { verify() { logicalRank(50, 'enter path'); } } dobjFor(GoThrough) { verify() { logicalRank(50, 'enter path'); } } ; modify AskConnector /* * This is the noun phrase we'll use when asking disambiguation * questions for this travel connector: "Which *one* do you want to * enter..." */ travelObjsPhrase = 'one' ; /* ------------------------------------------------------------------------ */ /* * English-specific changes for various nested room types. */ modify BasicChair /* by default, one sits *on* a chair */ objInPrep = 'on' actorInPrep = 'on' actorOutOfPrep = 'off of' ; modify BasicPlatform /* by default, one stands *on* a platform */ objInPrep = 'on' actorInPrep = 'on' actorOutOfPrep = 'off of' ; modify Booth /* by default, one is *in* a booth */ objInPrep = 'in' actorInPrep = 'in' actorOutOfPrep = 'out of' ; /* ------------------------------------------------------------------------ */ /* * Language modifications for Matchstick */ modify Matchstick /* "strike match" means "light match" */ dobjFor(Strike) asDobjFor(Burn) /* "light match" means "burn match" */ dobjFor(Light) asDobjFor(Burn) ; /* * Match state objects. We show "lit" as the state for a lit match, * nothing for an unlit match. */ matchStateLit: ThingState 'lit' stateTokens = ['lit'] ; matchStateUnlit: ThingState stateTokens = ['unlit'] ; /* ------------------------------------------------------------------------ */ /* * English-specific modifications for Room. */ modify Room /* * The ordinary 'name' property is used the same way it's used for * any other object, to refer to the room when it shows up in * library messages and the like: "You can't take the hallway." * * By default, we derive the name from the roomName by converting * the roomName to lower case. Virtually every room will need a * custom room name setting, since the room name is used mostly as a * title for the room, and good titles are hard to generate * mechanically. Many times, converting the roomName to lower case * will produce a decent name to use in messages: "Ice Cave" gives * us "You can't eat the ice cave." However, games will want to * customize the ordinary name separately in many cases, because the * elliptical, title-like format of the room name doesn't always * translate well to an object name: "West of Statue" gives us the * unworkable "You can't eat the west of statue"; better to make the * ordinary name something like "plaza". Note also that some rooms * have proper names that want to preserve their capitalization in * the ordinary name: "You can't eat the Hall of the Ancient Kings." * These cases need to be customized as well. */ name = (roomName.toLower()) /* * The "destination name" of the room. This is primarily intended * for use in showing exit listings, to describe the destination of * a travel connector leading away from our current location, if the * destination is known to the player character. We also use this * as the default source of the name in similar contexts, such as * when we can see this room from another room connected by a sense * connector. * * The destination name usually mirrors the room name, but we use * the name in prepositional phrases involving the room ("east, to * the alley"), so this name should include a leading article * (usually definite - "the") unless the name is proper ("east, to * Dinsley Plaza"). So, by default, we simply use the "theName" of * the room. In many cases, it's better to specify a custom * destName, because this name is used when the PC is outside of the * room, and thus can benefit from a more detailed description than * we'd normally use for the basic name. For example, the ordinary * name might simply be something like "hallway", but since we want * to be clear about exactly which hallway we're talking about when * we're elsewhere, we might want to use a destName like "the * basement hallway" or "the hallway outside the operating room". */ destName = (theName) /* * For top-level rooms, describe an object as being in the room by * describing it as being in the room's nominal drop destination, * since that's the nominal location for the objects directly in the * room. (In most cases, the nominal drop destination for a room is * its floor.) * * If the player character isn't in the same outermost room as this * container, use our remote name instead of the nominal drop * destination. The nominal drop destination is usually something * like the floor or the ground, so it's only suitable when we're in * the same location as what we're describing. */ childInName(childName) { /* if the PC isn't inside us, we're viewing this remotely */ if (!gPlayerChar.isIn(self)) return childInRemoteName(childName, gPlayerChar); else return getNominalDropDestination().childInName(childName); } childInNameWithOwner(chiName) { /* if the PC isn't inside us, we're viewing this remotely */ if (!gPlayerChar.isIn(self)) return inherited(chiName); else return getNominalDropDestination().childInNameWithOwner(chiName); } ; /* ------------------------------------------------------------------------ */ /* * English-specific modifications for the default room parts. */ modify Floor childInNameGen(childName, myName) { return childName + ' on ' + myName; } objInPrep = 'on' actorInPrep = 'on' actorOutOfPrep = 'off of' ; modify defaultFloor noun = 'floor' 'ground' name = 'floor' ; modify defaultGround noun = 'ground' 'floor' name = 'ground' ; modify DefaultWall noun='wall' plural='walls' name='wall'; modify defaultCeiling noun='ceiling' 'roof' name='ceiling'; modify defaultNorthWall adjective='n' 'north' name='north wall'; modify defaultSouthWall adjective='s' 'south' name='south wall'; modify defaultEastWall adjective='e' 'east' name='east wall'; modify defaultWestWall adjective='w' 'west' name='west wall'; modify defaultSky noun='sky' name='sky'; /* ------------------------------------------------------------------------ */ /* * The English-specific modifications for directions. */ modify Direction /* describe a traveler arriving from this direction */ sayArriving(traveler) { /* show the generic arrival message */ gLibMessages.sayArriving(traveler); } /* describe a traveler departing in this direction */ sayDeparting(traveler) { /* show the generic departure message */ gLibMessages.sayDeparting(traveler); } ; /* * The English-specific modifications for compass directions. */ modify CompassDirection /* describe a traveler arriving from this direction */ sayArriving(traveler) { /* show the generic compass direction description */ gLibMessages.sayArrivingDir(traveler, name); } /* describe a traveler departing in this direction */ sayDeparting(traveler) { /* show the generic compass direction description */ gLibMessages.sayDepartingDir(traveler, name); } ; /* * The English-specific definitions for the compass direction objects. * In addition to modifying the direction objects to define the name of * the direction, we add a 'directionName' grammar rule. */ #define DefineLangDir(root, dirNames, backPre) \ grammar directionName(root): dirNames: DirectionProd \ dir = root##Direction \ ; \ \ modify root##Direction \ name = #@root \ backToPrefix = backPre DefineLangDir(north, 'north' | 'n', 'back to the'); DefineLangDir(south, 'south' | 's', 'back to the'); DefineLangDir(east, 'east' | 'e', 'back to the'); DefineLangDir(west, 'west' | 'w', 'back to the'); DefineLangDir(northeast, 'northeast' | 'ne', 'back to the'); DefineLangDir(northwest, 'northwest' | 'nw', 'back to the'); DefineLangDir(southeast, 'southeast' | 'se', 'back to the'); DefineLangDir(southwest, 'southwest' | 'sw', 'back to the'); DefineLangDir(up, 'up' | 'u', 'back'); DefineLangDir(down, 'down' | 'd', 'back'); DefineLangDir(in, 'in', 'back'); DefineLangDir(out, 'out', 'back'); /* * The English-specific shipboard direction modifications. Certain of * the ship directions have no natural descriptions for arrival and/or * departure; for example, there's no good way to say "arriving from * fore." Others don't fit any regular pattern: "he goes aft" rather * than "he departs to aft." As a result, these are a bit irregular * compared to the compass directions and so are individually defined * below. */ DefineLangDir(port, 'port' | 'p', 'back to') sayArriving(trav) { gLibMessages.sayArrivingShipDir(trav, 'the port direction'); } sayDeparting(trav) { gLibMessages.sayDepartingShipDir(trav, 'port'); } ; DefineLangDir(starboard, 'starboard' | 'sb', 'back to') sayArriving(trav) { gLibMessages.sayArrivingShipDir(trav, 'starboard'); } sayDeparting(trav) { gLibMessages.sayDepartingShipDir(trav, 'starboard'); } ; DefineLangDir(aft, 'aft' | 'a', 'back to') sayArriving(trav) { gLibMessages.sayArrivingShipDir(trav, 'aft'); } sayDeparting(trav) { gLibMessages.sayDepartingAft(trav); } ; DefineLangDir(fore, 'fore' | 'forward' | 'f', 'back to') sayArriving(trav) { gLibMessages.sayArrivingShipDir(trav, 'forward'); } sayDeparting(trav) { gLibMessages.sayDepartingFore(trav); } ; /* ------------------------------------------------------------------------ */ /* * Some helper routines for the library messages. */ class MessageHelper: object /* * Show a list of objects for a disambiguation query. If * 'showIndefCounts' is true, we'll show the number of equivalent * items for each equivalent item; otherwise, we'll just show an * indefinite noun phrase for each equivalent item. */ askDisambigList(matchList, fullMatchList, showIndefCounts, dist) { /* show each item */ for (local i = 1, local len = matchList.length() ; i <= len ; ++i) { local equivCnt; local obj; /* get the current object */ obj = matchList[i].obj_; /* * if this isn't the first, add a comma; if this is the * last, add an "or" as well */ if (i == len) ", or "; else if (i != 1) ", "; /* * Check to see if more than one equivalent of this item * appears in the full list. */ for (equivCnt = 0, local j = 1, local fullLen = fullMatchList.length() ; j <= fullLen ; ++j) { /* * if this item is equivalent for the purposes of the * current distinguisher, count it */ if (!dist.canDistinguish(obj, fullMatchList[j].obj_)) { /* it's equivalent - count it */ ++equivCnt; } } /* show this item with the appropriate article */ if (equivCnt > 1) { /* * we have multiple equivalents - show either with an * indefinite article or with a count, depending on the * flags the caller provided */ if (showIndefCounts) { /* a count is desired for each equivalent group */ say(dist.countName(obj, equivCnt)); } else { /* no counts desired - show with an indefinite article */ say(dist.aName(obj)); } } else { /* there's only one - show with a definite article */ say(dist.theName(obj)); } } } /* * For a TAction result, select the short-form or long-form message, * according to the disambiguation status of the action. This is for * the ultra-terse default messages, such as "Taken" or "Dropped", * that sometimes need more descriptive variations. * * If there was no disambiguation involved, we'll use the short * version of the message. * * If there was unclear disambiguation involved (meaning that there * was more than one logical object matching a noun phrase, but the * parser was able to decide based on likelihood rankings), we'll * still use the short version, because we assume that the parser * will have generated a parenthetical announcement to point out its * choice. * * If there was clear disambiguation involved (meaning that more than * one in-scope object matched a noun phrase, but there was only one * choice that passed the logicalness tests), AND the announcement * mode (in gameMain.ambigAnnounceMode) is DescribeClear, we'll * choose the long-form message. */ shortTMsg(short, long) { /* check the disambiguation flags and the announcement mode */ if ((gAction.getDobjFlags() & (ClearDisambig | AlwaysAnnounce)) == ClearDisambig && gAction.getDobjCount() == 1 && gameMain.ambigAnnounceMode == DescribeClear) { /* clear disambig and DescribeClear mode - use the long message */ return long; } else { /* in other cases, use the short message */ return short; } } /* * For a TIAction result, select the short-form or long-form message. * This works just like shortTIMsg(), but takes into account both the * direct and indirect objects. */ shortTIMsg(short, long) { /* check the disambiguation flags and the announcement mode */ if (((gAction.getDobjFlags() & (ClearDisambig | AlwaysAnnounce)) == ClearDisambig || (gAction.getIobjFlags() & (ClearDisambig | AlwaysAnnounce)) == ClearDisambig) && gAction.getDobjCount() == 1 && gAction.getIobjCount() == 1 && gameMain.ambigAnnounceMode == DescribeClear) { /* clear disambig and DescribeClear mode - use the long message */ return long; } else { /* in other cases, use the short message */ return short; } } ; /* ------------------------------------------------------------------------ */ /* * Custom base resolver */ modify Resolver /* * Get the default in-scope object list for a given pronoun. We'll * look for a unique object in scope that matches the desired * pronoun, and return a ResolveInfo list if we find one. If there * aren't any objects in scope that match the pronoun, or multiple * objects are in scope, there's no default. */ getPronounDefault(typ, np) { local map = [PronounHim, &canMatchHim, PronounHer, &canMatchHer, PronounIt, &canMatchIt]; local idx = map.indexOf(typ); local filterProp = (idx != nil ? map[idx + 1] : nil); local lst; /* if we couldn't find a filter for the pronoun, ignore it */ if (filterProp == nil) return []; /* * filter the list of all possible defaults to those that match * the given pronoun */ lst = getAllDefaults.subset({x: x.obj_.(filterProp)}); /* * if the list contains exactly one element, then there's a * unique default; otherwise, there's either nothing here that * matches the pronoun or the pronoun is ambiguous, so there's * no default */ if (lst.length() == 1) { /* * we have a unique object, so they must be referring to it; * because this is just a guess, though, mark it as vague */ lst[1].flags_ |= UnclearDisambig; /* return the list */ return lst; } else { /* * the pronoun doesn't have a unique in-scope referent, so * we can't guess what they mean */ return []; } } ; /* ------------------------------------------------------------------------ */ /* * Custom interactive resolver. This is used for responses to * disambiguation questions and prompts for missing noun phrases. */ modify InteractiveResolver /* * Resolve a pronoun antecedent. We'll resolve a third-person * singular pronoun to the target actor if the target actor matches * in gender, and the target actor isn't the PC. This allows * exchanges like this: * *. >bob, examine *. What do you want Bob to look at? *. *. >his book * * In the above exchange, we'll treat "his" as referring to Bob, the * target actor of the action, because we have referred to Bob in * the partial command (the "BOB, EXAMINE") that triggered the * interactive question. */ resolvePronounAntecedent(typ, np, results, poss) { local lst; /* try resolving with the target actor as the antecedent */ if ((lst = resolvePronounAsTargetActor(typ)) != nil) return lst; /* use the inherited result */ return inherited(typ, np, results, poss); } /* * Get the reflexive third-person pronoun binding (himself, herself, * itself, themselves). If the target actor isn't the PC, and the * gender of the pronoun matches, we'll consider this as referring * to the target actor. This allows exchanges of this form: * *. >bob, examine *. What do you want Bob to examine? *. *. >himself */ getReflexiveBinding(typ) { local lst; /* try resolving with the target actor as the antecedent */ if ((lst = resolvePronounAsTargetActor(typ)) != nil) return lst; /* use the inherited result */ return inherited(typ); } /* * Try matching the given pronoun type to the target actor. If it * matches in gender, and the target actor isn't the PC, we'll * return a resolve list consisting of the target actor. If we * don't have a match, we'll return nil. */ resolvePronounAsTargetActor(typ) { /* * if the target actor isn't the player character, and the * target actor can match the given pronoun type, resolve the * pronoun as the target actor */ if (actor_.canMatchPronounType(typ) && !actor_.isPlayerChar()) { /* the match is the target actor */ return [new ResolveInfo(actor_, 0, nil)]; } /* we didn't match it */ return nil; } ; /* * Custom disambiguation resolver. */ modify DisambigResolver /* * Perform special resolution on pronouns used in interactive * responses. If the pronoun is HIM or HER, then look through the * list of possible matches for a matching gendered object, and use * it as the result if we find one. If we find more than one, then * use the default handling instead, treating the pronoun as * referring back to the simple antecedent previously set. */ resolvePronounAntecedent(typ, np, results, poss) { /* if it's a non-possessive HIM or HER, use our special handling */ if (!poss && typ is in (PronounHim, PronounHer)) { local prop; local sub; /* get the gender indicator property for the pronoun */ prop = (typ == PronounHim ? &canMatchHim : &canMatchHer); /* * Scan through the match list to find the objects that * match the gender of the pronoun. Note that if the player * character isn't referred to in the third person, we'll * ignore the player character for the purposes of matching * this pronoun - if we're calling the PC 'you', then we * wouldn't expect the player to refer to the PC as 'him' or * 'her'. */ sub = matchList.subset({x: x.obj_.(prop)}); /* if the list has a single entry, then use it as the match */ if (sub.length() == 1) return sub; /* * if it has more than one entry, it's still ambiguous, but * we might have narrowed it down, so throw a * still-ambiguous exception and let the interactive * disambiguation ask for further clarification */ results.ambiguousNounPhrase(nil, ResolveAsker, 'one', sub, matchList, matchList, 1, self); return []; } /* * if we get this far, it means we didn't use our special * handling, so use the inherited behavior */ return inherited(typ, np, results, poss); } ; /* ------------------------------------------------------------------------ */ /* * Distinguisher customizations for English. * * Each distinguisher must provide a method that gets the name of an item * for a disamgiguation query. Since these are inherently * language-specific, they're defined here. */ /* * The null distinguisher tells objects apart based strictly on the name * string. When we list objects, we simply show the basic name - since * we can tell apart our objects based on the base name, there's no need * to resort to other names. */ modify nullDistinguisher /* we can tell objects apart if they have different base names */ canDistinguish(a, b) { return a.name != b.name; } name(obj) { return obj.name; } aName(obj) { return obj.aName; } theName(obj) { return obj.theName; } countName(obj, cnt) { return obj.countName(cnt); } ; /* * The basic distinguisher can tell apart objects that are not "basic * equivalents" of one another. Thus, we need make no distinctions when * listing objects apart from showing their names. */ modify basicDistinguisher name(obj) { return obj.disambigName; } aName(obj) { return obj.aDisambigName; } theName(obj) { return obj.theDisambigName; } countName(obj, cnt) { return obj.countDisambigName(cnt); } ; /* * The ownership distinguisher tells objects apart based on who "owns" * them, so it shows the owner or location name when listing the object. */ modify ownershipDistinguisher name(obj) { return obj.theNameOwnerLoc(true); } aName(obj) { return obj.aNameOwnerLoc(true); } theName(obj) { return obj.theNameOwnerLoc(true); } countName(obj, cnt) { return obj.countNameOwnerLoc(cnt, true); } /* note that we're prompting based on this distinguisher */ notePrompt(lst) { /* * notify each object that we're referring to it by * owner/location in a disambiguation prompt */ foreach (local cur in lst) cur.obj_.notePromptByOwnerLoc(true); } ; /* * The location distinguisher tells objects apart based on their * containers, so it shows the location name when listing the object. */ modify locationDistinguisher name(obj) { return obj.theNameOwnerLoc(nil); } aName(obj) { return obj.aNameOwnerLoc(nil); } theName(obj) { return obj.theNameOwnerLoc(nil); } countName(obj, cnt) { return obj.countNameOwnerLoc(cnt, nil); } /* note that we're prompting based on this distinguisher */ notePrompt(lst) { /* notify the objects of their use in a disambiguation prompt */ foreach (local cur in lst) cur.obj_.notePromptByOwnerLoc(nil); } ; /* * The lit/unlit distinguisher tells apart objects based on whether * they're lit or unlit, so we list objects as lit or unlit explicitly. */ modify litUnlitDistinguisher name(obj) { return obj.nameLit; } aName(obj) { return obj.aNameLit; } theName(obj) { return obj.theNameLit; } countName(obj, cnt) { return obj.pluralNameLit; } ; /* ------------------------------------------------------------------------ */ /* * Enligh-specific light source modifications */ modify LightSource /* provide lit/unlit names for litUnlitDistinguisher */ nameLit = ((isLit ? 'lit ' : 'unlit ') + name) aNameLit() { /* * if this is a mass noun or a plural name, just use the name * with lit/unlit; otherwise, add "a" */ if (isPlural || isMassNoun) return (isLit ? 'lit ' : 'unlit ') + name; else return (isLit ? 'a lit ' : 'an unlit ') + name; } theNameLit = ((isLit ? 'the lit ' : 'the unlit ') + name) pluralNameLit = ((isLit ? 'lit ' : 'unlit ') + pluralName) /* * Allow 'lit' and 'unlit' as adjectives - but even though we define * these as our adjectives in the dictionary, we'll only accept the * one appropriate for our current state, thanks to our state * objects. */ adjective = 'lit' 'unlit' ; /* * Light source list states. An illuminated light source shows its * status as "providing light"; an unlit light source shows no extra * status. */ lightSourceStateOn: ThingState 'providing light' stateTokens = ['lit'] ; lightSourceStateOff: ThingState stateTokens = ['unlit'] ; /* ------------------------------------------------------------------------ */ /* * Wearable states - a wearable item can be either worn or not worn. */ /* "worn" */ wornState: ThingState 'being worn' /* * In listings of worn items, don't bother mentioning our 'worn' * status, as the entire list consists of items being worn - it * would be redundant to point out that the items in a list of items * being worn are being worn. */ wornName(lst) { return nil; } ; /* * "Unworn" state. Don't bother mentioning the status of an unworn item, * since this is the default for everything. */ unwornState: ThingState; /* ------------------------------------------------------------------------ */ /* * Typographical effects output filter. This filter looks for certain * sequences in the text and converts them to typographical equivalents. * Authors could simply write the HTML for the typographical markups in * the first place, but it's easier to write the typewriter-like * sequences and let this filter convert to HTML. * * We perform the following conversions: * * '---' -> &zwnbsp;— *. '--' -> &zwnbsp;– *. sentence-ending punctuation -> same +   * * Since this routine is called so frequently, we hard-code the * replacement strings, rather than using properties, for slightly faster * performance. Since this routine is so simple, games that want to * customize the replacement style should simply replace this entire * routine with a new routine that applies the customizations. * * Note that we define this filter in the English-specific part of the * library, because it seems almost certain that each language will want * to customize it for local conventions. */ typographicalOutputFilter: OutputFilter filterText(ostr, val) { /* * Look for sentence-ending punctuation, and put an 'en' space * after each occurrence. Recognize ends of sentences even if we * have closing quotes, parentheses, or other grouping characters * following the punctuation. Do this before the hyphen * substitutions so that we can look for ordinary hyphens rather * than all of the expanded versions. */ val = rexReplace(eosPattern, val, '%1\u2002', ReplaceAll); /* undo any abbreviations we mistook for sentence endings */ val = rexReplace(abbrevPat, val, '%1. ', ReplaceAll); /* * Replace dashes with typographical hyphens. Three hyphens in a * row become an em-dash, and two in a row become an en-dash. * Note that we look for the three-hyphen sequence first, because * if we did it the other way around, we'd incorrectly find the * first two hyphens of each '---' sequence and replace them with * an en-dash, causing us to miss the '---' sequences entirely. * * We put a no-break marker (\uFEFF) just before each hyphen, and * an okay-to-break marker (\u200B) just after, to ensure that we * won't have a line break between the preceding text and the * hyphen, and to indicate that a line break is specifically * allowed if needed to the right of the hyphen. */ val = val.findReplace(['---', '--'], ['\uFEFF—\u200B', '\uFEFF–\u200B']); /* return the result */ return val; } /* * The end-of-sentence pattern. This looks a bit complicated, but * all we're looking for is a period, exclamation point, or question * mark, optionally followed by any number of closing group marks * (right parentheses or square brackets, closing HTML tags, or * double or single quotes in either straight or curly styles), all * followed by an ordinary space. * * If a lower-case letter follows the space, though, we won't * consider it a sentence ending. This applies most commonly after * quoted passages ending with what would normally be sentence-ending * punctuation: "'Who are you?' he asked." In these cases, the * enclosing sentence isn't ending, so we don't want the extra space. * We can tell the enclosing sentence isn't ending because a * non-capital letter follows. * * Note that we specifically look only for ordinary spaces. Any * sentence-ending punctuation that's followed by a quoted space or * any typographical space overrides this substitution. */ eosPattern = static new RexPattern( '<case>' + '(' + '[.!?]' + '(' + '<rparen|rsquare|dquote|squote|\u2019|\u201D>' + '|<langle><^rangle>*<rangle>' + ')*' + ')' + ' +(?![-a-z])' ) /* pattern for abbreviations that were mistaken for sentence endings */ abbrevPat = static new RexPattern( '<nocase>%<(' + abbreviations + ')<dot>\u2002') /* * Common abbreviations. These are excluded from being treated as * sentence endings when they appear with a trailing period. * * Note that abbrevPat must be rebuilt manually if you change this on * the fly - abbrevPat is static, so it picks up the initial value of * this property at start-up, and doesn't re-evaluate it while the * game is running. */ abbreviations = 'mr|mrs|ms|dr|prof' ; /* ------------------------------------------------------------------------ */ /* * The English-specific message builder. */ langMessageBuilder: MessageBuilder /* * The English message substitution parameter table. * * Note that we specify two additional elements for each table entry * beyond the standard language-independent complement: * * info[4] = reflexive property - this is the property to invoke * when the parameter is used reflexively (in other words, its * target object is the same as the most recent target object used * in the nominative case). If this is nil, the parameter has no * reflexive form. * * info[5] = true if this is a nominative usage, nil if not. We use * this to determine which target objects are used in the nominative * case, so that we can remember those objects for subsequent * reflexive usages. */ paramList_ = [ /* parameters that imply the actor as the target object */ ['you/he', &theName, 'actor', nil, true], ['you/she', &theName, 'actor', nil, true], ['you\'re/he\'s', &itIsContraction, 'actor', nil, true], ['you\'re/she\'s', &itIsContraction, 'actor', nil, true], ['you\'re', &itIsContraction, 'actor', nil, true], ['you/him', &theNameObj, 'actor', &itReflexive, nil], ['you/her', &theNameObj, 'actor', &itReflexive, nil], ['your/her', &theNamePossAdj, 'actor', nil, nil], ['your/his', &theNamePossAdj, 'actor', nil, nil], ['your', &theNamePossAdj, 'actor', nil, nil], ['yours/hers', &theNamePossNoun, 'actor', nil, nil], ['yours/his', &theNamePossNoun, 'actor', nil, nil], ['yours', &theNamePossNoun, 'actor', nil, nil], ['yourself/himself', &itReflexive, 'actor', nil, nil], ['yourself/herself', &itReflexive, 'actor', nil, nil], ['yourself', &itReflexive, 'actor', nil, nil], /* parameters that don't imply any target object */ ['the/he', &theName, nil, nil, true], ['the/she', &theName, nil, nil, true], ['the/him', &theNameObj, nil, &itReflexive, nil], ['the/her', &theNameObj, nil, &itReflexive, nil], ['the\'s/her', &theNamePossAdj, nil, &itPossAdj, nil], ['the\'s/hers', &theNamePossNoun, nil, &itPossNoun, nil], /* * Verb 's' endings. In most cases, you should use 's/d', 's/ed', * or 's/?ed' rather than 's', except in places where you know you * will never need the past tense form, because 's' doesn't handle * the past tense. Don't use 's/?ed' with a literal question * mark; put a consonant in place of the question mark instead. */ ['s', &verbEndingS, nil, nil, true], ['s/d', &verbEndingSD, nil, nil, true], ['s/ed', &verbEndingSEd, nil, nil, true], ['s/?ed', &verbEndingSMessageBuilder_, nil, nil, true], ['es', &verbEndingEs, nil, nil, true], ['es/ed', &verbEndingEs, nil, nil, true], ['ies', &verbEndingIes, nil, nil, true], ['ies/ied', &verbEndingIes, nil, nil, true], ['is', &verbToBe, nil, nil, true], ['are', &verbToBe, nil, nil, true], ['was', &verbWas, nil, nil, true], ['were', &verbWas, nil, nil, true], ['has', &verbToHave, nil, nil, true], ['have', &verbToHave, nil, nil, true], ['does', &verbToDo, nil, nil, true], ['do', &verbToDo, nil, nil, true], ['goes', &verbToGo, nil, nil, true], ['go', &verbToGo, nil, nil, true], ['comes', &verbToCome, nil, nil, true], ['come', &verbToCome, nil, nil, true], ['leaves', &verbToLeave, nil, nil, true], ['leave', &verbToLeave, nil, nil, true], ['sees', &verbToSee, nil, nil, true], ['see', &verbToSee, nil, nil, true], ['says', &verbToSay, nil, nil, true], ['say', &verbToSay, nil, nil, true], ['must', &verbMust, nil, nil, true], ['can', &verbCan, nil, nil, true], ['cannot', &verbCannot, nil, nil, true], ['can\'t', &verbCant, nil, nil, true], ['will', &verbWill, nil, nil, true], ['won\'t', &verbWont, nil, nil, true], ['a/he', &aName, nil, nil, true], ['an/he', &aName, nil, nil, true], ['a/she', &aName, nil, nil, true], ['an/she', &aName, nil, nil, true], ['a/him', &aNameObj, nil, &itReflexive, nil], ['an/him', &aNameObj, nil, &itReflexive, nil], ['a/her', &aNameObj, nil, &itReflexive, nil], ['an/her', &aNameObj, nil, &itReflexive, nil], ['it/he', &itNom, nil, nil, true], ['it/she', &itNom, nil, nil, true], ['it/him', &itObj, nil, &itReflexive, nil], ['it/her', &itObj, nil, &itReflexive, nil], /* * note that we don't have its/his, because that leaves * ambiguous whether we want an adjective or noun form - so we * only use the feminine pronouns with these, to make the * meaning unambiguous */ ['its/her', &itPossAdj, nil, nil, nil], ['its/hers', &itPossNoun, nil, nil, nil], ['it\'s/he\'s', &itIsContraction, nil, nil, true], ['it\'s/she\'s', &itIsContraction, nil, nil, true], ['it\'s', &itIsContraction, nil, nil, true], ['that/he', &thatNom, nil, nil, true], ['that/she', &thatNom, nil, nil, true], ['that/him', &thatObj, nil, &itReflexive, nil], ['that/her', &thatObj, nil, &itReflexive, nil], ['that\'s', &thatIsContraction, nil, nil, true], ['itself', &itReflexive, nil, nil, nil], ['itself/himself', &itReflexive, nil, nil, nil], ['itself/herself', &itReflexive, nil, nil, nil], /* default preposition for standing in/on something */ ['on', &actorInName, nil, nil, nil], ['in', &actorInName, nil, nil, nil], ['outof', &actorOutOfName, nil, nil, nil], ['offof', &actorOutOfName, nil, nil, nil], ['onto', &actorIntoName, nil, nil, nil], ['into', &actorIntoName, nil, nil, nil], /* * The special invisible subject marker - this can be used to * mark the subject in sentences that vary from the * subject-verb-object structure that most English sentences * take. The usual SVO structure allows the message builder to * see the subject first in most sentences naturally, but in * unusual sentence forms it is sometimes useful to be able to * mark the subject explicitly. This doesn't actually result in * any output; it's purely for marking the subject for our * internal book-keeping. * * (The main reason the message builder wants to know the subject * in the first place is so that it can use a reflexive pronoun * if the same object ends up being used as a direct or indirect * object: "you can't open yourself" rather than "you can't open * you.") */ ['subj', &dummyName, nil, nil, true] ] /* * Add a hook to the generateMessage method, which we use to * pre-process the source string before expanding the substitution * parameters. */ generateMessage(orig) { return inherited(processOrig(orig)); } /* * Pre-process a source string containing substitution parameters, * before generating the expanded message from it. * * We use this hook to implement the special tense-switching syntax * {<present>|<past>}. Although it superficially looks like an * ordinary substitution parameter, we actually can't use the normal * parameter substitution framework for that, because we want to * allow the <present> and <past> substrings themselves to contain * substitution parameters, and the normal framework doesn't allow * for recursive substitution. * * We simply replace every sequence of the form {<present>|<past>} * with either <present> or <past>, depending on the current * narrative tense. We then substitute braces for square brackets in * the resulting string. This allows treating every bracketed tag * inside the tense-switching sequence as a regular substitution * parameter. * * For example, the sequence "{take[s]|took}" appearing in the * message string would be replaced with "take{s}" if the current * narrative tense is present, and would be replaced with "took" if * the current narrative tense is past. The string "take{s}", if * selected, would in turn be expanded to either "take" or "takes", * depending on the grammatical person of the subject, as per the * regular substitution mechanism. */ processOrig(str) { local idx = 1; local len; local match; local replStr; /* * Keep searching the string until we run out of character * sequences with a special meaning (specifically, we look for * substrings enclosed in braces, and stuttered opening braces). */ for (;;) { /* * Find the next special sequence. */ match = rexSearch(patSpecial, str, idx); /* * If there are no more special sequence, we're done * pre-processing the string. */ if (match == nil) break; /* * Remember the starting index and length of the special * sequence. */ idx = match[1]; len = match[2]; /* * Check if this special sequence matches our tense-switching * syntax. */ if (rexMatch(patTenseSwitching, str, idx) == nil) { /* * It doesn't, so forget about it and continue searching * from the end of this special sequence. */ idx += len; continue; } /* * Extract either the first or the second embedded string, * depending on the current narrative tense. */ match = rexGroup(tSel(1, 2)); replStr = match[3]; /* * Convert all square brackets to braces in the extracted * string. */ replStr = replStr.findReplace('[', '{', ReplaceAll); replStr = replStr.findReplace(']', '}', ReplaceAll); /* * In the original string, replace the tense-switching * sequence with the extracted string. */ str = str.substr(1, idx - 1) + replStr + str.substr(idx + len); /* * Move the index at the end of the substituted string. */ idx += match[2]; } /* * We're done - return the result. */ return str; } /* * Pre-compiled regular expression pattern matching any sequence with * a special meaning in a message string. * * We match either a stuttered opening brace, or a single opening * brace followed by any sequence of characters that doesn't contain * a closing brace followed by a closing brace. */ patSpecial = static new RexPattern ('<lbrace><lbrace>|<lbrace>(?!<lbrace>)((?:<^rbrace>)*)<rbrace>') /* * Pre-compiled regular expression pattern matching our special * tense-switching syntax. * * We match a single opening brace, followed by any sequence of * characters that doesn't contain a closing brace or a vertical bar, * followed by a vertical bar, followed by any sequence of characters * that doesn't contain a closing brace or a vertical bar, followed * by a closing brace. */ patTenseSwitching = static new RexPattern ( '<lbrace>(?!<lbrace>)((?:<^rbrace|vbar>)*)<vbar>' + '((?:<^rbrace|vbar>)*)<rbrace>' ) /* * The most recent target object used in the nominative case. We * note this so that we can supply reflexive mappings when the same * object is re-used in the objective case. This allows us to map * things like "you can't take you" to the better-sounding "you * can't take yourself". */ lastSubject_ = nil /* the parameter name of the last subject ('dobj', 'actor', etc) */ lastSubjectName_ = nil /* * Get the target object property mapping. If the target object is * the same as the most recent subject object (i.e., the last object * used in the nominative case), and this parameter has a reflexive * form property, we'll return the reflexive form property. * Otherwise, we'll return the standard property mapping. * * Also, if there was an exclamation mark at the end of any word in * the tag, we'll return a property returning a fixed-tense form of * the property for the tag. */ getTargetProp(targetObj, paramObj, info) { local ret; /* * If this target object matches the last subject, and we have a * reflexive rendering, return the property for the reflexive * rendering. * * Only use the reflexive rendering if the parameter name is * different - if the parameter name is the same, then presumably * the message will have been written with a reflexive pronoun or * not, exactly as the author wants it. When the author knows * going in that these two objects are structurally the same, * they want the exact usage they wrote. */ if (targetObj == lastSubject_ && paramObj != lastSubjectName_ && info[4] != nil) { /* use the reflexive rendering */ ret = info[4]; } else { /* no special handling; inherit the default handling */ ret = inherited(targetObj, paramObj, info); } /* if this is a nominative usage, note it as the last subject */ if (info[5]) { lastSubject_ = targetObj; lastSubjectName_ = paramObj; } /* * If there was an exclamation mark at the end of any word in the * parameter string (which we remember via the fixedTenseProp_ * property), store the original target property in * fixedTenseProp_ and use &propWithPresentMessageBuilder_ as the * target property instead. propWithPresentMessageBuilder_ acts * as a wrapper for the original target property, which it * invokes after temporarily switching to the present tense. */ if (fixedTenseProp_) { fixedTenseProp_ = ret; ret = &propWithPresentMessageBuilder_; } /* return the result */ return ret; } /* end-of-sentence match pattern */ patEndOfSentence = static new RexPattern('[.;:!?]<^alphanum>') /* * Process result text. */ processResult(txt) { /* * If the text contains any sentence-ending punctuation, reset * our internal memory of the subject of the sentence. We * consider the sentence to end with a period, semicolon, colon, * question mark, or exclamation point followed by anything * other than an alpha-numeric. (We require the secondary * character so that we don't confuse things like "3:00" or * "7.5" to contain sentence-ending punctuation.) */ if (rexSearch(patEndOfSentence, txt) != nil) { /* * we have a sentence ending in this run of text, so any * saved subject object will no longer apply after this text * - forget our subject object */ lastSubject_ = nil; lastSubjectName_ = nil; } /* return the inherited processing */ return inherited(txt); } /* some pre-compiled search patterns we use a lot */ patIdObjSlashIdApostS = static new RexPattern( '(<^space>+)(<space>+<^space>+)\'s(/<^space>+)$') patIdObjApostS = static new RexPattern( '(?!<^space>+\'s<space>)(<^space>+)(<space>+<^space>+)\'s$') patParamWithExclam = static new RexPattern('.*(!)(?:<space>.*|/.*|$)') patSSlashLetterEd = static new RexPattern( 's/(<alpha>ed)$|(<alpha>ed)/s$') /* * Rewrite a parameter string for a language-specific syntax * extension. * * For English, we'll handle the possessive apostrophe-s suffix * specially, by allowing the apostrophe-s to be appended to the * target object name. If we find an apostrophe-s on the target * object name, we'll move it to the preceding identifier name: * * the dobj's -> the's dobj *. the dobj's/he -> the's dobj/he *. he/the dobj's -> he/the's dobj * * We also use this method to check for the presence of an * exclamation mark at the end of any word in the parameter string * (triggering the fixed-tense handling), and to detect a parameter * string matching the {s/?ed} syntax, where ? is any letter, and * rewrite it literally as 's/?ed' literally. */ langRewriteParam(paramStr) { /* * Check for an exclamation mark at the end of any word in the * parameter string, and remember the result of the test. */ local exclam = rexMatch(patParamWithExclam, paramStr); fixedTenseProp_ = exclam; /* * Remove the exclamation mark, if any. */ if (exclam) { local exclamInd = rexGroup(1)[1]; paramStr = paramStr.substr(1, exclamInd - 1) + paramStr.substr(exclamInd + 1); } /* look for "id obj's" and "id1 obj's/id2" */ if (rexMatch(patIdObjSlashIdApostS, paramStr) != nil) { /* rewrite with the "'s" moved to the preceding parameter name */ paramStr = rexGroup(1)[3] + '\'s' + rexGroup(2)[3] + rexGroup(3)[3]; } else if (rexMatch(patIdObjApostS, paramStr) != nil) { /* rewrite with the "'s" moved to the preceding parameter name */ paramStr = rexGroup(1)[3] + '\'s' + rexGroup(2)[3]; } /* * Check if this parameter matches the {s/?ed} or {?ed/s} syntax. */ if (rexMatch(patSSlashLetterEd, paramStr)) { /* * It does - remember the past verb ending, and rewrite the * parameter literally as 's/?ed'. */ pastEnding_ = rexGroup(1)[3]; paramStr = 's/?ed'; } /* return our (possibly modified) result */ return paramStr; } /* * This property is used to temporarily store the past-tense ending * of a verb to be displayed by Thing.verbEndingSMessageBuilder_. * It's for internal use only; game authors shouldn't have any reason * to access it directly. */ pastEnding_ = nil /* * This property is used to temporarily store either a boolean value * indicating whether the last encountered parameter string had an * exclamation mark at the end of any word, or a property to be * invoked by Thing.propWithPresentMessageBuilder_. This field is * for internal use only; authors shouldn't have any reason to access * it directly. */ fixedTenseProp_ = nil ; /* ------------------------------------------------------------------------ */ /* * Temporarily override the current narrative tense and invoke a callback * function. */ withTense(usePastTense, callback) { /* * Remember the old value of the usePastTense flag. */ local oldUsePastTense = gameMain.usePastTense; /* * Set the new value. */ gameMain.usePastTense = usePastTense; /* * Invoke the callback (remembering the return value) and restore the * usePastTense flag on our way out. */ local ret; try { ret = callback(); } finally { gameMain.usePastTense = oldUsePastTense; } /* * Return the result. */ return ret; } /* ------------------------------------------------------------------------ */ /* * Functions for spelling out numbers. These functions take a numeric * value as input, and return a string with the number spelled out as * words in English. For example, given the number 52, we'd return a * string like 'fifty-two'. * * These functions obviously have language-specific implementations. * Note also that even their interfaces might vary by language. Some * languages might need additional information in the interface; for * example, some languages might need to know the grammatical context * (such as part of speech, case, or gender) of the result. * * Note that some of the spellIntXxx flags might not be meaningful in all * languages, because most of the flags are by their very nature * associated with language-specific idioms. Translations are free to * ignore flags that indicate variations with no local equivalent, and to * add their own language-specific flags as needed. */ /* * Spell out an integer number in words. Returns a string with the * spelled-out number. * * Note that this simple version of the function uses the default * options. If you want to specify non-default options with the * SpellIntXxx flags, you can call spellIntExt(). */ spellInt(val) { return spellIntExt(val, 0); } /* * Spell out an integer number in words, but only if it's below the given * threshold. It's often awkward in prose to spell out large numbers, * but exactly what constitutes a large number depends on context, so * this routine lets the caller specify the threshold. * * If the absolute value of val is less than (not equal to) the threshold * value, we'll return a string with the number spelled out. If the * absolute value is greater than or equal to the threshold value, we'll * return a string representing the number in decimal digits. */ spellIntBelow(val, threshold) { return spellIntBelowExt(val, threshold, 0, 0); } /* * Spell out an integer number in words if it's below a threshold, using * the spellIntXxx flags given in spellFlags to control the spelled-out * format, and using the DigitFormatXxx flags in digitFlags to control * the digit format. */ spellIntBelowExt(val, threshold, spellFlags, digitFlags) { local absval; /* compute the absolute value */ absval = (val < 0 ? -val : val); /* check the value to see whether to spell it or write it as digits */ if (absval < threshold) { /* it's below the threshold - spell it out in words */ return spellIntExt(val, spellFlags); } else { /* it's not below the threshold - write it as digits */ return intToDecimal(val, digitFlags); } } /* * Format a number as a string of decimal digits. The DigitFormatXxx * flags specify how the number is to be formatted.` */ intToDecimal(val, flags) { local str; local sep; /* perform the basic conversion */ str = toString(val); /* add group separators as needed */ if ((flags & DigitFormatGroupComma) != 0) { /* explicitly use a comma as a separator */ sep = ','; } else if ((flags & DigitFormatGroupPeriod) != 0) { /* explicitly use a period as a separator */ sep = '.'; } else if ((flags & DigitFormatGroupSep) != 0) { /* use the current languageGlobals separator */ sep = languageGlobals.digitGroupSeparator; } else { /* no separator */ sep = nil; } /* if there's a separator, add it in */ if (sep != nil) { local i; local len; /* * Insert the separator before each group of three digits. * Start at the right end of the string and work left: peel off * the last three digits and insert a comma. Then, move back * four characters through the string - another three-digit * group, plus the comma we inserted - and repeat. Keep going * until the amount we'd want to peel off the end is as long or * longer than the entire remaining string. */ for (i = 3, len = str.length() ; len > i ; i += 4) { /* insert this comma */ str = str.substr(1, len - i) + sep + str.substr(len - i + 1); /* note the new length */ len = str.length(); } } /* return the result */ return str; } /* * Spell out an integer number - "extended" interface with flags. The * "flags" argument is a (bitwise-OR'd) combination of SpellIntXxx * values, specifying the desired format of the result. */ spellIntExt(val, flags) { local str; local trailingSpace; local needAnd; local powers = [1000000000, ' billion ', 1000000, ' million ', 1000, ' thousand ', 100, ' hundred ']; /* start with an empty string */ str = ''; trailingSpace = nil; needAnd = nil; /* if it's zero, it's a special case */ if (val == 0) return 'zero'; /* * if the number is negative, note it in the string, and use the * absolute value */ if (val < 0) { str = 'negative '; val = -val; } /* do each named power of ten */ for (local i = 1 ; val >= 100 && i <= powers.length() ; i += 2) { /* * if we're in teen-hundreds mode, do the teen-hundreds - this * only works for values from 1,100 to 9,999, since a number like * 12,000 doesn't work this way - 'one hundred twenty hundred' is * no good */ if ((flags & SpellIntTeenHundreds) != 0 && val >= 1100 && val < 10000) { /* if desired, add a comma if there was a prior power group */ if (needAnd && (flags & SpellIntCommas) != 0) str = str.substr(1, str.length() - 1) + ', '; /* spell it out as a number of hundreds */ str += spellIntExt(val / 100, flags) + ' hundred '; /* take off the hundreds */ val %= 100; /* note the trailing space */ trailingSpace = true; /* we have something to put an 'and' after, if desired */ needAnd = true; /* * whatever's left is below 100 now, so there's no need to * keep scanning the big powers of ten */ break; } /* if we have something in this power range, apply it */ if (val >= powers[i]) { /* if desired, add a comma if there was a prior power group */ if (needAnd && (flags & SpellIntCommas) != 0) str = str.substr(1, str.length() - 1) + ', '; /* add the number of multiples of this power and the power name */ str += spellIntExt(val / powers[i], flags) + powers[i+1]; /* take it out of the remaining value */ val %= powers[i]; /* * note that we have a trailing space in the string (all of * the power-of-ten names have a trailing space, to make it * easy to tack on the remainder of the value) */ trailingSpace = true; /* we have something to put an 'and' after, if one is desired */ needAnd = true; } } /* * if we have anything left, and we have written something so far, * and the caller wanted an 'and' before the tens part, add the * 'and' */ if ((flags & SpellIntAndTens) != 0 && needAnd && val != 0) { /* add the 'and' */ str += 'and '; trailingSpace = true; } /* do the tens */ if (val >= 20) { /* anything above the teens is nice and regular */ str += ['twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'][val/10 - 1]; val %= 10; /* if it's non-zero, we'll add the units, so add a hyphen */ if (val != 0) str += '-'; /* we no longer have a trailing space in the string */ trailingSpace = nil; } else if (val >= 10) { /* we have a teen */ str += ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen'][val - 9]; /* we've finished with the number */ val = 0; /* there's no trailing space */ trailingSpace = nil; } /* if we have a units value, add it */ if (val != 0) { /* add the units name */ str += ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'][val]; /* we have no trailing space now */ trailingSpace = nil; } /* if there's a trailing space, remove it */ if (trailingSpace) str = str.substr(1, str.length() - 1); /* return the string */ return str; } /* * Return a string giving the numeric ordinal representation of a number: * 1st, 2nd, 3rd, 4th, etc. */ intOrdinal(n) { local s; /* start by getting the string form of the number */ s = toString(n); /* now add the appropriate suffix */ if (n >= 10 && n <= 19) { /* the teens all end in 'th' */ return s + 'th'; } else { /* * for anything but a teen, a number whose last digit is 1 * always has the suffix 'st' (for 'xxx-first', as in '141st'), * a number whose last digit is 2 always ends in 'nd' (for * 'xxx-second', as in '532nd'), a number whose last digit is 3 * ends in 'rd' (for 'xxx-third', as in '53rd'), and anything * else ends in 'th' */ switch(n % 10) { case 1: return s + 'st'; case 2: return s + 'nd'; case 3: return s + 'rd'; default: return s + 'th'; } } } /* * Return a string giving a fully spelled-out ordinal form of a number: * first, second, third, etc. */ spellIntOrdinal(n) { return spellIntOrdinalExt(n, 0); } /* * Return a string giving a fully spelled-out ordinal form of a number: * first, second, third, etc. This form takes the same flag values as * spellIntExt(). */ spellIntOrdinalExt(n, flags) { local s; /* get the spelled-out form of the number itself */ s = spellIntExt(n, flags); /* * If the number ends in 'one', change the ending to 'first'; 'two' * becomes 'second'; 'three' becomes 'third'; 'five' becomes * 'fifth'; 'eight' becomes 'eighth'; 'nine' becomes 'ninth'. If * the number ends in 'y', change the 'y' to 'ieth'. 'Zero' becomes * 'zeroeth'. For everything else, just add 'th' to the spelled-out * name */ if (s == 'zero') return 'zeroeth'; if (s.endsWith('one')) return s.substr(1, s.length() - 3) + 'first'; else if (s.endsWith('two')) return s.substr(1, s.length() - 3) + 'second'; else if (s.endsWith('three')) return s.substr(1, s.length() - 5) + 'third'; else if (s.endsWith('five')) return s.substr(1, s.length() - 4) + 'fifth'; else if (s.endsWith('eight')) return s.substr(1, s.length() - 5) + 'eighth'; else if (s.endsWith('nine')) return s.substr(1, s.length() - 4) + 'ninth'; else if (s.endsWith('y')) return s.substr(1, s.length() - 1) + 'ieth'; else return s + 'th'; } /* ------------------------------------------------------------------------ */ /* * Parse a spelled-out number. This is essentially the reverse of * spellInt() and related functions: we take a string that contains a * spelled-out number and return the integer value. This uses the * command parser's spelled-out number rules, so we can parse anything * that would be recognized as a number in a command. * * If the string contains numerals, we'll treat it as a number in digit * format: for example, if it contains '789', we'll return 789. * * If the string doesn't parse as a number, we return nil. */ parseInt(str) { try { /* tokenize the string */ local toks = cmdTokenizer.tokenize(str); /* parse it */ return parseIntTokens(toks); } catch (Exception exc) { /* * on any exception, just return nil to indicate that we couldn't * parse the string as a number */ return nil; } } /* * Parse a spelled-out number that's given as a token list (as returned * from Tokenizer.tokenize). If we can successfully parse the token list * as a number, we'll return the integer value. If not, we'll return * nil. */ parseIntTokens(toks) { try { /* * if the first token contains digits, treat it as a numeric * string value rather than a spelled-out number */ if (toks.length() != 0 && rexMatch('<digit>+', getTokOrig(toks[1])) != nil) return toInteger(getTokOrig(toks[1])); /* parse it using the spelledNumber production */ local lst = spelledNumber.parseTokens(toks, cmdDict); /* * if we got a match, return the integer value; if not, it's not * parseable as a number, so return nil */ return (lst.length() != 0 ? lst[1].getval() : nil); } catch (Exception exc) { /* * on any exception, just return nil to indicate that it's not * parseable as a number */ return nil; } } /* ------------------------------------------------------------------------ */ /* * Additional token types for US English. */ /* special "apostrophe-s" token */ enum token tokApostropheS; /* special apostrophe token for plural possessives ("the smiths' house") */ enum token tokPluralApostrophe; /* special abbreviation-period token */ enum token tokAbbrPeriod; /* special "#nnn" numeric token */ enum token tokPoundInt; /* * Command tokenizer for US English. Other language modules should * provide their own tokenizers to allow for differences in punctuation * and other lexical elements. */ cmdTokenizer: Tokenizer rules_ = static [ /* skip whitespace */ ['whitespace', new RexPattern('<Space>+'), nil, &tokCvtSkip, nil], /* certain punctuation marks */ ['punctuation', new RexPattern('[.,;:?!]'), tokPunct, nil, nil], /* * We have a special rule for spelled-out numbers from 21 to 99: * when we see a 'tens' word followed by a hyphen followed by a * digits word, we'll pull out the tens word, the hyphen, and * the digits word as separate tokens. */ ['spelled number', new RexPattern('<NoCase>(twenty|thirty|forty|fifty|sixty|' + 'seventy|eighty|ninety)-' + '(one|two|three|four|five|six|seven|eight|nine)' + '(?!<AlphaNum>)'), tokWord, &tokCvtSpelledNumber, nil], /* * Initials. We'll look for strings of three or two initials, * set off by periods but without spaces. We'll look for * three-letter initials first ("G.H.W. Billfold"), then * two-letter initials ("X.Y. Zed"), so that we find the longest * sequence that's actually in the dictionary. Note that we * don't have a separate rule for individual initials, since * we'll pick that up with the regular abbreviated word rule * below. * * Some games could conceivably extend this to allow strings of * initials of four letters or longer, but in practice people * tend to elide the periods in longer sets of initials, so that * the initials become an acronym, and thus would fit the * ordinary word token rule. */ ['three initials', new RexPattern('<alpha><period><alpha><period><alpha><period>'), tokWord, &tokCvtAbbr, &acceptAbbrTok], ['two initials', new RexPattern('<alpha><period><alpha><period>'), tokWord, &tokCvtAbbr, &acceptAbbrTok], /* * Abbbreviated word - this is a word that ends in a period, * such as "Mr.". This rule comes before the ordinary word rule * because we will only consider the period to be part of the * word (and not a separate token) if the entire string * including the period is in the main vocabulary dictionary. */ ['abbreviation', new RexPattern('<Alpha|-><AlphaNum|-|squote>*<period>'), tokWord, &tokCvtAbbr, &acceptAbbrTok], /* * A word ending in an apostrophe-s. We parse this as two * separate tokens: one for the word and one for the * apostrophe-s. */ ['apostrophe-s word', new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*<squote>[sS]'), tokWord, &tokCvtApostropheS, nil], /* * A plural word ending in an apostrophe. We parse this as two * separate tokens: one for the word and one for the apostrophe. */ ['plural possessive word', new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*<squote>' + '(?!<AlphaNum>)'), tokWord, &tokCvtPluralApostrophe, nil], /* * Words - note that we convert everything to lower-case. A word * must start with an alphabetic character, a hyphen, or an * ampersand; after the initial character, a word can contain * alphabetics, digits, hyphens, ampersands, and apostrophes. */ ['word', new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*'), tokWord, nil, nil], /* an abbreviation word starting with a number */ ['abbreviation with initial digit', new RexPattern('<Digit>(?=<AlphaNum|-|&|squote>*<Alpha|-|&|squote>)' + '<AlphaNum|-|&|squote>*<period>'), tokWord, &tokCvtAbbr, &acceptAbbrTok], /* * A word can start with a number, as long as there's something * other than numbers in the string - if it's all numbers, we * want to treat it as a numeric token. */ ['word with initial digit', new RexPattern('<Digit>(?=<AlphaNum|-|&|squote>*<Alpha|-|&|squote>)' + '<AlphaNum|-|&|squote>*'), tokWord, nil, nil], /* strings with ASCII "straight" quotes */ ['string ascii-quote', new RexPattern('<min>([`\'"])(.*)%1(?!<AlphaNum>)'), tokString, nil, nil], /* some people like to use single quotes like `this' */ ['string back-quote', new RexPattern('<min>`(.*)\'(?!<AlphaNum>)'), tokString, nil, nil], /* strings with Latin-1 curly quotes (single and double) */ ['string curly single-quote', new RexPattern('<min>\u2018(.*)\u2019'), tokString, nil, nil], ['string curly double-quote', new RexPattern('<min>\u201C(.*)\u201D'), tokString, nil, nil], /* * unterminated string - if we didn't just match a terminated * string, but we have what looks like the start of a string, * match to the end of the line */ ['string unterminated', new RexPattern('([`\'"\u2018\u201C](.*)'), tokString, nil, nil], /* integer numbers */ ['integer', new RexPattern('[0-9]+'), tokInt, nil, nil], /* numbers with a '#' preceding */ ['integer with #', new RexPattern('#[0-9]+'), tokPoundInt, nil, nil] ] /* * Handle an apostrophe-s word. We'll return this as two separate * tokens: one for the word preceding the apostrophe-s, and one for * the apostrophe-s itself. */ tokCvtApostropheS(txt, typ, toks) { local w; local s; /* * pull out the part up to but not including the apostrophe, and * pull out the apostrophe-s part */ w = txt.substr(1, txt.length() - 2); s = txt.substr(-2); /* add the part before the apostrophe as the main token type */ toks.append([w, typ, w]); /* add the apostrophe-s as a separate special token */ toks.append([s, tokApostropheS, s]); } /* * Handle a plural apostrophe word ("the smiths' house"). We'll * return this as two tokens: one for the plural word, and one for * the apostrophe. */ tokCvtPluralApostrophe(txt, typ, toks) { local w; local s; /* * pull out the part up to but not including the apostrophe, and * separately pull out the apostrophe */ w = txt.substr(1, txt.length() - 1); s = txt.substr(-1); /* add the part before the apostrophe as the main token type */ toks.append([w, typ, w]); /* add the apostrophe-s as a separate special token */ toks.append([s, tokPluralApostrophe, s]); } /* * Handle a spelled-out hyphenated number from 21 to 99. We'll * return this as three separate tokens: a word for the tens name, a * word for the hyphen, and a word for the units name. */ tokCvtSpelledNumber(txt, typ, toks) { /* parse the number into its three parts with a regular expression */ rexMatch(patAlphaDashAlpha, txt); /* add the part before the hyphen */ toks.append([rexGroup(1)[3], typ, rexGroup(1)[3]]); /* add the hyphen */ toks.append(['-', typ, '-']); /* add the part after the hyphen */ toks.append([rexGroup(2)[3], typ, rexGroup(2)[3]]); } patAlphaDashAlpha = static new RexPattern('(<alpha>+)-(<alpha>+)') /* * Check to see if we want to accept an abbreviated token - this is * a token that ends in a period, which we use for abbreviated words * like "Mr." or "Ave." We'll accept the token only if it appears * as given - including the period - in the dictionary. Note that * we ignore truncated matches, since the only way we'll accept a * period in a word token is as the last character; there is thus no * way that a token ending in a period could be a truncation of any * longer valid token. */ acceptAbbrTok(txt) { /* look up the word, filtering out truncated results */ return cmdDict.isWordDefined( txt, {result: (result & StrCompTrunc) == 0}); } /* * Process an abbreviated token. * * When we find an abbreviation, we'll enter it with the abbreviated * word minus the trailing period, plus the period as a separate * token. We'll mark the period as an "abbreviation period" so that * grammar rules will be able to consider treating it as an * abbreviation -- but since it's also a regular period, grammar * rules that treat periods as regular punctuation will also be able * to try to match the result. This will ensure that we try it both * ways - as abbreviation and as a word with punctuation - and pick * the one that gives us the best result. */ tokCvtAbbr(txt, typ, toks) { local w; /* add the part before the period as the ordinary token */ w = txt.substr(1, txt.length() - 1); toks.append([w, typ, w]); /* add the token for the "abbreviation period" */ toks.append(['.', tokAbbrPeriod, '.']); } /* * Given a list of token strings, rebuild the original input string. * We can't recover the exact input string, because the tokenization * process throws away whitespace information, but we can at least * come up with something that will display cleanly and produce the * same results when run through the tokenizer. */ buildOrigText(toks) { local str; /* start with an empty string */ str = ''; /* concatenate each token in the list */ for (local i = 1, local len = toks.length() ; i <= len ; ++i) { /* add the current token to the string */ str += getTokOrig(toks[i]); /* * if this looks like a hyphenated number that we picked * apart into two tokens, put it back together without * spaces */ if (i + 2 <= len && rexMatch(patSpelledTens, getTokVal(toks[i])) != nil && getTokVal(toks[i+1]) == '-' && rexMatch(patSpelledUnits, getTokVal(toks[i+2])) != nil) { /* * it's a hyphenated number, all right - put the three * tokens back together without any intervening spaces, * so ['twenty', '-', 'one'] turns into 'twenty-one' */ str += getTokOrig(toks[i+1]) + getTokOrig(toks[i+2]); /* skip ahead by the two extra tokens we're adding */ i += 2; } else if (i + 1 <= len && getTokType(toks[i]) == tokWord && getTokType(toks[i+1]) is in (tokApostropheS, tokPluralApostrophe)) { /* * it's a word followed by an apostrophe-s token - these * are appended together without any intervening spaces */ str += getTokOrig(toks[i+1]); /* skip the extra token we added */ ++i; } /* * If another token follows, and the next token isn't a * punctuation mark, and the previous token wasn't an open * paren, add a space before the next token. */ if (i != len && rexMatch(patPunct, getTokVal(toks[i+1])) == nil && getTokVal(toks[i]) != '(') str += ' '; } /* return the result string */ return str; } /* some pre-compiled regular expressions */ patSpelledTens = static new RexPattern( '<nocase>twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety') patSpelledUnits = static new RexPattern( '<nocase>one|two|three|four|five|six|seven|eight|nine') patPunct = static new RexPattern('[.,;:?!)]') ; /* ------------------------------------------------------------------------ */ /* * Grammar Rules */ /* * Command with explicit target actor. When a command starts with an * actor's name followed by a comma followed by a verb, we take it as * being directed to the actor. */ grammar firstCommandPhrase(withActor): singleNounOnly->actor_ ',' commandPhrase->cmd_ : FirstCommandProdWithActor /* "execute" the target actor phrase */ execActorPhrase(issuingActor) { /* flag that the actor's being addressed in the second person */ resolvedActor_.commandReferralPerson = SecondPerson; } ; grammar firstCommandPhrase(askTellActorTo): ('ask' | 'tell' | 'a' | 't') singleNounOnly->actor_ 'to' commandPhrase->cmd_ : FirstCommandProdWithActor /* "execute" the target actor phrase */ execActorPhrase(issuingActor) { /* * Since our phrasing is TELL <ACTOR> TO <DO SOMETHING>, the * actor clearly becomes the antecedent for a subsequent * pronoun. For example, in TELL BOB TO READ HIS BOOK, the word * HIS pretty clearly refers back to BOB. */ if (resolvedActor_ != nil) { /* set the possessive anaphor object to the actor */ resolvedActor_.setPossAnaphorObj(resolvedActor_); /* flag that the actor's being addressed in the third person */ resolvedActor_.commandReferralPerson = ThirdPerson; /* * in subsequent commands carried out by the issuer, the * target actor is now the pronoun antecedent (for example: * after TELL BOB TO GO NORTH, the command FOLLOW HIM means * to follow Bob) */ issuingActor.setPronounObj(resolvedActor_); } } ; /* * An actor-targeted command with a bad command phrase. This is used as * a fallback if we fail to match anything on the first attempt at * parsing the first command on a line. The point is to at least detect * the target actor phrase, if that much is valid, so that we better * customize error messages for the rest of the command. */ grammar actorBadCommandPhrase(main): singleNounOnly->actor_ ',' miscWordList | ('ask' | 'tell' | 'a' | 't') singleNounOnly->actor_ 'to' miscWordList : FirstCommandProdWithActor /* to resolve nouns, we merely resolve the actor */ resolveNouns(issuingActor, targetActor, results) { /* resolve the underlying actor phrase */ return actor_.resolveNouns(getResolver(issuingActor), results); } ; /* * Command-only conjunctions. These words and groups of words can * separate commands from one another, and can't be used to separate noun * phrases in a noun list. */ grammar commandOnlyConjunction(sentenceEnding): '.' | '!' : BasicProd /* these conjunctions end the sentence */ isEndOfSentence() { return true; } ; grammar commandOnlyConjunction(nonSentenceEnding): 'then' | 'and' 'then' | ',' 'then' | ',' 'and' 'then' | ';' : BasicProd /* these conjunctions do not end a sentence */ isEndOfSentence() { return nil; } ; /* * Command-or-noun conjunctions. These words and groups of words can be * used to separate commands from one another, and can also be used to * separate noun phrases in a noun list. */ grammar commandOrNounConjunction(main): ',' | 'and' | ',' 'and' : BasicProd /* these do not end a sentence */ isEndOfSentence() { return nil; } ; /* * Noun conjunctions. These words and groups of words can be used to * separate noun phrases from one another. Note that these do not need * to be exclusive to noun phrases - these can occur as command * conjunctions as well; this list is separated from * commandOrNounConjunction in case there are conjunctions that can never * be used as command conjunctions, since such conjunctions, which can * appear here, would not appear in commandOrNounConjunctions. */ grammar nounConjunction(main): ',' | 'and' | ',' 'and' : BasicProd /* these conjunctions do not end a sentence */ isEndOfSentence() { return nil; } ; /* ------------------------------------------------------------------------ */ /* * Noun list: one or more noun phrases connected with conjunctions. This * kind of noun list can end in a terminal noun phrase. * * Note that a single noun phrase is a valid noun list, since a list can * simply be a list of one. The separate production nounMultiList can be * used when multiple noun phrases are required. */ /* * a noun list can consist of a single terminal noun phrase */ grammar nounList(terminal): terminalNounPhrase->np_ : NounListProd resolveNouns(resolver, results) { /* resolve the underlying noun phrase */ return np_.resolveNouns(resolver, results); } ; /* * a noun list can consist of a list of a single complete (non-terminal) * noun phrase */ grammar nounList(nonTerminal): completeNounPhrase->np_ : NounListProd resolveNouns(resolver, results) { /* resolve the underlying noun phrase */ return np_.resolveNouns(resolver, results); } ; /* * a noun list can consist of a list with two or more entries */ grammar nounList(list): nounMultiList->lst_ : NounListProd resolveNouns(resolver, results) { /* resolve the underlying list */ return lst_.resolveNouns(resolver, results); } ; /* * An empty noun list is one with no words at all. This is matched when * a command requires a noun list but the player doesn't include one; * this construct has "badness" because we only want to match it when we * have no choice. */ grammar nounList(empty): [badness 500] : EmptyNounPhraseProd responseProd = nounList ; /* ------------------------------------------------------------------------ */ /* * Noun Multi List: two or more noun phrases connected by conjunctions. * This is almost the same as the basic nounList production, but this * type of production requires at least two noun phrases, whereas the * basic nounList production more generally defines its list as any * number - including one - of noun phrases. */ /* * a multi list can consist of a noun multi- list plus a terminal noun * phrase, separated by a conjunction */ grammar nounMultiList(multi): nounMultiList->lst_ nounConjunction terminalNounPhrase->np_ : NounListProd resolveNouns(resolver, results) { /* return a list of all of the objects from both underlying lists */ return np_.resolveNouns(resolver, results) + lst_.resolveNouns(resolver, results); } ; /* * a multi list can consist of a non-terminal multi list */ grammar nounMultiList(nonterminal): nonTerminalNounMultiList->lst_ : NounListProd resolveNouns(resolver, results) { /* resolve the underlying list */ return lst_.resolveNouns(resolver, results); } ; /* ------------------------------------------------------------------------ */ /* * A non-terminal noun multi list is a noun list made up of at least two * non-terminal noun phrases, connected by conjunctions. * * This is almost the same as the regular non-terminal noun list * production, but this production requires two or more underlying noun * phrases, whereas the basic non-terminal noun list matches any number * of underlying phrases, including one. */ /* * a non-terminal multi-list can consist of a pair of complete noun * phrases separated by a conjunction */ grammar nonTerminalNounMultiList(pair): completeNounPhrase->np1_ nounConjunction completeNounPhrase->np2_ : NounListProd resolveNouns(resolver, results) { /* return the combination of the two underlying noun phrases */ return np1_.resolveNouns(resolver, results) + np2_.resolveNouns(resolver, results); } ; /* * a non-terminal multi-list can consist of another non-terminal * multi-list plus a complete noun phrase, connected by a conjunction */ grammar nonTerminalNounMultiList(multi): nonTerminalNounMultiList->lst_ nounConjunction completeNounPhrase->np_ : NounListProd resolveNouns(resolver, results) { /* return the combination of the sublist and the noun phrase */ return lst_.resolveNouns(resolver, results) + np_.resolveNouns(resolver, results); } ; /* ------------------------------------------------------------------------ */ /* * "Except" list. This is a noun list that can contain anything that's * in a regular noun list plus some things that only make sense as * exceptions, such as possessive nouns (e.g., "mine"). */ grammar exceptList(single): exceptNounPhrase->np_ : ExceptListProd resolveNouns(resolver, results) { return np_.resolveNouns(resolver, results); } ; grammar exceptList(list): exceptNounPhrase->np_ nounConjunction exceptList->lst_ : ExceptListProd resolveNouns(resolver, results) { /* return a list consisting of all of our objects */ return np_.resolveNouns(resolver, results) + lst_.resolveNouns(resolver, results); } ; /* * An "except" noun phrase is a normal "complete" noun phrase or a * possessive noun phrase that doesn't explicitly qualify another phrase * (for example, "all the coins but bob's" - the "bob's" is just a * possessive noun phrase without another noun phrase attached, since it * implicitly qualifies "the coins"). */ grammar exceptNounPhrase(singleComplete): completeNounPhraseWithoutAll->np_ : ExceptListProd resolveNouns(resolver, results) { return np_.resolveNouns(resolver, results); } ; grammar exceptNounPhrase(singlePossessive): possessiveNounPhrase->poss_ : ButPossessiveProd ; /* ------------------------------------------------------------------------ */ /* * A single noun is sometimes required where, structurally, a list is not * allowed. Single nouns should not be used to prohibit lists where * there is no structural reason for the prohibition - these should be * used only where it doesn't make sense to use a list structurally. */ grammar singleNoun(normal): singleNounOnly->np_ : LayeredNounPhraseProd ; /* * An empty single noun is one with no words at all. This is matched * when a command requires a noun list but the player doesn't include * one; this construct has "badness" because we only want to match it * when we have no choice. */ grammar singleNoun(empty): [badness 500] : EmptyNounPhraseProd /* use a nil responseProd, so that we get the phrasing from the action */ responseProd = nil /* the fallback responseProd, if we can't get one from the action */ fallbackResponseProd = singleNoun ; /* * A user could attempt to use a noun list with more than one entry (a * "multi list") where a single noun is required. This is not a * grammatical error, so we accept it grammatically; however, for * disambiguation purposes we score it lower than a singleNoun production * with only one noun phrase, and if we try to resolve it, we'll fail * with an error. */ grammar singleNoun(multiple): nounMultiList->np_ : SingleNounWithListProd ; /* * A *structural* single noun phrase. This production is for use where a * single noun phrase (not a list of nouns) is required grammatically. */ grammar singleNounOnly(main): terminalNounPhrase->np_ | completeNounPhrase->np_ : SingleNounProd ; /* ------------------------------------------------------------------------ */ /* * Prepositionally modified single noun phrases. These can be used in * indirect object responses, so allow for interactions like this: * * >unlock door *. What do you want to unlock it with? * * >with the key * * The entire notion of prepositionally qualified noun phrases in * interactive indirect object responses is specific to English, so this * is implemented in the English module only. However, the general * notion of specialized responses to interactive indirect object queries * is handled in the language-independent library in some cases, in such * a way that the language-specific library can customize the behavior - * see TIAction.askIobjResponseProd. */ class PrepSingleNounProd: SingleNounProd resolveNouns(resolver, results) { return np_.resolveNouns(resolver, results); } /* * If the response starts with a preposition, it's pretty clearly a * response to the special query rather than a new command. */ isSpecialResponseMatch() { return (np_ != nil && np_.firstTokenIndex > 1); } ; /* * Same thing for a Topic phrase */ class PrepSingleTopicProd: TopicProd resolveNouns(resolver, results) { return np_.resolveNouns(resolver, results); } ; grammar inSingleNoun(main): singleNoun->np_ | ('in' | 'into' | 'in' 'to') singleNoun->np_ : PrepSingleNounProd ; grammar forSingleNoun(main): singleNoun->np_ | 'for' singleNoun->np_ : PrepSingleNounProd ; grammar toSingleNoun(main): singleNoun->np_ | 'to' singleNoun->np_ : PrepSingleNounProd ; grammar throughSingleNoun(main): singleNoun->np_ | ('through' | 'thru') singleNoun->np_ : PrepSingleNounProd ; grammar fromSingleNoun(main): singleNoun->np_ | 'from' singleNoun->np_ : PrepSingleNounProd ; grammar onSingleNoun(main): singleNoun->np_ | ('on' | 'onto' | 'on' 'to') singleNoun->np_ : PrepSingleNounProd ; grammar withSingleNoun(main): singleNoun->np_ | 'with' singleNoun->np_ : PrepSingleNounProd ; grammar atSingleNoun(main): singleNoun->np_ | 'at' singleNoun->np_ : PrepSingleNounProd ; grammar outOfSingleNoun(main): singleNoun->np_ | 'out' 'of' singleNoun->np_ : PrepSingleNounProd ; grammar aboutTopicPhrase(main): topicPhrase->np_ | 'about' topicPhrase->np_ : PrepSingleTopicProd ; /* ------------------------------------------------------------------------ */ /* * Complete noun phrase - this is a fully-qualified noun phrase that * cannot be modified with articles, quantifiers, or anything else. This * is the highest-level individual noun phrase. */ grammar completeNounPhrase(main): completeNounPhraseWithAll->np_ | completeNounPhraseWithoutAll->np_ : LayeredNounPhraseProd ; /* * Slightly better than a purely miscellaneous word list is a pair of * otherwise valid noun phrases connected by a preposition that's * commonly used in command phrases. This will match commands where the * user has assumed a command with a prepositional structure that doesn't * exist among the defined commands. Since we have badness, we'll be * ignored any time there's a valid command syntax with the same * prepositional structure. */ grammar completeNounPhrase(miscPrep): [badness 100] completeNounPhrase->np1_ ('with' | 'into' | 'in' 'to' | 'through' | 'thru' | 'for' | 'to' | 'onto' | 'on' 'to' | 'at' | 'under' | 'behind') completeNounPhrase->np2_ : NounPhraseProd resolveNouns(resolver, results) { /* note that we have an invalid prepositional phrase structure */ results.noteBadPrep(); /* resolve the underlying noun phrases, for scoring purposes */ np1_.resolveNouns(resolver, results); np2_.resolveNouns(resolver, results); /* return nothing */ return []; } ; /* * A qualified noun phrase can, all by itself, be a full noun phrase */ grammar completeNounPhraseWithoutAll(qualified): qualifiedNounPhrase->np_ : LayeredNounPhraseProd ; /* * Pronoun rules. A pronoun is a complete noun phrase; it does not allow * further qualification. */ grammar completeNounPhraseWithoutAll(it): 'it' : ItProd; grammar completeNounPhraseWithoutAll(them): 'them' : ThemProd; grammar completeNounPhraseWithoutAll(him): 'him' : HimProd; grammar completeNounPhraseWithoutAll(her): 'her' : HerProd; /* * Reflexive second-person pronoun, for things like "bob, look at * yourself" */ grammar completeNounPhraseWithoutAll(yourself): 'yourself' | 'yourselves' | 'you' : YouProd ; /* * Reflexive third-person pronouns. We accept these in places such as * the indirect object of a two-object verb. */ grammar completeNounPhraseWithoutAll(itself): 'itself' : ItselfProd /* check agreement of our binding */ checkAgreement(lst) { /* the result is required to be singular and ungendered */ return (lst.length() == 1 && lst[1].obj_.canMatchIt); } ; grammar completeNounPhraseWithoutAll(themselves): 'themself' | 'themselves' : ThemselvesProd /* check agreement of our binding */ checkAgreement(lst) { /* * For 'themselves', allow anything; we could balk at this * matching a single object that isn't a mass noun, but that * would be overly picky, and it would probably reject at least * a few things that really ought to be acceptable. Besides, * 'them' is the closest thing English has to a singular * gender-neutral pronoun, and some people intentionally use it * as such. */ return true; } ; grammar completeNounPhraseWithoutAll(himself): 'himself' : HimselfProd /* check agreement of our binding */ checkAgreement(lst) { /* the result is required to be singular and masculine */ return (lst.length() == 1 && lst[1].obj_.canMatchHim); } ; grammar completeNounPhraseWithoutAll(herself): 'herself' : HerselfProd /* check agreement of our binding */ checkAgreement(lst) { /* the result is required to be singular and feminine */ return (lst.length() == 1 && lst[1].obj_.canMatchHer); } ; /* * First-person pronoun, for referring to the speaker: "bob, look at me" */ grammar completeNounPhraseWithoutAll(me): 'me' | 'myself' : MeProd; /* * "All" and "all but". * * "All" is a "complete" noun phrase, because there's nothing else needed * to make it a noun phrase. We make this a special kind of complete * noun phrase because 'all' is not acceptable as a complete noun phrase * in some contexts where any of the other complete noun phrases are * acceptable. * * "All but" is a "terminal" noun phrase - this is a special kind of * complete noun phrase that cannot be followed by another noun phrase * with "and". "All but" is terminal because we want any and's that * follow it to be part of the exception list, so that we interpret "take * all but a and b" as excluding a and b, not as excluding a but then * including b as a separate list. */ grammar completeNounPhraseWithAll(main): 'all' | 'everything' : EverythingProd ; grammar terminalNounPhrase(allBut): ('all' | 'everything') ('but' | 'except' | 'except' 'for') exceptList->except_ : EverythingButProd ; /* * Plural phrase with an exclusion list. This is a terminal noun phrase * because it ends in an exclusion list. */ grammar terminalNounPhrase(pluralExcept): (qualifiedPluralNounPhrase->np_ | detPluralNounPhrase->np_) ('except' | 'except' 'for' | 'but' | 'but' 'not') exceptList->except_ : ListButProd ; /* * Qualified singular with an exception */ grammar terminalNounPhrase(anyBut): 'any' nounPhrase->np_ ('but' | 'except' | 'except' 'for' | 'but' 'not') exceptList->except_ : IndefiniteNounButProd ; /* ------------------------------------------------------------------------ */ /* * A qualified noun phrase is a noun phrase with an optional set of * qualifiers: a definite or indefinite article, a quantifier, words such * as 'any' and 'all', possessives, and locational specifiers ("the box * on the table"). * * Without qualification, a definite article is implicit, so we read * "take box" as equivalent to "take the box." * * Grammar rule instantiations in language-specific modules should set * property np_ to the underlying noun phrase match tree. */ /* * A qualified noun phrase can be either singular or plural. The number * is a feature of the overall phrase; the phrase might consist of * subphrases of different numbers (for example, "bob's coins" is plural * even though it contains a singular subphrase, "bob"; and "one of the * coins" is singular, even though its subphrase "coins" is plural). */ grammar qualifiedNounPhrase(main): qualifiedSingularNounPhrase->np_ | qualifiedPluralNounPhrase->np_ : LayeredNounPhraseProd ; /* ------------------------------------------------------------------------ */ /* * Singular qualified noun phrase. */ /* * A singular qualified noun phrase with an implicit or explicit definite * article. If there is no article, a definite article is implied (we * interpret "take box" as though it were "take the box"). */ grammar qualifiedSingularNounPhrase(definite): ('the' | 'the' 'one' | 'the' '1' | ) indetSingularNounPhrase->np_ : DefiniteNounProd ; /* * A singular qualified noun phrase with an explicit indefinite article. */ grammar qualifiedSingularNounPhrase(indefinite): ('a' | 'an') indetSingularNounPhrase->np_ : IndefiniteNounProd ; /* * A singular qualified noun phrase with an explicit arbitrary * determiner. */ grammar qualifiedSingularNounPhrase(arbitrary): ('any' | 'one' | '1' | 'any' ('one' | '1')) indetSingularNounPhrase->np_ : ArbitraryNounProd ; /* * A singular qualified noun phrase with a possessive adjective. */ grammar qualifiedSingularNounPhrase(possessive): possessiveAdjPhrase->poss_ indetSingularNounPhrase->np_ : PossessiveNounProd ; /* * A singular qualified noun phrase that arbitrarily selects from a * plural set. This is singular, even though the underlying noun phrase * is plural, because we're explicitly selecting one item. */ grammar qualifiedSingularNounPhrase(anyPlural): 'any' 'of' explicitDetPluralNounPhrase->np_ : ArbitraryNounProd ; /* * A singular object specified only by its containment, with a definite * article. */ grammar qualifiedSingularNounPhrase(theOneIn): 'the' 'one' ('that' ('is' | 'was') | 'that' tokApostropheS | ) ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') completeNounPhraseWithoutAll->cont_ : VagueContainerDefiniteNounPhraseProd /* * our main phrase is simply 'one' (so disambiguation prompts will * read "which one do you mean...") */ mainPhraseText = 'one' ; /* * A singular object specified only by its containment, with an * indefinite article. */ grammar qualifiedSingularNounPhrase(anyOneIn): ('anything' | 'one') ('that' ('is' | 'was') | 'that' tokApostropheS | ) ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') completeNounPhraseWithoutAll->cont_ : VagueContainerIndefiniteNounPhraseProd ; /* ------------------------------------------------------------------------ */ /* * An "indeterminate" singular noun phrase is a noun phrase without any * determiner. A determiner is a phrase that specifies the phrase's * number and indicates whether or not it refers to a specific object, * and if so fixes which object it refers to; determiners include * articles ("the", "a") and possessives. * * Note that an indeterminate phrase is NOT necessarily an indefinite * phrase. In fact, in most cases, we assume a definite usage when the * determiner is omitted: we take TAKE BOX as meaning TAKE THE BOX. This * is more or less the natural way an English speaker would interpret * this ill-formed phrasing, but even more than that, it's the * Adventurese convention, taking into account that most players enter * commands telegraphically and are accustomed to noun phrases being * definite by default. */ /* an indetermine noun phrase can be a simple noun phrase */ grammar indetSingularNounPhrase(basic): nounPhrase->np_ : LayeredNounPhraseProd ; /* * An indetermine noun phrase can specify a location for the object(s). * The location must be a singular noun phrase, but can itself be a fully * qualified noun phrase (so it can have possessives, articles, and * locational qualifiers of its own). * * Note that we take 'that are' even though the noun phrase is singular, * because what we consider a singular noun phrase can have plural usage * ("scissors", for example). */ grammar indetSingularNounPhrase(locational): nounPhrase->np_ ('that' ('is' | 'was') | 'that' tokApostropheS | 'that' ('are' | 'were') | ) ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') completeNounPhraseWithoutAll->cont_ : ContainerNounPhraseProd ; /* ------------------------------------------------------------------------ */ /* * Plural qualified noun phrase. */ /* * A simple unqualified plural phrase with determiner. Since this form * of plural phrase doesn't have any additional syntax that makes it an * unambiguous plural, we can only accept an actual plural for the * underlying phrase here - we can't accept an adjective phrase. */ grammar qualifiedPluralNounPhrase(determiner): ('any' | ) detPluralOnlyNounPhrase->np_ : LayeredNounPhraseProd ; /* plural phrase qualified with a number and optional "any" */ grammar qualifiedPluralNounPhrase(anyNum): ('any' | ) numberPhrase->quant_ indetPluralNounPhrase->np_ | ('any' | ) numberPhrase->quant_ 'of' explicitDetPluralNounPhrase->np_ : QuantifiedPluralProd ; /* plural phrase qualified with a number and "all" */ grammar qualifiedPluralNounPhrase(allNum): 'all' numberPhrase->quant_ indetPluralNounPhrase->np_ | 'all' numberPhrase->quant_ 'of' explicitDetPluralNounPhrase->np_ : ExactQuantifiedPluralProd ; /* plural phrase qualified with "both" */ grammar qualifiedPluralNounPhrase(both): 'both' detPluralNounPhrase->np_ | 'both' 'of' explicitDetPluralNounPhrase->np_ : BothPluralProd ; /* plural phrase qualified with "all" */ grammar qualifiedPluralNounPhrase(all): 'all' detPluralNounPhrase->np_ | 'all' 'of' explicitDetPluralNounPhrase->np_ : AllPluralProd ; /* vague plural phrase with location specified */ grammar qualifiedPluralNounPhrase(theOnesIn): ('the' 'ones' ('that' ('are' | 'were') | ) | ('everything' | 'all') ('that' ('is' | 'was') | 'that' tokApostropheS | )) ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') completeNounPhraseWithoutAll->cont_ : AllInContainerNounPhraseProd ; /* ------------------------------------------------------------------------ */ /* * A plural noun phrase with a determiner. The determiner can be * explicit (such as an article or possessive) or it can implied (the * implied determiner is the definite article, so "take boxes" is * understood as "take the boxes"). */ grammar detPluralNounPhrase(main): indetPluralNounPhrase->np_ | explicitDetPluralNounPhrase->np_ : LayeredNounPhraseProd ; /* ------------------------------------------------------------------------ */ /* * A determiner plural phrase with an explicit underlying plural (i.e., * excluding adjective phrases with no explicitly plural words). */ grammar detPluralOnlyNounPhrase(main): implicitDetPluralOnlyNounPhrase->np_ | explicitDetPluralOnlyNounPhrase->np_ : LayeredNounPhraseProd ; /* * An implicit determiner plural phrase is an indeterminate plural phrase * without any extra determiner - i.e., the determiner is implicit. * We'll treat this the same way we do a plural explicitly determined * with a definite article, since this is the most natural interpretation * in English. * * (This might seem like a pointless extra layer in the grammar, but it's * necessary for the resolution process to have a layer that explicitly * declares the phrase to be determined, even though the determiner is * implied in the structure. This extra layer is important because it * explicitly calls results.noteMatches(), which is needed for rankings * and the like.) */ grammar implicitDetPluralOnlyNounPhrase(main): indetPluralOnlyNounPhrase->np_ : DefinitePluralProd ; /* ------------------------------------------------------------------------ */ /* * A plural noun phrase with an explicit determiner. */ /* a plural noun phrase with a definite article */ grammar explicitDetPluralNounPhrase(definite): 'the' indetPluralNounPhrase->np_ : DefinitePluralProd ; /* a plural noun phrase with a definite article and a number */ grammar explicitDetPluralNounPhrase(definiteNumber): 'the' numberPhrase->quant_ indetPluralNounPhrase->np_ : ExactQuantifiedPluralProd ; /* a plural noun phrase with a possessive */ grammar explicitDetPluralNounPhrase(possessive): possessiveAdjPhrase->poss_ indetPluralNounPhrase->np_ : PossessivePluralProd ; /* a plural noun phrase with a possessive and a number */ grammar explicitDetPluralNounPhrase(possessiveNumber): possessiveAdjPhrase->poss_ numberPhrase->quant_ indetPluralNounPhrase->np_ : ExactQuantifiedPossessivePluralProd ; /* ------------------------------------------------------------------------ */ /* * A plural noun phrase with an explicit determiner and only an * explicitly plural underlying phrase. */ grammar explicitDetPluralOnlyNounPhrase(definite): 'the' indetPluralOnlyNounPhrase->np_ : AllPluralProd ; grammar explicitDetPluralOnlyNounPhrase(definiteNumber): 'the' numberPhrase->quant_ indetPluralNounPhrase->np_ : ExactQuantifiedPluralProd ; grammar explicitDetPluralOnlyNounPhrase(possessive): possessiveAdjPhrase->poss_ indetPluralOnlyNounPhrase->np_ : PossessivePluralProd ; grammar explicitDetPluralOnlyNounPhrase(possessiveNumber): possessiveAdjPhrase->poss_ numberPhrase->quant_ indetPluralNounPhrase->np_ : ExactQuantifiedPossessivePluralProd ; /* ------------------------------------------------------------------------ */ /* * An indeterminate plural noun phrase. * * For the basic indeterminate plural phrase, allow an adjective phrase * anywhere a plural phrase is allowed; this makes possible the * short-hand of omitting a plural word when the plural number is * unambiguous from context. */ /* a simple plural noun phrase */ grammar indetPluralNounPhrase(basic): pluralPhrase->np_ | adjPhrase->np_ : LayeredNounPhraseProd ; /* * A plural noun phrase with a locational qualifier. Note that even * though the overall phrase is plural (and so the main underlying noun * phrase is plural), the location phrase itself must always be singular. */ grammar indetPluralNounPhrase(locational): (pluralPhrase->np_ | adjPhrase->np_) ('that' ('are' | 'were') | ) ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') completeNounPhraseWithoutAll->cont_ : ContainerNounPhraseProd ; /* * An indetermine plural noun phrase with only explicit plural phrases. */ grammar indetPluralOnlyNounPhrase(basic): pluralPhrase->np_ : LayeredNounPhraseProd ; grammar indetPluralOnlyNounPhrase(locational): pluralPhrase->np_ ('that' ('are' | 'were') | ) ('in' | 'inside' | 'inside' 'of' | 'on' | 'from') completeNounPhraseWithoutAll->cont_ : ContainerNounPhraseProd ; /* ------------------------------------------------------------------------ */ /* * Noun Phrase. This is the basic noun phrase, which serves as a * building block for complete noun phrases. This type of noun phrase * can be qualified with articles, quantifiers, and possessives, and can * be used to construct possessives via the addition of "'s" at the end * of the phrase. * * In most cases, custom noun phrase rules should be added to this * production, as long as qualification (with numbers, articles, and * possessives) is allowed. For a custom noun phrase rule that cannot be * qualified, a completeNounPhrase rule should be added instead. */ grammar nounPhrase(main): compoundNounPhrase->np_ : LayeredNounPhraseProd ; /* * Plural phrase. This is the basic plural phrase, and corresponds to * the basic nounPhrase for plural forms. */ grammar pluralPhrase(main): compoundPluralPhrase->np_ : LayeredNounPhraseProd ; /* ------------------------------------------------------------------------ */ /* * Compound noun phrase. This is one or more noun phrases connected with * 'of', as in "piece of paper". The part after the 'of' is another * compound noun phrase. * * Note that this general rule does not allow the noun phrase after the * 'of' to be qualified with an article or number, except that we make an * exception to allow a definite article. Other cases ("a piece of four * papers") do not generally make sense, so we won't attempt to support * them; instead, games can add as special cases new nounPhrase rules for * specific literal sequences where more complex grammar is necessary. */ grammar compoundNounPhrase(simple): simpleNounPhrase->np_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { return np_.getVocabMatchList(resolver, results, extraFlags); } getAdjustedTokens() { return np_.getAdjustedTokens(); } ; grammar compoundNounPhrase(of): simpleNounPhrase->np1_ 'of'->of_ compoundNounPhrase->np2_ | simpleNounPhrase->np1_ 'of'->of_ 'the'->the_ compoundNounPhrase->np2_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { local lst1; local lst2; /* resolve the two underlying lists */ lst1 = np1_.getVocabMatchList(resolver, results, extraFlags); lst2 = np2_.getVocabMatchList(resolver, results, extraFlags); /* * the result is the intersection of the two lists, since we * want the list of objects with all of the underlying * vocabulary words */ return intersectNounLists(lst1, lst2); } getAdjustedTokens() { local ofLst; /* generate the 'of the' list from the original words */ if (the_ == nil) ofLst = [of_, &miscWord]; else ofLst = [of_, &miscWord, the_, &miscWord]; /* return the full list */ return np1_.getAdjustedTokens() + ofLst + np2_.getAdjustedTokens(); } ; /* ------------------------------------------------------------------------ */ /* * Compound plural phrase - same as a compound noun phrase, but involving * a plural part before the 'of'. */ /* * just a single plural phrase */ grammar compoundPluralPhrase(simple): simplePluralPhrase->np_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { return np_.getVocabMatchList(resolver, results, extraFlags); } getAdjustedTokens() { return np_.getAdjustedTokens(); } ; /* * <plural-phrase> of <noun-phrase> */ grammar compoundPluralPhrase(of): simplePluralPhrase->np1_ 'of'->of_ compoundNounPhrase->np2_ | simplePluralPhrase->np1_ 'of'->of_ 'the'->the_ compoundNounPhrase->np2_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { local lst1; local lst2; /* resolve the two underlying lists */ lst1 = np1_.getVocabMatchList(resolver, results, extraFlags); lst2 = np2_.getVocabMatchList(resolver, results, extraFlags); /* * the result is the intersection of the two lists, since we * want the list of objects with all of the underlying * vocabulary words */ return intersectNounLists(lst1, lst2); } getAdjustedTokens() { local ofLst; /* generate the 'of the' list from the original words */ if (the_ == nil) ofLst = [of_, &miscWord]; else ofLst = [of_, &miscWord, the_, &miscWord]; /* return the full list */ return np1_.getAdjustedTokens() + ofLst + np2_.getAdjustedTokens(); } ; /* ------------------------------------------------------------------------ */ /* * Simple noun phrase. This is the most basic noun phrase, which is * simply a noun, optionally preceded by one or more adjectives. */ /* * just a noun */ grammar simpleNounPhrase(noun): nounWord->noun_ : NounPhraseWithVocab /* generate a list of my resolved objects */ getVocabMatchList(resolver, results, extraFlags) { return noun_.getVocabMatchList(resolver, results, extraFlags); } getAdjustedTokens() { return noun_.getAdjustedTokens(); } ; /* * <adjective> <simple-noun-phrase> (this allows any number of adjectives * to be applied) */ grammar simpleNounPhrase(adjNP): adjWord->adj_ simpleNounPhrase->np_ : NounPhraseWithVocab /* generate a list of my resolved objects */ getVocabMatchList(resolver, results, extraFlags) { /* * return the list of objects in scope matching our adjective * plus the list from the underlying noun phrase */ return intersectNounLists( adj_.getVocabMatchList(resolver, results, extraFlags), np_.getVocabMatchList(resolver, results, extraFlags)); } getAdjustedTokens() { return adj_.getAdjustedTokens() + np_.getAdjustedTokens(); } ; /* * A simple noun phrase can also include a number or a quoted string * before or after a noun. A number can be spelled out or written with * numerals; we consider both forms equivalent in meaning. * * A number in this type of usage is grammatically equivalent to an * adjective - it's not meant to quantify the rest of the noun phrase, * but rather is simply an adjective-like modifier. For example, an * elevator's control panel might have a set of numbered buttons which we * want to refer to as "button 1," "button 2," and so on. It is * frequently the case that numeric adjectives are equally at home before * or after their noun: "push 3 button" or "push button 3". In addition, * we accept a number by itself as a lone adjective, as in "push 3". */ /* * just a numeric/string adjective (for things like "push 3", "push #3", * 'push "G"') */ grammar simpleNounPhrase(number): literalAdjPhrase->adj_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* * note that this counts as an adjective-ending phrase, since we * don't have a noun involved */ results.noteAdjEnding(); /* pass through to the underlying literal adjective phrase */ local lst = adj_.getVocabMatchList(resolver, results, extraFlags | EndsWithAdj); /* if in global scope, also try a noun interpretation */ if (resolver.isGlobalScope) lst = adj_.addNounMatchList(lst, resolver, results, extraFlags); /* return the result */ return lst; } getAdjustedTokens() { /* pass through to the underlying literal adjective phrase */ return adj_.getAdjustedTokens(); } ; /* * <literal-adjective> <noun> (for things like "board 44 bus" or 'push * "G" button') */ grammar simpleNounPhrase(numberAndNoun): literalAdjPhrase->adj_ nounWord->noun_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { local nounList; local adjList; /* get the list of objects matching the rest of the noun phrase */ nounList = noun_.getVocabMatchList(resolver, results, extraFlags); /* get the list of objects matching the literal adjective */ adjList = adj_.getVocabMatchList(resolver, results, extraFlags); /* intersect the two lists and return the results */ return intersectNounLists(nounList, adjList); } getAdjustedTokens() { return adj_.getAdjustedTokens() + noun_.getAdjustedTokens(); } ; /* * <noun> <literal-adjective> (for things like "press button 3" or 'put * tab "A" in slot "B"') */ grammar simpleNounPhrase(nounAndNumber): nounWord->noun_ literalAdjPhrase->adj_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { local nounList; local adjList; /* get the list of objects matching the rest of the noun phrase */ nounList = noun_.getVocabMatchList(resolver, results, extraFlags); /* get the literal adjective matches */ adjList = adj_.getVocabMatchList(resolver, results, extraFlags); /* intersect the two lists and return the results */ return intersectNounLists(nounList, adjList); } getAdjustedTokens() { return noun_.getAdjustedTokens() + adj_.getAdjustedTokens(); } ; /* * A simple noun phrase can also end in an adjective, which allows * players to refer to objects using only their unique adjectives rather * than their full names, which is sometimes more convenient: just "take * gold" rather than "take gold key." * * When a particular phrase can be interpreted as either ending in an * adjective or ending in a noun, we will always take the noun-ending * interpretation - in such cases, the adjective-ending interpretation is * probably a weak binding. For example, "take pizza" almost certainly * refers to the pizza itself when "pizza" and "pizza box" are both * present, but it can also refer just to the box when no pizza is * present. * * Equivalent to a noun phrase ending in an adjective is a noun phrase * ending with an adjective followed by "one," as in "the red one." */ grammar simpleNounPhrase(adj): adjWord->adj_ : NounPhraseWithVocab /* generate a list of my resolved objects */ getVocabMatchList(resolver, results, extraFlags) { /* note in the results that we end in an adjective */ results.noteAdjEnding(); /* generate a list of objects matching the adjective */ local lst = adj_.getVocabMatchList( resolver, results, extraFlags | EndsWithAdj); /* if in global scope, also try a noun interpretation */ if (resolver.isGlobalScope) lst = adj_.addNounMatchList(lst, resolver, results, extraFlags); /* return the result */ return lst; } getAdjustedTokens() { /* return the adjusted token list for the adjective */ return adj_.getAdjustedTokens(); } ; grammar simpleNounPhrase(adjAndOne): adjective->adj_ 'one' : NounPhraseWithVocab /* generate a list of my resolved objects */ getVocabMatchList(resolver, results, extraFlags) { /* * This isn't exactly an adjective ending, but consider it as * such anyway, since we're not matching 'one' to a vocabulary * word - we're just using it as a grammatical marker that we're * not providing a real noun. If there's another match for * which 'one' is a noun, that one is definitely preferred to * this one; the adj-ending marking will ensure that we choose * the other one. */ results.noteAdjEnding(); /* generate a list of objects matching the adjective */ return getWordMatches(adj_, &adjective, resolver, extraFlags | EndsWithAdj, VocabTruncated); } getAdjustedTokens() { return [adj_, &adjective]; } ; /* * In the worst case, a simple noun phrase can be constructed from * arbitrary words that don't appear in our dictionary. */ grammar simpleNounPhrase(misc): [badness 200] miscWordList->lst_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* get the match list from the underlying list */ local lst = lst_.getVocabMatchList(resolver, results, extraFlags); /* * If there are no matches, note in the results that we have an * arbitrary word list. Note that we do this only if there are * no matches, because we might match non-dictionary words to an * object with a wildcard in its vocabulary words, in which case * this is a valid, matching phrase after all. */ if (lst == nil || lst.length() == 0) results.noteMiscWordList(lst_.getOrigText()); /* return the match list */ return lst; } getAdjustedTokens() { return lst_.getAdjustedTokens(); } ; /* * If the command has qualifiers but omits everything else, we can have * an empty simple noun phrase. */ grammar simpleNounPhrase(empty): [badness 600] : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* we have an empty noun phrase */ return results.emptyNounPhrase(resolver); } getAdjustedTokens() { return []; } ; /* ------------------------------------------------------------------------ */ /* * An AdjPhraseWithVocab is an English-specific subclass of * NounPhraseWithVocab, specifically for noun phrases that contain * entirely adjectives. */ class AdjPhraseWithVocab: NounPhraseWithVocab /* the property for the adjective literal - this is usually adj_ */ adjVocabProp = &adj_ /* * Add the vocabulary matches that we'd get if we were treating our * adjective as a noun. This combines the noun interpretation with a * list of matches we got for the adjective version. */ addNounMatchList(lst, resolver, results, extraFlags) { /* get the word matches with a noun interpretation of our adjective */ local nLst = getWordMatches( self.(adjVocabProp), &noun, resolver, extraFlags, VocabTruncated); /* combine the lists and return the result */ return combineWordMatches(lst, nLst); } ; /* ------------------------------------------------------------------------ */ /* * A "literal adjective" phrase is a number or string used as an * adjective. */ grammar literalAdjPhrase(number): numberPhrase->num_ | poundNumberPhrase->num_ : AdjPhraseWithVocab adj_ = (num_.getStrVal()) getVocabMatchList(resolver, results, extraFlags) { local numList; /* * get the list of objects matching the numeral form of the * number as an adjective */ numList = getWordMatches(num_.getStrVal(), &adjective, resolver, extraFlags, VocabTruncated); /* add the list of objects matching the special '#' wildcard */ numList += getWordMatches('#', &adjective, resolver, extraFlags, VocabTruncated); /* return the combined lists */ return numList; } getAdjustedTokens() { return [num_.getStrVal(), &adjective]; } ; grammar literalAdjPhrase(string): quotedStringPhrase->str_ : AdjPhraseWithVocab adj_ = (str_.getStringText().toLower()) getVocabMatchList(resolver, results, extraFlags) { local strList; local wLst; /* * get the list of objects matching the string with the quotes * removed */ strList = getWordMatches(str_.getStringText().toLower(), &literalAdjective, resolver, extraFlags, VocabTruncated); /* add the list of objects matching the literal-adjective wildcard */ wLst = getWordMatches('\u0001', &literalAdjective, resolver, extraFlags, VocabTruncated); strList = combineWordMatches(strList, wLst); /* return the combined lists */ return strList; } getAdjustedTokens() { return [str_.getStringText().toLower(), &adjective]; } ; /* * In many cases, we might want to write what is semantically a literal * string qualifier without the quotes. For example, we might want to * refer to an elevator button that's labeled "G" as simply "button G", * without any quotes around the "G". To accommodate these cases, we * provide the literalAdjective part-of-speech. We'll match these parts * of speech the same way we'd match them if they were quoted. */ grammar literalAdjPhrase(literalAdj): literalAdjective->adj_ : AdjPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { local lst; /* get a list of objects in scope matching our literal adjective */ lst = getWordMatches(adj_, &literalAdjective, resolver, extraFlags, VocabTruncated); /* if the scope is global, also include ordinary adjective matches */ if (resolver.isGlobalScope) { /* get the ordinary adjective bindings */ local aLst = getWordMatches(adj_, &adjective, resolver, extraFlags, VocabTruncated); /* global scope - combine the lists */ lst = combineWordMatches(lst, aLst); } /* return the result */ return lst; } getAdjustedTokens() { return [adj_, &literalAdjective]; } ; /* ------------------------------------------------------------------------ */ /* * A noun word. This can be either a simple 'noun' vocabulary word, or * it can be an abbreviated noun with a trailing abbreviation period. */ class NounWordProd: NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { local w; local nLst; /* get our word text */ w = getNounText(); /* get the list of matches as nouns */ nLst = getWordMatches(w, &noun, resolver, extraFlags, VocabTruncated); /* * If the resolver indicates that we're in a "global" scope, * *also* include any additional matches as adjectives. * * Normally, when we're operating in limited, local scopes, we * use the structure of the phrasing to determine whether to * match a noun or adjective; if we have a match for a given word * as a noun, we'll treat it only as a noun. This allows us to * take PIZZA to refer to the pizza (for which 'pizza' is defined * as a noun) rather than to the PIZZA BOX (for which 'pizza' is * a mere adjective) when both are in scope. It's obvious which * the player means in such cases, so we can be smart about * choosing the stronger match. * * In cases of global scope, though, it's much harder to guess * about the player's intentions. When the player types PIZZA, * they might be thinking of the box even though there's a pizza * somewhere else in the game. Since the two objects might be in * entirely different locations, both out of view, we can't * assume that one or the other is more likely on the basis of * which is closer to the player's senses. So, it's better to * allow both to match for now, and decide later, based on the * context of the command, which was actually meant. */ if (resolver.isGlobalScope) { /* get the list of matching adjectives */ local aLst = getWordMatches(w, &adjective, resolver, extraFlags, VocabTruncated); /* combine it with the noun list */ nLst = combineWordMatches(nLst, aLst); } /* return the match list */ return nLst; } getAdjustedTokens() { /* the noun includes the period as part of the literal text */ return [getNounText(), &noun]; } /* the actual text of the noun to match to the dictionary */ getNounText() { return noun_; } ; grammar nounWord(noun): noun->noun_ : NounWordProd ; grammar nounWord(nounAbbr): noun->noun_ tokAbbrPeriod->period_ : NounWordProd /* * for dictionary matching purposes, include the text of our noun * with the period attached - the period is part of the dictionary * entry for an abbreviated word */ getNounText() { return noun_ + period_; } ; /* ------------------------------------------------------------------------ */ /* * An adjective word. This can be either a simple 'adjective' vocabulary * word, or it can be an 'adjApostS' vocabulary word plus a 's token. */ grammar adjWord(adj): adjective->adj_ : AdjPhraseWithVocab /* generate a list of resolved objects */ getVocabMatchList(resolver, results, extraFlags) { /* return a list of objects in scope matching our adjective */ return getWordMatches(adj_, &adjective, resolver, extraFlags, VocabTruncated); } getAdjustedTokens() { return [adj_, &adjective]; } ; grammar adjWord(adjApostS): adjApostS->adj_ tokApostropheS->apost_ : AdjPhraseWithVocab /* generate a list of resolved objects */ getVocabMatchList(resolver, results, extraFlags) { /* return a list of objects in scope matching our adjective */ return getWordMatches(adj_, &adjApostS, resolver, extraFlags, VocabTruncated); } getAdjustedTokens() { return [adj_, &adjApostS]; } ; grammar adjWord(adjAbbr): adjective->adj_ tokAbbrPeriod->period_ : AdjPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* * return the list matching our adjective *with* the period * attached; the period is part of the dictionary entry for an * abbreviated word */ return getWordMatches(adj_ + period_, &adjective, resolver, extraFlags, VocabTruncated); } getAdjustedTokens() { /* the adjective includes the period as part of the literal text */ return [adj_ + period_, &adjective]; } ; /* ------------------------------------------------------------------------ */ /* * Possessive phrase. This is a noun phrase expressing ownership of * another object. * * Note that all possessive phrases that can possibly be ambiguous must * define getOrigMainText() to return the "main noun phrase" text. In * English, this means that we must omit any "'s" suffix. This is needed * only when the phrase can be ambiguous, so pronouns don't need it since * they are inherently unambiguous. */ grammar possessiveAdjPhrase(its): 'its' : ItsAdjProd /* we only agree with a singular ungendered noun */ checkAnaphorAgreement(lst) { return lst.length() == 1 && lst[1].obj_.canMatchIt; } ; grammar possessiveAdjPhrase(his): 'his' : HisAdjProd /* we only agree with a singular masculine noun */ checkAnaphorAgreement(lst) { return lst.length() == 1 && lst[1].obj_.canMatchHim; } ; grammar possessiveAdjPhrase(her): 'her' : HerAdjProd /* we only agree with a singular feminine noun */ checkAnaphorAgreement(lst) { return lst.length() == 1 && lst[1].obj_.canMatchHer; } ; grammar possessiveAdjPhrase(their): 'their' : TheirAdjProd /* we only agree with a single noun that has plural usage */ checkAnaphorAgreement(lst) { return lst.length() == 1 && lst[1].obj_.isPlural; } ; grammar possessiveAdjPhrase(your): 'your' : YourAdjProd /* we are non-anaphoric */ checkAnaphorAgreement(lst) { return nil; } ; grammar possessiveAdjPhrase(my): 'my' : MyAdjProd /* we are non-anaphoric */ checkAnaphorAgreement(lst) { return nil; } ; grammar possessiveAdjPhrase(npApostropheS): ('the' | ) nounPhrase->np_ tokApostropheS->apost_ : LayeredNounPhraseProd /* get the original text without the "'s" suffix */ getOrigMainText() { /* return just the basic noun phrase part */ return np_.getOrigText(); } ; grammar possessiveAdjPhrase(ppApostropheS): ('the' | ) pluralPhrase->np_ (tokApostropheS->apost_ | tokPluralApostrophe->apost_) : LayeredNounPhraseProd /* get the original text without the "'s" suffix */ getOrigMainText() { /* return just the basic noun phrase part */ return np_.getOrigText(); } resolveNouns(resolver, results) { /* note that we have a plural phrase, structurally speaking */ results.notePlural(); /* inherit the default handling */ return inherited(resolver, results); } /* the possessive phrase is plural */ isPluralPossessive = true ; /* * Possessive noun phrases. These are similar to possessive phrases, but * are stand-alone phrases that can act as nouns rather than as * qualifiers for other noun phrases. For example, for a first-person * player character, "mine" would be a possessive noun phrase referring * to an object owned by the player character. * * Note that many of the words used for possessive nouns are the same as * for possessive adjectives - for example "his" is the same in either * case, as are "'s" words. However, we make the distinction internally * because the two have different grammatical uses, and some of the words * do differ ("her" vs "hers", for example). */ grammar possessiveNounPhrase(its): 'its': ItsNounProd; grammar possessiveNounPhrase(his): 'his': HisNounProd; grammar possessiveNounPhrase(hers): 'hers': HersNounProd; grammar possessiveNounPhrase(theirs): 'theirs': TheirsNounProd; grammar possessiveNounPhrase(yours): 'yours' : YoursNounProd; grammar possessiveNounPhrase(mine): 'mine' : MineNounProd; grammar possessiveNounPhrase(npApostropheS): ('the' | ) (nounPhrase->np_ tokApostropheS->apost_ | pluralPhrase->np (tokApostropheS->apost_ | tokPluralApostrophe->apost_)) : LayeredNounPhraseProd /* get the original text without the "'s" suffix */ getOrigMainText() { /* return just the basic noun phrase part */ return np_.getOrigText(); } ; /* ------------------------------------------------------------------------ */ /* * Simple plural phrase. This is the most basic plural phrase, which is * simply a plural noun, optionally preceded by one or more adjectives. * * (English doesn't have any sort of adjective declension in number, so * there's no need to distinguish between plural and singular adjectives; * this equivalent rule in languages with adjective-noun agreement in * number would use plural adjectives here as well as plural nouns.) */ grammar simplePluralPhrase(plural): plural->plural_ : NounPhraseWithVocab /* generate a list of my resolved objects */ getVocabMatchList(resolver, results, extraFlags) { local lst; /* get the list of matching plurals */ lst = getWordMatches(plural_, &plural, resolver, extraFlags, PluralTruncated); /* get the list of matching 'noun' definitions */ local nLst = getWordMatches(plural_, &noun, resolver, extraFlags, VocabTruncated); /* get the combined list */ local comboLst = combineWordMatches(lst, nLst); /* * If we're in global scope, add in the matches for just plain * 'noun' properties as well. This is important because we'll * sometimes want to define a word that's actually a plural * usage (in terms of the real-world English) under the 'noun' * property. This occurs particularly when a single game-world * object represents a multiplicity of real-world objects. When * the scope is global, it's hard to anticipate all of the * possible interactions with vocabulary along these lines, so * it's easiest just to include the 'noun' matches. */ if (resolver.isGlobalScope) { /* keep the combined list */ lst = comboLst; } else if (comboLst.length() > lst.length()) { /* * ordinary scope, so don't include the noun matches; but * since we found extra items to add, at least mark the * plural matches as potentially ambiguous */ lst.forEach({x: x.flags_ |= UnclearDisambig}); } /* return the result list */ return lst; } getAdjustedTokens() { return [plural_, &plural]; } ; grammar simplePluralPhrase(adj): adjWord->adj_ simplePluralPhrase->np_ : NounPhraseWithVocab /* resolve my object list */ getVocabMatchList(resolver, results, extraFlags) { /* * return the list of objects in scope matching our adjective * plus the list from the underlying noun phrase */ return intersectNounLists( adj_.getVocabMatchList(resolver, results, extraFlags), np_.getVocabMatchList(resolver, results, extraFlags)); } getAdjustedTokens() { return adj_.getAdjustedTokens() + np_.getAdjustedTokens(); } ; grammar simplePluralPhrase(poundNum): poundNumberPhrase->num_ simplePluralPhrase->np_ : NounPhraseWithVocab /* resolve my object list */ getVocabMatchList(resolver, results, extraFlags) { local baseList; local numList; /* get the base list for the rest of the phrase */ baseList = np_.getVocabMatchList(resolver, results, extraFlags); /* get the numeric matches, including numeric wildcards */ numList = getWordMatches(num_.getStrVal(), &adjective, resolver, extraFlags, VocabTruncated) + getWordMatches('#', &adjective, resolver, extraFlags, VocabTruncated); /* return the intersection of the lists */ return intersectNounLists(numList, baseList); } getAdjustedTokens() { return [num_.getStrVal(), &adjective] + np_.getAdjustedTokens(); } ; /* * A simple plural phrase can end with an adjective and "ones," as in * "the red ones." */ grammar simplePluralPhrase(adjAndOnes): adjective->adj_ 'ones' : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* generate a list of objects matching the adjective */ return getWordMatches(adj_, &adjective, resolver, extraFlags | EndsWithAdj, VocabTruncated); } getAdjustedTokens() { return [adj_, &adjective]; } ; /* * If the command has qualifiers that require a plural, but omits * everything else, we can have an empty simple noun phrase. */ grammar simplePluralPhrase(empty): [badness 600] : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* we have an empty noun phrase */ return results.emptyNounPhrase(resolver); } getAdjustedTokens() { return []; } ; /* * A simple plural phrase can match unknown words as a last resort. */ grammar simplePluralPhrase(misc): [badness 300] miscWordList->lst_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* get the match list from the underlying list */ local lst = lst_.getVocabMatchList(resolver, results, extraFlags); /* * if there are no matches, note in the results that we have an * arbitrary word list that doesn't correspond to any object */ if (lst == nil || lst.length() == 0) results.noteMiscWordList(lst_.getOrigText()); /* return the vocabulary match list */ return lst; } getAdjustedTokens() { return lst_.getAdjustedTokens(); } ; /* ------------------------------------------------------------------------ */ /* * An "adjective phrase" is a phrase made entirely of adjectives. */ grammar adjPhrase(adj): adjective->adj_ : AdjPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* note the adjective ending */ results.noteAdjEnding(); /* return the match list */ local lst = getWordMatches(adj_, &adjective, resolver, extraFlags | EndsWithAdj, VocabTruncated); /* if in global scope, also try a noun interpretation */ if (resolver.isGlobalScope) lst = addNounMatchList(lst, resolver, results, extraFlags); /* return the result */ return lst; } getAdjustedTokens() { return [adj_, &adjective]; } ; grammar adjPhrase(adjAdj): adjective->adj_ adjPhrase->ap_ : NounPhraseWithVocab /* generate a list of my resolved objects */ getVocabMatchList(resolver, results, extraFlags) { /* * return the list of objects in scope matching our adjective * plus the list from the underlying adjective phrase */ return intersectWordMatches( adj_, &adjective, resolver, extraFlags, VocabTruncated, ap_.getVocabMatchList(resolver, results, extraFlags)); } getAdjustedTokens() { return [adj_, &adjective] + ap_.getAdjustedTokens(); } ; /* ------------------------------------------------------------------------ */ /* * A "topic" is a special type of noun phrase used in commands like "ask * <actor> about <topic>." We define a topic as simply an ordinary * single-noun phrase. We distinguish this in the grammar to allow games * to add special syntax for these. */ grammar topicPhrase(main): singleNoun->np_ : TopicProd ; /* * Explicitly match a miscellaneous word list as a topic. * * This might seem redundant with the ordinary topicPhrase that accepts a * singleNoun, because singleNoun can match a miscellaneous word list. * The difference is that singleNoun only matches a miscWordList with a * "badness" value, whereas we match a miscWordList here without any * badness. We want to be more tolerant of unrecognized input in topic * phrases than in ordinary noun phrases, because it's in the nature of * topic phrases to go outside of what's implemented directly in the * simulation model. At a grammatical level, we don't want to treat * topic phrases that we can resolve to the simulation model any * differently than we treat those we can't resolve, so we must add this * rule to eliminate the badness that singleNoun associated with a * miscWordList match. * * Note that we do prefer resolvable noun phrase matches to miscWordList * matches, but we handle this preference with the resolver's scoring * mechanism rather than with badness. */ grammar topicPhrase(misc): miscWordList->np_ : TopicProd resolveNouns(resolver, results) { /* note in the results that we have an arbitrary word list */ results.noteMiscWordList(np_.getOrigText()); /* inherit the default TopicProd behavior */ return inherited(resolver, results); } ; /* ------------------------------------------------------------------------ */ /* * A "quoted string" phrase is a literal enclosed in single or double * quotes. * * Note that this is a separate production from literalPhrase. This * production can be used when *only* a quoted string is allowed. The * literalPhrase production allows both quoted and unquoted text. */ grammar quotedStringPhrase(main): tokString->str_ : LiteralProd /* * get my string, with the quotes trimmed off (so we return simply * the contents of the string) */ getStringText() { return stripQuotesFrom(str_); } ; /* * Service routine: strip quotes from a *possibly* quoted string. If the * string starts with a quote, we'll remove the open quote. If it starts * with a quote and it ends with a corresponding close quote, we'll * remove that as well. */ stripQuotesFrom(str) { local hasOpen; local hasClose; /* presume we won't find open or close quotes */ hasOpen = hasClose = nil; /* * Check for quotes. We'll accept regular ASCII "straight" single * or double quotes, as well as Latin-1 curly single or double * quotes. The curly quotes must be used in their normal */ if (str.startsWith('\'') || str.startsWith('"')) { /* single or double quote - check for a matching close quote */ hasOpen = true; hasClose = (str.length() > 2 && str.endsWith(str.substr(1, 1))); } else if (str.startsWith('`')) { /* single in-slanted quote - check for either type of close */ hasOpen = true; hasClose = (str.length() > 2 && (str.endsWith('`') || str.endsWith('\''))); } else if (str.startsWith('\u201C')) { /* it's a curly double quote */ hasOpen = true; hasClose = str.endsWith('\u201D'); } else if (str.startsWith('\u2018')) { /* it's a curly single quote */ hasOpen = true; hasClose = str.endsWith('\u2019'); } /* trim off the quotes */ if (hasOpen) { if (hasClose) str = str.substr(2, str.length() - 2); else str = str.substr(2); } /* return the modified text */ return str; } /* ------------------------------------------------------------------------ */ /* * A "literal" is essentially any phrase. This can include a quoted * string, a number, or any set of word tokens. */ grammar literalPhrase(string): quotedStringPhrase->str_ : LiteralProd getLiteralText(results, action, which) { /* get the text from our underlying quoted string */ return str_.getStringText(); } getTentativeLiteralText() { /* * our result will never change, so our tentative text is the * same as our regular literal text */ return str_.getStringText(); } resolveLiteral(results) { /* flag the literal text */ results.noteLiteral(str_.getOrigText()); } ; grammar literalPhrase(miscList): miscWordList->misc_ : LiteralProd getLiteralText(results, action, which) { /* get my original text */ local txt = misc_.getOrigText(); /* * if our underlying miscWordList has only one token, strip * quotes, in case that token is a quoted string token */ if (misc_.getOrigTokenList().length() == 1) txt = stripQuotesFrom(txt); /* return the text */ return txt; } getTentativeLiteralText() { /* our regular text is permanent, so simply use it now */ return misc_.getOrigText(); } resolveLiteral(results) { /* * note the length of our literal phrase - when we have a choice * of interpretations, we prefer to choose shorter literal * phrases, since this means that we'll have more of our tokens * being fully interpreted rather than bunched into an * uninterpreted literal */ results.noteLiteral(misc_.getOrigText()); } ; /* * In case we have a verb grammar rule that calls for a literal phrase, * but the player enters a command with nothing in that slot, match an * empty token list as a last resort. Since this phrasing has a badness, * we won't match it unless we don't have any better structural match. */ grammar literalPhrase(empty): [badness 400]: EmptyLiteralPhraseProd resolveLiteral(results) { } ; /* ------------------------------------------------------------------------ */ /* * An miscellaneous word list is a list of one or more words of any kind: * any word, any integer, or any apostrophe-S token will do. Note that * known and unknown words can be mixed in an unknown word list; we care * only that the list is made up of tokWord, tokInt, tokApostropheS, * and/or abbreviation-period tokens. * * Note that this kind of phrase is often used with a 'badness' value. * However, we don't assign any badness here, because a miscellaneous * word list might be perfectly valid in some contexts; instead, any * productions that include a misc word list should specify badness as * desired. */ grammar miscWordList(wordOrNumber): tokWord->txt_ | tokInt->txt_ | tokApostropheS->txt_ | tokPluralApostrophe->txt_ | tokPoundInt->txt_ | tokString->txt_ | tokAbbrPeriod->txt_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* we don't match anything directly with our vocabulary */ return []; } getAdjustedTokens() { /* our token type is the special miscellaneous word type */ return [txt_, &miscWord]; } ; grammar miscWordList(list): (tokWord->txt_ | tokInt->txt_ | tokApostropheS->txt_ | tokPluralApostrophe->txt_ | tokAbbrPeriod->txt_ | tokPoundInt->txt_ | tokString->txt_) miscWordList->lst_ : NounPhraseWithVocab getVocabMatchList(resolver, results, extraFlags) { /* we don't match anything directly with our vocabulary */ return []; } getAdjustedTokens() { /* our token type is the special miscellaneous word type */ return [txt_, &miscWord] + lst_.getAdjustedTokens(); } ; /* ------------------------------------------------------------------------ */ /* * A main disambiguation phrase consists of a disambiguation phrase, * optionally terminated with a period. */ grammar mainDisambigPhrase(main): disambigPhrase->dp_ | disambigPhrase->dp_ '.' : BasicProd resolveNouns(resolver, results) { return dp_.resolveNouns(resolver, results); } getResponseList() { return dp_.getResponseList(); } ; /* * A "disambiguation phrase" is a phrase that answers a disambiguation * question ("which book do you mean..."). * * A disambiguation question can be answered with several types of * syntax: * *. all/everything/all of them *. both/both of them *. any/any of them *. <disambig list> *. the <ordinal list> ones *. the former/the latter * * Note that we assign non-zero badness to all of the ordinal * interpretations, so that we will take an actual vocabulary * interpretation instead of an ordinal interpretation whenever possible. * For example, if an object's name is actually "the third button," this * will give us greater affinity for using "third" as an adjective than * as an ordinal in our own list. */ grammar disambigPhrase(all): 'all' | 'everything' | 'all' 'of' 'them' : DisambigProd resolveNouns(resolver, results) { /* they want everything we proposed - return the whole list */ return removeAmbigFlags(resolver.getAll(self)); } /* there's only me in the response list */ getResponseList() { return [self]; } ; grammar disambigPhrase(both): 'both' | 'both' 'of' 'them' : DisambigProd resolveNouns(resolver, results) { /* * they want two items - return the whole list (if it has more * than two items, we'll simply act as though they wanted all of * them) */ return removeAmbigFlags(resolver.getAll(self)); } /* there's only me in the response list */ getResponseList() { return [self]; } ; grammar disambigPhrase(any): 'any' | 'any' 'of' 'them' : DisambigProd resolveNouns(resolver, results) { local lst; /* they want any item - arbitrarily pick the first one */ lst = resolver.matchList.sublist(1, 1); /* * add the "unclear disambiguation" flag to the item we picked, * to indicate that the selection was arbitrary */ if (lst.length() > 0) lst[1].flags_ |= UnclearDisambig; /* return the result */ return lst; } /* there's only me in the response list */ getResponseList() { return [self]; } ; grammar disambigPhrase(list): disambigList->lst_ : DisambigProd resolveNouns(resolver, results) { return removeAmbigFlags(lst_.resolveNouns(resolver, results)); } /* there's only me in the response list */ getResponseList() { return lst_.getResponseList(); } ; grammar disambigPhrase(ordinalList): disambigOrdinalList->lst_ 'ones' | 'the' disambigOrdinalList->lst_ 'ones' : DisambigProd resolveNouns(resolver, results) { /* return the list with the ambiguity flags removed */ return removeAmbigFlags(lst_.resolveNouns(resolver, results)); } /* the response list consists of my single ordinal list item */ getResponseList() { return [lst_]; } ; /* * A disambig list consists of one or more disambig list items, connected * by noun phrase conjunctions. */ grammar disambigList(single): disambigListItem->item_ : DisambigProd resolveNouns(resolver, results) { return item_.resolveNouns(resolver, results); } /* the response list consists of my single item */ getResponseList() { return [item_]; } ; grammar disambigList(list): disambigListItem->item_ commandOrNounConjunction disambigList->lst_ : DisambigProd resolveNouns(resolver, results) { return item_.resolveNouns(resolver, results) + lst_.resolveNouns(resolver, results); } /* my response list consists of each of our list items */ getResponseList() { return [item_] + lst_.getResponseList(); } ; /* * Base class for ordinal disambiguation items */ class DisambigOrdProd: DisambigProd resolveNouns(resolver, results) { /* note the ordinal match */ results.noteDisambigOrdinal(); /* select the result by the ordinal */ return selectByOrdinal(ord_, resolver, results); } selectByOrdinal(ordTok, resolver, results) { local idx; local matchList = resolver.ordinalMatchList; /* * look up the meaning of the ordinal word (note that we assume * that each ordinalWord is unique, since we only create one of * each) */ idx = cmdDict.findWord(ordTok, &ordinalWord)[1].numval; /* * if it's the special value -1, it indicates that we should * select the *last* item in the list */ if (idx == -1) idx = matchList.length(); /* if it's outside the limits of the match list, it's an error */ if (idx > matchList.length()) { /* note the problem */ results.noteOrdinalOutOfRange(ordTok); /* no results */ return []; } /* return the selected item as a one-item list */ return matchList.sublist(idx, 1); } ; /* * A disambig vocab production is the base class for disambiguation * phrases that involve vocabulary words. */ class DisambigVocabProd: DisambigProd ; /* * A disambig list item consists of: * *. first/second/etc *. the first/second/etc *. first one/second one/etc *. the first one/the second one/etc *. <compound noun phrase> *. possessive */ grammar disambigListItem(ordinal): ordinalWord->ord_ | ordinalWord->ord_ 'one' | 'the' ordinalWord->ord_ | 'the' ordinalWord->ord_ 'one' : DisambigOrdProd ; grammar disambigListItem(noun): completeNounPhraseWithoutAll->np_ | terminalNounPhrase->np_ : DisambigVocabProd resolveNouns(resolver, results) { /* get the matches for the underlying noun phrase */ local lst = np_.resolveNouns(resolver, results); /* note the matches */ results.noteMatches(lst); /* return the match list */ return lst; } ; grammar disambigListItem(plural): pluralPhrase->np_ : DisambigVocabProd resolveNouns(resolver, results) { local lst; /* * get the underlying match list; since we explicitly have a * plural, the result doesn't need to be unique, so simply * return everything we find */ lst = np_.resolveNouns(resolver, results); /* * if we didn't get anything, it's an error; otherwise, take * everything, since we explicitly wanted a plural usage */ if (lst.length() == 0) results.noMatch(resolver.getAction(), np_.getOrigText()); else results.noteMatches(lst); /* return the list */ return lst; } ; grammar disambigListItem(possessive): possessiveNounPhrase->poss_ : DisambigPossessiveProd ; /* * A disambig ordinal list consists of two or more ordinal words * separated by noun phrase conjunctions. Note that there is a minimum * of two entries in the list. */ grammar disambigOrdinalList(tail): ordinalWord->ord1_ ('and' | ',') ordinalWord->ord2_ : DisambigOrdProd resolveNouns(resolver, results) { /* note the pair of ordinal matches */ results.noteDisambigOrdinal(); results.noteDisambigOrdinal(); /* combine the selections of our two ordinals */ return selectByOrdinal(ord1_, resolver, results) + selectByOrdinal(ord2_, resolver, results); } ; grammar disambigOrdinalList(head): ordinalWord->ord_ ('and' | ',') disambigOrdinalList->lst_ : DisambigOrdProd resolveNouns(resolver, results) { /* note the ordinal match */ results.noteDisambigOrdinal(); /* combine the selections of our ordinal and the sublist */ return selectByOrdinal(ord_, resolver, results) + lst_.resolveNouns(resolver, results); } ; /* ------------------------------------------------------------------------ */ /* * Ordinal words. We define a limited set of these, since we only use * them in a few special contexts where it would be unreasonable to need * even as many as define here. */ #define defOrdinal(str, val) object ordinalWord=#@str numval=val defOrdinal(former, 1); defOrdinal(first, 1); defOrdinal(second, 2); defOrdinal(third, 3); defOrdinal(fourth, 4); defOrdinal(fifth, 5); defOrdinal(sixth, 6); defOrdinal(seventh, 7); defOrdinal(eighth, 8); defOrdinal(ninth, 9); defOrdinal(tenth, 10); defOrdinal(eleventh, 11); defOrdinal(twelfth, 12); defOrdinal(thirteenth, 13); defOrdinal(fourteenth, 14); defOrdinal(fifteenth, 15); defOrdinal(sixteenth, 16); defOrdinal(seventeenth, 17); defOrdinal(eighteenth, 18); defOrdinal(nineteenth, 19); defOrdinal(twentieth, 20); defOrdinal(1st, 1); defOrdinal(2nd, 2); defOrdinal(3rd, 3); defOrdinal(4th, 4); defOrdinal(5th, 5); defOrdinal(6th, 6); defOrdinal(7th, 7); defOrdinal(8th, 8); defOrdinal(9th, 9); defOrdinal(10th, 10); defOrdinal(11th, 11); defOrdinal(12th, 12); defOrdinal(13th, 13); defOrdinal(14th, 14); defOrdinal(15th, 15); defOrdinal(16th, 16); defOrdinal(17th, 17); defOrdinal(18th, 18); defOrdinal(19th, 19); defOrdinal(20th, 20); /* * the special 'last' ordinal - the value -1 is special to indicate the * last item in a list */ defOrdinal(last, -1); defOrdinal(latter, -1); /* ------------------------------------------------------------------------ */ /* * A numeric production. These can be either spelled-out numbers (such * as "fifty-seven") or numbers entered in digit form (as in "57"). */ class NumberProd: BasicProd /* get the numeric (integer) value */ getval() { return 0; } /* * Get the string version of the numeric value. This should return * a string, but the string should be in digit form. If the * original entry was in digit form, then the original entry should * be returned; otherwise, a string should be constructed from the * integer value. By default, we'll do the latter. */ getStrVal() { return toString(getval()); } ; /* * A quantifier is simply a number, entered with numerals or spelled out. */ grammar numberPhrase(digits): tokInt->num_ : NumberProd /* get the numeric value */ getval() { return toInteger(num_); } /* * get the string version of the numeric value - since the token was * an integer to start with, return the actual integer value */ getStrVal() { return num_; } ; grammar numberPhrase(spelled): spelledNumber->num_ : NumberProd /* get the numeric value */ getval() { return num_.getval(); } ; /* * A number phrase preceded by a pound sign. We distinguish this kind of * number phrase from plain numbers, since this kind has a somewhat more * limited set of valid contexts. */ grammar poundNumberPhrase(main): tokPoundInt->num_ : NumberProd /* * get the numeric value - a tokPoundInt token has a pound sign * followed by digits, so the numeric value is the value of the * substring following the '#' sign */ getval() { return toInteger(num_.substr(2)); } /* * get the string value - we have a number token following the '#', * so simply return the part after the '#' */ getStrVal() { return num_.substr(2); } ; /* * Number literals. We'll define a set of special objects for numbers: * each object defines a number and a value for the number. */ #define defDigit(num, val) object digitWord=#@num numval=val #define defTeen(num, val) object teenWord=#@num numval=val #define defTens(num, val) object tensWord=#@num numval=val defDigit(one, 1); defDigit(two, 2); defDigit(three, 3); defDigit(four, 4); defDigit(five, 5); defDigit(six, 6); defDigit(seven, 7); defDigit(eight, 8); defDigit(nine, 9); defTeen(ten, 10); defTeen(eleven, 11); defTeen(twelve, 12); defTeen(thirteen, 13); defTeen(fourteen, 14); defTeen(fifteen, 15); defTeen(sixteen, 16); defTeen(seventeen, 17); defTeen(eighteen, 18); defTeen(nineteen, 19); defTens(twenty, 20); defTens(thirty, 30); defTens(forty, 40); defTens(fifty, 50); defTens(sixty, 60); defTens(seventy, 70); defTens(eighty, 80); defTens(ninety, 90); grammar spelledSmallNumber(digit): digitWord->num_ : NumberProd getval() { /* * Look up the units word - there should be only one in the * dictionary, since these are our special words. Return the * object's numeric value property 'numval', which gives the * number for the name. */ return cmdDict.findWord(num_, &digitWord)[1].numval; } ; grammar spelledSmallNumber(teen): teenWord->num_ : NumberProd getval() { /* look up the dictionary word for the number */ return cmdDict.findWord(num_, &teenWord)[1].numval; } ; grammar spelledSmallNumber(tens): tensWord->num_ : NumberProd getval() { /* look up the dictionary word for the number */ return cmdDict.findWord(num_, &tensWord)[1].numval; } ; grammar spelledSmallNumber(tensAndUnits): tensWord->tens_ '-'->sep_ digitWord->units_ | tensWord->tens_ digitWord->units_ : NumberProd getval() { /* look up the words, and add up the values */ return cmdDict.findWord(tens_, &tensWord)[1].numval + cmdDict.findWord(units_, &digitWord)[1].numval; } ; grammar spelledSmallNumber(zero): 'zero' : NumberProd getval() { return 0; } ; grammar spelledHundred(small): spelledSmallNumber->num_ : NumberProd getval() { return num_.getval(); } ; grammar spelledHundred(hundreds): spelledSmallNumber->hun_ 'hundred' : NumberProd getval() { return hun_.getval() * 100; } ; grammar spelledHundred(hundredsPlus): spelledSmallNumber->hun_ 'hundred' spelledSmallNumber->num_ | spelledSmallNumber->hun_ 'hundred' 'and'->and_ spelledSmallNumber->num_ : NumberProd getval() { return hun_.getval() * 100 + num_.getval(); } ; grammar spelledHundred(aHundred): 'a' 'hundred' : NumberProd getval() { return 100; } ; grammar spelledHundred(aHundredPlus): 'a' 'hundred' 'and' spelledSmallNumber->num_ : NumberProd getval() { return 100 + num_.getval(); } ; grammar spelledThousand(thousands): spelledHundred->thou_ 'thousand' : NumberProd getval() { return thou_.getval() * 1000; } ; grammar spelledThousand(thousandsPlus): spelledHundred->thou_ 'thousand' spelledHundred->num_ : NumberProd getval() { return thou_.getval() * 1000 + num_.getval(); } ; grammar spelledThousand(thousandsAndSmall): spelledHundred->thou_ 'thousand' 'and' spelledSmallNumber->num_ : NumberProd getval() { return thou_.getval() * 1000 + num_.getval(); } ; grammar spelledThousand(aThousand): 'a' 'thousand' : NumberProd getval() { return 1000; } ; grammar spelledThousand(aThousandAndSmall): 'a' 'thousand' 'and' spelledSmallNumber->num_ : NumberProd getval() { return 1000 + num_.getval(); } ; grammar spelledMillion(millions): spelledHundred->mil_ 'million': NumberProd getval() { return mil_.getval() * 1000000; } ; grammar spelledMillion(millionsPlus): spelledHundred->mil_ 'million' (spelledThousand->nxt_ | spelledHundred->nxt_) : NumberProd getval() { return mil_.getval() * 1000000 + nxt_.getval(); } ; grammar spelledMillion(aMillion): 'a' 'million' : NumberProd getval() { return 1000000; } ; grammar spelledMillion(aMillionAndSmall): 'a' 'million' 'and' spelledSmallNumber->num_ : NumberProd getval() { return 1000000 + num_.getval(); } ; grammar spelledMillion(millionsAndSmall): spelledHundred->mil_ 'million' 'and' spelledSmallNumber->num_ : NumberProd getval() { return mil_.getval() * 1000000 + num_.getval(); } ; grammar spelledNumber(main): spelledHundred->num_ | spelledThousand->num_ | spelledMillion->num_ : NumberProd getval() { return num_.getval(); } ; /* ------------------------------------------------------------------------ */ /* * "OOPS" command syntax */ grammar oopsCommand(main): oopsPhrase->oops_ | oopsPhrase->oops_ '.' : BasicProd getNewTokens() { return oops_.getNewTokens(); } ; grammar oopsPhrase(main): 'oops' miscWordList->lst_ | 'oops' ',' miscWordList->lst_ | 'o' miscWordList->lst_ | 'o' ',' miscWordList->lst_ : BasicProd getNewTokens() { return lst_.getOrigTokenList(); } ; grammar oopsPhrase(missing): 'oops' | 'o' : BasicProd getNewTokens() { return nil; } ; /* ------------------------------------------------------------------------ */ /* * finishGame options. We provide descriptions and keywords for the * option objects here, because these are inherently language-specific. * * Note that we provide hyperlinks for our descriptions when possible. * When we're in plain text mode, we can't show links, so we'll instead * show an alternate form with the single-letter response highlighted in * the text. We don't highlight the single-letter response in the * hyperlinked version because (a) if the user wants a shortcut, they can * simply click the hyperlink, and (b) most UI's that show hyperlinks * show a distinctive appearance for the hyperlink itself, so adding even * more highlighting within the hyperlink starts to look awfully busy. */ modify finishOptionQuit desc = "<<aHrefAlt('quit', 'QUIT', '<b>Q</b>UIT', 'Leave the story')>>" responseKeyword = 'quit' responseChar = 'q' ; modify finishOptionRestore desc = "<<aHrefAlt('restore', 'RESTORE', '<b>R</b>ESTORE', 'Restore a saved position')>> a saved position" responseKeyword = 'restore' responseChar = 'r' ; modify finishOptionRestart desc = "<<aHrefAlt('restart', 'RESTART', 'RE<b>S</b>TART', 'Start the story over from the beginning')>> the story" responseKeyword = 'restart' responseChar = 's' ; modify finishOptionUndo desc = "<<aHrefAlt('undo', 'UNDO', '<b>U</b>NDO', 'Undo the last move')>> the last move" responseKeyword = 'undo' responseChar = 'u' ; modify finishOptionCredits desc = "see the <<aHrefAlt('credits', 'CREDITS', '<b>C</b>REDITS', 'Show credits')>>" responseKeyword = 'credits' responseChar = 'c' ; modify finishOptionFullScore desc = "see your <<aHrefAlt('full score', 'FULL SCORE', '<b>F</b>ULL SCORE', 'Show full score')>>" responseKeyword = 'full score' responseChar = 'f' ; modify finishOptionAmusing desc = "see some <<aHrefAlt('amusing', 'AMUSING', '<b>A</b>MUSING', 'Show some amusing things to try')>> things to try" responseKeyword = 'amusing' responseChar = 'a' ; modify restoreOptionStartOver desc = "<<aHrefAlt('start', 'START', '<b>S</b>TART', 'Start from the beginning')>> the game from the beginning" responseKeyword = 'start' responseChar = 's' ; modify restoreOptionRestoreAnother desc = "<<aHrefAlt('restore', 'RESTORE', '<b>R</b>ESTORE', 'Restore a saved position')>> a different saved position" ; /* ------------------------------------------------------------------------ */ /* * Context for Action.getVerbPhrase(). This keeps track of pronoun * antecedents in cases where we're stringing together a series of verb * phrases. */ class GetVerbPhraseContext: object /* get the objective form of an object, using a pronoun as appropriate */ objNameObj(obj) { /* * if it's the pronoun antecedent, use the pronoun form; * otherwise, use the full name */ if (obj == pronounObj) return obj.itObj; else return obj.theNameObj; } /* are we showing the given object pronomially? */ isObjPronoun(obj) { return (obj == pronounObj); } /* set the pronoun antecedent */ setPronounObj(obj) { pronounObj = obj; } /* the pronoun antecedent */ pronounObj = nil ; /* * Default getVerbPhrase context. This can be used when no other context * is needed. This context instance has no state - it doesn't track any * antecedents. */ defaultGetVerbPhraseContext: GetVerbPhraseContext /* we don't remember any antecedents */ setPronounObj(obj) { } ; /* ------------------------------------------------------------------------ */ /* * Implicit action context. This is passed to the message methods that * generate implicit action announcements, to indicate the context in * which the message is to be used. */ class ImplicitAnnouncementContext: object /* * Should we use the infinitive form of the verb, or the participle * form for generating the announcement? By default, use use the * participle form: "(first OPENING THE BOX)". */ useInfPhrase = nil /* is this message going in a list? */ isInList = nil /* * Are we in a sublist of 'just trying' or 'just asking' messages? * (We can only have sublist groupings one level deep, so we don't * need to worry about what kind of sublist we're in.) */ isInSublist = nil /* our getVerbPhrase context - by default, don't use one */ getVerbCtx = nil /* generate the announcement message given the action description */ buildImplicitAnnouncement(txt) { /* if we're not in a list, make it a full, stand-alone message */ if (!isInList) txt = '<./p0>\n<.assume>first ' + txt + '<./assume>\n'; /* return the result */ return txt; } ; /* the standard implicit action announcement context */ standardImpCtx: ImplicitAnnouncementContext; /* the "just trying" implicit action announcement context */ tryingImpCtx: ImplicitAnnouncementContext /* * The action was merely attempted, so use the infinitive phrase in * the announcement: "(first trying to OPEN THE BOX)". */ useInfPhrase = true /* build the announcement */ buildImplicitAnnouncement(txt) { /* * If we're not in a list of 'trying' messages, add the 'trying' * prefix message to the action description. This isn't * necessary if we're in a 'trying' list, since the list itself * will have the 'trying' part. */ if (!isInSublist) txt = 'trying to ' + txt; /* now build the message into the full text as usual */ return inherited(txt); } ; /* * The "asking question" implicit action announcement context. By * default, we generate the message exactly the same way we do for the * 'trying' case. */ askingImpCtx: tryingImpCtx; /* * A class for messages appearing in a list. Within a list, we want to * keep track of the last direct object, so that we can refer to it with * a pronoun later in the list. */ class ListImpCtx: ImplicitAnnouncementContext, GetVerbPhraseContext /* * Set the appropriate base context for the given implicit action * announcement report (an ImplicitActionAnnouncement object). */ setBaseCtx(ctx) { /* * if this is a failed attempt, use a 'trying' context; * otherwise, use a standard context */ if (ctx.justTrying) baseCtx = tryingImpCtx; else if (ctx.justAsking) baseCtx = askingImpCtx; else baseCtx = standardImpCtx; } /* we're in a list */ isInList = true /* we are our own getVerbPhrase context */ getVerbCtx = (self) /* delegate the phrase format to our underlying announcement context */ useInfPhrase = (delegated baseCtx) /* build the announcement using our underlying context */ buildImplicitAnnouncement(txt) { return delegated baseCtx(txt); } /* our base context - we delegate some unoverridden behavior to this */ baseCtx = nil ; /* ------------------------------------------------------------------------ */ /* * Language-specific Action modifications. */ modify Action /* * In the English grammar, all 'predicate' grammar definitions * (which are usually made via the VerbRule macro) are associated * with Action match tree objects; in fact, each 'predicate' grammar * match tree is the specific Action subclass associated with the * grammar for the predicate. This means that the Action associated * with a grammar match is simply the grammar match object itself. * Hence, we can resolve the action for a 'predicate' match simply * by returning the match itself: it is the Action as well as the * grammar match. * * This approach ('grammar predicate' matches are based on Action * subclasses) works well for languages like English that encode the * role of each phrase in the word order of the sentence. * * Languages that encode phrase roles using case markers or other * devices tend to be freer with word order. As a result, * 'predicate' grammars for such languages should generally not * attempt to capture all possible word orderings for a given * action, but should instead take the complementary approach of * capturing the possible overall sentence structures independently * of verb phrases, and plug in a verb phrase as a component, just * like noun phrases plug into the English grammar. In these cases, * the match objects will NOT be Action subclasses; the Action * objects will instead be buried down deeper in the match tree. * Hence, resolveAction() must be defined on whatever class is used * to construct 'predicate' grammar matches, instead of on Action, * since Action will not be a 'predicate' match. */ resolveAction(issuingActor, targetActor) { return self; } /* * Return the interrogative pronoun for a missing object in one of * our object roles. In most cases, this is simply "what", but for * some actions, "whom" is more appropriate (for example, the direct * object of "ask" is implicitly a person, so "whom" is most * appropriate for this role). */ whatObj(which) { /* intransitive verbs have no objects, so there's nothing to show */ } /* * Translate an interrogative word for whatObj. If the word is * 'whom', translate to the library message for 'whom'; this allows * authors to use 'who' rather than 'whom' as the objective form of * 'who', which sounds less stuffy to many people. */ whatTranslate(txt) { /* * if it's 'whom', translate to the library message for 'whom'; * otherwise, just show the word as is */ return (txt == 'whom' ? gLibMessages.whomPronoun : txt); } /* * Return a string with the appropriate pronoun (objective form) for * a list of object matches, with the given resolved cardinality. * This list is a list of ResolveInfo objects. */ objListPronoun(objList) { local himCnt, herCnt, themCnt; local FirstPersonCnt, SecondPersonCnt; local resolvedNumber; /* if there's no object list at all, just use 'it' */ if (objList == nil || objList == []) return 'it'; /* note the number of objects in the resolved list */ resolvedNumber = objList.length(); /* * In the tentatively resolved object list, we might have hidden * away ambiguous matches. Expand those back into the list so * we have the full list of in-scope matches. */ foreach (local cur in objList) { /* * if this one has hidden ambiguous objects, add the hidden * objects back into our list */ if (cur.extraObjects != nil) objList += cur.extraObjects; } /* * if the desired cardinality is plural and the object list has * more than one object, simply say 'them' */ if (objList.length() > 1 && resolvedNumber > 1) return 'them'; /* * singular cardinality - count masculine and feminine objects, * and count the referral persons */ himCnt = herCnt = themCnt = 0; FirstPersonCnt = SecondPersonCnt = 0; foreach (local cur in objList) { /* if it's masculine, count it */ if (cur.obj_.isHim) ++himCnt; /* if it's feminine, count it */ if (cur.obj_.isHer) ++herCnt; /* if it has plural usage, count it */ if (cur.obj_.isPlural) ++themCnt; /* if it's first person usage, count it */ if (cur.obj_.referralPerson == FirstPerson) ++FirstPersonCnt; /* if it's second person usage, count it */ if (cur.obj_.referralPerson == SecondPerson) ++SecondPersonCnt; } /* * if they all have plural usage, show "them"; if they're all of * one gender, show "him" or "her" as appropriate; if they're * all neuter, show "it"; otherwise, show "them" */ if (themCnt == objList.length()) return 'them'; else if (FirstPersonCnt == objList.length()) return 'myself'; else if (SecondPersonCnt == objList.length()) return 'yourself'; else if (himCnt == objList.length() && herCnt == 0) return 'him'; else if (herCnt == objList.length() && himCnt == 0) return 'her'; else if (herCnt == 0 && himCnt == 0) return 'it'; else return 'them'; } /* * Announce a default object used with this action. * * 'resolvedAllObjects' indicates where we are in the command * processing: this is true if we've already resolved all of the * other objects in the command, nil if not. We use this * information to get the phrasing right according to the situation. */ announceDefaultObject(obj, whichObj, resolvedAllObjects) { /* * the basic action class takes no objects, so there can be no * default announcement */ return ''; } /* * Announce all defaulted objects in the action. By default, we * show nothing. */ announceAllDefaultObjects(allResolved) { } /* * Return a phrase describing the action performed implicitly, as a * participle phrase. 'ctx' is an ImplicitAnnouncementContext object * describing the context in which we're generating the phrase. * * This comes in two forms: if the context indicates we're only * attempting the action, we'll return an infinitive phrase ("open * the box") for use in a larger participle phrase describing the * attempt ("trying to..."). Otherwise, we'll be describing the * action as actually having been performed, so we'll return a * present participle phrase ("opening the box"). */ getImplicitPhrase(ctx) { /* * Get the phrase. Use the infinitive or participle form, as * indicated in the context. */ return getVerbPhrase(ctx.useInfPhrase, ctx.getVerbCtx); } /* * Get the infinitive form of the action. We are NOT to include the * infinitive complementizer (i.e., "to") as part of the result, * since the complementizer isn't used in all contexts in which we * might want to use the infinitive; for example, we don't want a * "to" in phrases involving an auxiliary verb, such as "he can open * the box." */ getInfPhrase() { /* return the verb phrase in infinitive form */ return getVerbPhrase(true, nil); } /* * Get the root infinitive form of our verb phrase as part of a * question in which one of the verb's objects is the "unknown" of * the interrogative. 'which' is one of the role markers * (DirectObject, IndirectObject, etc), indicating which object is * the subject of the interrogative. * * For example, for the verb UNLOCK <dobj> WITH <iobj>, if the * unknown is the direct object, the phrase we'd return would be * "unlock": this would plug into contexts such as "what do you want * to unlock." If the indirect object is the unknown for the same * verb, the phrase would be "unlock it with", which would plug in as * "what do you want to unlock it with". * * Note that we are NOT to include the infinitive complementizer * (i.e., "to") as part of the phrase we generate, since the * complementizer isn't used in some contexts where the infinitive * conjugation is needed (for example, "what should I <infinitive>"). */ getQuestionInf(which) { /* * for a verb without objects, this is the same as the basic * infinitive */ return getInfPhrase(); } /* * Get a string describing the full action in present participle * form, using the current command objects: "taking the watch", * "putting the book on the shelf" */ getParticiplePhrase() { /* return the verb phrase in participle form */ return getVerbPhrase(nil, nil); } /* * Get the full verb phrase in either infinitive or participle * format. This is a common handler for getInfinitivePhrase() and * getParticiplePhrase(). * * 'ctx' is a GetVerbPhraseContext object, which lets us keep track * of antecedents when we're stringing together multiple verb * phrases. 'ctx' can be nil if the verb phrase is being used in * isolation. */ getVerbPhrase(inf, ctx) { /* * parse the verbPhrase into the parts before and after the * slash, and any additional text following the slash part */ rexMatch('(.*)/(<alphanum|-|squote>+)(.*)', verbPhrase); /* return the appropriate parts */ if (inf) { /* * infinitive - we want the part before the slash, plus the * extra prepositions (or whatever) after the switched part */ return rexGroup(1)[3] + rexGroup(3)[3]; } else { /* participle - it's the part after the slash */ return rexGroup(2)[3] + rexGroup(3)[3]; } } /* * Show the "noMatch" library message. For most verbs, we use the * basic "you can't see that here". Verbs that are mostly used with * intangible objects, such as LISTEN TO and SMELL, might want to * override this to use a less visually-oriented message. */ noMatch(msgObj, actor, txt) { msgObj.noMatchCannotSee(actor, txt); } /* * Verb flags - these are used to control certain aspects of verb * formatting. By default, we have no special flags. */ verbFlags = 0 /* add a space prefix/suffix to a string if the string is non-empty */ spPrefix(str) { return (str == '' ? str : ' ' + str); } spSuffix(str) { return (str == '' ? str : str + ' '); } ; /* * English-specific additions for single-object verbs. */ modify TAction /* return an interrogative word for an object of the action */ whatObj(which) { /* * Show the interrogative for our direct object - this is the * last word enclosed in parentheses in our verbPhrase string. */ rexSearch('<lparen>.*?(<alpha>+)<rparen>', verbPhrase); return whatTranslate(rexGroup(1)[3]); } /* announce a default object used with this action */ announceDefaultObject(obj, whichObj, resolvedAllObjects) { /* * get any direct object preposition - this is the part inside * the "(what)" specifier parens, excluding the last word */ rexSearch('<lparen>(.*<space>+)?<alpha>+<rparen>', verbPhrase); local prep = (rexGroup(1) == nil ? '' : rexGroup(1)[3]); /* do any verb-specific adjustment of the preposition */ if (prep != nil) prep = adjustDefaultObjectPrep(prep, obj); /* * get the object name - we need to distinguish from everything * else in scope, since we considered everything in scope when * making our pick */ local nm = obj.getAnnouncementDistinguisher( gActor.scopeList()).theName(obj); /* show the preposition (if any) and the object */ return (prep == '' ? nm : prep + nm); } /* * Adjust the preposition. In some cases, the verb will want to vary * the preposition according to the object. This method can return a * custom preposition in place of the one in the verbPhrase. By * default, we just use the fixed preposition from the verbPhrase, * which is passed in to us in 'prep'. */ adjustDefaultObjectPrep(prep, obj) { return prep; } /* announce all defaulted objects */ announceAllDefaultObjects(allResolved) { /* announce a defaulted direct object if appropriate */ maybeAnnounceDefaultObject(dobjList_, DirectObject, allResolved); } /* show the verb's basic infinitive form for an interrogative */ getQuestionInf(which) { /* * Show the present-tense verb form (removing the participle * part - the "/xxxing" part). Include any prepositions * attached to the verb itself or to the direct object (inside * the "(what)" parens). */ rexSearch('(.*)/<alphanum|-|squote>+(.*?)<space>+' + '<lparen>(.*?)<space>*?<alpha>+<rparen>', verbPhrase); return rexGroup(1)[3] + spPrefix(rexGroup(2)[3]) + spPrefix(rexGroup(3)[3]); } /* get the verb phrase in infinitive or participle form */ getVerbPhrase(inf, ctx) { local dobj; local dobjText; local dobjIsPronoun; local ret; /* use the default pronoun context if one wasn't supplied */ if (ctx == nil) ctx = defaultGetVerbPhraseContext; /* get the direct object */ dobj = getDobj(); /* note if it's a pronoun */ dobjIsPronoun = ctx.isObjPronoun(dobj); /* get the direct object name */ dobjText = ctx.objNameObj(dobj); /* get the phrasing */ ret = getVerbPhrase1(inf, verbPhrase, dobjText, dobjIsPronoun); /* set the pronoun antecedent to my direct object */ ctx.setPronounObj(dobj); /* return the result */ return ret; } /* * Given the text of the direct object phrase, build the verb phrase * for a one-object verb. This is a class method that can be used by * other kinds of verbs (i.e., non-TActions) that use phrasing like a * single object. * * 'inf' is a flag indicating whether to use the infinitive form * (true) or the present participle form (nil); 'vp' is the * verbPhrase string; 'dobjText' is the direct object phrase's text; * and 'dobjIsPronoun' is true if the dobj text is rendered as a * pronoun. */ getVerbPhrase1(inf, vp, dobjText, dobjIsPronoun) { local ret; local dprep; local vcomp; /* * parse the verbPhrase: pick out the 'infinitive/participle' * part, the complementizer part up to the '(what)' direct * object placeholder, and any preposition within the '(what)' * specifier */ rexMatch('(.*)/(<alphanum|-|squote>+)(.*) ' + '<lparen>(.*?)<space>*?<alpha>+<rparen>(.*)', vp); /* start off with the infinitive or participle, as desired */ if (inf) ret = rexGroup(1)[3]; else ret = rexGroup(2)[3]; /* get the prepositional complementizer */ vcomp = rexGroup(3)[3]; /* get the direct object preposition */ dprep = rexGroup(4)[3]; /* do any verb-specific adjustment of the preposition */ if (dprep != nil) dprep = adjustDefaultObjectPrep(dprep, getDobj()); /* * if the direct object is not a pronoun, put the complementizer * BEFORE the direct object (the 'up' in "PICKING UP THE BOX") */ if (!dobjIsPronoun) ret += spPrefix(vcomp); /* add the direct object preposition */ ret += spPrefix(dprep); /* add the direct object, using the pronoun form if applicable */ ret += ' ' + dobjText; /* * if the direct object is a pronoun, put the complementizer * AFTER the direct object (the 'up' in "PICKING IT UP") */ if (dobjIsPronoun) ret += spPrefix(vcomp); /* * if there's any suffix following the direct object * placeholder, add it at the end of the phrase */ ret += rexGroup(5)[3]; /* return the complete phrase string */ return ret; } ; /* * English-specific additions for two-object verbs. */ modify TIAction /* * Flag: omit the indirect object in a query for a missing direct * object. For many verbs, if we already know the indirect object * and we need to ask for the direct object, the query sounds best * when it includes the indirect object: "what do you want to put in * it?" or "what do you want to take from it?". This is the * default phrasing. * * However, the corresponding query for some verbs sounds weird: * "what do you want to dig in with it?" or "whom do you want to ask * about it?". For such actions, this property should be set to * true to indicate that the indirect object should be omitted from * the queries, which will change the phrasing to "what do you want * to dig in", "whom do you want to ask", and so on. */ omitIobjInDobjQuery = nil /* * For VerbRules: does this verb rule have a prepositional or * structural phrasing of the direct and indirect object slots? That * is, are the object slots determined by a prepositional marker, or * purely by word order? For most English verbs with two objects, * the indirect object is marked by a preposition: GIVE BOOK TO BOB, * PUT BOOK IN BOX. There are a few English verbs that don't include * any prespositional markers for the objects, though, and assign the * noun phrase roles purely by the word order: GIVE BOB BOOK, SHOW * BOB BOOK, THROW BOB BOOK. We define these phrasings with separate * verb rules, which we mark with this property. * * We use this in ranking verb matches. Non-prepositional verb * structures are especially prone to matching where they shouldn't, * because we can often find a way to pick out words to fill the * slots in the absence of any marker words. For example, GIVE GREEN * BOOK could be interpreted as GIVE BOOK TO GREEN, where GREEN is * assumed to be an adjective-ending noun phrase; but the player * probably means to give the green book to someone who they assumed * would be filled in as a default. So, whenever we find an * interpretation that involves a non-prespositional phrasing, we'll * use this flag to know we should be suspicious of it and try * alternative phrasing first. * * Most two-object verbs in English use prepositional markers, so * we'll set this as the default. Individual VerbRules that use * purely structural phrasing should override this. */ isPrepositionalPhrasing = true /* resolve noun phrases */ resolveNouns(issuingActor, targetActor, results) { /* * If we're a non-prepositional phrasing, it means that we have * the VERB IOBJ DOBJ word ordering (as in GIVE BOB BOX or THROW * BOB COIN). For grammar match ranking purposes, give these * phrasings a lower match probability when the dobj phrase * doesn't have a clear qualifier. If the dobj phrase starts * with 'the' or a qualifier word like that (GIVE BOB THE BOX), * then it's pretty clear that the structural phrasing is right * after all; but if there's no qualifier, we could reading too * much into the word order. We could have something like GIVE * GREEN BOX, where we *could* treat this as two objects, but we * could just as well have a missing indirect object phrase. */ if (!isPrepositionalPhrasing) { /* * If the direct object phrase starts with 'a', 'an', 'the', * 'some', or 'any', the grammar is pretty clearly a good * match for the non-prepositional phrasing. Otherwise, it's * suspect, so rank it accordingly. */ if (rexMatch('(a|an|the|some|any)<space>', dobjMatch.getOrigText()) == nil) { /* note this as weak phrasing level 100 */ results.noteWeakPhrasing(100); } } /* inherit the base handling */ inherited(issuingActor, targetActor, results); } /* get the interrogative for one of our objects */ whatObj(which) { switch (which) { case DirectObject: /* * the direct object interrogative is the first word in * parentheses in our verbPhrase string */ rexSearch('<lparen>.*?(<alpha>+)<rparen>', verbPhrase); break; case IndirectObject: /* * the indirect object interrogative is the second * parenthesized word in our verbPhrase string */ rexSearch('<rparen>.*<lparen>.*?(<alpha>+)<rparen>', verbPhrase); break; } /* show the group match */ return whatTranslate(rexGroup(1)[3]); } /* announce a default object used with this action */ announceDefaultObject(obj, whichObj, resolvedAllObjects) { /* presume we won't have a verb or preposition */ local verb = ''; local prep = ''; /* * Check the full phrasing - if we're showing the direct object, * but an indirect object was supplied, use the verb's * participle form ("asking bob") in the default string, since * we must clarify that we're not tagging the default string on * to the command line. Don't include the participle form if we * don't know all the objects yet, since in this case we are in * fact tagging the default string onto the command so far, as * there's nothing else in the command to get in the way. */ if (whichObj == DirectObject && resolvedAllObjects) { /* * extract the verb's participle form (including any * complementizer phrase) */ rexSearch('/(<^lparen>+) <lparen>', verbPhrase); verb = rexGroup(1)[3] + ' '; } /* get the preposition to use, if any */ switch(whichObj) { case DirectObject: /* use the preposition in the first "(what)" phrase */ rexSearch('<lparen>(.*?)<space>*<alpha>+<rparen>', verbPhrase); prep = rexGroup(1)[3]; break; case IndirectObject: /* use the preposition in the second "(what)" phrase */ rexSearch('<rparen>.*<lparen>(.*?)<space>*<alpha>+<rparen>', verbPhrase); prep = rexGroup(1)[3]; break; } /* * get the object name - we need to distinguish from everything * else in scope, since we considered everything in scope when * making our pick */ local nm = obj.getAnnouncementDistinguisher( gActor.scopeList()).theName(obj); /* build and return the complete phrase */ return spSuffix(verb) + spSuffix(prep) + nm; } /* announce all defaulted objects */ announceAllDefaultObjects(allResolved) { /* announce a defaulted direct object if appropriate */ maybeAnnounceDefaultObject(dobjList_, DirectObject, allResolved); /* announce a defaulted indirect object if appropriate */ maybeAnnounceDefaultObject(iobjList_, IndirectObject, allResolved); } /* show the verb's basic infinitive form for an interrogative */ getQuestionInf(which) { local ret; local vcomp; local dprep; local iprep; local pro; /* * Our verb phrase can one of three formats, depending on which * object role we're asking about (the part in <angle brackets> * is the part we're responsible for generating). In these * formats, 'verb' is the verb infinitive; 'comp' is the * complementizer, if any (e.g., the 'up' in 'pick up'); 'dprep' * is the direct object preposition (the 'in' in 'dig in x with * y'); and 'iprep' is the indirect object preposition (the * 'with' in 'dig in x with y'). * * asking for dobj: verb vcomp dprep iprep it ('what do you want * to <open with it>?', '<dig in with it>', '<look up in it>'). * * asking for dobj, but suppressing the iobj part: verb vcomp * dprep ('what do you want to <turn>?', '<look up>?', '<dig * in>') * * asking for iobj: verb dprep it vcomp iprep ('what do you want * to <open it with>', '<dig in it with>', '<look it up in>' */ /* parse the verbPhrase into its component parts */ rexMatch('(.*)/<alphanum|-|squote>+(?:<space>+(<^lparen>*))?' + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>' + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>', verbPhrase); /* pull out the verb */ ret = rexGroup(1)[3]; /* pull out the verb complementizer */ vcomp = (rexGroup(2) == nil ? '' : rexGroup(2)[3]); /* pull out the direct and indirect object prepositions */ dprep = rexGroup(3)[3]; iprep = rexGroup(4)[3]; /* get the pronoun for the other object phrase */ pro = getOtherMessageObjectPronoun(which); /* check what we're asking about */ if (which == DirectObject) { /* add the <vcomp dprep> part in all cases */ ret += spPrefix(vcomp) + spPrefix(dprep); /* add the <iprep it> part if we want the indirect object part */ if (!omitIobjInDobjQuery && pro != nil) ret += spPrefix(iprep) + ' ' + pro; } else { /* add the <dprep it> part if appropriate */ if (pro != nil) ret += spPrefix(dprep) + ' ' + pro; /* add the <vcomp iprep> part */ ret += spPrefix(vcomp) + spPrefix(iprep); } /* return the result */ return ret; } /* * Get the pronoun for the message object in the given role. */ getOtherMessageObjectPronoun(which) { local lst; /* * Get the resolution list (or tentative resolution list) for the * *other* object, since we want to show a pronoun representing * the other object. If we don't have a fully-resolved list for * the other object, use the tentative resolution list, which * we're guaranteed to have by the time we start resolving * anything (and thus by the time we need to ask for objects). */ lst = (which == DirectObject ? iobjList_ : dobjList_); if (lst == nil || lst == []) lst = (which == DirectObject ? tentativeIobj_ : tentativeDobj_); /* if we found an object list, use the pronoun for the list */ if (lst != nil && lst != []) { /* we got a list - return a suitable pronoun for this list */ return objListPronoun(lst); } else { /* there's no object list, so there's no pronoun */ return nil; } } /* get the verb phrase in infinitive or participle form */ getVerbPhrase(inf, ctx) { local dobj, dobjText, dobjIsPronoun; local iobj, iobjText; local ret; /* use the default context if one wasn't supplied */ if (ctx == nil) ctx = defaultGetVerbPhraseContext; /* get the direct object information */ dobj = getDobj(); dobjText = ctx.objNameObj(dobj); dobjIsPronoun = ctx.isObjPronoun(dobj); /* get the indirect object information */ iobj = getIobj(); iobjText = (iobj != nil ? ctx.objNameObj(iobj) : nil); /* get the phrasing */ ret = getVerbPhrase2(inf, verbPhrase, dobjText, dobjIsPronoun, iobjText); /* * Set the antecedent for the next verb phrase. Our direct * object is normally the antecedent; however, if the indirect * object matches the current antecedent, keep the current * antecedent, so that 'it' (or whatever) remains the same for * the next verb phrase. */ if (ctx.pronounObj != iobj) ctx.setPronounObj(dobj); /* return the result */ return ret; } /* * Get the verb phrase for a two-object (dobj + iobj) phrasing. This * is a class method, so that it can be reused by unrelated (i.e., * non-TIAction) classes that also use two-object syntax but with * other internal structures. This is the two-object equivalent of * TAction.getVerbPhrase1(). */ getVerbPhrase2(inf, vp, dobjText, dobjIsPronoun, iobjText) { local ret; local vcomp; local dprep, iprep; /* parse the verbPhrase into its component parts */ rexMatch('(.*)/(<alphanum|-|squote>+)(?:<space>+(<^lparen>*))?' + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>' + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>', vp); /* start off with the infinitive or participle, as desired */ if (inf) ret = rexGroup(1)[3]; else ret = rexGroup(2)[3]; /* get the complementizer */ vcomp = (rexGroup(3) == nil ? '' : rexGroup(3)[3]); /* get the direct and indirect object prepositions */ dprep = rexGroup(4)[3]; iprep = rexGroup(5)[3]; /* * add the complementizer BEFORE the direct object, if the * direct object is being shown as a full name ("PICK UP BOX") */ if (!dobjIsPronoun) ret += spPrefix(vcomp); /* * add the direct object and its preposition, using a pronoun if * applicable */ ret += spPrefix(dprep) + ' ' + dobjText; /* * add the complementizer AFTER the direct object, if the direct * object is shown as a pronoun ("PICK IT UP") */ if (dobjIsPronoun) ret += spPrefix(vcomp); /* if we have an indirect object, add it with its preposition */ if (iobjText != nil) ret += spPrefix(iprep) + ' ' + iobjText; /* return the result phrase */ return ret; } ; /* * English-specific additions for verbs taking a literal phrase as the * sole object. */ modify LiteralAction /* provide a base verbPhrase, in case an instance leaves it out */ verbPhrase = 'verb/verbing (what)' /* get an interrogative word for an object of the action */ whatObj(which) { /* use the same processing as TAction */ return delegated TAction(which); } getVerbPhrase(inf, ctx) { /* handle this as though the literal were a direct object phrase */ return TAction.getVerbPhrase1(inf, verbPhrase, gLiteral, nil); } getQuestionInf(which) { /* use the same handling as for a regular one-object action */ return delegated TAction(which); } ; /* * English-specific additions for verbs of a direct object and a literal * phrase. */ modify LiteralTAction announceDefaultObject(obj, whichObj, resolvedAllObjects) { /* * Use the same handling as for a regular two-object action. We * can only default the actual object in this kind of verb; the * actual object always fills the DirectObject slot, but in * message generation it might use a different slot, so use the * message generation slot here. */ return delegated TIAction(obj, whichMessageObject, resolvedAllObjects); } whatObj(which) { /* use the same handling we use for a regular two-object action */ return delegated TIAction(which); } getQuestionInf(which) { /* * use the same handling as for a two-object action (but note * that we override getMessageObjectPronoun(), which will affect * the way we present the verb infinitive in some cases) */ return delegated TIAction(which); } /* * When we want to show a verb infinitive phrase that involves a * pronoun for the literal phrase, refer to the literal as 'that' * rather than 'it' or anything else. */ getOtherMessageObjectPronoun(which) { /* * If we're asking about the literal phrase, then the other * pronoun is for the resolved object: so, return the pronoun * for the direct object phrase, because we *always* store the * non-literal in the direct object slot, regardless of the * actual phrasing of the action. * * If we're asking about the resolved object (i.e., not the * literal phrase), then return 'that' as the pronoun for the * literal phrase. */ if (which == whichMessageLiteral) { /* * we're asking about the literal, so the other pronoun is * for the resolved object, which is always in the direct * object slot (so the 'other' slot is effectively the * indirect object) */ return delegated TIAction(IndirectObject); } else { /* * We're asking about the resolved object, so the other * pronoun is for the literal phrase: always use 'that' to * refer to the literal phrase. */ return 'that'; } } getVerbPhrase(inf, ctx) { local dobj, dobjText, dobjIsPronoun; local litText; local ret; /* use the default context if one wasn't supplied */ if (ctx == nil) ctx = defaultGetVerbPhraseContext; /* get the direct object information */ dobj = getDobj(); dobjText = ctx.objNameObj(dobj); dobjIsPronoun = ctx.isObjPronoun(dobj); /* get our literal text */ litText = gLiteral; /* * Use the standard two-object phrasing. The order of the * phrasing depends on whether our literal phrase is in the * direct or indirect object slot. */ if (whichMessageLiteral == DirectObject) ret = TIAction.getVerbPhrase2(inf, verbPhrase, litText, nil, dobjText); else ret = TIAction.getVerbPhrase2(inf, verbPhrase, dobjText, dobjIsPronoun, litText); /* use the direct object as the antecedent for the next phrase */ ctx.setPronounObj(dobj); /* return the result */ return ret; } ; /* * English-specific additions for verbs taking a topic phrase as the sole * object. */ modify TopicAction /* get an interrogative word for an object of the action */ whatObj(which) { /* use the same processing as TAction */ return delegated TAction(which); } getVerbPhrase(inf, ctx) { /* handle this as though the topic text were a direct object phrase */ return TAction.getVerbPhrase1( inf, verbPhrase, getTopic().getTopicText().toLower(), nil); } getQuestionInf(which) { /* use the same handling as for a regular one-object action */ return delegated TAction(which); } ; /* * English-specific additions for verbs with topic phrases. */ modify TopicTAction announceDefaultObject(obj, whichObj, resolvedAllObjects) { /* * Use the same handling as for a regular two-object action. We * can only default the actual object in this kind of verb; the * actual object always fills the DirectObject slot, but in * message generation it might use a different slot, so use the * message generation slot here. */ return delegated TIAction(obj, whichMessageObject, resolvedAllObjects); } whatObj(which) { /* use the same handling we use for a regular two-object action */ return delegated TIAction(which); } getQuestionInf(which) { /* use the same handling as for a regular two-object action */ return delegated TIAction(which); } getOtherMessageObjectPronoun(which) { /* * If we're asking about the topic, then the other pronoun is * for the resolved object, which is always in the direct object * slot. If we're asking about the resolved object, then return * a pronoun for the topic. */ if (which == whichMessageTopic) { /* * we want the pronoun for the resolved object, which is * always in the direct object slot (so the 'other' slot is * effectively the indirect object) */ return delegated TIAction(IndirectObject); } else { /* return a generic pronoun for the topic */ return 'that'; } } getVerbPhrase(inf, ctx) { local dobj, dobjText, dobjIsPronoun; local topicText; local ret; /* use the default context if one wasn't supplied */ if (ctx == nil) ctx = defaultGetVerbPhraseContext; /* get the direct object information */ dobj = getDobj(); dobjText = ctx.objNameObj(dobj); dobjIsPronoun = ctx.isObjPronoun(dobj); /* get our topic phrase */ topicText = getTopic().getTopicText().toLower(); /* * Use the standard two-object phrasing. The order of the * phrasing depends on whether our topic phrase is in the direct * or indirect object slot. */ if (whichMessageTopic == DirectObject) ret = TIAction.getVerbPhrase2(inf, verbPhrase, topicText, nil, dobjText); else ret = TIAction.getVerbPhrase2(inf, verbPhrase, dobjText, dobjIsPronoun, topicText); /* use the direct object as the antecedent for the next phrase */ ctx.setPronounObj(dobj); /* return the result */ return ret; } ; /* ------------------------------------------------------------------------ */ /* * Verbs. * * The actual body of each of our verbs is defined in the main * language-independent part of the library. We only define the * language-specific grammar rules here. */ VerbRule(Take) ('take' | 'pick' 'up' | 'get') dobjList | 'pick' dobjList 'up' : TakeAction verbPhrase = 'take/taking (what)' ; VerbRule(TakeFrom) ('take' | 'get') dobjList ('from' | 'out' 'of' | 'off' | 'off' 'of') singleIobj | 'remove' dobjList 'from' singleIobj : TakeFromAction verbPhrase = 'take/taking (what) (from what)' ; VerbRule(Remove) 'remove' dobjList : RemoveAction verbPhrase = 'remove/removing (what)' ; VerbRule(Drop) ('drop' | 'put' 'down' | 'set' 'down') dobjList | ('put' | 'set') dobjList 'down' : DropAction verbPhrase = 'drop/dropping (what)' ; VerbRule(Examine) ('examine' | 'inspect' | 'x' | 'look' 'at' | 'l' 'at' | 'look' | 'l') dobjList : ExamineAction verbPhrase = 'examine/examining (what)' ; VerbRule(Read) 'read' dobjList : ReadAction verbPhrase = 'read/reading (what)' ; VerbRule(LookIn) ('look' | 'l') ('in' | 'inside') dobjList : LookInAction verbPhrase = 'look/looking (in what)' ; VerbRule(Search) 'search' dobjList : SearchAction verbPhrase = 'search/searching (what)' ; VerbRule(LookThrough) ('look' | 'l') ('through' | 'thru' | 'out') dobjList : LookThroughAction verbPhrase = 'look/looking (through what)' ; VerbRule(LookUnder) ('look' | 'l') 'under' dobjList : LookUnderAction verbPhrase = 'look/looking (under what)' ; VerbRule(LookBehind) ('look' | 'l') 'behind' dobjList : LookBehindAction verbPhrase = 'look/looking (behind what)' ; VerbRule(Feel) ('feel' | 'touch') dobjList : FeelAction verbPhrase = 'touch/touching (what)' ; VerbRule(Taste) 'taste' dobjList : TasteAction verbPhrase = 'taste/tasting (what)' ; VerbRule(Smell) ('smell' | 'sniff') dobjList : SmellAction verbPhrase = 'smell/smelling (what)' /* * use the "not aware" version of the no-match message - the object * of SMELL is often intangible, so the default "you can't see that" * message is often incongruous for this verb */ noMatch(msgObj, actor, txt) { msgObj.noMatchNotAware(actor, txt); } ; VerbRule(SmellImplicit) 'smell' | 'sniff' : SmellImplicitAction verbPhrase = 'smell/smelling' ; VerbRule(ListenTo) ('hear' | 'listen' 'to' ) dobjList : ListenToAction verbPhrase = 'listen/listening (to what)' /* * use the "not aware" version of the no-match message - the object * of LISTEN TO is often intangible, so the default "you can't see * that" message is often incongruous for this verb */ noMatch(msgObj, actor, txt) { msgObj.noMatchNotAware(actor, txt); } ; VerbRule(ListenImplicit) 'listen' | 'hear' : ListenImplicitAction verbPhrase = 'listen/listening' ; VerbRule(PutIn) ('put' | 'place' | 'set') dobjList ('in' | 'into' | 'in' 'to' | 'inside' | 'inside' 'of') singleIobj : PutInAction verbPhrase = 'put/putting (what) (in what)' askIobjResponseProd = inSingleNoun ; VerbRule(PutOn) ('put' | 'place' | 'drop' | 'set') dobjList ('on' | 'onto' | 'on' 'to' | 'upon') singleIobj | 'put' dobjList 'down' 'on' singleIobj : PutOnAction verbPhrase = 'put/putting (what) (on what)' askIobjResponseProd = onSingleNoun ; VerbRule(PutUnder) ('put' | 'place' | 'set') dobjList 'under' singleIobj : PutUnderAction verbPhrase = 'put/putting (what) (under what)' ; VerbRule(PutBehind) ('put' | 'place' | 'set') dobjList 'behind' singleIobj : PutBehindAction verbPhrase = 'put/putting (what) (behind what)' ; VerbRule(PutInWhat) [badness 500] ('put' | 'place') dobjList : PutInAction verbPhrase = 'put/putting (what) (in what)' construct() { /* set up the empty indirect object phrase */ iobjMatch = new EmptyNounPhraseProd(); iobjMatch.responseProd = inSingleNoun; } ; VerbRule(Wear) ('wear' | 'don' | 'put' 'on') dobjList | 'put' dobjList 'on' : WearAction verbPhrase = 'wear/wearing (what)' ; VerbRule(Doff) ('doff' | 'take' 'off') dobjList | 'take' dobjList 'off' : DoffAction verbPhrase = 'take/taking off (what)' ; VerbRule(Kiss) 'kiss' singleDobj : KissAction verbPhrase = 'kiss/kissing (whom)' ; VerbRule(AskFor) ('ask' | 'a') singleDobj 'for' singleTopic | ('ask' | 'a') 'for' singleTopic 'from' singleDobj : AskForAction verbPhrase = 'ask/asking (whom) (for what)' omitIobjInDobjQuery = true askDobjResponseProd = singleNoun askIobjResponseProd = forSingleNoun ; VerbRule(AskWhomFor) ('ask' | 'a') 'for' singleTopic : AskForAction verbPhrase = 'ask/asking (whom) (for what)' omitIobjInDobjQuery = true construct() { /* set up the empty direct object phrase */ dobjMatch = new EmptyNounPhraseProd(); dobjMatch.responseProd = singleNoun; } ; VerbRule(AskAbout) ('ask' | 'a') singleDobj 'about' singleTopic : AskAboutAction verbPhrase = 'ask/asking (whom) (about what)' omitIobjInDobjQuery = true askDobjResponseProd = singleNoun ; VerbRule(AskAboutImplicit) 'a' singleTopic : AskAboutAction verbPhrase = 'ask/asking (whom) (about what)' omitIobjInDobjQuery = true construct() { /* set up the empty direct object phrase */ dobjMatch = new EmptyNounPhraseProd(); dobjMatch.responseProd = singleNoun; } ; VerbRule(AskAboutWhat) [badness 500] 'ask' singleDobj : AskAboutAction verbPhrase = 'ask/asking (whom) (about what)' askDobjResponseProd = singleNoun omitIobjInDobjQuery = true construct() { /* set up the empty topic phrase */ topicMatch = new EmptyNounPhraseProd(); topicMatch.responseProd = aboutTopicPhrase; } ; VerbRule(TellAbout) ('tell' | 't') singleDobj 'about' singleTopic : TellAboutAction verbPhrase = 'tell/telling (whom) (about what)' askDobjResponseProd = singleNoun omitIobjInDobjQuery = true ; VerbRule(TellAboutImplicit) 't' singleTopic : TellAboutAction verbPhrase = 'tell/telling (whom) (about what)' omitIobjInDobjQuery = true construct() { /* set up the empty direct object phrase */ dobjMatch = new EmptyNounPhraseProd(); dobjMatch.responseProd = singleNoun; } ; VerbRule(TellAboutWhat) [badness 500] 'tell' singleDobj : TellAboutAction verbPhrase = 'tell/telling (whom) (about what)' askDobjResponseProd = singleNoun omitIobjInDobjQuery = true construct() { /* set up the empty topic phrase */ topicMatch = new EmptyNounPhraseProd(); topicMatch.responseProd = aboutTopicPhrase; } ; VerbRule(AskVague) [badness 500] 'ask' singleDobj singleTopic : AskVagueAction verbPhrase = 'ask/asking (whom)' ; VerbRule(TellVague) [badness 500] 'tell' singleDobj singleTopic : AskVagueAction verbPhrase = 'tell/telling (whom)' ; VerbRule(TalkTo) ('greet' | 'say' 'hello' 'to' | 'talk' 'to') singleDobj : TalkToAction verbPhrase = 'talk/talking (to whom)' askDobjResponseProd = singleNoun ; VerbRule(TalkToWhat) [badness 500] 'talk' : TalkToAction verbPhrase = 'talk/talking (to whom)' askDobjResponseProd = singleNoun construct() { /* set up the empty direct object phrase */ dobjMatch = new EmptyNounPhraseProd(); dobjMatch.responseProd = onSingleNoun; } ; VerbRule(Topics) 'topics' : TopicsAction verbPhrase = 'show/showing topics' ; VerbRule(Hello) ('say' | ) ('hello' | 'hallo' | 'hi') : HelloAction verbPhrase = 'say/saying hello' ; VerbRule(Goodbye) ('say' | ()) ('goodbye' | 'good-bye' | 'good' 'bye' | 'bye') : GoodbyeAction verbPhrase = 'say/saying goodbye' ; VerbRule(Yes) 'yes' | 'affirmative' | 'say' 'yes' : YesAction verbPhrase = 'say/saying yes' ; VerbRule(No) 'no' | 'negative' | 'say' 'no' : NoAction verbPhrase = 'say/saying no' ; VerbRule(Yell) 'yell' | 'scream' | 'shout' | 'holler' : YellAction verbPhrase = 'yell/yelling' ; VerbRule(GiveTo) ('give' | 'offer') dobjList 'to' singleIobj : GiveToAction verbPhrase = 'give/giving (what) (to whom)' askIobjResponseProd = toSingleNoun ; VerbRule(GiveToType2) ('give' | 'offer') singleIobj dobjList : GiveToAction verbPhrase = 'give/giving (what) (to whom)' askIobjResponseProd = toSingleNoun /* this is a non-prepositional phrasing */ isPrepositionalPhrasing = nil ; VerbRule(GiveToWhom) ('give' | 'offer') dobjList : GiveToAction verbPhrase = 'give/giving (what) (to whom)' construct() { /* set up the empty indirect object phrase */ iobjMatch = new ImpliedActorNounPhraseProd(); iobjMatch.responseProd = toSingleNoun; } ; VerbRule(ShowTo) 'show' dobjList 'to' singleIobj : ShowToAction verbPhrase = 'show/showing (what) (to whom)' askIobjResponseProd = toSingleNoun ; VerbRule(ShowToType2) 'show' singleIobj dobjList : ShowToAction verbPhrase = 'show/showing (what) (to whom)' askIobjResponseProd = toSingleNoun /* this is a non-prepositional phrasing */ isPrepositionalPhrasing = nil ; VerbRule(ShowToWhom) 'show' dobjList : ShowToAction verbPhrase = 'show/showing (what) (to whom)' construct() { /* set up the empty indirect object phrase */ iobjMatch = new ImpliedActorNounPhraseProd(); iobjMatch.responseProd = toSingleNoun; } ; VerbRule(Throw) ('throw' | 'toss') dobjList : ThrowAction verbPhrase = 'throw/throwing (what)' ; VerbRule(ThrowAt) ('throw' | 'toss') dobjList 'at' singleIobj : ThrowAtAction verbPhrase = 'throw/throwing (what) (at what)' askIobjResponseProd = atSingleNoun ; VerbRule(ThrowTo) ('throw' | 'toss') dobjList 'to' singleIobj : ThrowToAction verbPhrase = 'throw/throwing (what) (to whom)' askIobjResponseProd = toSingleNoun ; VerbRule(ThrowToType2) 'throw' singleIobj dobjList : ThrowToAction verbPhrase = 'throw/throwing (what) (to whom)' askIobjResponseProd = toSingleNoun /* this is a non-prepositional phrasing */ isPrepositionalPhrasing = nil ; VerbRule(ThrowDir) ('throw' | 'toss') dobjList ('to' ('the' | ) | ) singleDir : ThrowDirAction verbPhrase = ('throw/throwing (what) ' + dirMatch.dir.name) ; /* a special rule for THROW DOWN <dobj> */ VerbRule(ThrowDirDown) 'throw' ('down' | 'd') dobjList : ThrowDirAction verbPhrase = ('throw/throwing (what) down') /* the direction is fixed as 'down' for this phrasing */ getDirection() { return downDirection; } ; VerbRule(Follow) 'follow' singleDobj : FollowAction verbPhrase = 'follow/following (whom)' askDobjResponseProd = singleNoun ; VerbRule(Attack) ('attack' | 'kill' | 'hit' | 'kick' | 'punch') singleDobj : AttackAction verbPhrase = 'attack/attacking (whom)' askDobjResponseProd = singleNoun ; VerbRule(AttackWith) ('attack' | 'kill' | 'hit' | 'kick' | 'punch' | 'strike') singleDobj 'with' singleIobj : AttackWithAction verbPhrase = 'attack/attacking (whom) (with what)' askDobjResponseProd = singleNoun askIobjResponseProd = withSingleNoun ; VerbRule(Inventory) 'i' | 'inventory' | 'take' 'inventory' : InventoryAction verbPhrase = 'take/taking inventory' ; VerbRule(InventoryTall) 'i' 'tall' | 'inventory' 'tall' : InventoryTallAction verbPhrase = 'take/taking "tall" inventory' ; VerbRule(InventoryWide) 'i' 'wide' | 'inventory' 'wide' : InventoryWideAction verbPhrase = 'take/taking "wide" inventory' ; VerbRule(Wait) 'z' | 'wait' : WaitAction verbPhrase = 'wait/waiting' ; VerbRule(Look) 'look' | 'look' 'around' | 'l' | 'l' 'around' : LookAction verbPhrase = 'look/looking around' ; VerbRule(Quit) 'quit' | 'q' : QuitAction verbPhrase = 'quit/quitting' ; VerbRule(Again) 'again' | 'g' : AgainAction verbPhrase = 'repeat/repeating the last command' ; VerbRule(Footnote) ('footnote' | 'note') singleNumber : FootnoteAction verbPhrase = 'show/showing a footnote' ; VerbRule(FootnotesFull) 'footnotes' 'full' : FootnotesFullAction verbPhrase = 'enable/enabling all footnotes' ; VerbRule(FootnotesMedium) 'footnotes' 'medium' : FootnotesMediumAction verbPhrase = 'enable/enabling new footnotes' ; VerbRule(FootnotesOff) 'footnotes' 'off' : FootnotesOffAction verbPhrase = 'hide/hiding footnotes' ; VerbRule(FootnotesStatus) 'footnotes' : FootnotesStatusAction verbPhrase = 'show/showing footnote status' ; VerbRule(TipsOn) ('tips' | 'tip') 'on' : TipModeAction stat_ = true verbPhrase = 'turn/turning tips on' ; VerbRule(TipsOff) ('tips' | 'tip') 'off' : TipModeAction stat_ = nil verbPhrase = 'turn/turning tips off' ; VerbRule(Verbose) 'verbose' : VerboseAction verbPhrase = 'enter/entering VERBOSE mode' ; VerbRule(Terse) 'terse' | 'brief' : TerseAction verbPhrase = 'enter/entering BRIEF mode' ; VerbRule(Score) 'score' | 'status' : ScoreAction verbPhrase = 'show/showing score' ; VerbRule(FullScore) 'full' 'score' | 'fullscore' | 'full' : FullScoreAction verbPhrase = 'show/showing full score' ; VerbRule(Notify) 'notify' : NotifyAction verbPhrase = 'show/showing notification status' ; VerbRule(NotifyOn) 'notify' 'on' : NotifyOnAction verbPhrase = 'turn/turning on score notification' ; VerbRule(NotifyOff) 'notify' 'off' : NotifyOffAction verbPhrase = 'turn/turning off score notification' ; VerbRule(Save) 'save' : SaveAction verbPhrase = 'save/saving' ; VerbRule(SaveString) 'save' quotedStringPhrase->fname_ : SaveStringAction verbPhrase = 'save/saving' ; VerbRule(Restore) 'restore' : RestoreAction verbPhrase = 'restore/restoring' ; VerbRule(RestoreString) 'restore' quotedStringPhrase->fname_ : RestoreStringAction verbPhrase = 'restore/restoring' ; VerbRule(SaveDefaults) 'save' 'defaults' : SaveDefaultsAction verbPhrase = 'save/saving defaults' ; VerbRule(RestoreDefaults) 'restore' 'defaults' : RestoreDefaultsAction verbPhrase = 'restore/restoring defaults' ; VerbRule(Restart) 'restart' : RestartAction verbPhrase = 'restart/restarting' ; VerbRule(Pause) 'pause' : PauseAction verbPhrase = 'pause/pausing' ; VerbRule(Undo) 'undo' : UndoAction verbPhrase = 'undo/undoing' ; VerbRule(Version) 'version' : VersionAction verbPhrase = 'show/showing version' ; VerbRule(Credits) 'credits' : CreditsAction verbPhrase = 'show/showing credits' ; VerbRule(About) 'about' : AboutAction verbPhrase = 'show/showing story information' ; VerbRule(Script) 'script' | 'script' 'on' : ScriptAction verbPhrase = 'start/starting scripting' ; VerbRule(ScriptString) 'script' quotedStringPhrase->fname_ : ScriptStringAction verbPhrase = 'start/starting scripting' ; VerbRule(ScriptOff) 'script' 'off' | 'unscript' : ScriptOffAction verbPhrase = 'end/ending scripting' ; VerbRule(Record) 'record' | 'record' 'on' : RecordAction verbPhrase = 'start/starting command recording' ; VerbRule(RecordString) 'record' quotedStringPhrase->fname_ : RecordStringAction verbPhrase = 'start/starting command recording' ; VerbRule(RecordEvents) 'record' 'events' | 'record' 'events' 'on' : RecordEventsAction verbPhrase = 'start/starting event recording' ; VerbRule(RecordEventsString) 'record' 'events' quotedStringPhrase->fname_ : RecordEventsStringAction verbPhrase = 'start/starting command recording' ; VerbRule(RecordOff) 'record' 'off' : RecordOffAction verbPhrase = 'end/ending command recording' ; VerbRule(ReplayString) 'replay' ('quiet'->quiet_ | 'nonstop'->nonstop_ | ) (quotedStringPhrase->fname_ | ) : ReplayStringAction verbPhrase = 'replay/replaying command recording' /* set the appropriate option flags */ scriptOptionFlags = ((quiet_ != nil ? ScriptFileQuiet : 0) | (nonstop_ != nil ? ScriptFileNonstop : 0)) ; VerbRule(ReplayQuiet) 'rq' (quotedStringPhrase->fname_ | ) : ReplayStringAction scriptOptionFlags = ScriptFileQuiet ; VerbRule(VagueTravel) 'go' | 'walk' : VagueTravelAction verbPhrase = 'go/going' ; VerbRule(Travel) 'go' singleDir | singleDir : TravelAction verbPhrase = ('go/going ' + dirMatch.dir.name) ; /* * Create a TravelVia subclass merely so we can supply a verbPhrase. * (The parser looks for subclasses of each specific Action class to find * its verb phrase, since the language-specific Action definitions are * always in the language module's 'grammar' subclasses. We don't need * an actual grammar rule, since this isn't an input-able verb, so we * merely need to create a regular subclass in order for the verbPhrase * to get found.) */ class EnTravelVia: TravelViaAction verbPhrase = 'use/using (what)' ; VerbRule(Port) 'go' 'to' ('port' | 'p') : PortAction dirMatch: DirectionProd { dir = portDirection } verbPhrase = 'go/going to port' ; VerbRule(Starboard) 'go' 'to' ('starboard' | 'sb') : StarboardAction dirMatch: DirectionProd { dir = starboardDirection } verbPhrase = 'go/going to starboard' ; VerbRule(In) 'enter' : InAction dirMatch: DirectionProd { dir = inDirection } verbPhrase = 'enter/entering' ; VerbRule(Out) 'exit' | 'leave' : OutAction dirMatch: DirectionProd { dir = outDirection } verbPhrase = 'exit/exiting' ; VerbRule(GoThrough) ('walk' | 'go' ) ('through' | 'thru') singleDobj : GoThroughAction verbPhrase = 'go/going (through what)' askDobjResponseProd = singleNoun ; VerbRule(Enter) ('enter' | 'in' | 'into' | 'in' 'to' | ('walk' | 'go') ('to' | 'in' | 'in' 'to' | 'into')) singleDobj : EnterAction verbPhrase = 'enter/entering (what)' askDobjResponseProd = singleNoun ; VerbRule(GoBack) 'back' | 'go' 'back' | 'return' : GoBackAction verbPhrase = 'go/going back' ; VerbRule(Dig) ('dig' | 'dig' 'in') singleDobj : DigAction verbPhrase = 'dig/digging (in what)' askDobjResponseProd = inSingleNoun ; VerbRule(DigWith) ('dig' | 'dig' 'in') singleDobj 'with' singleIobj : DigWithAction verbPhrase = 'dig/digging (in what) (with what)' omitIobjInDobjQuery = true askDobjResponseProd = inSingleNoun askIobjResponseProd = withSingleNoun ; VerbRule(Jump) 'jump' : JumpAction verbPhrase = 'jump/jumping' ; VerbRule(JumpOffI) 'jump' 'off' : JumpOffIAction verbPhrase = 'jump/jumping off' ; VerbRule(JumpOff) 'jump' 'off' singleDobj : JumpOffAction verbPhrase = 'jump/jumping (off what)' askDobjResponseProd = singleNoun ; VerbRule(JumpOver) ('jump' | 'jump' 'over') singleDobj : JumpOverAction verbPhrase = 'jump/jumping (over what)' askDobjResponseProd = singleNoun ; VerbRule(Push) ('push' | 'press') dobjList : PushAction verbPhrase = 'push/pushing (what)' ; VerbRule(Pull) 'pull' dobjList : PullAction verbPhrase = 'pull/pulling (what)' ; VerbRule(Move) 'move' dobjList : MoveAction verbPhrase = 'move/moving (what)' ; VerbRule(MoveTo) ('push' | 'move') dobjList ('to' | 'under') singleIobj : MoveToAction verbPhrase = 'move/moving (what) (to what)' askIobjResponseProd = toSingleNoun omitIobjInDobjQuery = true ; VerbRule(MoveWith) 'move' singleDobj 'with' singleIobj : MoveWithAction verbPhrase = 'move/moving (what) (with what)' askDobjResponseProd = singleNoun askIobjResponseProd = withSingleNoun omitIobjInDobjQuery = true ; VerbRule(Turn) ('turn' | 'twist' | 'rotate') dobjList : TurnAction verbPhrase = 'turn/turning (what)' ; VerbRule(TurnWith) ('turn' | 'twist' | 'rotate') singleDobj 'with' singleIobj : TurnWithAction verbPhrase = 'turn/turning (what) (with what)' askDobjResponseProd = singleNoun askIobjResponseProd = withSingleNoun ; VerbRule(TurnTo) ('turn' | 'twist' | 'rotate') singleDobj 'to' singleLiteral : TurnToAction verbPhrase = 'turn/turning (what) (to what)' askDobjResponseProd = singleNoun omitIobjInDobjQuery = true ; VerbRule(Set) 'set' dobjList : SetAction verbPhrase = 'set/setting (what)' ; VerbRule(SetTo) 'set' singleDobj 'to' singleLiteral : SetToAction verbPhrase = 'set/setting (what) (to what)' askDobjResponseProd = singleNoun omitIobjInDobjQuery = true ; VerbRule(TypeOn) 'type' 'on' singleDobj : TypeOnAction verbPhrase = 'type/typing (on what)' ; VerbRule(TypeLiteralOn) 'type' singleLiteral 'on' singleDobj : TypeLiteralOnAction verbPhrase = 'type/typing (what) (on what)' askDobjResponseProd = singleNoun ; VerbRule(TypeLiteralOnWhat) [badness 500] 'type' singleLiteral : TypeLiteralOnAction verbPhrase = 'type/typing (what) (on what)' construct() { /* set up the empty direct object phrase */ dobjMatch = new EmptyNounPhraseProd(); dobjMatch.responseProd = onSingleNoun; } ; VerbRule(EnterOn) 'enter' singleLiteral ('on' | 'in' | 'in' 'to' | 'into' | 'with') singleDobj : EnterOnAction verbPhrase = 'enter/entering (what) (on what)' askDobjResponseProd = singleNoun ; VerbRule(EnterOnWhat) 'enter' singleLiteral : EnterOnAction verbPhrase = 'enter/entering (what) (on what)' construct() { /* * ENTER <text> is a little special, because it could mean ENTER * <text> ON <keypad>, or it could mean GO INTO <object>. It's * hard to tell which based on the grammar alone, so we have to * do some semantic analysis to make a good decision about it. * * We'll start by assuming it's the ENTER <text> ON <iobj> form * of the command, and we'll look for a suitable default object * to serve as the iobj. If we can't find a suitable default, we * won't prompt for the missing object as we usually would. * Instead, we'll try re-parsing the command as GO INTO. To do * this, use our custom "asker" - this won't actually prompt for * the missing object, but will instead retry the command as a GO * INTO command. */ dobjMatch = new EmptyNounPhraseProd(); dobjMatch.setPrompt(onSingleNoun, enterOnWhatAsker); } ; /* our custom "asker" for the missing iobj in an "ENTER <text>" command */ enterOnWhatAsker: ResolveAsker askMissingObject(targetActor, action, which) { /* * This method is called when the resolver has failed to find a * suitable default for the missing indirect object of ENTER * <text> ON <iobj>. * * Instead of issuing the prompt that we'd normally issue under * these circumstances, assume that we're totally wrong about the * way we've been interpreting the command: assume that it's not * meant as ENTER <text> ON <iobj> after all, but was actually * meant as GO IN <object>. So, rephrase the command as such and * start over with the new phrasing. */ throw new ReplacementCommandStringException( 'get in ' + action.getLiteral(), gIssuingActor, gActor); } ; VerbRule(Consult) 'consult' singleDobj : ConsultAction verbPhrase = 'consult/consulting (what)' askDobjResponseProd = singleNoun ; VerbRule(ConsultAbout) 'consult' singleDobj ('on' | 'about') singleTopic | 'search' singleDobj 'for' singleTopic | (('look' | 'l') ('up' | 'for') | 'find' | 'search' 'for' | 'read' 'about') singleTopic 'in' singleDobj | ('look' | 'l') singleTopic 'up' 'in' singleDobj : ConsultAboutAction verbPhrase = 'consult/consulting (what) (about what)' omitIobjInDobjQuery = true askDobjResponseProd = singleNoun ; VerbRule(ConsultWhatAbout) (('look' | 'l') ('up' | 'for') | 'find' | 'search' 'for' | 'read' 'about') singleTopic | ('look' | 'l') singleTopic 'up' : ConsultAboutAction verbPhrase = 'look/looking up (what) (in what)' whichMessageTopic = DirectObject construct() { /* set up the empty direct object phrase */ dobjMatch = new EmptyNounPhraseProd(); dobjMatch.responseProd = inSingleNoun; } ; VerbRule(Switch) 'switch' dobjList : SwitchAction verbPhrase = 'switch/switching (what)' ; VerbRule(Flip) 'flip' dobjList : FlipAction verbPhrase = 'flip/flipping (what)' ; VerbRule(TurnOn) ('activate' | ('turn' | 'switch') 'on') dobjList | ('turn' | 'switch') dobjList 'on' : TurnOnAction verbPhrase = 'turn/turning on (what)' ; VerbRule(TurnOff) ('deactivate' | ('turn' | 'switch') 'off') dobjList | ('turn' | 'switch') dobjList 'off' : TurnOffAction verbPhrase = 'turn/turning off (what)' ; VerbRule(Light) 'light' dobjList : LightAction verbPhrase = 'light/lighting (what)' ; DefineTAction(Strike); VerbRule(Strike) 'strike' dobjList : StrikeAction verbPhrase = 'strike/striking (what)' ; VerbRule(Burn) ('burn' | 'ignite' | 'set' 'fire' 'to') dobjList : BurnAction verbPhrase = 'light/lighting (what)' ; VerbRule(BurnWith) ('light' | 'burn' | 'ignite' | 'set' 'fire' 'to') singleDobj 'with' singleIobj : BurnWithAction verbPhrase = 'light/lighting (what) (with what)' omitIobjInDobjQuery = true askDobjResponseProd = singleNoun askIobjResponseProd = withSingleNoun ; VerbRule(Extinguish) ('extinguish' | 'douse' | 'put' 'out' | 'blow' 'out') dobjList | ('blow' | 'put') dobjList 'out' : ExtinguishAction verbPhrase = 'extinguish/extinguishing (what)' ; VerbRule(Break) ('break' | 'ruin' | 'destroy' | 'wreck') dobjList : BreakAction verbPhrase = 'break/breaking (what)' ; VerbRule(CutWithWhat) [badness 500] 'cut' singleDobj : CutWithAction verbPhrase = 'cut/cutting (what) (with what)' construct() { /* set up the empty indirect object phrase */ iobjMatch = new EmptyNounPhraseProd(); iobjMatch.responseProd = withSingleNoun; } ; VerbRule(CutWith) 'cut' singleDobj 'with' singleIobj : CutWithAction verbPhrase = 'cut/cutting (what) (with what)' askDobjResponseProd = singleNoun askIobjResponseProd = withSingleNoun ; VerbRule(Eat) ('eat' | 'consume') dobjList : EatAction verbPhrase = 'eat/eating (what)' ; VerbRule(Drink) ('drink' | 'quaff' | 'imbibe') dobjList : DrinkAction verbPhrase = 'drink/drinking (what)' ; VerbRule(Pour) 'pour' dobjList : PourAction verbPhrase = 'pour/pouring (what)' ; VerbRule(PourInto) 'pour' dobjList ('in' | 'into' | 'in' 'to') singleIobj : PourIntoAction verbPhrase = 'pour/pouring (what) (into what)' askIobjResponseProd = inSingleNoun ; VerbRule(PourOnto) 'pour' dobjList ('on' | 'onto' | 'on' 'to') singleIobj : PourOntoAction verbPhrase = 'pour/pouring (what) (onto what)' askIobjResponseProd = onSingleNoun ; VerbRule(Climb) 'climb' singleDobj : ClimbAction verbPhrase = 'climb/climbing (what)' askDobjResponseProd = singleNoun ; VerbRule(ClimbUp) ('climb' | 'go' | 'walk') 'up' singleDobj : ClimbUpAction verbPhrase = 'climb/climbing (up what)' askDobjResponseProd = singleNoun ; VerbRule(ClimbUpWhat) [badness 200] ('climb' | 'go' | 'walk') 'up' : ClimbUpAction verbPhrase = 'climb/climbing (up what)' askDobjResponseProd = singleNoun construct() { dobjMatch = new EmptyNounPhraseProd(); dobjMatch.responseProd = onSingleNoun; } ; VerbRule(ClimbDown) ('climb' | 'go' | 'walk') 'down' singleDobj : ClimbDownAction verbPhrase = 'climb/climbing (down what)' askDobjResponseProd = singleNoun ; VerbRule(ClimbDownWhat) [badness 200] ('climb' | 'go' | 'walk') 'down' : ClimbDownAction verbPhrase = 'climb/climbing (down what)' askDobjResponseProd = singleNoun construct() { dobjMatch = new EmptyNounPhraseProd(); dobjMatch.responseProd = onSingleNoun; } ; VerbRule(Clean) 'clean' dobjList : CleanAction verbPhrase = 'clean/cleaning (what)' ; VerbRule(CleanWith) 'clean' dobjList 'with' singleIobj : CleanWithAction verbPhrase = 'clean/cleaning (what) (with what)' askIobjResponseProd = withSingleNoun omitIobjInDobjQuery = true ; VerbRule(AttachTo) ('attach' | 'connect') dobjList 'to' singleIobj : AttachToAction askIobjResponseProd = toSingleNoun verbPhrase = 'attach/attaching (what) (to what)' ; VerbRule(AttachToWhat) [badness 500] ('attach' | 'connect') dobjList : AttachToAction verbPhrase = 'attach/attaching (what) (to what)' construct() { /* set up the empty indirect object phrase */ iobjMatch = new EmptyNounPhraseProd(); iobjMatch.responseProd = toSingleNoun; } ; VerbRule(DetachFrom) ('detach' | 'disconnect') dobjList 'from' singleIobj : DetachFromAction verbPhrase = 'detach/detaching (what) (from what)' askIobjResponseProd = fromSingleNoun ; VerbRule(Detach) ('detach' | 'disconnect') dobjList : DetachAction verbPhrase = 'detach/detaching (what)' ; VerbRule(Open) 'open' dobjList : OpenAction verbPhrase = 'open/opening (what)' ; VerbRule(Close) ('close' | 'shut') dobjList : CloseAction verbPhrase = 'close/closing (what)' ; VerbRule(Lock) 'lock' dobjList : LockAction verbPhrase = 'lock/locking (what)' ; VerbRule(Unlock) 'unlock' dobjList : UnlockAction verbPhrase = 'unlock/unlocking (what)' ; VerbRule(LockWith) 'lock' singleDobj 'with' singleIobj : LockWithAction verbPhrase = 'lock/locking (what) (with what)' omitIobjInDobjQuery = true askDobjResponseProd = singleNoun askIobjResponseProd = withSingleNoun ; VerbRule(UnlockWith) 'unlock' singleDobj 'with' singleIobj : UnlockWithAction verbPhrase = 'unlock/unlocking (what) (with what)' omitIobjInDobjQuery = true askDobjResponseProd = singleNoun askIobjResponseProd = withSingleNoun ; VerbRule(SitOn) 'sit' ('on' | 'in' | 'down' 'on' | 'down' 'in') singleDobj : SitOnAction verbPhrase = 'sit/sitting (on what)' askDobjResponseProd = singleNoun /* use the actorInPrep, if there's a direct object available */ adjustDefaultObjectPrep(prep, obj) { return (obj != nil ? obj.actorInPrep + ' ' : prep); } ; VerbRule(Sit) 'sit' ( | 'down') : SitAction verbPhrase = 'sit/sitting down' ; VerbRule(LieOn) 'lie' ('on' | 'in' | 'down' 'on' | 'down' 'in') singleDobj : LieOnAction verbPhrase = 'lie/lying (on what)' askDobjResponseProd = singleNoun /* use the actorInPrep, if there's a direct object available */ adjustDefaultObjectPrep(prep, obj) { return (obj != nil ? obj.actorInPrep + ' ' : prep); } ; VerbRule(Lie) 'lie' ( | 'down') : LieAction verbPhrase = 'lie/lying down' ; VerbRule(StandOn) ('stand' ('on' | 'in' | 'onto' | 'on' 'to' | 'into' | 'in' 'to') | 'climb' ('on' | 'onto' | 'on' 'to')) singleDobj : StandOnAction verbPhrase = 'stand/standing (on what)' askDobjResponseProd = singleNoun /* use the actorInPrep, if there's a direct object available */ adjustDefaultObjectPrep(prep, obj) { return (obj != nil ? obj.actorInPrep + ' ' : prep); } ; VerbRule(Stand) 'stand' | 'stand' 'up' | 'get' 'up' : StandAction verbPhrase = 'stand/standing up' ; VerbRule(GetOutOf) ('out' 'of' | 'get' 'out' 'of' | 'climb' 'out' 'of' | 'leave' | 'exit') singleDobj : GetOutOfAction verbPhrase = 'get/getting (out of what)' askDobjResponseProd = singleNoun /* use the actorOutOfPrep, if there's a direct object available */ adjustDefaultObjectPrep(prep, obj) { return (obj != nil ? obj.actorOutOfPrep + ' ' : prep); } ; VerbRule(GetOffOf) 'get' ('off' | 'off' 'of' | 'down' 'from') singleDobj : GetOffOfAction verbPhrase = 'get/getting (off of what)' askDobjResponseProd = singleNoun /* use the actorOutOfPrep, if there's a direct object available */ adjustDefaultObjectPrep(prep, obj) { return (obj != nil ? obj.actorOutOfPrep + ' ' : prep); } ; VerbRule(GetOut) 'get' 'out' | 'get' 'off' | 'get' 'down' | 'disembark' | 'climb' 'out' : GetOutAction verbPhrase = 'get/getting out' ; VerbRule(Board) ('board' | ('get' ('in' | 'into' | 'in' 'to' | 'on' | 'onto' | 'on' 'to')) | ('climb' ('in' | 'into' | 'in' 'to'))) singleDobj : BoardAction verbPhrase = 'get/getting (in what)' askDobjResponseProd = singleNoun ; VerbRule(Sleep) 'sleep' : SleepAction verbPhrase = 'sleep/sleeping' ; VerbRule(Fasten) ('fasten' | 'buckle' | 'buckle' 'up') dobjList : FastenAction verbPhrase = 'fasten/fastening (what)' ; VerbRule(FastenTo) ('fasten' | 'buckle') dobjList 'to' singleIobj : FastenToAction verbPhrase = 'fasten/fastening (what) (to what)' askIobjResponseProd = toSingleNoun ; VerbRule(Unfasten) ('unfasten' | 'unbuckle') dobjList : UnfastenAction verbPhrase = 'unfasten/unfastening (what)' ; VerbRule(UnfastenFrom) ('unfasten' | 'unbuckle') dobjList 'from' singleIobj : UnfastenFromAction verbPhrase = 'unfasten/unfastening (what) (from what)' askIobjResponseProd = fromSingleNoun ; VerbRule(PlugInto) 'plug' dobjList ('in' | 'into' | 'in' 'to') singleIobj : PlugIntoAction verbPhrase = 'plug/plugging (what) (into what)' askIobjResponseProd = inSingleNoun ; VerbRule(PlugIntoWhat) [badness 500] 'plug' dobjList : PlugIntoAction verbPhrase = 'plug/plugging (what) (into what)' construct() { /* set up the empty indirect object phrase */ iobjMatch = new EmptyNounPhraseProd(); iobjMatch.responseProd = inSingleNoun; } ; VerbRule(PlugIn) 'plug' dobjList 'in' | 'plug' 'in' dobjList : PlugInAction verbPhrase = 'plug/plugging (what)' ; VerbRule(UnplugFrom) 'unplug' dobjList 'from' singleIobj : UnplugFromAction verbPhrase = 'unplug/unplugging (what) (from what)' askIobjResponseProd = fromSingleNoun ; VerbRule(Unplug) 'unplug' dobjList : UnplugAction verbPhrase = 'unplug/unplugging (what)' ; VerbRule(Screw) 'screw' dobjList : ScrewAction verbPhrase = 'screw/screwing (what)' ; VerbRule(ScrewWith) 'screw' dobjList 'with' singleIobj : ScrewWithAction verbPhrase = 'screw/screwing (what) (with what)' omitIobjInDobjQuery = true askIobjResponseProd = withSingleNoun ; VerbRule(Unscrew) 'unscrew' dobjList : UnscrewAction verbPhrase = 'unscrew/unscrewing (what)' ; VerbRule(UnscrewWith) 'unscrew' dobjList 'with' singleIobj : UnscrewWithAction verbPhrase = 'unscrew/unscrewing (what) (with what)' omitIobjInDobjQuery = true askIobjResponseProd = withSingleNoun ; VerbRule(PushTravelDir) ('push' | 'pull' | 'drag' | 'move') singleDobj singleDir : PushTravelDirAction verbPhrase = ('push/pushing (what) ' + dirMatch.dir.name) ; VerbRule(PushTravelThrough) ('push' | 'pull' | 'drag' | 'move') singleDobj ('through' | 'thru') singleIobj : PushTravelThroughAction verbPhrase = 'push/pushing (what) (through what)' ; VerbRule(PushTravelEnter) ('push' | 'pull' | 'drag' | 'move') singleDobj ('in' | 'into' | 'in' 'to') singleIobj : PushTravelEnterAction verbPhrase = 'push/pushing (what) (into what)' ; VerbRule(PushTravelGetOutOf) ('push' | 'pull' | 'drag' | 'move') singleDobj 'out' ('of' | ) singleIobj : PushTravelGetOutOfAction verbPhrase = 'push/pushing (what) (out of what)' ; VerbRule(PushTravelClimbUp) ('push' | 'pull' | 'drag' | 'move') singleDobj 'up' singleIobj : PushTravelClimbUpAction verbPhrase = 'push/pushing (what) (up what)' omitIobjInDobjQuery = true ; VerbRule(PushTravelClimbDown) ('push' | 'pull' | 'drag' | 'move') singleDobj 'down' singleIobj : PushTravelClimbDownAction verbPhrase = 'push/pushing (what) (down what)' ; VerbRule(Exits) 'exits' : ExitsAction verbPhrase = 'exits/showing exits' ; VerbRule(ExitsMode) 'exits' ('on'->on_ | 'all'->on_ | 'off'->off_ | 'none'->off_ | ('status' ('line' | ) | 'statusline') 'look'->on_ | 'look'->on_ ('status' ('line' | ) | 'statusline') | 'status'->stat_ ('line' | ) | 'statusline'->stat_ | 'look'->look_) : ExitsModeAction verbPhrase = 'turn/turning off exits display' ; VerbRule(HintsOff) 'hints' 'off' : HintsOffAction verbPhrase = 'disable/disabling hints' ; VerbRule(Hint) 'hint' | 'hints' : HintAction verbPhrase = 'show/showing hints' ; VerbRule(Oops) ('oops' | 'o') singleLiteral : OopsAction verbPhrase = 'oops/correcting (what)' ; VerbRule(OopsOnly) ('oops' | 'o') : OopsIAction verbPhrase = 'oops/correcting' ; /* ------------------------------------------------------------------------ */ /* * "debug" verb - special verb to break into the debugger. We'll only * compile this into the game if we're compiling a debug version to begin * with, since a non-debug version can't be run under the debugger. */ #ifdef __DEBUG VerbRule(Debug) 'debug' : DebugAction verbPhrase = 'debug/debugging' ; #endif /* __DEBUG */
TADS 3 Library Manual
Generated on 5/16/2013 from TADS version 3.1.3
Generated on 5/16/2013 from TADS version 3.1.3