cfad47cfa3/t3compiler/tads3/lib/adv3/en_us/en_us.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/*
4
 *   Copyright 2000, 2006 Michael J. Roberts.  All Rights Reserved.
5
 *.  Past-tense extensions written by Michel Nizette, and incorporated by
6
 *   permission.
7
 *   
8
 *   TADS 3 Library - English (United States variant) implementation
9
 *   
10
 *   This defines the parts of the TADS 3 library that are specific to the
11
 *   English language as spoken (and written) in the United States.
12
 *   
13
 *   We have attempted to isolate here the parts of the library that are
14
 *   language-specific, so that translations to other languages or dialects
15
 *   can be created by replacing this module, without changing the rest of
16
 *   the library.
17
 *   
18
 *   In addition to this module, a separate set of US English messages are
19
 *   defined in the various msg_xxx.t modules.  Those modules define
20
 *   messages in English for different stylistic variations.  For a given
21
 *   game, the author must select one of the message modules - but only
22
 *   one, since they all define variations of the same messages.  To
23
 *   translate the library, a translator must create at least one module
24
 *   defining those messages as well; only one message module is required
25
 *   per language.
26
 *   
27
 *   The past-tense system was contributed by Michel Nizette.
28
 *   
29
 *.                                  -----
30
 *   
31
 *   "Watch an immigrant struggling with a second language or a stroke
32
 *   patient with a first one, or deconstruct a snatch of baby talk, or try
33
 *   to program a computer to understand English, and ordinary speech
34
 *   begins to look different."
35
 *   
36
 *.         Stephen Pinker, "The Language Instinct"
37
 */
38
39
#include "tads.h"
40
#include "tok.h"
41
#include "adv3.h"
42
#include "en_us.h"
43
#include <vector.h>
44
#include <dict.h>
45
#include <gramprod.h>
46
#include <strcomp.h>
47
48
49
/* ------------------------------------------------------------------------ */
50
/*
51
 *   Fill in the default language for the GameInfo metadata class.
52
 */
53
modify GameInfoModuleID
54
    languageCode = 'en-US'
55
;
56
57
/* ------------------------------------------------------------------------ */
58
/*
59
 *   Simple yes/no confirmation.  The caller must display a prompt; we'll
60
 *   read a command line response, then return true if it's an affirmative
61
 *   response, nil if not.
62
 */
63
yesOrNo()
64
{
65
    /* switch to no-command mode for the interactive input */
66
    "<.commandnone>";
67
68
    /*
69
     *   Read a line of input.  Do not allow real-time event processing;
70
     *   this type of prompt is used in the middle of a command, so we
71
     *   don't want any interruptions.  Note that the caller must display
72
     *   any desired prompt, and since we don't allow interruptions, we
73
     *   won't need to redisplay the prompt, so we pass nil for the prompt
74
     *   callback.
75
     */
76
    local str = inputManager.getInputLine(nil, nil);
77
78
    /* switch back to mid-command mode */
79
    "<.commandmid>";
80
81
    /*
82
     *   If they answered with something starting with 'Y', it's
83
     *   affirmative, otherwise it's negative.  In reading the response,
84
     *   ignore any leading whitespace.
85
     */
86
    return rexMatch('<space>*[yY]', str) != nil;
87
}
88
89
/* ------------------------------------------------------------------------ */
90
/*
91
 *   During start-up, install a case-insensitive truncating comparator in
92
 *   the main dictionary.
93
 */
94
PreinitObject
95
    execute()
96
    {
97
        /* set up the main dictionary's comparator */
98
        languageGlobals.setStringComparator(
99
            new StringComparator(gameMain.parserTruncLength, nil, []));
100
    }
101
102
    /*
103
     *   Make sure we run BEFORE the main library preinitializer, so that
104
     *   we install the comparator in the dictionary before we add the
105
     *   vocabulary words to the dictionary.  This doesn't make any
106
     *   difference in terms of the correctness of the dictionary, since
107
     *   the dictionary will automatically rebuild itself whenever we
108
     *   install a new comparator, but it makes the preinitialization run
109
     *   a tiny bit faster by avoiding that rebuild step.
110
     */
111
    execAfterMe = [adv3LibPreinit]
112
;
113
114
/* ------------------------------------------------------------------------ */
115
/*
116
 *   Language-specific globals
117
 */
118
languageGlobals: object
119
    /*
120
     *   Set the StringComparator object for the parser.  This sets the
121
     *   comparator that's used in the main command parser dictionary. 
122
     */
123
    setStringComparator(sc)
124
    {
125
        /* remember it globally, and set it in the main dictionary */
126
        dictComparator = sc;
127
        cmdDict.setComparator(sc);
128
    }
129
130
    /*
131
     *   The character to use to separate groups of digits in large
132
     *   numbers.  US English uses commas; most Europeans use periods.
133
     *
134
     *   Note that this setting does not affect system-level BigNumber
135
     *   formatting, but this information can be passed when calling
136
     *   BigNumber formatting routines.
137
     */
138
    digitGroupSeparator = ','
139
140
    /*
141
     *   The decimal point to display in floating-point numbers.  US
142
     *   English uses a period; most Europeans use a comma.
143
     *
144
     *   Note that this setting doesn't affect system-level BigNumber
145
     *   formatting, but this information can be passed when calling
146
     *   BigNumber formatting routines.
147
     */
148
    decimalPointCharacter = '.'
149
150
    /* the main dictionary's string comparator */
151
    dictComparator = nil
152
;
153
154
155
/* ------------------------------------------------------------------------ */
156
/*
157
 *   Language-specific extension of the default gameMain object
158
 *   implementation.
159
 */
160
modify GameMainDef
161
    /*
162
     *   Option setting: the parser's truncation length for player input.
163
     *   As a convenience to the player, we can allow the player to
164
     *   truncate long words, entering only the first, say, 6 characters.
165
     *   For example, rather than typing "x flashlight", we could allow the
166
     *   player to simply type "x flashl" - truncating "flashlight" to six
167
     *   letters.
168
     *   
169
     *   We use a default truncation length of 6, but games can change this
170
     *   by overriding this property in gameMain.  We use a default of 6
171
     *   mostly because that's what the old Infocom games did - many
172
     *   long-time IF players are accustomed to six-letter truncation from
173
     *   those games.  Shorter lengths are superficially more convenient
174
     *   for the player, obviously, but there's a trade-off, which is that
175
     *   shorter truncation lengths create more potential for ambiguity.
176
     *   For some games, a longer length might actually be better for the
177
     *   player, because it would reduce spurious ambiguity due to the
178
     *   parser matching short input against long vocabulary words.
179
     *   
180
     *   If you don't want to allow the player to truncate long words at
181
     *   all, set this to nil.  This will require the player to type every
182
     *   word in its entirety.
183
     *   
184
     *   Note that changing this property dynamicaly will have no effect.
185
     *   The library only looks at it once, during library initialization
186
     *   at the very start of the game.  If you want to change the
187
     *   truncation length dynamically, you must instead create a new
188
     *   StringComparator object with the new truncation setting, and call
189
     *   languageGlobals.setStringComparator() to select the new object.  
190
     */
191
    parserTruncLength = 6
192
193
    /*
194
     *   Option: are we currently using a past tense narrative?  By
195
     *   default, we aren't.
196
     *
197
     *   This property can be reset at any time during the game in order to
198
     *   switch between the past and present tenses.  The macro
199
     *   setPastTense can be used for this purpose: it just provides a
200
     *   shorthand for setting gameMain.usePastTense directly.
201
     *
202
     *   Authors who want their game to start in the past tense can achieve
203
     *   this by overriding this property on their gameMain object and
204
     *   giving it a value of true.
205
     */
206
    usePastTense = nil
207
;
208
209
210
/* ------------------------------------------------------------------------ */
211
/*
212
 *   Language-specific modifications for ThingState.
213
 */
214
modify ThingState
215
    /*
216
     *   Our state-specific tokens.  This is a list of vocabulary words
217
     *   that are state-specific: that is, if a word is in this list, the
218
     *   word can ONLY refer to this object if the object is in a state
219
     *   with that word in its list.
220
     *   
221
     *   The idea is that you set up the object's "static" vocabulary with
222
     *   the *complete* list of words for all of its possible states.  For
223
     *   example:
224
     *   
225
     *.     + Matchstick 'lit unlit match';
226
     *   
227
     *   Then, you define the states: in the "lit" state, the word 'lit' is
228
     *   in the stateTokens list; in the "unlit" state, the word 'unlit' is
229
     *   in the list.  By putting the words in the state lists, you
230
     *   "reserve" the words to their respective states.  When the player
231
     *   enters a command, the parser will limit object matches so that the
232
     *   reserved state-specific words can only refer to objects in the
233
     *   corresponding states.  Hence, if the player refers to a "lit
234
     *   match", the word 'lit' will only match an object in the "lit"
235
     *   state, because 'lit' is a reserved state-specific word associated
236
     *   with the "lit" state.
237
     *   
238
     *   You can re-use a word in multiple states.  For example, you could
239
     *   have a "red painted" state and a "blue painted" state, along with
240
     *   an "unpainted" state.
241
     */
242
    stateTokens = []
243
244
    /*
245
     *   Match the name of an object in this state.  We'll check the token
246
     *   list for any words that apply only to *other* states the object
247
     *   can assume; if we find any, we'll reject the match, since the
248
     *   phrase must be referring to an object in a different state.
249
     */
250
    matchName(obj, origTokens, adjustedTokens, states)
251
    {
252
        /* scan each word in our adjusted token list */
253
        for (local i = 1, local len = adjustedTokens.length() ;
254
             i <= len ; i += 2)
255
        {
256
            /* get the current token */
257
            local cur = adjustedTokens[i];
258
259
            /*
260
             *   If this token is in our own state-specific token list,
261
             *   it's acceptable as a match to this object.  (It doesn't
262
             *   matter whether or not it's in any other state's token list
263
             *   if it's in our own, because its presence in our own makes
264
             *   it an acceptable matching word when we're in this state.) 
265
             */
266
            if (stateTokens.indexWhich({t: t == cur}) != nil)
267
                continue;
268
269
            /*
270
             *   It's not in our own state-specific token list.  Check to
271
             *   see if the word appears in ANOTHER state's token list: if
272
             *   it does, then this word CAN'T match an object in this
273
             *   state, because the token is special to that other state
274
             *   and thus can't refer to an object in a state without the
275
             *   token. 
276
             */
277
            if (states.indexWhich(
278
                {s: s.stateTokens.indexOf(cur) != nil}) != nil)
279
                return nil;
280
        }
281
282
        /* we didn't find any objection, so we can match this phrase */
283
        return obj;
284
    }
285
286
    /*
287
     *   Check a token list for any tokens matching any of our
288
     *   state-specific words.  Returns true if we find any such words,
289
     *   nil if not.
290
     *
291
     *   'toks' is the *adjusted* token list used in matchName().
292
     */
293
    findStateToken(toks)
294
    {
295
        /*
296
         *   Scan the token list for a match to any of our state-specific
297
         *   words.  Since we're using the adjusted token list, every
298
         *   other entry is a part of speech, so work through the list in
299
         *   pairs.
300
         */
301
        for (local i = 1, local len = toks.length() ; i <= len ; i += 2)
302
        {
303
            /*
304
             *   if this token matches any of our state tokens, indicate
305
             *   that we found a match
306
             */
307
            if (stateTokens.indexWhich({x: x == toks[i]}) != nil)
308
                return true;
309
        }
310
311
        /* we didn't find a match */
312
        return nil;
313
    }
314
315
    /* get our name */
316
    listName(lst) { return listName_; }
317
318
    /*
319
     *   our list name setting - we define this so that we can be easily
320
     *   initialized with a template (we can't initialize listName()
321
     *   directly in this manner because it's a method, but we define the
322
     *   listName() method to simply return this property value, which we
323
     *   can initialize with a template)
324
     */
325
    listName_ = nil
326
;
327
328
/* ------------------------------------------------------------------------ */
329
/*
330
 *   Language-specific modifications for VocabObject.
331
 */
332
modify VocabObject
333
    /*
334
     *   The vocabulary initializer string for the object - this string
335
     *   can be initialized (most conveniently via a template) to a string
336
     *   of this format:
337
     *
338
     *   'adj adj adj noun/noun/noun*plural plural plural'
339
     *
340
     *   The noun part of the string can be a hyphen, '-', in which case
341
     *   it means that the string doesn't specify a noun or plural at all.
342
     *   This can be useful when nouns and plurals are all inherited from
343
     *   base classes, and only adjectives are to be specified.  (In fact,
344
     *   any word that consists of a single hyphen will be ignored, but
345
     *   this is generally only useful for the adjective-only case.)
346
     *
347
     *   During preinitialization, we'll parse this string and generate
348
     *   dictionary entries and individual vocabulary properties for the
349
     *   parts of speech we find.
350
     *
351
     *   Note that the format described above is specific to the English
352
     *   version of the library.  Non-English versions will probably want
353
     *   to use different formats to conveniently encode appropriate
354
     *   language-specific information in the initializer string.  See the
355
     *   comments for initializeVocabWith() for more details.
356
     *
357
     *   You can use the special wildcard # to match any numeric
358
     *   adjective.  This only works as a wildcard when it stands alone,
359
     *   so a string like "7#" is matched as that literal string, not as a
360
     *   wildcard.  If you want to use a pound sign as a literal
361
     *   adjective, just put it in double quotes.
362
     *
363
     *   You can use the special wildcard "\u0001" (include the double
364
     *   quotes within the string) to match any literal adjective.  This
365
     *   is the literal adjective equivalent of the pound sign.  We use
366
     *   this funny character value because it is unlikely ever to be
367
     *   interesting in user input.
368
     *
369
     *   If you want to match any string for a noun and/or adjective, you
370
     *   can't do it with this property.  Instead, just add the property
371
     *   value noun='*' to the object.
372
     */
373
    vocabWords = ''
374
375
    /*
376
     *   On dynamic construction, initialize our vocabulary words and add
377
     *   them to the dictionary.
378
     */
379
    construct()
380
    {
381
        /* initialize our vocabulary words from vocabWords */
382
        initializeVocab();
383
384
        /* add our vocabulary words to the dictionary */
385
        addToDictionary(&noun);
386
        addToDictionary(&adjective);
387
        addToDictionary(&plural);
388
        addToDictionary(&adjApostS);
389
        addToDictionary(&literalAdjective);
390
    }
391
392
    /* add the words from a dictionary property to the global dictionary */
393
    addToDictionary(prop)
394
    {
395
        /* if we have any words defined, add them to the dictionary */
396
        if (self.(prop) != nil)
397
            cmdDict.addWord(self, self.(prop), prop);
398
    }
399
400
    /* initialize the vocabulary from vocabWords */
401
    initializeVocab()
402
    {
403
        /* inherit vocabulary from this class and its superclasses */
404
        inheritVocab(self, new Vector(10));
405
    }
406
407
    /*
408
     *   Inherit vocabulary from this class and its superclasses, adding
409
     *   the words to the given target object.  'target' is the object to
410
     *   which we add our vocabulary words, and 'done' is a vector of
411
     *   classes that have been visited so far.
412
     *
413
     *   Since a class can be inherited more than once in an inheritance
414
     *   tree (for example, a class can have multiple superclasses, each
415
     *   of which have a common base class), we keep a vector of all of
416
     *   the classes we've visited.  If we're already in the vector, we'll
417
     *   skip adding vocabulary for this class or its superclasses, since
418
     *   we must have already traversed this branch of the tree from
419
     *   another subclass.
420
     */
421
    inheritVocab(target, done)
422
    {
423
        /*
424
         *   if we're in the list of classes handled already, don't bother
425
         *   visiting me again
426
         */
427
        if (done.indexOf(self) != nil)
428
            return;
429
430
        /* add myself to the list of classes handled already */
431
        done.append(self);
432
433
        /* 
434
         *   add words from our own vocabWords to the target object (but
435
         *   only if it's our own - not if it's only inherited, as we'll
436
         *   pick up the inherited ones explicitly in a bit) 
437
         */
438
        if (propDefined(&vocabWords, PropDefDirectly))
439
            target.initializeVocabWith(vocabWords);
440
441
        /* add vocabulary from each of our superclasses */
442
        foreach (local sc in getSuperclassList())
443
            sc.inheritVocab(target, done);
444
    }
445
446
    /*
447
     *   Initialize our vocabulary from the given string.  This parses the
448
     *   given vocabulary initializer string and adds the words defined in
449
     *   the string to the dictionary.
450
     *
451
     *   Note that this parsing is intentionally located in the
452
     *   English-specific part of the library, because it is expected that
453
     *   other languages will want to define their own vocabulary
454
     *   initialization string formats.  For example, a language with
455
     *   gendered nouns might want to use gendered articles in the
456
     *   initializer string as an author-friendly way of defining noun
457
     *   gender; languages with inflected (declined) nouns and/or
458
     *   adjectives might want to encode inflected forms in the
459
     *   initializer.  Non-English language implementations are free to
460
     *   completely redefine the format - there's no need to follow the
461
     *   conventions of the English format in other languages where
462
     *   different formats would be more convenient.
463
     */
464
    initializeVocabWith(str)
465
    {
466
        local sectPart;
467
        local modList = [];
468
469
        /* start off in the adjective section */
470
        sectPart = &adjective;
471
472
        /* scan the string until we run out of text */
473
        while (str != '')
474
        {
475
            local len;
476
            local cur;
477
478
            /*
479
             *   if it starts with a quote, find the close quote;
480
             *   otherwise, find the end of the current token by seeking
481
             *   the next delimiter
482
             */
483
            if (str.startsWith('"'))
484
            {
485
                /* find the close quote */
486
                len = str.find('"', 2);
487
            }
488
            else
489
            {
490
                /* no quotes - find the next delimiter */
491
                len = rexMatch('<^space|star|/>*', str);
492
            }
493
494
            /* if there's no match, use the whole rest of the string */
495
            if (len == nil)
496
                len = str.length();
497
498
            /* if there's anything before the delimiter, extract it */
499
            if (len != 0)
500
            {
501
                /* extract the part up to but not including the delimiter */
502
                cur = str.substr(1, len);
503
504
                /*
505
                 *   if we're in the adjectives, and either this is the
506
                 *   last token or the next delimiter is not a space, this
507
                 *   is implicitly a noun
508
                 */
509
                if (sectPart == &adjective
510
                    && (len == str.length()
511
                        || str.substr(len + 1, 1) != ' '))
512
                {
513
                    /* move to the noun section */
514
                    sectPart = &noun;
515
                }
516
517
                /*
518
                 *   if the word isn't a single hyphen (in which case it's
519
                 *   a null word placeholder, not an actual vocabulary
520
                 *   word), add it to our own appropriate part-of-speech
521
                 *   property and to the dictionary
522
                 */
523
                if (cur != '-')
524
                {
525
                    /*
526
                     *   by default, use the part of speech of the current
527
                     *   string section as the part of speech for this
528
                     *   word
529
                     */
530
                    local wordPart = sectPart;
531
532
                    /*
533
                     *   Check for parentheses, which indicate that the
534
                     *   token is "weak."  This doesn't affect anything
535
                     *   about the token or its part of speech except that
536
                     *   we must include the token in our list of weak
537
                     *   tokens.
538
                     */
539
                    if (cur.startsWith('(') && cur.endsWith(')'))
540
                    {
541
                        /* it's a weak token - remove the parens */
542
                        cur = cur.substr(2, cur.length() - 2);
543
544
                        /*
545
                         *   if we don't have a weak token list yet,
546
                         *   create the list
547
                         */
548
                        if (weakTokens == nil)
549
                            weakTokens = [];
550
551
                        /* add the token to the weak list */
552
                        weakTokens += cur;
553
                    }
554
555
                    /*
556
                     *   Check for special formats: quoted strings,
557
                     *   apostrophe-S words.  These formats are mutually
558
                     *   exclusive.
559
                     */
560
                    if (cur.startsWith('"'))
561
                    {
562
                        /*
563
                         *   It's a quoted string, so it's a literal
564
                         *   adjective.
565
                         */
566
567
                        /* remove the quote(s) */
568
                        if (cur.endsWith('"'))
569
                            cur = cur.substr(2, cur.length() - 2);
570
                        else
571
                            cur = cur.substr(2);
572
573
                        /* change the part of speech to 'literal adjective' */
574
                        wordPart = &literalAdjective;
575
                    }
576
                    else if (cur.endsWith('\'s'))
577
                    {
578
                        /*
579
                         *   It's an apostrophe-s word.  Remove the "'s"
580
                         *   suffix and add the root word using adjApostS
581
                         *   as the part of speech.  The grammar rules are
582
                         *   defined to allow this part of speech to be
583
                         *   used exclusively with "'s" suffixes in input.
584
                         *   Since the tokenizer always pulls the "'s"
585
                         *   suffix off of a word in the input, we have to
586
                         *   store any vocabulary words with "'s" suffixes
587
                         *   the same way, with the "'s" suffixes removed.
588
                         */
589
590
                        /* change the part of speech to adjApostS */
591
                        wordPart = &adjApostS;
592
593
                        /* remove the "'s" suffix from the string */
594
                        cur = cur.substr(1, cur.length() - 2);
595
                    }
596
597
                    /* add the word to our own list for this part of speech */
598
                    if (self.(wordPart) == nil)
599
                        self.(wordPart) = [cur];
600
                    else
601
                        self.(wordPart) += cur;
602
603
                    /* add it to the dictionary */
604
                    cmdDict.addWord(self, cur, wordPart);
605
606
                    if (cur.endsWith('.'))
607
                    {
608
                        local abbr;
609
610
                        /*
611
                         *   It ends with a period, so this is an
612
                         *   abbreviated word.  Enter the abbreviation
613
                         *   both with and without the period.  The normal
614
                         *   handling will enter it with the period, so we
615
                         *   only need to enter it specifically without.
616
                         */
617
                        abbr = cur.substr(1, cur.length() - 1);
618
                        self.(wordPart) += abbr;
619
                        cmdDict.addWord(self, abbr, wordPart);
620
                    }
621
622
                    /* note that we added to this list */
623
                    if (modList.indexOf(wordPart) == nil)
624
                        modList += wordPart;
625
                }
626
            }
627
628
            /* if we have a delimiter, see what we have */
629
            if (len + 1 < str.length())
630
            {
631
                /* check the delimiter */
632
                switch(str.substr(len + 1, 1))
633
                {
634
                case ' ':
635
                    /* stick with the current part */
636
                    break;
637
638
                case '*':
639
                    /* start plurals */
640
                    sectPart = &plural;
641
                    break;
642
643
                case '/':
644
                    /* start alternative nouns */
645
                    sectPart = &noun;
646
                    break;
647
                }
648
649
                /* remove the part up to and including the delimiter */
650
                str = str.substr(len + 2);
651
652
                /* skip any additional spaces following the delimiter */
653
                if ((len = rexMatch('<space>+', str)) != nil)
654
                    str = str.substr(len + 1);
655
            }
656
            else
657
            {
658
                /* we've exhausted the string - we're done */
659
                break;
660
            }
661
        }
662
663
        /* uniquify each word list we updated */
664
        foreach (local p in modList)
665
            self.(p) = self.(p).getUnique();
666
    }
667
;
668
669
/* ------------------------------------------------------------------------ */
670
/*
671
 *   Language-specific modifications for Thing.  This class contains the
672
 *   methods and properties of Thing that need to be replaced when the
673
 *   library is translated to another language.
674
 *   
675
 *   The properties and methods defined here should generally never be used
676
 *   by language-independent library code, because everything defined here
677
 *   is specific to English.  Translators are thus free to change the
678
 *   entire scheme defined here.  For example, the notions of number and
679
 *   gender are confined to the English part of the library; other language
680
 *   implementations can completely replace these attributes, so they're
681
 *   not constrained to emulate their own number and gender systems with
682
 *   the English system.  
683
 */
684
modify Thing
685
    /*
686
     *   Flag that this object's name is rendered as a plural (this
687
     *   applies to both a singular noun with plural usage, such as
688
     *   "pants" or "scissors," and an object used in the world model to
689
     *   represent a collection of real-world objects, such as "shrubs").
690
     */
691
    isPlural = nil
692
693
    /*
694
     *   Flag that this is object's name is a "mass noun" - that is, a
695
     *   noun denoting a continuous (effectively infinitely divisible)
696
     *   substance or material, such as water, wood, or popcorn; and
697
     *   certain abstract concepts, such as knowledge or beauty.  Mass
698
     *   nouns are never rendered in the plural, and use different
699
     *   determiners than ordinary ("count") nouns: "some popcorn" vs "a
700
     *   kernel", for example.
701
     */
702
    isMassNoun = nil
703
704
    /*
705
     *   Flags indicating that the object should be referred to with
706
     *   gendered pronouns (such as 'he' or 'she' rather than 'it').
707
     *
708
     *   Note that these flags aren't mutually exclusive, so it's legal
709
     *   for the object to have both masculine and feminine usage.  This
710
     *   can be useful when creating collective objects that represent
711
     *   more than one individual, for example.
712
     */
713
    isHim = nil
714
    isHer = nil
715
716
    /*
717
     *   Flag indicating that the object can be referred to with a neuter
718
     *   pronoun ('it').  By default, this is true if the object has
719
     *   neither masculine nor feminine gender, but it can be overridden
720
     *   so that an object has both gendered and ungendered usage.  This
721
     *   can be useful for collective objects, as well as for cases where
722
     *   gendered usage varies by speaker or situation, such as animals.
723
     */
724
    isIt
725
    {
726
        /* by default, we're an 'it' if we're not a 'him' or a 'her' */
727
        return !(isHim || isHer);
728
    }
729
730
    /*
731
     *   Test to see if we can match the pronouns 'him', 'her', 'it', and
732
     *   'them'.  By default, these simply test the corresponding isXxx
733
     *   flags (except 'canMatchThem', which tests 'isPlural' to see if the
734
     *   name has plural usage).
735
     */
736
    canMatchHim = (isHim)
737
    canMatchHer = (isHer)
738
    canMatchIt = (isIt)
739
    canMatchThem = (isPlural)
740
741
    /* can we match the given PronounXxx pronoun type specifier? */
742
    canMatchPronounType(typ)
743
    {
744
        /* check the type, and return the appropriate indicator property */
745
        switch (typ)
746
        {
747
        case PronounHim:
748
            return canMatchHim;
749
750
        case PronounHer:
751
            return canMatchHer;
752
753
        case PronounIt:
754
            return canMatchIt;
755
756
        case PronounThem:
757
            return canMatchThem;
758
759
        default:
760
            return nil;
761
        }
762
    }
763
764
    /*
765
     *   The grammatical cardinality of this item when it appears in a
766
     *   list.  This is used to ensure verb agreement when mentioning the
767
     *   item in a list of items.  ("Cardinality" is a fancy word for "how
768
     *   many items does this look like").
769
     *
770
     *   English only distinguishes two degrees of cardinality in its
771
     *   grammar: one, or many.  That is, when constructing a sentence, the
772
     *   only thing the grammar cares about is whether an object is
773
     *   singular or plural: IT IS on the table, THEY ARE on the table.
774
     *   Since English only distinguishes these two degrees, two is the
775
     *   same as a hundred is the same as a million for grammatical
776
     *   purposes, so we'll consider our cardinality to be 2 if we're
777
     *   plural, 1 otherwise.
778
     *
779
     *   Some languages don't express cardinality at all in their grammar,
780
     *   and others distinguish cardinality in greater detail than just
781
     *   singular-vs-plural, which is why this method has to be in the
782
     *   language-specific part of the library.
783
     */
784
    listCardinality(lister) { return isPlural ? 2 : 1; }
785
786
    /*
787
     *   Proper name flag.  This indicates that the 'name' property is the
788
     *   name of a person or place.  We consider proper names to be fully
789
     *   qualified, so we don't add articles for variations on the name
790
     *   such as 'theName'.
791
     */
792
    isProperName = nil
793
794
    /*
795
     *   Qualified name flag.  This indicates that the object name, as
796
     *   given by the 'name' property, is already fully qualified, so
797
     *   doesn't need qualification by an article like "the" or "a" when
798
     *   it appears in a sentence.  By default, a name is considered
799
     *   qualified if it's a proper name, but this can be overridden to
800
     *   mark a non-proper name as qualified when needed.
801
     */
802
    isQualifiedName = (isProperName)
803
804
    /*
805
     *   The name of the object - this is a string giving the object's
806
     *   short description, for constructing sentences that refer to the
807
     *   object by name.  Each instance should override this to define the
808
     *   name of the object.  This string should not contain any articles;
809
     *   we use this string as the root to generate various forms of the
810
     *   object's name for use in different places in sentences.
811
     */
812
    name = ''
813
814
    /*
815
     *   The name of the object, for the purposes of disambiguation
816
     *   prompts.  This should almost always be the object's ordinary
817
     *   name, so we return self.name by default.
818
     *
819
     *   In rare cases, it might be desirable to override this.  In
820
     *   particular, if a game has two objects that are NOT defined as
821
     *   basic equivalents of one another (which means that the parser
822
     *   will always ask for disambiguation when the two are ambiguous
823
     *   with one another), but the two nonetheless have identical 'name'
824
     *   properties, this property should be overridden for one or both
825
     *   objects to give them different names.  This will ensure that we
826
     *   avoid asking questions of the form "which do you mean, the coin,
827
     *   or the coin?".  In most cases, non-equivalent objects will have
828
     *   distinct 'name' properties to begin with, so this is not usually
829
     *   an issue.
830
     *
831
     *   When overriding this method, take care to override
832
     *   theDisambigName, aDisambigName, countDisambigName, and/or
833
     *   pluralDisambigName as needed.  Those routines must be overridden
834
     *   only when the default algorithms for determining articles and
835
     *   plurals fail to work properly for the disambigName (for example,
836
     *   the indefinite article algorithm fails with silent-h words like
837
     *   "hour", so if disambigName is "hour", aDisambigName must be
838
     *   overridden).  In most cases, the automatic algorithms will
839
     *   produce acceptable results, so the default implementations of
840
     *   these other routines can be used without customization.
841
     */
842
    disambigName = (name)
843
844
    /*
845
     *   The "equivalence key" is the value we use to group equivalent
846
     *   objects.  Note that we can only treat objects as equivalent when
847
     *   they're explicitly marked with isEquivalent=true, so the
848
     *   equivalence key is irrelevant for objects not so marked.
849
     *   
850
     *   Since the main point of equivalence is to allow creation of groups
851
     *   of like-named objects that are interchangeable in listings and in
852
     *   command input, we use the basic disambiguation name as the
853
     *   equivalence key.  
854
     */
855
    equivalenceKey = (disambigName)
856
857
    /*
858
     *   The definite-article name for disambiguation prompts.
859
     *
860
     *   By default, if the disambiguation name is identical to the
861
     *   regular name (i.e, the string returned by self.disambigName is
862
     *   the same as the string returned by self.name), then we simply
863
     *   return self.theName.  Since the base name is the same in either
864
     *   case, presumably the definite article names should be the same as
865
     *   well.  This way, if the object overrides theName to do something
866
     *   special, then we'll use the same definite-article name for
867
     *   disambiguation prompts.
868
     *
869
     *   If the disambigName isn't the same as the regular name, then
870
     *   we'll apply the same algorithm to the base disambigName that we
871
     *   normally do to the regular name to produce the theName.  This
872
     *   way, if the disambigName is overridden, we'll use the overridden
873
     *   disambigName to produce the definite-article version, using the
874
     *   standard definite-article algorithm.
875
     *
876
     *   Note that there's an aspect of this conditional approach that
877
     *   might not be obvious.  It might look as though the test is
878
     *   redundant: if name == disambigName, after all, and the default
879
     *   theName returns theNameFrom(name), then this ought to be
880
     *   identical to returning theNameFrom(disambigName).  The subtlety
881
     *   is that theName could be overridden to produce a custom result,
882
     *   in which case returning theNameFrom(disambigName) would return
883
     *   something different, which probably wouldn't be correct: the
884
     *   whole reason theName would be overridden is that the algorithmic
885
     *   determination (theNameFrom) gets it wrong.  So, by calling
886
     *   theName directly when disambigName is the same as name, we are
887
     *   assured that we pick up any override in theName.
888
     *
889
     *   Note that in rare cases, neither of these default approaches will
890
     *   produce the right result; this will happen if the object uses a
891
     *   custom disambigName, but that name doesn't fit the normal
892
     *   algorithmic pattern for applying a definite article.  In these
893
     *   cases, the object should simply override this method to specify
894
     *   the custom name.
895
     */
896
    theDisambigName = (name == disambigName
897
                       ? theName : theNameFrom(disambigName))
898
899
    /*
900
     *   The indefinite-article name for disambiguation prompts.  We use
901
     *   the same logic here as in theDisambigName.
902
     */
903
    aDisambigName = (name == disambigName ? aName : aNameFrom(disambigName))
904
905
    /*
906
     *   The counted name for disambiguation prompts.  We use the same
907
     *   logic here as in theDisambigName.
908
     */
909
    countDisambigName(cnt)
910
    {
911
        return (name == disambigName && pluralName == pluralDisambigName
912
                ? countName(cnt)
913
                : countNameFrom(cnt, disambigName, pluralDisambigName));
914
    }
915
916
    /*
917
     *   The plural name for disambiguation prompts.  We use the same
918
     *   logic here as in theDisambigName.
919
     */
920
    pluralDisambigName = (name == disambigName
921
                          ? pluralName : pluralNameFrom(disambigName))
922
923
    /*
924
     *   The name of the object, for the purposes of disambiguation prompts
925
     *   to disambiguation among this object and basic equivalents of this
926
     *   object (i.e., objects of the same class marked with
927
     *   isEquivalent=true).
928
     *
929
     *   This is used in disambiguation prompts in place of the actual text
930
     *   typed by the user.  For example, suppose the user types ">take
931
     *   coin", then we ask for help disambiguating, and the player types
932
     *   ">gold".  This narrows things down to, say, three gold coins, but
933
     *   they're in different locations so we need to ask for further
934
     *   disambiguation.  Normally, we ask "which gold do you mean",
935
     *   because the player typed "gold" in the input.  Once we're down to
936
     *   equivalents, we don't have to rely on the input text any more,
937
     *   which is good because the input text could be fragmentary (as in
938
     *   our present example).  Since we have only equivalents, we can use
939
     *   the actual name of the objects (they're all the same, after all).
940
     *   This property gives the name we use.
941
     *
942
     *   For English, this is simply the object's ordinary disambiguation
943
     *   name.  This property is separate from 'name' and 'disambigName'
944
     *   for the sake of languages that need to use an inflected form in
945
     *   this context.
946
     */
947
    disambigEquivName = (disambigName)
948
949
    /*
950
     *   Single-item listing description.  This is used to display the
951
     *   item when it appears as a single (non-grouped) item in a list.
952
     *   By default, we just show the indefinite article description.
953
     */
954
    listName = (aName)
955
956
    /*
957
     *   Return a string giving the "counted name" of the object - that is,
958
     *   a phrase describing the given number of the object.  For example,
959
     *   for a red book, and a count of 5, we might return "five red
960
     *   books".  By default, we use countNameFrom() to construct a phrase
961
     *   from the count and either our regular (singular) 'name' property
962
     *   or our 'pluralName' property, according to whether count is 1 or
963
     *   more than 1.  
964
     */
965
    countName(count) { return countNameFrom(count, name, pluralName); }
966
967
    /*
968
     *   Returns a string giving a count applied to the name string.  The
969
     *   name must be given in both singular and plural forms.
970
     */
971
    countNameFrom(count, singularStr, pluralStr)
972
    {
973
        /* if the count is one, use 'one' plus the singular name */
974
        if (count == 1)
975
            return 'one ' + singularStr;
976
977
        /*
978
         *   Get the number followed by a space - spell out numbers below
979
         *   100, but use numerals to denote larger numbers.  Append the
980
         *   plural name to the number and return the result.
981
         */
982
        return spellIntBelowExt(count, 100, 0, DigitFormatGroupSep)
983
            + ' ' + pluralStr;
984
    }
985
986
    /*
987
     *   Get the 'pronoun selector' for the various pronoun methods.  This
988
     *   returns:
989
     *   
990
     *.  - singular neuter = 1
991
     *.  - singular masculine = 2
992
     *.  - singular feminine = 3
993
     *.  - plural = 4
994
     */
995
    pronounSelector = (isPlural ? 4 : isHer ? 3 : isHim ? 2 : 1)
996
997
    /*
998
     *   get a string with the appropriate pronoun for the object for the
999
     *   nominative case, objective case, possessive adjective, possessive
1000
     *   noun
1001
     */
1002
    itNom { return ['it', 'he', 'she', 'they'][pronounSelector]; }
1003
    itObj { return ['it', 'him', 'her', 'them'][pronounSelector]; }
1004
    itPossAdj { return ['its', 'his', 'her', 'their'][pronounSelector]; }
1005
    itPossNoun { return ['its', 'his', 'hers', 'theirs'][pronounSelector]; }
1006
1007
    /* get the object reflexive pronoun (itself, etc) */
1008
    itReflexive
1009
    {
1010
        return ['itself', 'himself', 'herself', 'themselves']
1011
               [pronounSelector];
1012
    }
1013
1014
    /* demonstrative pronouns ('that' or 'those') */
1015
    thatNom { return ['that', 'he', 'she', 'those'][pronounSelector]; }
1016
    thatIsContraction
1017
    {
1018
        return thatNom + tSel(isPlural ? ' are' : '&rsquo;s', ' ' + verbToBe);
1019
    }
1020
    thatObj { return ['that', 'him', 'her', 'those'][pronounSelector]; }
1021
1022
    /*
1023
     *   get a string with the appropriate pronoun for the object plus the
1024
     *   correct conjugation of 'to be'
1025
     */
1026
    itIs { return itNom + ' ' + verbToBe; }
1027
1028
    /* get a pronoun plus a 'to be' contraction */
1029
    itIsContraction
1030
    {
1031
        return itNom
1032
            + tSel(isPlural ? '&rsquo;re' : '&rsquo;s', ' ' + verbToBe);
1033
    }
1034
1035
    /*
1036
     *   get a string with the appropriate pronoun for the object plus the
1037
     *   correct conjugation of the given regular verb for the appropriate
1038
     *   person
1039
     */
1040
    itVerb(verb)
1041
    {
1042
        return itNom + ' ' + conjugateRegularVerb(verb);
1043
    }
1044
1045
    /*
1046
     *   Conjugate a regular verb in the present or past tense for our
1047
     *   person and number.
1048
     *
1049
     *   In the present tense, this is pretty easy: we add an 's' for the
1050
     *   third person singular, and leave the verb unchanged for plural (it
1051
     *   asks, they ask).  The only complication is that we must check some
1052
     *   special cases to add the -s suffix: -y -> -ies (it carries), -o ->
1053
     *   -oes (it goes).
1054
     *
1055
     *   In the past tense, we can equally easily figure out when to use
1056
     *   -d, -ed, or -ied.  However, we have a more serious problem: for
1057
     *   some verbs, the last consonant of the verb stem should be repeated
1058
     *   (as in deter -> deterred), and for others it shouldn't (as in
1059
     *   gather -> gathered).  To figure out which rule applies, we would
1060
     *   sometimes need to know whether the last syllable is stressed, and
1061
     *   unfortunately there is no easy way to determine that
1062
     *   programmatically.
1063
     *
1064
     *   Therefore, we do *not* handle the case where the last consonant is
1065
     *   repeated in the past tense.  You shouldn't use this method for
1066
     *   this case; instead, treat it as you would handle an irregular
1067
     *   verb, by explicitly specifying the correct past tense form via the
1068
     *   tSel macro.  For example, to generate the properly conjugated form
1069
     *   of the verb "deter" for an object named "thing", you could use an
1070
     *   expression such as:
1071
     *
1072
     *   'deter' + tSel(thing.verbEndingS, 'red')
1073
     *
1074
     *   This would correctly generate "deter", "deters", or "deterred"
1075
     *   depending on the number of the object named "thing" and on the
1076
     *   current narrative tense.
1077
     */
1078
    conjugateRegularVerb(verb)
1079
    {
1080
        /*
1081
         *   Which tense are we currently using?
1082
         */
1083
        if (gameMain.usePastTense)
1084
        {
1085
            /*
1086
             *   We want the past tense form.
1087
             *
1088
             *   If the last letter is 'e', simply add 'd'.
1089
             */
1090
            if (verb.endsWith('e')) return verb + 'd';
1091
1092
            /*
1093
             *   Otherwise, if the verb ending would become 'ies' in the
1094
             *   third-person singular present, then it becomes 'ied' in
1095
             *   the past.
1096
             */
1097
            else if (rexMatch(iesEndingPat, verb))
1098
                    return verb.substr(1, verb.length() - 1) + 'ied';
1099
1100
            /*
1101
             *   Otherwise, use 'ed' as the ending.  Don't try to determine
1102
             *   if the last consonant should be repeated: that's too
1103
             *   complicated.  We'll just ignore the possibility.
1104
             */
1105
            else return verb + 'ed';
1106
        }
1107
        else
1108
        {
1109
            /*
1110
             *   We want the present tense form.
1111
             *
1112
             *   Check our number and person.
1113
             */
1114
            if (isPlural)
1115
            {
1116
                /*
1117
                 *   We're plural, so simply use the base verb form ("they
1118
                 *   ask").
1119
                 */
1120
                return verb;
1121
            }
1122
            else
1123
            {
1124
                /*
1125
                 *   Third-person singular, so we must add the -s suffix.
1126
                 *   Check for special spelling cases:
1127
                 *
1128
                 *   '-y' changes to '-ies', unless the 'y' is preceded by
1129
                 *   a vowel
1130
                 *
1131
                 *   '-sh', '-ch', and '-o' endings add suffix '-es'
1132
                 */
1133
                if (rexMatch(iesEndingPat, verb))
1134
                    return verb.substr(1, verb.length() - 1) + 'ies';
1135
                else if (rexMatch(esEndingPat, verb))
1136
                    return verb + 'es';
1137
                else
1138
                    return verb + 's';
1139
            }
1140
        }
1141
    }
1142
1143
    /* verb-ending patterns for figuring out which '-s' ending to add */
1144
    iesEndingPat = static new RexPattern('.*[^aeiou]y$')
1145
    esEndingPat = static new RexPattern('.*(o|ch|sh)$')
1146
1147
    /*
1148
     *   Get the name with a definite article ("the box").  By default, we
1149
     *   use our standard definite article algorithm to apply an article
1150
     *   to self.name.
1151
     *
1152
     *   The name returned must be in the nominative case (which makes no
1153
     *   difference unless the name is a pronoun, since in English
1154
     *   ordinary nouns don't vary according to how they're used in a
1155
     *   sentence).
1156
     */
1157
    theName = (theNameFrom(name))
1158
1159
    /*
1160
     *   theName in objective case.  In most cases, this is identical to
1161
     *   the normal theName, so we use that by default.  This must be
1162
     *   overridden if theName is a pronoun (which is usually only the
1163
     *   case for player character actors; see our language-specific Actor
1164
     *   modifications for information on that case).
1165
     */
1166
    theNameObj { return theName; }
1167
1168
    /*
1169
     *   Generate the definite-article name from the given name string.
1170
     *   If my name is already qualified, don't add an article; otherwise,
1171
     *   add a 'the' as the prefixed definite article.
1172
     */
1173
    theNameFrom(str) { return (isQualifiedName ? '' : 'the ') + str; }
1174
1175
    /*
1176
     *   theName as a possessive adjective (Bob's book, your book).  If the
1177
     *   name's usage is singular (i.e., isPlural is nil), we'll simply add
1178
     *   an apostrophe-S.  If the name is plural, and it ends in an "s",
1179
     *   we'll just add an apostrophe (no S).  If it's plural and doesn't
1180
     *   end in "s", we'll add an apostrophe-S.
1181
     *
1182
     *   Note that some people disagree about the proper usage for
1183
     *   singular-usage words (especially proper names) that end in 's'.
1184
     *   Some people like to use a bare apostrophe for any name that ends
1185
     *   in 's' (so Chris -> Chris'); other people use apostrophe-s for
1186
     *   singular words that end in an "s" sound and a bare apostrophe for
1187
     *   words that end in an "s" that sounds like a "z" (so Charles
1188
     *   Dickens -> Charles Dickens').  However, most usage experts agree
1189
     *   that proper names take an apostrophe-S in almost all cases, even
1190
     *   when ending with an "s": "Chris's", "Charles Dickens's".  That's
1191
     *   what we do here.
1192
     *
1193
     *   Note that this algorithm doesn't catch all of the special
1194
     *   exceptions in conventional English usage.  For example, Greek
1195
     *   names ending with "-es" are usually written with the bare
1196
     *   apostrophe, but we don't have a property that tells us whether the
1197
     *   name is Greek or not, so we can't catch this case.  Likewise, some
1198
     *   authors like to possessive-ize words that end with an "s" sound
1199
     *   with a bare apostrophe, as in "for appearance' sake", and we don't
1200
     *   attempt to catch these either.  For any of these exceptions, you
1201
     *   must override this method for the individual object.
1202
     */
1203
    theNamePossAdj
1204
    {
1205
        /* add apostrophe-S, unless it's a plural ending with 's' */
1206
        return theName
1207
            + (isPlural && theName.endsWith('s') ? '&rsquo;' : '&rsquo;s');
1208
    }
1209
1210
    /*
1211
     *   TheName as a possessive noun (that is Bob's, that is yours).  We
1212
     *   simply return the possessive adjective name, since the two forms
1213
     *   are usually identical in English (except for pronouns, where they
1214
     *   sometimes differ: "her" for the adjective vs "hers" for the noun).
1215
     */
1216
    theNamePossNoun = (theNamePossAdj)
1217
1218
    /*
1219
     *   theName with my nominal owner explicitly stated, if we have a
1220
     *   nominal owner: "your backpack," "Bob's flashlight."  If we have
1221
     *   no nominal owner, this is simply my theName.
1222
     */
1223
    theNameWithOwner()
1224
    {
1225
        local owner;
1226
1227
        /*
1228
         *   if we have a nominal owner, show with our owner name;
1229
         *   otherwise, just show our regular theName
1230
         */
1231
        if ((owner = getNominalOwner()) != nil)
1232
            return owner.theNamePossAdj + ' ' + name;
1233
        else
1234
            return theName;
1235
    }
1236
1237
    /*
1238
     *   Default preposition to use when an object is in/on this object.
1239
     *   By default, we use 'in' as the preposition; subclasses can
1240
     *   override to use others (such as 'on' for a surface).
1241
     */
1242
    objInPrep = 'in'
1243
1244
    /*
1245
     *   Default preposition to use when an actor is in/on this object (as
1246
     *   a nested location), and full prepositional phrase, with no article
1247
     *   and with an indefinite article.  By default, we use the objInPrep
1248
     *   for actors as well.
1249
     */
1250
    actorInPrep = (objInPrep)
1251
1252
    /* preposition to use when an actor is being removed from this location */
1253
    actorOutOfPrep = 'out of'
1254
1255
    /* preposition to use when an actor is being moved into this location */
1256
    actorIntoPrep
1257
    {
1258
        if (actorInPrep is in ('in', 'on'))
1259
            return actorInPrep + 'to';
1260
        else
1261
            return actorInPrep;
1262
    }
1263
1264
    /*
1265
     *   describe an actor as being in/being removed from/being moved into
1266
     *   this location
1267
     */
1268
    actorInName = (actorInPrep + ' ' + theNameObj)
1269
    actorInAName = (actorInPrep + ' ' + aNameObj)
1270
    actorOutOfName = (actorOutOfPrep + ' ' + theNameObj)
1271
    actorIntoName = (actorIntoPrep + ' ' + theNameObj)
1272
1273
    /*
1274
     *   A prepositional phrase that can be used to describe things that
1275
     *   are in this room as seen from a remote point of view.  This
1276
     *   should be something along the lines of "in the airlock", "at the
1277
     *   end of the alley", or "on the lawn".
1278
     *
1279
     *   'pov' is the point of view from which we're seeing this room;
1280
     *   this might be
1281
     *
1282
     *   We use this phrase in cases where we need to describe things in
1283
     *   this room when viewed from a point of view outside of the room
1284
     *   (i.e., in a different top-level room).  By default, we'll use our
1285
     *   actorInName.
1286
     */
1287
    inRoomName(pov) { return actorInName; }
1288
1289
    /*
1290
     *   Provide the prepositional phrase for an object being put into me.
1291
     *   For a container, for example, this would say "into the box"; for
1292
     *   a surface, it would say "onto the table."  By default, we return
1293
     *   our library message given by our putDestMessage property; this
1294
     *   default is suitable for most cases, but individual objects can
1295
     *   customize as needed.  When customizing this, be sure to make the
1296
     *   phrase suitable for use in sentences like "You put the book
1297
     *   <<putInName>>" and "The book falls <<putInName>>" - the phrase
1298
     *   should be suitable for a verb indicating active motion by the
1299
     *   object being received.
1300
     */
1301
    putInName() { return gLibMessages.(putDestMessage)(self); }
1302
1303
    /*
1304
     *   Get a description of an object within this object, describing the
1305
     *   object's location as this object.  By default, we'll append "in
1306
     *   <theName>" to the given object name.
1307
     */
1308
    childInName(childName)
1309
        { return childInNameGen(childName, theName); }
1310
1311
    /*
1312
     *   Get a description of an object within this object, showing the
1313
     *   owner of this object.  This is similar to childInName, but
1314
     *   explicitly shows the owner of the containing object, if any: "the
1315
     *   flashlight in bob's backpack".
1316
     */
1317
    childInNameWithOwner(childName)
1318
        { return childInNameGen(childName, theNameWithOwner); }
1319
1320
    /*
1321
     *   get a description of an object within this object, as seen from a
1322
     *   remote location
1323
     */
1324
    childInRemoteName(childName, pov)
1325
        { return childInNameGen(childName, inRoomName(pov)); }
1326
1327
    /*
1328
     *   Base routine for generating childInName and related names.  Takes
1329
     *   the name to use for the child and the name to use for me, and
1330
     *   combines them appropriately.
1331
     *
1332
     *   In most cases, this is the only one of the various childInName
1333
     *   methods that needs to be overridden per subclass, since the others
1334
     *   are defined in terms of this one.  Note also that if the only
1335
     *   thing you need to do is change the preposition from 'in' to
1336
     *   something else, you can just override objInPrep instead.
1337
     */
1338
    childInNameGen(childName, myName)
1339
        { return childName + ' ' + objInPrep + ' ' + myName; }
1340
1341
    /*
1342
     *   Get my name (in various forms) distinguished by my owner or
1343
     *   location.
1344
     *
1345
     *   If the object has an owner, and either we're giving priority to
1346
     *   the owner or our immediate location is the same as the owner,
1347
     *   we'll show using a possessive form with the owner ("bob's
1348
     *   flashlight").  Otherwise, we'll show the name distinguished by
1349
     *   our immediate container ("the flashlight in the backpack").
1350
     *
1351
     *   These are used by the ownership and location distinguishers to
1352
     *   list objects according to owners in disambiguation lists.  The
1353
     *   ownership distinguisher gives priority to naming by ownership,
1354
     *   regardless of the containment relationship between owner and
1355
     *   self; the location distinguisher gives priority to naming by
1356
     *   location, showing the owner only if the owner is the same as the
1357
     *   location.
1358
     *
1359
     *   We will presume that objects with proper names are never
1360
     *   indistinguishable from other objects with proper names, so we
1361
     *   won't worry about cases like "Bob's Bill".  This leaves us free
1362
     *   to use appropriate articles in all cases.
1363
     */
1364
    aNameOwnerLoc(ownerPriority)
1365
    {
1366
        local owner;
1367
1368
        /* show in owner or location format, as appropriate */
1369
        if ((owner = getNominalOwner()) != nil
1370
            && (ownerPriority || isDirectlyIn(owner)))
1371
        {
1372
            local ret;
1373
1374
            /*
1375
             *   we have an owner - show as "one of Bob's items" (or just
1376
             *   "Bob's items" if this is a mass noun or a proper name)
1377
             */
1378
            ret = owner.theNamePossAdj + ' ' + pluralName;
1379
            if (!isMassNoun && !isPlural)
1380
                ret = 'one of ' + ret;
1381
1382
            /* return the result */
1383
            return ret;
1384
        }
1385
        else
1386
        {
1387
            /* we have no owner - show as "an item in the location" */
1388
            return location.childInNameWithOwner(aName);
1389
        }
1390
    }
1391
    theNameOwnerLoc(ownerPriority)
1392
    {
1393
        local owner;
1394
1395
        /* show in owner or location format, as appropriate */
1396
        if ((owner = getNominalOwner()) != nil
1397
            && (ownerPriority || isDirectlyIn(owner)))
1398
        {
1399
            /* we have an owner - show as "Bob's item" */
1400
            return owner.theNamePossAdj + ' ' + name;
1401
        }
1402
        else
1403
        {
1404
            /* we have no owner - show as "the item in the location" */
1405
            return location.childInNameWithOwner(theName);
1406
        }
1407
    }
1408
    countNameOwnerLoc(cnt, ownerPriority)
1409
    {
1410
        local owner;
1411
1412
        /* show in owner or location format, as appropriate */
1413
        if ((owner = getNominalOwner()) != nil
1414
            && (ownerPriority || isDirectlyIn(owner)))
1415
        {
1416
            /* we have an owner - show as "Bob's five items" */
1417
            return owner.theNamePossAdj + ' ' + countName(cnt);
1418
        }
1419
        else
1420
        {
1421
            /* we have no owner - show as "the five items in the location" */
1422
            return location.childInNameWithOwner('the ' + countName(cnt));
1423
        }
1424
    }
1425
1426
    /*
1427
     *   Note that I'm being used in a disambiguation prompt by
1428
     *   owner/location.  If we're showing the owner, we'll set the
1429
     *   antecedent for the owner's pronoun, if the owner is a 'him' or
1430
     *   'her'; this allows the player to refer back to our prompt text
1431
     *   with appropriate pronouns.
1432
     */
1433
    notePromptByOwnerLoc(ownerPriority)
1434
    {
1435
        local owner;
1436
1437
        /* show in owner or location format, as appropriate */
1438
        if ((owner = getNominalOwner()) != nil
1439
            && (ownerPriority || isDirectlyIn(owner)))
1440
        {
1441
            /* we are showing by owner - let the owner know about it */
1442
            owner.notePromptByPossAdj();
1443
        }
1444
    }
1445
1446
    /*
1447
     *   Note that we're being used in a prompt question with our
1448
     *   possessive adjective.  If we're a 'him' or a 'her', set our
1449
     *   pronoun antecedent so that the player's response to the prompt
1450
     *   question can refer back to the prompt text by pronoun.
1451
     */
1452
    notePromptByPossAdj()
1453
    {
1454
        if (isHim)
1455
            gPlayerChar.setHim(self);
1456
        if (isHer)
1457
            gPlayerChar.setHer(self);
1458
    }
1459
1460
    /*
1461
     *   My name with an indefinite article.  By default, we figure out
1462
     *   which article to use (a, an, some) automatically.
1463
     *
1464
     *   In rare cases, the automatic determination might get it wrong,
1465
     *   since some English spellings defy all of the standard
1466
     *   orthographic rules and must simply be handled as special cases;
1467
     *   for example, the algorithmic determination doesn't know about
1468
     *   silent-h words like "hour".  When the automatic determination
1469
     *   gets it wrong, simply override this routine to specify the
1470
     *   correct article explicitly.
1471
     */
1472
    aName = (aNameFrom(name))
1473
1474
    /* the indefinite-article name in the objective case */
1475
    aNameObj { return aName; }
1476
1477
    /*
1478
     *   Apply an indefinite article ("a box", "an orange", "some lint")
1479
     *   to the given name.  We'll try to figure out which indefinite
1480
     *   article to use based on what kind of noun phrase we use for our
1481
     *   name (singular, plural, or a "mass noun" like "lint"), and our
1482
     *   spelling.
1483
     *
1484
     *   By default, we'll use the article "a" if the name starts with a
1485
     *   consonant, or "an" if it starts with a vowel.
1486
     *
1487
     *   If the name starts with a "y", we'll look at the second letter;
1488
     *   if it's a consonant, we'll use "an", otherwise "a" (hence "an
1489
     *   yttrium block" but "a yellow brick").
1490
     *
1491
     *   If the object is marked as having plural usage, we will use
1492
     *   "some" as the article ("some pants" or "some shrubs").
1493
     *
1494
     *   Some objects will want to override the default behavior, because
1495
     *   the lexical rules about when to use "a" and "an" are not without
1496
     *   exception.  For example, silent-"h" words ("honor") are written
1497
     *   with "an", and "h" words with a pronounced but weakly stressed
1498
     *   initial "h" are sometimes used with "an" ("an historian").  Also,
1499
     *   some 'y' words might not follow the generic 'y' rule.
1500
     *
1501
     *   'U' words are especially likely not to follow any lexical rule -
1502
     *   any 'u' word that sounds like it starts with 'y' should use 'a'
1503
     *   rather than 'an', but there's no good way to figure that out just
1504
     *   looking at the spelling (consider "a universal symbol" and "an
1505
     *   unimportant word", or "a unanimous decision" and "an unassuming
1506
     *   man").  We simply always use 'an' for a word starting with 'u',
1507
     *   but this will have to be overridden when the 'u' sounds like 'y'.
1508
     */
1509
    aNameFrom(str)
1510
    {
1511
        /* remember the original source string */
1512
        local inStr = str;
1513
1514
        /*
1515
         *   The complete list of unaccented, accented, and ligaturized
1516
         *   Latin vowels from the Unicode character set.  (The Unicode
1517
         *   database doesn't classify characters as vowels or the like,
1518
         *   so it seems the only way we can come up with this list is
1519
         *   simply to enumerate the vowels.)
1520
         *
1521
         *   These are all lower-case letters; all of these are either
1522
         *   exclusively lower-case or have upper-case equivalents that
1523
         *   map to these lower-case letters.
1524
         *
1525
         *   (Note an implementation detail: the compiler will append all
1526
         *   of these strings together at compile time, so we don't have
1527
         *   to perform all of this concatenation work each time we
1528
         *   execute this method.)
1529
         *
1530
         *   Note that we consider any word starting with an '8' to start
1531
         *   with a vowel, since 'eight' and 'eighty' both take 'an'.
1532
         */
1533
        local vowels = '8aeiou\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6'
1534
                       + '\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF'
1535
                       + '\u00F2\u00F3\u00F4\u00F5\u00F6\u00F8\u00F9\u00FA'
1536
                       + '\u00FB\u00FC\u0101\u0103\u0105\u0113\u0115\u0117'
1537
                       + '\u0119\u011B\u0129\u012B\u012D\u012F\u014D\u014F'
1538
                       + '\u0151\u0169\u016B\u016D\u016F\u0171\u0173\u01A1'
1539
                       + '\u01A3\u01B0\u01CE\u01D0\u01D2\u01D4\u01D6\u01D8'
1540
                       + '\u01DA\u01DC\u01DF\u01E1\u01E3\u01EB\u01ED\u01FB'
1541
                       + '\u01FD\u01FF\u0201\u0203\u0205\u0207\u0209\u020B'
1542
                       + '\u020D\u020F\u0215\u0217\u0254\u025B\u0268\u0289'
1543
                       + '\u1E01\u1E15\u1E17\u1E19\u1E1B\u1E1D\u1E2D\u1E2F'
1544
                       + '\u1E4D\u1E4F\u1E51\u1E53\u1E73\u1E75\u1E77\u1E79'
1545
                       + '\u1E7B\u1E9A\u1EA1\u1EA3\u1EA5\u1EA7\u1EA9\u1EAB'
1546
                       + '\u1EAD\u1EAF\u1EB1\u1EB3\u1EB5\u1EB7\u1EB9\u1EBB'
1547
                       + '\u1EBD\u1EBF\u1EC1\u1EC3\u1EC5\u1EC7\u1EC9\u1ECB'
1548
                       + '\u1ECD\u1ECF\u1ED1\u1ED3\u1ED5\u1ED7\u1ED9\u1EDB'
1549
                       + '\u1EDD\u1EDF\u1EE1\u1EE3\u1EE5\u1EE7\u1EE9\u1EEB'
1550
                       + '\u1EED\u1EEF\u1EF1\uFF41\uFF4F\uFF55';
1551
1552
        /*
1553
         *   A few upper-case vowels in unicode don't have lower-case
1554
         *   mappings - consider them separately.
1555
         */
1556
        local vowelsUpperOnly = '\u0130\u019f';
1557
1558
        /*
1559
         *   the various accented forms of the letter 'y' - these are all
1560
         *   lower-case versions; the upper-case versions all map to these
1561
         */
1562
        local ys = 'y\u00FD\u00FF\u0177\u01B4\u1E8F\u1E99\u1EF3'
1563
                   + '\u1EF5\u1EF7\u1EF9\u24B4\uFF59';
1564
1565
        /* if the name is already qualified, don't add an article at all */
1566
        if (isQualifiedName)
1567
            return str;
1568
1569
        /* if it's plural or a mass noun, use "some" as the article */
1570
        if (isPlural || isMassNoun)
1571
        {
1572
            /* use "some" as the article */
1573
            return 'some ' + str;
1574
        }
1575
        else
1576
        {
1577
            local firstChar;
1578
            local firstCharLower;
1579
1580
            /* if it's empty, just use "a" */
1581
            if (inStr == '')
1582
                return 'a';
1583
1584
            /* get the first character of the name */
1585
            firstChar = inStr.substr(1, 1);
1586
1587
            /* skip any leading HTML tags */
1588
            if (rexMatch(patTagOrQuoteChar, firstChar) != nil)
1589
            {
1590
                /*
1591
                 *   Scan for tags.  Note that this pattern isn't quite
1592
                 *   perfect, as it won't properly ignore close-brackets
1593
                 *   that are inside quoted material, but it should be good
1594
                 *   enough for nearly all cases in practice.  In cases too
1595
                 *   complex for this pattern, the object will simply have
1596
                 *   to override aDesc.
1597
                 */
1598
                local len = rexMatch(patLeadingTagOrQuote, inStr);
1599
1600
                /* if we got a match, strip out the leading tags */
1601
                if (len != nil)
1602
                {
1603
                    /* strip off the leading tags */
1604
                    inStr = inStr.substr(len + 1);
1605
1606
                    /* re-fetch the first character */
1607
                    firstChar = inStr.substr(1, 1);
1608
                }
1609
            }
1610
1611
            /* get the lower-case version of the first character */
1612
            firstCharLower = firstChar.toLower();
1613
1614
            /*
1615
             *   if the first word of the name is only one letter long,
1616
             *   treat it specially
1617
             */
1618
            if (rexMatch(patOneLetterWord, inStr) != nil)
1619
            {
1620
                /*
1621
                 *   We have a one-letter first word, such as "I-beam" or
1622
                 *   "M-ray sensor", or just "A".  Choose the article based
1623
                 *   on the pronunciation of the letter as a letter.
1624
                 */
1625
                return (rexMatch(patOneLetterAnWord, inStr) != nil
1626
                        ? 'an ' : 'a ') + str;
1627
            }
1628
1629
            /*
1630
             *   look for the first character in the lower-case and
1631
             *   upper-case-only vowel lists - if we find it, it takes
1632
             *   'an'
1633
             */
1634
            if (vowels.find(firstCharLower) != nil
1635
                || vowelsUpperOnly.find(firstChar) != nil)
1636
            {
1637
                /* it starts with a vowel */
1638
                return 'an ' + str;
1639
            }
1640
            else if (ys.find(firstCharLower) != nil)
1641
            {
1642
                local secondChar;
1643
1644
                /* get the second character, if there is one */
1645
                secondChar = inStr.substr(2, 1);
1646
1647
                /*
1648
                 *   It starts with 'y' - if the second letter is a
1649
                 *   consonant, assume the 'y' is a vowel sound, hence we
1650
                 *   should use 'an'; otherwise assume the 'y' is a
1651
                 *   diphthong 'ei' sound, which means we should use 'a'.
1652
                 *   If there's no second character at all, or the second
1653
                 *   character isn't alphabetic, use 'a' - "a Y" or "a
1654
                 *   Y-connector".
1655
                 */
1656
                if (secondChar == ''
1657
                    || rexMatch(patIsAlpha, secondChar) == nil
1658
                    || vowels.find(secondChar.toLower()) != nil
1659
                    || vowelsUpperOnly.find(secondChar) != nil)
1660
                {
1661
                    /*
1662
                     *   it's just one character, or the second character
1663
                     *   is non-alphabetic, or the second character is a
1664
                     *   vowel - in any of these cases, use 'a'
1665
                     */
1666
                    return 'a ' + str;
1667
                }
1668
                else
1669
                {
1670
                    /* the second character is a consonant - use 'an' */
1671
                    return 'an ' + str;
1672
                }
1673
            }
1674
            else if (rexMatch(patElevenEighteen, inStr) != nil)
1675
            {
1676
                /*
1677
                 *   it starts with '11' or '18', so it takes 'an' ('an
1678
                 *   11th-hour appeal', 'an 18-hole golf course')
1679
                 */
1680
                return 'an ' + str;
1681
            }
1682
            else
1683
            {
1684
                /* it starts with a consonant */
1685
                return 'a ' + str;
1686
            }
1687
        }
1688
    }
1689
1690
    /* pre-compile some regular expressions for aName */
1691
    patTagOrQuoteChar = static new RexPattern('[<"\']')
1692
    patLeadingTagOrQuote = static new RexPattern(
1693
        '(<langle><^rangle>+<rangle>|"|\')+')
1694
    patOneLetterWord = static new RexPattern('<alpha>(<^alpha>|$)')
1695
    patOneLetterAnWord = static new RexPattern('<nocase>[aefhilmnorsx]')
1696
    patIsAlpha = static new RexPattern('<alpha>')
1697
    patElevenEighteen = static new RexPattern('1[18](<^digit>|$)')
1698
1699
    /*
1700
     *   Get the default plural name.  By default, we'll use the
1701
     *   algorithmic plural determination, which is based on the spelling
1702
     *   of the name.
1703
     *
1704
     *   The algorithm won't always get it right, since some English
1705
     *   plurals are irregular ("men", "children", "Attorneys General").
1706
     *   When the name doesn't fit the regular spelling patterns for
1707
     *   plurals, the object should simply override this routine to return
1708
     *   the correct plural name string.
1709
     */
1710
    pluralName = (pluralNameFrom(name))
1711
1712
    /*
1713
     *   Get the plural form of the given name string.  If the name ends in
1714
     *   anything other than 'y', we'll add an 's'; otherwise we'll replace
1715
     *   the 'y' with 'ies'.  We also handle abbreviations and individual
1716
     *   letters specially.
1717
     *
1718
     *   This can only deal with simple adjective-noun forms.  For more
1719
     *   complicated forms, particularly for compound words, it must be
1720
     *   overridden (e.g., "Attorney General" -> "Attorneys General",
1721
     *   "man-of-war" -> "men-of-war").  Likewise, names with irregular
1722
     *   plurals ('child' -> 'children', 'man' -> 'men') must be handled
1723
     *   with overrides.
1724
     */
1725
    pluralNameFrom(str)
1726
    {
1727
        local len;
1728
        local lastChar;
1729
        local lastPair;
1730
1731
        /*
1732
         *   if it's marked as having plural usage, just use the ordinary
1733
         *   name, since it's already plural
1734
         */
1735
        if (isPlural)
1736
            return str;
1737
1738
        /* check for a 'phrase of phrase' format */
1739
        if (rexMatch(patOfPhrase, str) != nil)
1740
        {
1741
            local ofSuffix;
1742
1743
            /*
1744
             *   Pull out the two parts - the part up to the 'of' is the
1745
             *   part we'll actually pluralize, and the rest is a suffix
1746
             *   we'll stick on the end of the pluralized part.
1747
             */
1748
            str = rexGroup(1)[3];
1749
            ofSuffix = rexGroup(2)[3];
1750
1751
            /*
1752
             *   now pluralize the part up to the 'of' using the normal
1753
             *   rules, then add the rest back in at the end
1754
             */
1755
            return pluralNameFrom(str) + ofSuffix;
1756
        }
1757
1758
        /* if there's no short description, return an empty string */
1759
        len = str.length();
1760
        if (len == 0)
1761
            return '';
1762
1763
        /*
1764
         *   If it's only one character long, handle it specially.  If it's
1765
         *   a lower-case letter, add an apostrophe-S.  If it's a capital
1766
         *   A, E, I, M, U, or V, we'll add apostrophe-S (because these
1767
         *   could be confused with words or common abbreviations if we
1768
         *   just added "s": As, Es, Is, Ms, Us, Vs).  If it's anything
1769
         *   else (any other capital letter, or any non-letter character),
1770
         *   we'll just add an "s".
1771
         */
1772
        if (len == 1)
1773
        {
1774
            if (rexMatch(patSingleApostropheS, str) != nil)
1775
                return str + '&rsquo;s';
1776
            else
1777
                return str + 's';
1778
        }
1779
1780
        /* get the last character of the name, and the last pair of chars */
1781
        lastChar = str.substr(len, 1);
1782
        lastPair = (len == 1 ? lastChar : str.substr(len - 1, 2));
1783
1784
        /*
1785
         *   If the last letter is a capital letter, assume it's an
1786
         *   abbreviation without embedded periods (CPA, PC), in which case
1787
         *   we just add an "s" (CPAs, PCs).  Likewise, if it's a number,
1788
         *   just add "s": "the 1940s", "the low 20s".
1789
         */
1790
        if (rexMatch(patUpperOrDigit, lastChar) != nil)
1791
            return str + 's';
1792
1793
        /*
1794
         *   If the last character is a period, it must be an abbreviation
1795
         *   with embedded periods (B.A., B.S., Ph.D.).  In these cases,
1796
         *   add an apostrophe-S.
1797
         */
1798
        if (lastChar == '.')
1799
            return str + '&rsquo;s';
1800
1801
        /*
1802
         *   If it ends in a non-vowel followed by 'y', change -y to -ies.
1803
         *   (This doesn't apply if a vowel precedes a terminal 'y'; in
1804
         *   such cases, we'll use the normal '-s' ending instead: "survey"
1805
         *   -> "surveys", "essay" -> "essays", "day" -> "days".)
1806
         */
1807
        if (rexMatch(patVowelY, lastPair) != nil)
1808
            return str.substr(1, len - 1) + 'ies';
1809
1810
        /* if it ends in s, x, z, or h, add -es */
1811
        if ('sxzh'.find(lastChar) != nil)
1812
            return str + 'es';
1813
1814
        /* for anything else, just add -s */
1815
        return str + 's';
1816
    }
1817
1818
    /* some pre-compiled patterns for pluralName */
1819
    patSingleApostropheS = static new RexPattern('<case><lower|A|E|I|M|U|V>')
1820
    patUpperOrDigit = static new RexPattern('<upper|digit>')
1821
    patVowelY = static new RexPattern('[^aeoiu]y')
1822
    patOfPhrase = static new RexPattern(
1823
        '<nocase>(.+?)(<space>+of<space>+.+)')
1824
1825
    /* get my name plus a being verb ("the box is") */
1826
    nameIs { return theName + ' ' + verbToBe; }
1827
1828
    /* get my name plus a negative being verb ("the box isn't") */
1829
    nameIsnt { return nameIs + 'n&rsquo;t'; }
1830
1831
    /*
1832
     *   My name with the given regular verb in agreement: in the present
1833
     *   tense, if my name has singular usage, we'll add 's' to the verb,
1834
     *   otherwise we won't.  In the past tense, we'll add 'd' (or 'ed').
1835
     *   This can't be used with irregular verbs, or with regular verbs
1836
     *   that have the last consonant repeated before the past -ed ending,
1837
     *   such as "deter".
1838
     */
1839
    nameVerb(verb) { return theName + ' ' + conjugateRegularVerb(verb); }
1840
1841
    /* being verb agreeing with this object as subject */
1842
    verbToBe
1843
    {
1844
        return tSel(isPlural ? 'are' : 'is', isPlural ? 'were' : 'was');
1845
    }
1846
1847
    /* past tense being verb agreeing with object as subject */
1848
    verbWas { return tSel(isPlural ? 'were' : 'was', 'had been'); }
1849
1850
    /* 'have' verb agreeing with this object as subject */
1851
    verbToHave { return tSel(isPlural ? 'have' : 'has', 'had'); }
1852
1853
    /*
1854
     *   A few common irregular verbs and name-plus-verb constructs,
1855
     *   defined for convenience.
1856
     */
1857
    verbToDo = (tSel('do' + verbEndingEs, 'did'))
1858
    nameDoes = (theName + ' ' + verbToDo)
1859
    verbToGo = (tSel('go' + verbEndingEs, 'went'))
1860
    verbToCome = (tSel('come' + verbEndingS, 'came'))
1861
    verbToLeave = (tSel('leave' + verbEndingS, 'left'))
1862
    verbToSee = (tSel('see' + verbEndingS, 'saw'))
1863
    nameSees = (theName + ' ' + verbToSee)
1864
    verbToSay = (tSel('say' + verbEndingS, 'said'))
1865
    nameSays = (theName + ' ' + verbToSay)
1866
    verbMust = (tSel('must', 'had to'))
1867
    verbCan = (tSel('can', 'could'))
1868
    verbCannot = (tSel('cannot', 'could not'))
1869
    verbCant = (tSel('can&rsquo;t', 'couldn&rsquo;t'))
1870
    verbWill = (tSel('will', 'would'))
1871
    verbWont = (tSel('won&rsquo;t', 'wouldn&rsquo;t'))
1872
1873
    /*
1874
     *   Verb endings for regular '-s' verbs, agreeing with this object as
1875
     *   the subject.  We define several methods each of which handles the
1876
     *   past tense differently.
1877
     *
1878
     *   verbEndingS doesn't try to handle the past tense at all - use it
1879
     *   only in places where you know for certain that you'll never need
1880
     *   the past tense form, or in expressions constructed with the tSel
1881
     *   macro: use verbEndingS as the macro's first argument, and specify
1882
     *   the past tense ending explicitly as the second argument.  For
1883
     *   example, you could generate the correctly conjugated form of the
1884
     *   verb "to fit" for an object named "key" with an expression such
1885
     *   as:
1886
     *
1887
     *   'fit' + tSel(key.verbEndingS, 'ted')
1888
     *
1889
     *   This would generate 'fit', 'fits', or 'fitted' according to number
1890
     *   and tense.
1891
     *
1892
     *   verbEndingSD and verbEndingSEd return 'd' and 'ed' respectively in
1893
     *   the past tense.
1894
     *
1895
     *   verbEndingSMessageBuilder_ is for internal use only: it assumes
1896
     *   that the correct ending to be displayed in the past tense is
1897
     *   stored in langMessageBuilder.pastEnding_.  It is used as part of
1898
     *   the string parameter substitution mechanism.
1899
     */
1900
    verbEndingS { return isPlural ? '' : 's'; }
1901
    verbEndingSD = (tSel(verbEndingS, 'd'))
1902
    verbEndingSEd = (tSel(verbEndingS, 'ed'))
1903
    verbEndingSMessageBuilder_ =
1904
        (tSel(verbEndingS, langMessageBuilder.pastEnding_))
1905
1906
    /*
1907
     *   Verb endings (present or past) for regular '-es/-ed' and
1908
     *   '-y/-ies/-ied' verbs, agreeing with this object as the subject.
1909
     */
1910
    verbEndingEs { return tSel(isPlural ? '' : 'es', 'ed'); }
1911
    verbEndingIes { return tSel(isPlural ? 'y' : 'ies', 'ied'); }
1912
1913
    /*
1914
     *   Dummy name - this simply displays nothing; it's used for cases
1915
     *   where messageBuilder substitutions want to refer to an object (for
1916
     *   internal bookkeeping) without actually showing the name of the
1917
     *   object in the output text.  This should always simply return an
1918
     *   empty string.
1919
     */
1920
    dummyName = ''
1921
1922
    /*
1923
     *   Invoke a property (with an optional argument list) on this object
1924
     *   while temporarily switching to the present tense, and return the
1925
     *   result.
1926
     */
1927
    propWithPresent(prop, [args])
1928
    {
1929
        return withPresent({: self.(prop)(args...)});
1930
    }
1931
1932
    /*
1933
     *   Method for internal use only: invoke on this object the property
1934
     *   stored in langMessageBuilder.fixedTenseProp_ while temporarily
1935
     *   switching to the present tense, and return the result.  This is
1936
     *   used as part of the string parameter substitution mechanism.
1937
     */
1938
    propWithPresentMessageBuilder_
1939
    {
1940
        return propWithPresent(langMessageBuilder.fixedTenseProp_);
1941
    }
1942
1943
    /*
1944
     *   For the most part, "strike" has the same meaning as "hit", so
1945
     *   define this as a synonym for "attack" most objects.  There are a
1946
     *   few English idioms where "strike" means something different, as
1947
     *   in "strike match" or "strike tent."
1948
     */
1949
    dobjFor(Strike) asDobjFor(Attack)
1950
;
1951
1952
/* ------------------------------------------------------------------------ */
1953
/*
1954
 *   An object that uses the same name as another object.  This maps all of
1955
 *   the properties involved in supplying the object's name, number, and
1956
 *   other usage information from this object to a given target object, so
1957
 *   that all messages involving this object use the same name as the
1958
 *   target object.  This is a mix-in class that can be used with any other
1959
 *   class.
1960
 *   
1961
 *   Note that we map only the *reported* name for the object.  We do NOT
1962
 *   give this object any vocabulary from the other object; in other words,
1963
 *   we don't enter this object into the dictionary with the other object's
1964
 *   vocabulary words.  
1965
 */
1966
class NameAsOther: object
1967
    /* the target object - we'll use the same name as this object */
1968
    targetObj = nil
1969
1970
    /* map our naming and usage properties to the target object */
1971
    isPlural = (targetObj.isPlural)
1972
    isMassNoun = (targetObj.isMassNoun)
1973
    isHim = (targetObj.isHim)
1974
    isHer = (targetObj.isHer)
1975
    isIt = (targetObj.isIt)
1976
    isProperName = (targetObj.isProperName)
1977
    isQualifiedName = (targetObj.isQualifiedName)
1978
    name = (targetObj.name)
1979
1980
    /* map the derived name properties as well, in case any are overridden */
1981
    disambigName = (targetObj.disambigName)
1982
    theDisambigName = (targetObj.theDisambigName)
1983
    aDisambigName = (targetObj.aDisambigName)
1984
    countDisambigName(cnt) { return targetObj.countDisambigName(cnt); }
1985
    disambigEquivName = (targetObj.disambigEquivName)
1986
    listName = (targetObj.listName)
1987
    countName(cnt) { return targetObj.countName(cnt); }
1988
1989
    /* map the pronoun properites, in case any are overridden */
1990
    itNom = (targetObj.itNom)
1991
    itObj = (targetObj.itObj)
1992
    itPossAdj = (targetObj.itPossAdj)
1993
    itPossNoun = (targetObj.itPossNoun)
1994
    itReflexive = (targetObj.itReflexive)
1995
    thatNom = (targetObj.thatNom)
1996
    thatObj = (targetObj.thatObj)
1997
    thatIsContraction = (targetObj.thatIsContraction)
1998
    itIs = (targetObj.itIs)
1999
    itIsContraction = (targetObj.itIsContraction)
2000
    itVerb(verb) { return targetObj.itVerb(verb); }
2001
    conjugateRegularVerb(verb)
2002
        { return targetObj.conjugateRegularVerb(verb); }
2003
    theName = (targetObj.theName)
2004
    theNameObj = (targetObj.theNameObj)
2005
    theNamePossAdj = (targetObj.theNamePossAdj)
2006
    theNamePossNoun = (targetObj.theNamePossNoun)
2007
    theNameWithOwner = (targetObj.theNameWithOwner)
2008
    aNameOwnerLoc(ownerPri)
2009
        { return targetObj.aNameOwnerLoc(ownerPri); }
2010
    theNameOwnerLoc(ownerPri)
2011
        { return targetObj.theNameOwnerLoc(ownerPri); }
2012
    countNameOwnerLoc(cnt, ownerPri)
2013
        { return targetObj.countNameOwnerLoc(cnt, ownerPri); }
2014
    notePromptByOwnerLoc(ownerPri)
2015
        { targetObj.notePromptByOwnerLoc(ownerPri); }
2016
    notePromptByPossAdj()
2017
        { targetObj.notePromptByPossAdj(); }
2018
    aName = (targetObj.aName)
2019
    aNameObj = (targetObj.aNameObj)
2020
    pluralName = (targetObj.pluralName)
2021
    nameIs = (targetObj.nameIs)
2022
    nameIsnt = (targetObj.nameIsnt)
2023
    nameVerb(verb) { return targetObj.nameVerb(verb); }
2024
    verbToBe = (targetObj.verbToBe)
2025
    verbWas = (targetObj.verbWas)
2026
    verbToHave = (targetObj.verbToHave)
2027
    verbToDo = (targetObj.verbToDo)
2028
    nameDoes = (targetObj.nameDoes)
2029
    verbToGo = (targetObj.verbToGo)
2030
    verbToCome = (targetObj.verbToCome)
2031
    verbToLeave = (targetObj.verbToLeave)
2032
    verbToSee = (targetObj.verbToSee)
2033
    nameSees = (targetObj.nameSees)
2034
    verbToSay = (targetObj.verbToSay)
2035
    nameSays = (targetObj.nameSays)
2036
    verbMust = (targetObj.verbMust)
2037
    verbCan = (targetObj.verbCan)
2038
    verbCannot = (targetObj.verbCannot)
2039
    verbCant = (targetObj.verbCant)
2040
    verbWill = (targetObj.verbWill)
2041
    verbWont = (targetObj.verbWont)
2042
2043
    verbEndingS = (targetObj.verbEndingS)
2044
    verbEndingSD = (targetObj.verbEndingSD)
2045
    verbEndingSEd = (targetObj.verbEndingSEd)
2046
    verbEndingEs = (targetObj.verbEndingEs)
2047
    verbEndingIes = (targetObj.verbEndingIes)
2048
;
2049
2050
/*
2051
 *   Name as Parent - this is a special case of NameAsOther that uses the
2052
 *   lexical parent of a nested object as the target object.  (The lexical
2053
 *   parent is the enclosing object in a nested object definition; in other
2054
 *   words, it's the object in which the nested object is embedded.)  
2055
 */
2056
class NameAsParent: NameAsOther
2057
    targetObj = (lexicalParent)
2058
;
2059
2060
/*
2061
 *   ChildNameAsOther is a mix-in class that can be used with NameAsOther
2062
 *   to add the various childInXxx naming to the mapped properties.  The
2063
 *   childInXxx names are the names generated when another object is
2064
 *   described as located within this object; by mapping these properties
2065
 *   to our target object, we ensure that we use exactly the same phrasing
2066
 *   as we would if the contained object were actually contained by our
2067
 *   target rather than by us.
2068
 *   
2069
 *   Note that this should always be used in combination with NameAsOther:
2070
 *   
2071
 *   myObj: NameAsOther, ChildNameAsOther, Thing ...
2072
 *   
2073
 *   You can also use it the same way in combination with a subclass of
2074
 *   NameAsOther, such as NameAsParent.  
2075
 */
2076
class ChildNameAsOther: object
2077
    objInPrep = (targetObj.objInPrep)
2078
    actorInPrep = (targetObj.actorInPrep)
2079
    actorOutOfPrep = (targetObj.actorOutOfPrep)
2080
    actorIntoPrep = (targetObj.actorIntoPrep)
2081
    childInName(childName) { return targetObj.childInName(childName); }
2082
    childInNameWithOwner(childName)
2083
        { return targetObj.childInNameWithOwner(childName); }
2084
    childInNameGen(childName, myName)
2085
        { return targetObj.childInNameGen(childName, myName); }
2086
    actorInName = (targetObj.actorInName)
2087
    actorOutOfName = (targetObj.actorOutOfName)
2088
    actorIntoName = (targetObj.actorIntoName)
2089
    actorInAName = (targetObj.actorInAName)
2090
;
2091
2092
2093
/* ------------------------------------------------------------------------ */
2094
/*
2095
 *   Language modifications for the specialized container types
2096
 */
2097
modify Surface
2098
    /*
2099
     *   objects contained in a Surface are described as being on the
2100
     *   Surface
2101
     */
2102
    objInPrep = 'on'
2103
    actorInPrep = 'on'
2104
    actorOutOfPrep = 'off of'
2105
;
2106
2107
modify Underside
2108
    objInPrep = 'under'
2109
    actorInPrep = 'under'
2110
    actorOutOfPrep = 'from under'
2111
;
2112
2113
modify RearContainer
2114
    objInPrep = 'behind'
2115
    actorInPrep = 'behind'
2116
    actorOutOfPrep = 'from behind'
2117
;
2118
2119
/* ------------------------------------------------------------------------ */
2120
/*
2121
 *   Language modifications for Actor.
2122
 *   
2123
 *   An Actor has a "referral person" setting, which determines how we
2124
 *   refer to the actor; this is almost exclusively for the use of the
2125
 *   player character.  The typical convention is that we refer to the
2126
 *   player character in the second person, but a game can override this on
2127
 *   an actor-by-actor basis.  
2128
 */
2129
modify Actor
2130
    /* by default, use my pronoun for my name */
2131
    name = (itNom)
2132
2133
    /*
2134
     *   Pronoun selector.  This returns an index for selecting pronouns
2135
     *   or other words based on number and gender, taking into account
2136
     *   person, number, and gender.  The value returned is the sum of the
2137
     *   following components:
2138
     *
2139
     *   number/gender:
2140
     *.  - singular neuter = 1
2141
     *.  - singular masculine = 2
2142
     *.  - singular feminine = 3
2143
     *.  - plural = 4
2144
     *
2145
     *   person:
2146
     *.  - first person = 0
2147
     *.  - second person = 4
2148
     *.  - third person = 8
2149
     *
2150
     *   The result can be used as a list selector as follows (1=first
2151
     *   person, etc; s=singular, p=plural; n=neuter, m=masculine,
2152
     *   f=feminine):
2153
     *
2154
     *   [1/s/n, 1/s/m, 1/s/f, 1/p, 2/s/n, 2/s/m, 2/s/f, 2/p,
2155
     *.  3/s/n, 3/s/m, 3/s/f, 3/p]
2156
     */
2157
    pronounSelector
2158
    {
2159
        return ((referralPerson - FirstPerson)*4
2160
                + (isPlural ? 4 : isHim ? 2 : isHer ? 3 : 1));
2161
    }
2162
2163
    /*
2164
     *   get the verb form selector index for the person and number:
2165
     *
2166
     *   [1/s, 2/s, 3/s, 1/p, 2/p, 3/p]
2167
     */
2168
    conjugationSelector
2169
    {
2170
        return (referralPerson + (isPlural ? 3 : 0));
2171
    }
2172
2173
    /*
2174
     *   get an appropriate pronoun for the object in the appropriate
2175
     *   person for the nominative case, objective case, possessive
2176
     *   adjective, possessive noun, and objective reflexive
2177
     */
2178
    itNom
2179
    {
2180
        return ['I', 'I', 'I', 'we',
2181
               'you', 'you', 'you', 'you',
2182
               'it', 'he', 'she', 'they'][pronounSelector];
2183
    }
2184
    itObj
2185
    {
2186
        return ['me', 'me', 'me', 'us',
2187
               'you', 'you', 'you', 'you',
2188
               'it', 'him', 'her', 'them'][pronounSelector];
2189
    }
2190
    itPossAdj
2191
    {
2192
        return ['my', 'my', 'my', 'our',
2193
               'your', 'your', 'your', 'your',
2194
               'its', 'his', 'her', 'their'][pronounSelector];
2195
    }
2196
    itPossNoun
2197
    {
2198
        return ['mine', 'mine', 'mine', 'ours',
2199
               'yours', 'yours', 'yours', 'yours',
2200
               'its', 'his', 'hers', 'theirs'][pronounSelector];
2201
    }
2202
    itReflexive
2203
    {
2204
        return ['myself', 'myself', 'myself', 'ourselves',
2205
               'yourself', 'yourself', 'yourself', 'yourselves',
2206
               'itself', 'himself', 'herself', 'themselves'][pronounSelector];
2207
    }
2208
2209
    /*
2210
     *   Demonstrative pronoun, nominative case.  We'll use personal a
2211
     *   personal pronoun if we have a gender or we're in the first or
2212
     *   second person, otherwise we'll use 'that' or 'those' as we would
2213
     *   for an inanimate object.
2214
     */
2215
    thatNom
2216
    {
2217
        return ['I', 'I', 'I', 'we',
2218
               'you', 'you', 'you', 'you',
2219
               'that', 'he', 'she', 'those'][pronounSelector];
2220
    }
2221
2222
    /* demonstrative pronoun, objective case */
2223
    thatObj
2224
    {
2225
        return ['me', 'me', 'me', 'us',
2226
               'you', 'you', 'you', 'you',
2227
               'that', 'him', 'her', 'those'][pronounSelector];
2228
    }
2229
2230
    /* demonstrative pronoun, nominative case with 'is' contraction */
2231
    thatIsContraction
2232
    {
2233
        return thatNom
2234
            + tSel(['&rsquo;m', '&rsquo;re', '&rsquo;s',
2235
                    '&rsquo;re', '&rsquo;re', ' are'][conjugationSelector],
2236
                   ' ' + verbToBe);
2237
    }
2238
2239
    /*
2240
     *   We don't need to override itIs: the base class handling works for
2241
     *   actors too.
2242
     */
2243
2244
    /* get my pronoun with a being verb contraction ("the box's") */
2245
    itIsContraction
2246
    {
2247
        return itNom + tSel(
2248
            '&rsquo;'
2249
            + ['m', 're', 's', 're', 're', 're'][conjugationSelector],
2250
            ' ' + verbToBe);
2251
    }
2252
2253
    /*
2254
     *   Conjugate a regular verb in the present or past tense for our
2255
     *   person and number.
2256
     *
2257
     *   In the present tense, this is pretty easy: we add an 's' for the
2258
     *   third person singular, and leave the verb unchanged for every
2259
     *   other case.  The only complication is that we must check some
2260
     *   special cases to add the -s suffix: -y -> -ies, -o -> -oes.
2261
     *
2262
     *   In the past tense, we use the inherited handling since the past
2263
     *   tense ending doesn't vary with person.
2264
     */
2265
    conjugateRegularVerb(verb)
2266
    {
2267
        /*
2268
         *   If we're in the third person or if we use the past tense,
2269
         *   inherit the default handling; otherwise, use the base verb
2270
         *   form regardless of number (regular verbs use the same
2271
         *   conjugated forms for every case but third person singular: I
2272
         *   ask, you ask, we ask, they ask).
2273
         */
2274
        if (referralPerson != ThirdPerson && !gameMain.usePastTense)
2275
        {
2276
            /*
2277
             *   we're not using the third-person or the past tense, so the
2278
             *   conjugation is the same as the base verb form
2279
             */
2280
            return verb;
2281
        }
2282
        else
2283
        {
2284
            /*
2285
             *   we're using the third person or the past tense, so inherit
2286
             *   the base class handling, which conjugates these forms
2287
             */
2288
            return inherited(verb);
2289
        }
2290
    }
2291
2292
    /*
2293
     *   Get the name with a definite article ("the box").  If the
2294
     *   narrator refers to us in the first or second person, use a
2295
     *   pronoun rather than the short description.
2296
     */
2297
    theName
2298
        { return (referralPerson == ThirdPerson ? inherited : itNom); }
2299
2300
    /* theName in objective case */
2301
    theNameObj
2302
        { return (referralPerson == ThirdPerson ? inherited : itObj); }
2303
2304
    /* theName as a possessive adjective */
2305
    theNamePossAdj
2306
        { return (referralPerson == ThirdPerson ? inherited : itPossAdj); }
2307
2308
    /* theName as a possessive noun */
2309
    theNamePossNoun
2310
    { return (referralPerson == ThirdPerson ? inherited : itPossNoun); }
2311
2312
    /*
2313
     *   Get the name with an indefinite article.  Use the same rules of
2314
     *   referral person as for definite articles.
2315
     */
2316
    aName { return (referralPerson == ThirdPerson ? inherited : itNom); }
2317
2318
    /* aName in objective case */
2319
    aNameObj { return (referralPerson == ThirdPerson ? inherited : itObj); }
2320
2321
    /* being verb agreeing with this object as subject */
2322
    verbToBe
2323
    {
2324
        return tSel(['am', 'are', 'is', 'are', 'are', 'are'],
2325
                    ['was', 'were', 'was', 'were', 'were', 'were'])
2326
               [conjugationSelector];
2327
    }
2328
2329
    /* past tense being verb agreeing with this object as subject */
2330
    verbWas
2331
    {
2332
        return tSel(['was', 'were', 'was', 'were', 'were', 'were']
2333
                    [conjugationSelector], 'had been');
2334
    }
2335
2336
    /* 'have' verb agreeing with this object as subject */
2337
    verbToHave
2338
    {
2339
        return tSel(['have', 'have', 'has', 'have', 'have', 'have']
2340
                    [conjugationSelector], 'had');
2341
    }
2342
2343
    /*
2344
     *   verb endings for regular '-s' and '-es' verbs, agreeing with this
2345
     *   object as the subject
2346
     */
2347
    verbEndingS
2348
    {
2349
        return ['', '', 's', '', '', ''][conjugationSelector];
2350
    }
2351
    verbEndingEs
2352
    {
2353
        return tSel(['', '', 'es', '', '', ''][conjugationSelector], 'ed');
2354
    }
2355
    verbEndingIes
2356
    {
2357
        return tSel(['y', 'y', 'ies', 'y', 'y', 'y'][conjugationSelector],
2358
                    'ied');
2359
    }
2360
2361
    /* "I'm not" doesn't fit the regular "+n't" rule */
2362
    nameIsnt
2363
    {
2364
        return conjugationSelector == 1 && !gameMain.usePastTense
2365
            ? 'I&rsquo;m not' : inherited;
2366
    }
2367
2368
    /*
2369
     *   Show my name for an arrival/departure message.  If we've been seen
2370
     *   before by the player character, we'll show our definite name,
2371
     *   otherwise our indefinite name.
2372
     */
2373
    travelerName(arriving)
2374
        { say(gPlayerChar.hasSeen(self) ? theName : aName); }
2375
2376
    /*
2377
     *   Test to see if we can match the third-person pronouns.  We'll
2378
     *   match these if our inherited test says we match them AND we can
2379
     *   be referred to in the third person.
2380
     */
2381
    canMatchHim = (inherited && canMatch3rdPerson)
2382
    canMatchHer = (inherited && canMatch3rdPerson)
2383
    canMatchIt = (inherited && canMatch3rdPerson)
2384
    canMatchThem = (inherited && canMatch3rdPerson)
2385
2386
    /*
2387
     *   Test to see if we can match a third-person pronoun ('it', 'him',
2388
     *   'her', 'them').  We can unless we're the player character and the
2389
     *   player character is referred to in the first or second person.
2390
     */
2391
    canMatch3rdPerson = (!isPlayerChar || referralPerson == ThirdPerson)
2392
2393
    /*
2394
     *   Set a pronoun antecedent to the given list of ResolveInfo objects.
2395
     *   Pronoun handling is language-specific, so this implementation is
2396
     *   part of the English library, not the generic library.
2397
     *
2398
     *   If only one object is present, we'll set the object to be the
2399
     *   antecedent of 'it', 'him', or 'her', according to the object's
2400
     *   gender.  We'll also set the object as the single antecedent for
2401
     *   'them'.
2402
     *
2403
     *   If we have multiple objects present, we'll set the list to be the
2404
     *   antecedent of 'them', and we'll forget about any antecedent for
2405
     *   'it'.
2406
     *
2407
     *   Note that the input is a list of ResolveInfo objects, so we must
2408
     *   pull out the underlying game objects when setting the antecedents.
2409
     */
2410
    setPronoun(lst)
2411
    {
2412
        /* if the list is empty, ignore it */
2413
        if (lst == [])
2414
            return;
2415
2416
        /*
2417
         *   if we have multiple objects, the entire list is the antecedent
2418
         *   for 'them'; otherwise, it's a singular antecedent which
2419
         *   depends on its gender
2420
         */
2421
        if (lst.length() > 1)
2422
        {
2423
            local objs = lst.mapAll({x: x.obj_});
2424
2425
            /* it's 'them' */
2426
            setThem(objs);
2427
2428
            /* forget any 'it' */
2429
            setIt(nil);
2430
        }
2431
        else if (lst.length() == 1)
2432
        {
2433
            /*
2434
             *   We have only one object, so set it as an antecedent
2435
             *   according to its gender.
2436
             */
2437
            setPronounObj(lst[1].obj_);
2438
        }
2439
    }
2440
2441
    /*
2442
     *   Set a pronoun to refer to multiple potential antecedents.  This is
2443
     *   used when the verb has multiple noun slots - UNLOCK DOOR WITH KEY.
2444
     *   For verbs like this, we have no way of knowing in advance whether
2445
     *   a future pronoun will refer back to the direct object or the
2446
     *   indirect object (etc) - we could just assume that 'it' will refer
2447
     *   to the direct object, but this won't always be what the player
2448
     *   intended.  In natural English, pronoun antecedents must often be
2449
     *   inferred from context at the time of use - so we use the same
2450
     *   approach.
2451
     *
2452
     *   Pass an argument list consisting of ResolveInfo lists - that is,
2453
     *   pass one argument per noun slot in the verb, and make each
2454
     *   argument a list of ResolveInfo objects.  In other words, you call
2455
     *   this just as you would setPronoun(), except you can pass more than
2456
     *   one list argument.
2457
     *
2458
     *   We'll store the multiple objects as antecedents.  When we need to
2459
     *   resolve a future singular pronoun, we'll figure out which of the
2460
     *   multiple antecedents is the most logical choice in the context of
2461
     *   the pronoun's usage.
2462
     */
2463
    setPronounMulti([args])
2464
    {
2465
        local lst, subLst;
2466
        local gotThem;
2467
2468
        /*
2469
         *   If there's a plural list, it's 'them'.  Arbitrarily use only
2470
         *   the first plural list if there's more than one.
2471
         */
2472
        if ((lst = args.valWhich({x: x.length() > 1})) != nil)
2473
        {
2474
            /* set 'them' to the plural list */
2475
            setPronoun(lst);
2476
2477
            /* note that we had a clear 'them' */
2478
            gotThem = true;
2479
        }
2480
2481
        /* from now on, consider only the sublists with exactly one item */
2482
        args = args.subset({x: x.length() == 1});
2483
2484
        /* get a list of the singular items from the lists */
2485
        lst = args.mapAll({x: x[1].obj_});
2486
2487
        /*
2488
         *   Set 'it' to all of the items that can match 'it'; do likewise
2489
         *   with 'him' and 'her'.  If there are no objects that can match
2490
         *   a given pronoun, leave that pronoun unchanged.  
2491
         */
2492
        if ((subLst = lst.subset({x: x.canMatchIt})).length() > 0)
2493
            setIt(subLst);
2494
        if ((subLst = lst.subset({x: x.canMatchHim})).length() > 0)
2495
            setHim(subLst);
2496
        if ((subLst = lst.subset({x: x.canMatchHer})).length() > 0)
2497
            setHer(subLst);
2498
2499
        /*
2500
         *   set 'them' to the potential 'them' matches, if we didn't
2501
         *   already find a clear plural list
2502
         */
2503
        if (!gotThem
2504
            && (subLst = lst.subset({x: x.canMatchThem})).length() > 0)
2505
            setThem(subLst);
2506
    }
2507
2508
    /*
2509
     *   Set a pronoun antecedent to the given ResolveInfo list, for the
2510
     *   specified type of pronoun.  We don't have to worry about setting
2511
     *   other types of pronouns to this antecedent - we specifically want
2512
     *   to set the given pronoun type.  This is language-dependent
2513
     *   because we still have to figure out the number (i.e. singular or
2514
     *   plural) of the pronoun type.
2515
     */
2516
    setPronounByType(typ, lst)
2517
    {
2518
        /* check for singular or plural pronouns */
2519
        if (typ == PronounThem)
2520
        {
2521
            /* it's plural - set a list antecedent */
2522
            setPronounAntecedent(typ, lst.mapAll({x: x.obj_}));
2523
        }
2524
        else
2525
        {
2526
            /* it's singular - set an individual antecedent */
2527
            setPronounAntecedent(typ, lst[1].obj_);
2528
        }
2529
    }
2530
2531
    /*
2532
     *   Set a pronoun antecedent to the given simulation object (usually
2533
     *   an object descended from Thing).
2534
     */
2535
    setPronounObj(obj)
2536
    {
2537
        /*
2538
         *   Actually use the object's "identity object" as the antecedent
2539
         *   rather than the object itself.  In some cases, we use multiple
2540
         *   program objects to represent what appears to be a single
2541
         *   object in the game; in these cases, the internal program
2542
         *   objects all point to the "real" object as their identity
2543
         *   object.  Whenever we're manipulating one of these internal
2544
         *   program objects, we want to make sure that its the
2545
         *   player-visible object - the identity object - that appears as
2546
         *   the antecedent for subsequent references.
2547
         */
2548
        obj = obj.getIdentityObject();
2549
2550
        /*
2551
         *   Set the appropriate pronoun antecedent, depending on the
2552
         *   object's gender.
2553
         *
2554
         *   Note that we'll set an object to be the antecedent for both
2555
         *   'him' and 'her' if the object has both masculine and feminine
2556
         *   usage.
2557
         */
2558
2559
        /* check for masculine usage */
2560
        if (obj.canMatchHim)
2561
            setHim(obj);
2562
2563
        /* check for feminine usage */
2564
        if (obj.canMatchHer)
2565
            setHer(obj);
2566
2567
        /* check for neuter usage */
2568
        if (obj.canMatchIt)
2569
            setIt(obj);
2570
2571
        /* check for third-person plural usage */
2572
        if (obj.canMatchThem)
2573
            setThem([obj]);
2574
    }
2575
2576
    /* set a possessive anaphor */
2577
    setPossAnaphorObj(obj)
2578
    {
2579
        /* check for each type of usage */
2580
        if (obj.canMatchHim)
2581
            possAnaphorTable[PronounHim] = obj;
2582
        if (obj.canMatchHer)
2583
            possAnaphorTable[PronounHer] = obj;
2584
        if (obj.canMatchIt)
2585
            possAnaphorTable[PronounIt] = obj;
2586
        if (obj.canMatchThem)
2587
            possAnaphorTable[PronounThem] = [obj];
2588
    }
2589
;
2590
2591
/* ------------------------------------------------------------------------ */
2592
/*
2593
 *   Give the postures some additional attributes
2594
 */
2595
2596
modify Posture
2597
2598
    /*
2599
     *   Intransitive and transitive forms of the verb, for use in library
2600
     *   messages.  Each of these methods simply calls one of the two
2601
     *   corresponding fixed-tense properties, depending on the current
2602
     *   tense.
2603
     */
2604
    msgVerbI = (tSel(msgVerbIPresent, msgVerbIPast))
2605
    msgVerbT = (tSel(msgVerbTPresent, msgVerbTPast))
2606
2607
    /*
2608
     *   Fixed-tense versions of the above properties, to be defined
2609
     *   individually by each instance of the Posture class.
2610
     */
2611
2612
    /* our present-tense intransitive form ("he stands up") */
2613
    // msgVerbIPresent = 'stand{s} up'
2614
2615
    /* our past-tense intransitive form ("he stood up") */
2616
    // msgVerbIPast = 'stood up'
2617
2618
    /* our present-tense transitive form ("he stands on the chair") */
2619
    // msgVerbTPresent = 'stand{s}'
2620
2621
    /* our past-tense transitive form ("he stood on the chair") */
2622
    // msgVerbTPast = 'stood'
2623
2624
    /* our participle form */
2625
    // participle = 'standing'
2626
;
2627
2628
modify standing
2629
    msgVerbIPresent = 'stand{s} up'
2630
    msgVerbIPast = 'stood up'
2631
    msgVerbTPresent = 'stand{s}'
2632
    msgVerbTPast = 'stood'
2633
    participle = 'standing'
2634
;
2635
2636
modify sitting
2637
    msgVerbIPresent = 'sit{s} down'
2638
    msgVerbIPast = 'sat down'
2639
    msgVerbTPresent = 'sit{s}'
2640
    msgVerbTPast = 'sat'
2641
    participle = 'sitting'
2642
;
2643
2644
modify lying
2645
    msgVerbIPresent = 'lie{s} down'
2646
    msgVerbIPast = 'lay down'
2647
    msgVerbTPresent = 'lie{s}'
2648
    msgVerbTPast = 'lay'
2649
    participle = 'lying'
2650
;
2651
2652
/* ------------------------------------------------------------------------ */
2653
/*
2654
 *   For our various topic suggestion types, we can infer the full name
2655
 *   from the short name fairly easily.
2656
 */
2657
modify SuggestedAskTopic
2658
    fullName = ('ask {it targetActor/him} about ' + name)
2659
;
2660
2661
modify SuggestedTellTopic
2662
    fullName = ('tell {it targetActor/him} about ' + name)
2663
;
2664
2665
modify SuggestedAskForTopic
2666
    fullName = ('ask {it targetActor/him} for ' + name)
2667
;
2668
2669
modify SuggestedGiveTopic
2670
    fullName = ('give {it targetActor/him} ' + name)
2671
;
2672
2673
modify SuggestedShowTopic
2674
    fullName = ('show {it targetActor/him} ' + name)
2675
;
2676
2677
modify SuggestedYesTopic
2678
    name = 'yes'
2679
    fullName = 'say yes'
2680
;
2681
2682
modify SuggestedNoTopic
2683
    name = 'no'
2684
    fullName = 'say no'
2685
;
2686
2687
/* ------------------------------------------------------------------------ */
2688
/*
2689
 *   Provide custom processing of the player input for matching
2690
 *   SpecialTopic patterns.  When we're trying to match a player's command
2691
 *   to a set of active special topics, we'll run the input through this
2692
 *   processing to produce the string that we actually match against the
2693
 *   special topics.
2694
 *
2695
 *   First, we'll remove any punctuation marks.  This ensures that we'll
2696
 *   still match a special topic, for example, if the player puts a period
2697
 *   or a question mark at the end of the command.
2698
 *
2699
 *   Second, if the user's input starts with "A" or "T" (the super-short
2700
 *   forms of the ASK ABOUT and TELL ABOUT commands), remove the "A" or "T"
2701
 *   and keep the rest of the input.  Some users might think that special
2702
 *   topic suggestions are meant as ask/tell topics, so they might
2703
 *   instinctively try these as A/T commands.
2704
 *
2705
 *   Users *probably* won't be tempted to do the same thing with the full
2706
 *   forms of the commands (e.g., ASK BOB ABOUT APOLOGIZE, TELL BOB ABOUT
2707
 *   EXPLAIN).  It's more a matter of habit of using A or T for interaction
2708
 *   that would tempt a user to phrase a special topic this way; once
2709
 *   you're typing out the full form of the command, it generally won't be
2710
 *   grammatical, as special topics generally contain the sense of a verb
2711
 *   in their phrasing.
2712
 */
2713
modify specialTopicPreParser
2714
    processInputStr(str)
2715
    {
2716
        /*
2717
         *   remove most punctuation from the string - we generally want to
2718
         *   ignore these, as we mostly just want to match keywords
2719
         */
2720
        str = rexReplace(punctPat, str, '', ReplaceAll);
2721
2722
        /* if it starts with "A" or "T", strip off the leading verb */
2723
        if (rexMatch(aOrTPat, str) != nil)
2724
            str = rexGroup(1)[3];
2725
2726
        /* return the processed result */
2727
        return str;
2728
    }
2729
2730
    /* pattern for string starting with "A" or "T" verbs */
2731
    aOrTPat = static new RexPattern(
2732
        '<nocase><space>*[at]<space>+(<^space>.*)$')
2733
2734
    /* pattern to eliminate punctuation marks from the string */
2735
    punctPat = static new RexPattern('[.?!,;:]');
2736
;
2737
2738
/*
2739
 *   For SpecialTopic matches, treat some strings as "weak": if the user's
2740
 *   input consists of just one of these weak strings and nothing else,
2741
 *   don't match the topic.
2742
 */
2743
modify SpecialTopic
2744
    matchPreParse(str, procStr)
2745
    {
2746
        /* if it's one of our 'weak' strings, don't match */
2747
        if (rexMatch(weakPat, str) != nil)
2748
            return nil;
2749
2750
        /* it's not a weak string, so match as usual */
2751
        return inherited(str, procStr);
2752
    }
2753
2754
    /*
2755
     *   Our "weak" strings - 'i', 'l', 'look': these are weak because a
2756
     *   user typing one of these strings by itself is probably actually
2757
     *   trying to enter the command of the same name, rather than entering
2758
     *   a special topic.  These come up in cases where the special topic
2759
     *   is something like "say I don't know" or "tell him you'll look into
2760
     *   it".
2761
     */
2762
    weakPat = static new RexPattern('<nocase><space>*(i|l|look)<space>*$')
2763
;
2764
2765
/* ------------------------------------------------------------------------ */
2766
/*
2767
 *   English-specific Traveler changes
2768
 */
2769
modify Traveler
2770
    /*
2771
     *   Get my location's name, from the PC's perspective, for describing
2772
     *   my arrival to or departure from my current location.  We'll
2773
     *   simply return our location's destName, or "the area" if it
2774
     *   doesn't have one.
2775
     */
2776
    travelerLocName()
2777
    {
2778
        /* get our location's name from the PC's perspective */
2779
        local nm = location.getDestName(gPlayerChar, gPlayerChar.location);
2780
2781
        /* if there's a name, return it; otherwise, use "the area" */
2782
        return (nm != nil ? nm : 'the area');
2783
    }
2784
2785
    /*
2786
     *   Get my "remote" location name, from the PC's perspective.  This
2787
     *   returns my location name, but only if my location is remote from
2788
     *   the PC's perspective - that is, my location has to be outside of
2789
     *   the PC's top-level room.  If we're within the PC's top-level
2790
     *   room, we'll simply return an empty string.
2791
     */
2792
    travelerRemoteLocName()
2793
    {
2794
        /*
2795
         *   if my location is outside of the PC's outermost room, we're
2796
         *   remote, so return my location name; otherwise, we're local,
2797
         *   so we don't need a remote name at all
2798
         */
2799
        if (isIn(gPlayerChar.getOutermostRoom()))
2800
            return '';
2801
        else
2802
            return travelerLocName;
2803
    }
2804
;
2805
2806
/* ------------------------------------------------------------------------ */
2807
/*
2808
 *   English-specific Vehicle changes
2809
 */
2810
modify Vehicle
2811
    /*
2812
     *   Display the name of the traveler, for use in an arrival or
2813
     *   departure message.
2814
     */
2815
    travelerName(arriving)
2816
    {
2817
        /*
2818
         *   By default, start with the indefinite name if we're arriving,
2819
         *   or the definite name if we're leaving.
2820
         *
2821
         *   If we're leaving, presumably they've seen us before, since we
2822
         *   were already in the room to start with.  Since we've been
2823
         *   seen before, the definite is appropriate.
2824
         *
2825
         *   If we're arriving, even if we're not being seen for the first
2826
         *   time, we haven't been seen yet in this place around this
2827
         *   time, so the indefinite is appropriate.
2828
         */
2829
        say(arriving ? aName : theName);
2830
2831
        /* show the list of actors aboard */
2832
        aboardVehicleListerObj.showList(
2833
            libGlobal.playerChar, nil, allContents(), 0, 0,
2834
            libGlobal.playerChar.visibleInfoTable(), nil);
2835
    }
2836
;
2837
2838
/* ------------------------------------------------------------------------ */
2839
/*
2840
 *   English-specific PushTraveler changes
2841
 */
2842
modify PushTraveler
2843
    /*
2844
     *   When an actor is pushing an object from one room to another, show
2845
     *   its name with an additional clause indicating the object being
2846
     *   moved along with us.
2847
     */
2848
    travelerName(arriving)
2849
    {
2850
        "<<gPlayerChar.hasSeen(self) ? theName : aName>>,
2851
        pushing <<obj_.theNameObj>>,";
2852
    }
2853
;
2854
2855
2856
/* ------------------------------------------------------------------------ */
2857
/*
2858
 *   English-specific travel connector changes
2859
 */
2860
2861
modify PathPassage
2862
    /* treat "take path" the same as "enter path" or "go through path" */
2863
    dobjFor(Take) maybeRemapTo(
2864
        gAction.getEnteredVerbPhrase() == 'take (dobj)', TravelVia, self)
2865
2866
    dobjFor(Enter)
2867
    {
2868
        verify() { logicalRank(50, 'enter path'); }
2869
    }
2870
    dobjFor(GoThrough)
2871
    {
2872
        verify() { logicalRank(50, 'enter path'); }
2873
    }
2874
;
2875
2876
modify AskConnector
2877
    /*
2878
     *   This is the noun phrase we'll use when asking disambiguation
2879
     *   questions for this travel connector: "Which *one* do you want to
2880
     *   enter..."
2881
     */
2882
    travelObjsPhrase = 'one'
2883
;
2884
2885
/* ------------------------------------------------------------------------ */
2886
/*
2887
 *   English-specific changes for various nested room types.
2888
 */
2889
modify BasicChair
2890
    /* by default, one sits *on* a chair */
2891
    objInPrep = 'on'
2892
    actorInPrep = 'on'
2893
    actorOutOfPrep = 'off of'
2894
;
2895
2896
modify BasicPlatform
2897
    /* by default, one stands *on* a platform */
2898
    objInPrep = 'on'
2899
    actorInPrep = 'on'
2900
    actorOutOfPrep = 'off of'
2901
;
2902
2903
modify Booth
2904
    /* by default, one is *in* a booth */
2905
    objInPrep = 'in'
2906
    actorInPrep = 'in'
2907
    actorOutOfPrep = 'out of'
2908
;
2909
2910
/* ------------------------------------------------------------------------ */
2911
/*
2912
 *   Language modifications for Matchstick
2913
 */
2914
modify Matchstick
2915
    /* "strike match" means "light match" */
2916
    dobjFor(Strike) asDobjFor(Burn)
2917
2918
    /* "light match" means "burn match" */
2919
    dobjFor(Light) asDobjFor(Burn)
2920
;
2921
2922
/*
2923
 *   Match state objects.  We show "lit" as the state for a lit match,
2924
 *   nothing for an unlit match.
2925
 */
2926
matchStateLit: ThingState 'lit'
2927
    stateTokens = ['lit']
2928
;
2929
matchStateUnlit: ThingState
2930
    stateTokens = ['unlit']
2931
;
2932
2933
2934
/* ------------------------------------------------------------------------ */
2935
/*
2936
 *   English-specific modifications for Room.
2937
 */
2938
modify Room
2939
    /*
2940
     *   The ordinary 'name' property is used the same way it's used for
2941
     *   any other object, to refer to the room when it shows up in
2942
     *   library messages and the like: "You can't take the hallway."
2943
     *
2944
     *   By default, we derive the name from the roomName by converting
2945
     *   the roomName to lower case.  Virtually every room will need a
2946
     *   custom room name setting, since the room name is used mostly as a
2947
     *   title for the room, and good titles are hard to generate
2948
     *   mechanically.  Many times, converting the roomName to lower case
2949
     *   will produce a decent name to use in messages: "Ice Cave" gives
2950
     *   us "You can't eat the ice cave."  However, games will want to
2951
     *   customize the ordinary name separately in many cases, because the
2952
     *   elliptical, title-like format of the room name doesn't always
2953
     *   translate well to an object name: "West of Statue" gives us the
2954
     *   unworkable "You can't eat the west of statue"; better to make the
2955
     *   ordinary name something like "plaza".  Note also that some rooms
2956
     *   have proper names that want to preserve their capitalization in
2957
     *   the ordinary name: "You can't eat the Hall of the Ancient Kings."
2958
     *   These cases need to be customized as well.
2959
     */
2960
    name = (roomName.toLower())
2961
2962
    /*
2963
     *   The "destination name" of the room.  This is primarily intended
2964
     *   for use in showing exit listings, to describe the destination of
2965
     *   a travel connector leading away from our current location, if the
2966
     *   destination is known to the player character.  We also use this
2967
     *   as the default source of the name in similar contexts, such as
2968
     *   when we can see this room from another room connected by a sense
2969
     *   connector.
2970
     *
2971
     *   The destination name usually mirrors the room name, but we use
2972
     *   the name in prepositional phrases involving the room ("east, to
2973
     *   the alley"), so this name should include a leading article
2974
     *   (usually definite - "the") unless the name is proper ("east, to
2975
     *   Dinsley Plaza").  So, by default, we simply use the "theName" of
2976
     *   the room.  In many cases, it's better to specify a custom
2977
     *   destName, because this name is used when the PC is outside of the
2978
     *   room, and thus can benefit from a more detailed description than
2979
     *   we'd normally use for the basic name.  For example, the ordinary
2980
     *   name might simply be something like "hallway", but since we want
2981
     *   to be clear about exactly which hallway we're talking about when
2982
     *   we're elsewhere, we might want to use a destName like "the
2983
     *   basement hallway" or "the hallway outside the operating room".
2984
     */
2985
    destName = (theName)
2986
2987
    /*
2988
     *   For top-level rooms, describe an object as being in the room by
2989
     *   describing it as being in the room's nominal drop destination,
2990
     *   since that's the nominal location for the objects directly in the
2991
     *   room.  (In most cases, the nominal drop destination for a room is
2992
     *   its floor.)
2993
     *
2994
     *   If the player character isn't in the same outermost room as this
2995
     *   container, use our remote name instead of the nominal drop
2996
     *   destination.  The nominal drop destination is usually something
2997
     *   like the floor or the ground, so it's only suitable when we're in
2998
     *   the same location as what we're describing.
2999
     */
3000
    childInName(childName)
3001
    {
3002
        /* if the PC isn't inside us, we're viewing this remotely */
3003
        if (!gPlayerChar.isIn(self))
3004
            return childInRemoteName(childName, gPlayerChar);
3005
        else
3006
            return getNominalDropDestination().childInName(childName);
3007
    }
3008
    childInNameWithOwner(chiName)
3009
    {
3010
        /* if the PC isn't inside us, we're viewing this remotely */
3011
        if (!gPlayerChar.isIn(self))
3012
            return inherited(chiName);
3013
        else
3014
            return getNominalDropDestination().childInNameWithOwner(chiName);
3015
    }
3016
;
3017
3018
/* ------------------------------------------------------------------------ */
3019
/*
3020
 *   English-specific modifications for the default room parts.
3021
 */
3022
3023
modify Floor
3024
    childInNameGen(childName, myName) { return childName + ' on ' + myName; }
3025
    objInPrep = 'on'
3026
    actorInPrep = 'on'
3027
    actorOutOfPrep = 'off of'
3028
;
3029
3030
modify defaultFloor
3031
    noun = 'floor' 'ground'
3032
    name = 'floor'
3033
;
3034
3035
modify defaultGround
3036
    noun = 'ground' 'floor'
3037
    name = 'ground'
3038
;
3039
3040
modify DefaultWall noun='wall' plural='walls' name='wall';
3041
modify defaultCeiling noun='ceiling' 'roof' name='ceiling';
3042
modify defaultNorthWall adjective='n' 'north' name='north wall';
3043
modify defaultSouthWall adjective='s' 'south' name='south wall';
3044
modify defaultEastWall adjective='e' 'east' name='east wall';
3045
modify defaultWestWall adjective='w' 'west' name='west wall';
3046
modify defaultSky noun='sky' name='sky';
3047
3048
3049
/* ------------------------------------------------------------------------ */
3050
/*
3051
 *   The English-specific modifications for directions.
3052
 */
3053
modify Direction
3054
    /* describe a traveler arriving from this direction */
3055
    sayArriving(traveler)
3056
    {
3057
        /* show the generic arrival message */
3058
        gLibMessages.sayArriving(traveler);
3059
    }
3060
3061
    /* describe a traveler departing in this direction */
3062
    sayDeparting(traveler)
3063
    {
3064
        /* show the generic departure message */
3065
        gLibMessages.sayDeparting(traveler);
3066
    }
3067
;
3068
3069
/*
3070
 *   The English-specific modifications for compass directions.
3071
 */
3072
modify CompassDirection
3073
    /* describe a traveler arriving from this direction */
3074
    sayArriving(traveler)
3075
    {
3076
        /* show the generic compass direction description */
3077
        gLibMessages.sayArrivingDir(traveler, name);
3078
    }
3079
3080
    /* describe a traveler departing in this direction */
3081
    sayDeparting(traveler)
3082
    {
3083
        /* show the generic compass direction description */
3084
        gLibMessages.sayDepartingDir(traveler, name);
3085
    }
3086
;
3087
3088
/*
3089
 *   The English-specific definitions for the compass direction objects.
3090
 *   In addition to modifying the direction objects to define the name of
3091
 *   the direction, we add a 'directionName' grammar rule.
3092
 */
3093
#define DefineLangDir(root, dirNames, backPre) \
3094
grammar directionName(root): dirNames: DirectionProd \
3095
   dir = root##Direction \
3096
; \
3097
\
3098
modify root##Direction \
3099
   name = #@root \
3100
   backToPrefix = backPre
3101
3102
DefineLangDir(north, 'north' | 'n', 'back to the');
3103
DefineLangDir(south, 'south' | 's', 'back to the');
3104
DefineLangDir(east, 'east' | 'e', 'back to the');
3105
DefineLangDir(west, 'west' | 'w', 'back to the');
3106
DefineLangDir(northeast, 'northeast' | 'ne', 'back to the');
3107
DefineLangDir(northwest, 'northwest' | 'nw', 'back to the');
3108
DefineLangDir(southeast, 'southeast' | 'se', 'back to the');
3109
DefineLangDir(southwest, 'southwest' | 'sw', 'back to the');
3110
DefineLangDir(up, 'up' | 'u', 'back');
3111
DefineLangDir(down, 'down' | 'd', 'back');
3112
DefineLangDir(in, 'in', 'back');
3113
DefineLangDir(out, 'out', 'back');
3114
3115
/*
3116
 *   The English-specific shipboard direction modifications.  Certain of
3117
 *   the ship directions have no natural descriptions for arrival and/or
3118
 *   departure; for example, there's no good way to say "arriving from
3119
 *   fore."  Others don't fit any regular pattern: "he goes aft" rather
3120
 *   than "he departs to aft."  As a result, these are a bit irregular
3121
 *   compared to the compass directions and so are individually defined
3122
 *   below.
3123
 */
3124
3125
DefineLangDir(port, 'port' | 'p', 'back to')
3126
    sayArriving(trav)
3127
        { gLibMessages.sayArrivingShipDir(trav, 'the port direction'); }
3128
    sayDeparting(trav)
3129
        { gLibMessages.sayDepartingShipDir(trav, 'port'); }
3130
;
3131
3132
DefineLangDir(starboard, 'starboard' | 'sb', 'back to')
3133
    sayArriving(trav)
3134
        { gLibMessages.sayArrivingShipDir(trav, 'starboard'); }
3135
    sayDeparting(trav)
3136
        { gLibMessages.sayDepartingShipDir(trav, 'starboard'); }
3137
;
3138
3139
DefineLangDir(aft, 'aft' | 'a', 'back to')
3140
    sayArriving(trav) { gLibMessages.sayArrivingShipDir(trav, 'aft'); }
3141
    sayDeparting(trav) { gLibMessages.sayDepartingAft(trav); }
3142
;
3143
3144
DefineLangDir(fore, 'fore' | 'forward' | 'f', 'back to')
3145
    sayArriving(trav) { gLibMessages.sayArrivingShipDir(trav, 'forward'); }
3146
    sayDeparting(trav) { gLibMessages.sayDepartingFore(trav); }
3147
;
3148
3149
/* ------------------------------------------------------------------------ */
3150
/*
3151
 *   Some helper routines for the library messages.
3152
 */
3153
class MessageHelper: object
3154
    /*
3155
     *   Show a list of objects for a disambiguation query.  If
3156
     *   'showIndefCounts' is true, we'll show the number of equivalent
3157
     *   items for each equivalent item; otherwise, we'll just show an
3158
     *   indefinite noun phrase for each equivalent item.
3159
     */
3160
    askDisambigList(matchList, fullMatchList, showIndefCounts, dist)
3161
    {
3162
        /* show each item */
3163
        for (local i = 1, local len = matchList.length() ; i <= len ; ++i)
3164
        {
3165
            local equivCnt;
3166
            local obj;
3167
3168
            /* get the current object */
3169
            obj = matchList[i].obj_;
3170
3171
            /*
3172
             *   if this isn't the first, add a comma; if this is the
3173
             *   last, add an "or" as well
3174
              */
3175
            if (i == len)
3176
                ", or ";
3177
            else if (i != 1)
3178
                ", ";
3179
3180
            /*
3181
             *   Check to see if more than one equivalent of this item
3182
             *   appears in the full list.
3183
             */
3184
            for (equivCnt = 0, local j = 1,
3185
                 local fullLen = fullMatchList.length() ; j <= fullLen ; ++j)
3186
            {
3187
                /*
3188
                 *   if this item is equivalent for the purposes of the
3189
                 *   current distinguisher, count it
3190
                 */
3191
                if (!dist.canDistinguish(obj, fullMatchList[j].obj_))
3192
                {
3193
                    /* it's equivalent - count it */
3194
                    ++equivCnt;
3195
                }
3196
            }
3197
3198
            /* show this item with the appropriate article */
3199
            if (equivCnt > 1)
3200
            {
3201
                /*
3202
                 *   we have multiple equivalents - show either with an
3203
                 *   indefinite article or with a count, depending on the
3204
                 *   flags the caller provided
3205
                 */
3206
                if (showIndefCounts)
3207
                {
3208
                    /* a count is desired for each equivalent group */
3209
                    say(dist.countName(obj, equivCnt));
3210
                }
3211
                else
3212
                {
3213
                    /* no counts desired - show with an indefinite article */
3214
                    say(dist.aName(obj));
3215
                }
3216
            }
3217
            else
3218
            {
3219
                /* there's only one - show with a definite article */
3220
                say(dist.theName(obj));
3221
            }
3222
        }
3223
    }
3224
3225
    /*
3226
     *   For a TAction result, select the short-form or long-form message,
3227
     *   according to the disambiguation status of the action.  This is for
3228
     *   the ultra-terse default messages, such as "Taken" or "Dropped",
3229
     *   that sometimes need more descriptive variations.
3230
     *   
3231
     *   If there was no disambiguation involved, we'll use the short
3232
     *   version of the message.
3233
     *   
3234
     *   If there was unclear disambiguation involved (meaning that there
3235
     *   was more than one logical object matching a noun phrase, but the
3236
     *   parser was able to decide based on likelihood rankings), we'll
3237
     *   still use the short version, because we assume that the parser
3238
     *   will have generated a parenthetical announcement to point out its
3239
     *   choice.
3240
     *   
3241
     *   If there was clear disambiguation involved (meaning that more than
3242
     *   one in-scope object matched a noun phrase, but there was only one
3243
     *   choice that passed the logicalness tests), AND the announcement
3244
     *   mode (in gameMain.ambigAnnounceMode) is DescribeClear, we'll
3245
     *   choose the long-form message.  
3246
     */
3247
    shortTMsg(short, long)
3248
    {
3249
        /* check the disambiguation flags and the announcement mode */
3250
        if ((gAction.getDobjFlags() & (ClearDisambig | AlwaysAnnounce))
3251
            == ClearDisambig
3252
            && gAction.getDobjCount() == 1
3253
            && gameMain.ambigAnnounceMode == DescribeClear)
3254
        {
3255
            /* clear disambig and DescribeClear mode - use the long message */
3256
            return long;
3257
        }
3258
        else
3259
        {
3260
            /* in other cases, use the short message */
3261
            return short;
3262
        }
3263
    }
3264
3265
    /*
3266
     *   For a TIAction result, select the short-form or long-form message.
3267
     *   This works just like shortTIMsg(), but takes into account both the
3268
     *   direct and indirect objects. 
3269
     */
3270
    shortTIMsg(short, long)
3271
    {
3272
        /* check the disambiguation flags and the announcement mode */
3273
        if (((gAction.getDobjFlags() & (ClearDisambig | AlwaysAnnounce))
3274
             == ClearDisambig
3275
             || (gAction.getIobjFlags() & (ClearDisambig | AlwaysAnnounce))
3276
             == ClearDisambig)
3277
            && gAction.getDobjCount() == 1
3278
            && gAction.getIobjCount() == 1
3279
            && gameMain.ambigAnnounceMode == DescribeClear)
3280
        {
3281
            /* clear disambig and DescribeClear mode - use the long message */
3282
            return long;
3283
        }
3284
        else
3285
        {
3286
            /* in other cases, use the short message */
3287
            return short;
3288
        }
3289
    }
3290
;
3291
3292
/* ------------------------------------------------------------------------ */
3293
/*
3294
 *   Custom base resolver
3295
 */
3296
modify Resolver
3297
    /*
3298
     *   Get the default in-scope object list for a given pronoun.  We'll
3299
     *   look for a unique object in scope that matches the desired
3300
     *   pronoun, and return a ResolveInfo list if we find one.  If there
3301
     *   aren't any objects in scope that match the pronoun, or multiple
3302
     *   objects are in scope, there's no default.
3303
     */
3304
    getPronounDefault(typ, np)
3305
    {
3306
        local map = [PronounHim, &canMatchHim,
3307
                     PronounHer, &canMatchHer,
3308
                     PronounIt, &canMatchIt];
3309
        local idx = map.indexOf(typ);
3310
        local filterProp = (idx != nil ? map[idx + 1] : nil);
3311
        local lst;
3312
3313
        /* if we couldn't find a filter for the pronoun, ignore it */
3314
        if (filterProp == nil)
3315
            return [];
3316
3317
        /*
3318
         *   filter the list of all possible defaults to those that match
3319
         *   the given pronoun
3320
         */
3321
        lst = getAllDefaults.subset({x: x.obj_.(filterProp)});
3322
3323
        /*
3324
         *   if the list contains exactly one element, then there's a
3325
         *   unique default; otherwise, there's either nothing here that
3326
         *   matches the pronoun or the pronoun is ambiguous, so there's
3327
         *   no default
3328
         */
3329
        if (lst.length() == 1)
3330
        {
3331
            /*
3332
             *   we have a unique object, so they must be referring to it;
3333
             *   because this is just a guess, though, mark it as vague
3334
             */
3335
            lst[1].flags_ |= UnclearDisambig;
3336
3337
            /* return the list */
3338
            return lst;
3339
        }
3340
        else
3341
        {
3342
            /*
3343
             *   the pronoun doesn't have a unique in-scope referent, so
3344
             *   we can't guess what they mean
3345
             */
3346
            return [];
3347
        }
3348
    }
3349
;
3350
3351
/* ------------------------------------------------------------------------ */
3352
/*
3353
 *   Custom interactive resolver.  This is used for responses to
3354
 *   disambiguation questions and prompts for missing noun phrases.
3355
 */
3356
modify InteractiveResolver
3357
    /*
3358
     *   Resolve a pronoun antecedent.  We'll resolve a third-person
3359
     *   singular pronoun to the target actor if the target actor matches
3360
     *   in gender, and the target actor isn't the PC.  This allows
3361
     *   exchanges like this:
3362
     *
3363
     *.  >bob, examine
3364
     *.  What do you want Bob to look at?
3365
     *.
3366
     *.  >his book
3367
     *
3368
     *   In the above exchange, we'll treat "his" as referring to Bob, the
3369
     *   target actor of the action, because we have referred to Bob in
3370
     *   the partial command (the "BOB, EXAMINE") that triggered the
3371
     *   interactive question.
3372
     */
3373
    resolvePronounAntecedent(typ, np, results, poss)
3374
    {
3375
        local lst;
3376
3377
        /* try resolving with the target actor as the antecedent */
3378
        if ((lst = resolvePronounAsTargetActor(typ)) != nil)
3379
            return lst;
3380
3381
        /* use the inherited result */
3382
        return inherited(typ, np, results, poss);
3383
    }
3384
3385
    /*
3386
     *   Get the reflexive third-person pronoun binding (himself, herself,
3387
     *   itself, themselves).  If the target actor isn't the PC, and the
3388
     *   gender of the pronoun matches, we'll consider this as referring
3389
     *   to the target actor.  This allows exchanges of this form:
3390
     *
3391
     *.  >bob, examine
3392
     *.  What do you want Bob to examine?
3393
     *.
3394
     *.  >himself
3395
     */
3396
    getReflexiveBinding(typ)
3397
    {
3398
        local lst;
3399
3400
        /* try resolving with the target actor as the antecedent */
3401
        if ((lst = resolvePronounAsTargetActor(typ)) != nil)
3402
            return lst;
3403
3404
        /* use the inherited result */
3405
        return inherited(typ);
3406
    }
3407
3408
    /*
3409
     *   Try matching the given pronoun type to the target actor.  If it
3410
     *   matches in gender, and the target actor isn't the PC, we'll
3411
     *   return a resolve list consisting of the target actor.  If we
3412
     *   don't have a match, we'll return nil.
3413
     */
3414
    resolvePronounAsTargetActor(typ)
3415
    {
3416
        /*
3417
         *   if the target actor isn't the player character, and the
3418
         *   target actor can match the given pronoun type, resolve the
3419
         *   pronoun as the target actor
3420
         */
3421
        if (actor_.canMatchPronounType(typ) && !actor_.isPlayerChar())
3422
        {
3423
            /* the match is the target actor */
3424
            return [new ResolveInfo(actor_, 0)];
3425
        }
3426
3427
        /* we didn't match it */
3428
        return nil;
3429
    }
3430
;
3431
3432
/*
3433
 *   Custom disambiguation resolver.
3434
 */
3435
modify DisambigResolver
3436
    /*
3437
     *   Perform special resolution on pronouns used in interactive
3438
     *   responses.  If the pronoun is HIM or HER, then look through the
3439
     *   list of possible matches for a matching gendered object, and use
3440
     *   it as the result if we find one.  If we find more than one, then
3441
     *   use the default handling instead, treating the pronoun as
3442
     *   referring back to the simple antecedent previously set.
3443
     */
3444
    resolvePronounAntecedent(typ, np, results, poss)
3445
    {
3446
        /* if it's a non-possessive HIM or HER, use our special handling */
3447
        if (!poss && typ is in (PronounHim, PronounHer))
3448
        {
3449
            local prop;
3450
            local sub;
3451
3452
            /* get the gender indicator property for the pronoun */
3453
            prop = (typ == PronounHim ? &canMatchHim : &canMatchHer);
3454
3455
            /*
3456
             *   Scan through the match list to find the objects that
3457
             *   match the gender of the pronoun.  Note that if the player
3458
             *   character isn't referred to in the third person, we'll
3459
             *   ignore the player character for the purposes of matching
3460
             *   this pronoun - if we're calling the PC 'you', then we
3461
             *   wouldn't expect the player to refer to the PC as 'him' or
3462
             *   'her'.
3463
             */
3464
            sub = matchList.subset({x: x.obj_.(prop)});
3465
3466
            /* if the list has a single entry, then use it as the match */
3467
            if (sub.length() == 1)
3468
                return sub;
3469
3470
            /*
3471
             *   if it has more than one entry, it's still ambiguous, but
3472
             *   we might have narrowed it down, so throw a
3473
             *   still-ambiguous exception and let the interactive
3474
             *   disambiguation ask for further clarification
3475
             */
3476
            results.ambiguousNounPhrase(nil, ResolveAsker, 'one',
3477
                                        sub, matchList, matchList,
3478
                                        1, self);
3479
            return [];
3480
        }
3481
3482
        /*
3483
         *   if we get this far, it means we didn't use our special
3484
         *   handling, so use the inherited behavior
3485
         */
3486
        return inherited(typ, np, results, poss);
3487
    }
3488
;
3489
3490
/* ------------------------------------------------------------------------ */
3491
/*
3492
 *   Distinguisher customizations for English.
3493
 *   
3494
 *   Each distinguisher must provide a method that gets the name of an item
3495
 *   for a disamgiguation query.  Since these are inherently
3496
 *   language-specific, they're defined here.  
3497
 */
3498
3499
/*
3500
 *   The null distinguisher tells objects apart based strictly on the name
3501
 *   string.  When we list objects, we simply show the basic name - since
3502
 *   we can tell apart our objects based on the base name, there's no need
3503
 *   to resort to other names.  
3504
 */
3505
modify nullDistinguisher
3506
    /* we can tell objects apart if they have different base names */
3507
    canDistinguish(a, b) { return a.name != b.name; }
3508
3509
    name(obj) { return obj.name; }
3510
    aName(obj) { return obj.aName; }
3511
    theName(obj) { return obj.theName; }
3512
    countName(obj, cnt) { return obj.countName(cnt); }
3513
;
3514
3515
3516
/*
3517
 *   The basic distinguisher can tell apart objects that are not "basic
3518
 *   equivalents" of one another.  Thus, we need make no distinctions when
3519
 *   listing objects apart from showing their names.  
3520
 */
3521
modify basicDistinguisher
3522
    name(obj) { return obj.disambigName; }
3523
    aName(obj) { return obj.aDisambigName; }
3524
    theName(obj) { return obj.theDisambigName; }
3525
    countName(obj, cnt) { return obj.countDisambigName(cnt); }
3526
;
3527
3528
/*
3529
 *   The ownership distinguisher tells objects apart based on who "owns"
3530
 *   them, so it shows the owner or location name when listing the object. 
3531
 */
3532
modify ownershipDistinguisher
3533
    name(obj) { return obj.theNameOwnerLoc(true); }
3534
    aName(obj) { return obj.aNameOwnerLoc(true); }
3535
    theName(obj) { return obj.theNameOwnerLoc(true); }
3536
    countName(obj, cnt) { return obj.countNameOwnerLoc(cnt, true); }
3537
3538
    /* note that we're prompting based on this distinguisher */
3539
    notePrompt(lst)
3540
    {
3541
        /*
3542
         *   notify each object that we're referring to it by
3543
         *   owner/location in a disambiguation prompt
3544
         */
3545
        foreach (local cur in lst)
3546
            cur.obj_.notePromptByOwnerLoc(true);
3547
    }
3548
;
3549
3550
/*
3551
 *   The location distinguisher tells objects apart based on their
3552
 *   containers, so it shows the location name when listing the object. 
3553
 */
3554
modify locationDistinguisher
3555
    name(obj) { return obj.theNameOwnerLoc(nil); }
3556
    aName(obj) { return obj.aNameOwnerLoc(nil); }
3557
    theName(obj) { return obj.theNameOwnerLoc(nil); }
3558
    countName(obj, cnt) { return obj.countNameOwnerLoc(cnt, nil); }
3559
3560
    /* note that we're prompting based on this distinguisher */
3561
    notePrompt(lst)
3562
    {
3563
        /* notify the objects of their use in a disambiguation prompt */
3564
        foreach (local cur in lst)
3565
            cur.obj_.notePromptByOwnerLoc(nil);
3566
    }
3567
;
3568
3569
/*
3570
 *   The lit/unlit distinguisher tells apart objects based on whether
3571
 *   they're lit or unlit, so we list objects as lit or unlit explicitly.  
3572
 */
3573
modify litUnlitDistinguisher
3574
    name(obj) { return obj.nameLit; }
3575
    aName(obj) { return obj.aNameLit; }
3576
    theName(obj) { return obj.theNameLit; }
3577
    countName(obj, cnt) { return obj.pluralNameLit; }
3578
;
3579
3580
/* ------------------------------------------------------------------------ */
3581
/*
3582
 *   Enligh-specific light source modifications
3583
 */
3584
modify LightSource
3585
    /* provide lit/unlit names for litUnlitDistinguisher */
3586
    nameLit = ((isLit ? 'lit ' : 'unlit ') + name)
3587
    aNameLit()
3588
    {
3589
        /*
3590
         *   if this is a mass noun or a plural name, just use the name
3591
         *   with lit/unlit; otherwise, add "a"
3592
         */
3593
        if (isPlural || isMassNoun)
3594
            return (isLit ? 'lit ' : 'unlit ') + name;
3595
        else
3596
            return (isLit ? 'a lit ' : 'an unlit ') + name;
3597
    }
3598
    theNameLit = ((isLit ? 'the lit ' : 'the unlit ') + name)
3599
    pluralNameLit = ((isLit ? 'lit ' : 'unlit ') + pluralName)
3600
3601
    /*
3602
     *   Allow 'lit' and 'unlit' as adjectives - but even though we define
3603
     *   these as our adjectives in the dictionary, we'll only accept the
3604
     *   one appropriate for our current state, thanks to our state
3605
     *   objects.
3606
     */
3607
    adjective = 'lit' 'unlit'
3608
;
3609
3610
/*
3611
 *   Light source list states.  An illuminated light source shows its
3612
 *   status as "providing light"; an unlit light source shows no extra
3613
 *   status.
3614
 */
3615
lightSourceStateOn: ThingState 'providing light'
3616
    stateTokens = ['lit']
3617
;
3618
lightSourceStateOff: ThingState
3619
    stateTokens = ['unlit']
3620
;
3621
3622
/* ------------------------------------------------------------------------ */
3623
/*
3624
 *   Wearable states - a wearable item can be either worn or not worn.
3625
 */
3626
3627
/* "worn" */
3628
wornState: ThingState 'being worn'
3629
    /*
3630
     *   In listings of worn items, don't bother mentioning our 'worn'
3631
     *   status, as the entire list consists of items being worn - it
3632
     *   would be redundant to point out that the items in a list of items
3633
     *   being worn are being worn.
3634
     */
3635
    wornName(lst) { return nil; }
3636
;
3637
3638
/*
3639
 *   "Unworn" state.  Don't bother mentioning the status of an unworn item,
3640
 *   since this is the default for everything.  
3641
 */
3642
unwornState: ThingState;
3643
3644
3645
/* ------------------------------------------------------------------------ */
3646
/*
3647
 *   Typographical effects output filter.  This filter looks for certain
3648
 *   sequences in the text and converts them to typographical equivalents.
3649
 *   Authors could simply write the HTML for the typographical markups in
3650
 *   the first place, but it's easier to write the typewriter-like
3651
 *   sequences and let this filter convert to HTML.
3652
 *
3653
 *   We perform the following conversions:
3654
 *
3655
 *   '---' -> &zwnbsp;&emdash;
3656
 *.  '--' -> &zwnbsp;&endash;
3657
 *.  sentence-ending punctuation -> same + &ensp;
3658
 *
3659
 *   Since this routine is called so frequently, we hard-code the
3660
 *   replacement strings, rather than using properties, for slightly faster
3661
 *   performance.  Since this routine is so simple, games that want to
3662
 *   customize the replacement style should simply replace this entire
3663
 *   routine with a new routine that applies the customizations.
3664
 *
3665
 *   Note that we define this filter in the English-specific part of the
3666
 *   library, because it seems almost certain that each language will want
3667
 *   to customize it for local conventions.
3668
 */
3669
typographicalOutputFilter: OutputFilter
3670
    filterText(ostr, val)
3671
    {
3672
        /*
3673
         *   Look for sentence-ending punctuation, and put an 'en' space
3674
         *   after each occurrence.  Recognize ends of sentences even if we
3675
         *   have closing quotes, parentheses, or other grouping characters
3676
         *   following the punctuation.  Do this before the hyphen
3677
         *   substitutions so that we can look for ordinary hyphens rather
3678
         *   than all of the expanded versions.
3679
         */
3680
        val = rexReplace(eosPattern, val, '%1\u2002', ReplaceAll);
3681
3682
        /* undo any abbreviations we mistook for sentence endings */
3683
        val = rexReplace(abbrevPat, val, '%1. ', ReplaceAll);
3684
3685
        /*
3686
         *   Replace dashes with typographical hyphens.  Note that we check
3687
         *   for the three-hyphen sequence first, because if we did it the
3688
         *   other way around, we'd incorrectly find the first two hyphens
3689
         *   of each '---' sequence and replace them with an en-dash,
3690
         *   causing us to miss the '---' sequences entirely.
3691
         *
3692
         *   We put a no-break marker (\uFEFF) just before each hyphen, and
3693
         *   an okay-to-break marker (\u200B) just after, to ensure that we
3694
         *   won't have a line break between the preceding text and the
3695
         *   hyphen, and to indicate that a line break is specifically
3696
         *   allowed if needed to the right of the hyphen.
3697
         */
3698
        val = val.findReplace('---', '\uFEFF&mdash;\u200B', ReplaceAll);
3699
        val = val.findReplace('--',  '\uFEFF&ndash;\u200B', ReplaceAll);
3700
3701
        /* return the result */
3702
        return val;
3703
    }
3704
3705
    /*
3706
     *   The end-of-sentence pattern.  This looks a bit complicated, but
3707
     *   all we're looking for is a period, exclamation point, or question
3708
     *   mark, optionally followed by any number of closing group marks
3709
     *   (right parentheses or square brackets, closing HTML tags, or
3710
     *   double or single quotes in either straight or curly styles), all
3711
     *   followed by an ordinary space.
3712
     *
3713
     *   If a lower-case letter follows the space, though, we won't
3714
     *   consider it a sentence ending.  This applies most commonly after
3715
     *   quoted passages ending with what would normally be sentence-ending
3716
     *   punctuation: "'Who are you?' he asked."  In these cases, the
3717
     *   enclosing sentence isn't ending, so we don't want the extra space.
3718
     *   We can tell the enclosing sentence isn't ending because a
3719
     *   non-capital letter follows.
3720
     *
3721
     *   Note that we specifically look only for ordinary spaces.  Any
3722
     *   sentence-ending punctuation that's followed by a quoted space or
3723
     *   any typographical space overrides this substitution.
3724
     */
3725
    eosPattern = static new RexPattern(
3726
        '<case>'
3727
        + '('
3728
        +   '[.!?]'
3729
        +   '('
3730
        +     '<rparen|rsquare|dquote|squote|\u2019|\u201D>'
3731
        +     '|<langle><^rangle>*<rangle>'
3732
        +   ')*'
3733
        + ')'
3734
        + ' +(?![-a-z])'
3735
        )
3736
3737
    /* pattern for abbreviations that were mistaken for sentence endings */
3738
    abbrevPat = static new RexPattern(
3739
        '<nocase>%<(' + abbreviations + ')<dot>\u2002')
3740
3741
    /* 
3742
     *   Common abbreviations.  These are excluded from being treated as
3743
     *   sentence endings when they appear with a trailing period.
3744
     *   
3745
     *   Note that abbrevPat must be rebuilt manually if you change this on
3746
     *   the fly - abbrevPat is static, so it picks up the initial value of
3747
     *   this property at start-up, and doesn't re-evaluate it while the
3748
     *   game is running.  
3749
     */
3750
    abbreviations = 'mr|mrs|ms|dr|prof'
3751
;
3752
3753
/* ------------------------------------------------------------------------ */
3754
/*
3755
 *   The English-specific message builder.
3756
 */
3757
langMessageBuilder: MessageBuilder
3758
3759
    /*
3760
     *   The English message substitution parameter table.
3761
     *
3762
     *   Note that we specify two additional elements for each table entry
3763
     *   beyond the standard language-independent complement:
3764
     *
3765
     *   info[4] = reflexive property - this is the property to invoke
3766
     *   when the parameter is used reflexively (in other words, its
3767
     *   target object is the same as the most recent target object used
3768
     *   in the nominative case).  If this is nil, the parameter has no
3769
     *   reflexive form.
3770
     *
3771
     *   info[5] = true if this is a nominative usage, nil if not.  We use
3772
     *   this to determine which target objects are used in the nominative
3773
     *   case, so that we can remember those objects for subsequent
3774
     *   reflexive usages.
3775
     */
3776
    paramList_ =
3777
    [
3778
        /* parameters that imply the actor as the target object */
3779
        ['you/he', &theName, 'actor', nil, true],
3780
        ['you/she', &theName, 'actor', nil, true],
3781
        ['you\'re/he\'s', &itIsContraction, 'actor', nil, true],
3782
        ['you\'re/she\'s', &itIsContraction, 'actor', nil, true],
3783
        ['you\'re', &itIsContraction, 'actor', nil, true],
3784
        ['you/him', &theNameObj, 'actor', &itReflexive, nil],
3785
        ['you/her', &theNameObj, 'actor', &itReflexive, nil],
3786
        ['your/her', &theNamePossAdj, 'actor', nil, nil],
3787
        ['your/his', &theNamePossAdj, 'actor', nil, nil],
3788
        ['your', &theNamePossAdj, 'actor', nil, nil],
3789
        ['yours/hers', &theNamePossNoun, 'actor', nil, nil],
3790
        ['yours/his', &theNamePossNoun, 'actor', nil, nil],
3791
        ['yours', &theNamePossNoun, 'actor', nil, nil],
3792
        ['yourself/himself', &itReflexive, 'actor', nil, nil],
3793
        ['yourself/herself', &itReflexive, 'actor', nil, nil],
3794
        ['yourself', &itReflexive, 'actor', nil, nil],
3795
3796
        /* parameters that don't imply any target object */
3797
        ['the/he', &theName, nil, nil, true],
3798
        ['the/she', &theName, nil, nil, true],
3799
        ['the/him', &theNameObj, nil, &itReflexive, nil],
3800
        ['the/her', &theNameObj, nil, &itReflexive, nil],
3801
        ['the\'s/her', &theNamePossAdj, nil, &itPossAdj, nil],
3802
        ['the\'s/hers', &theNamePossNoun, nil, &itPossNoun, nil],
3803
3804
        /*
3805
         *  Verb 's' endings.  In most cases, you should use 's/d', 's/ed',
3806
         *  or 's/?ed' rather than 's', except in places where you know you
3807
         *  will never need the past tense form, because 's' doesn't handle
3808
         *  the past tense.  Don't use 's/?ed' with a literal question
3809
         *  mark; put a consonant in place of the question mark instead.
3810
         */
3811
        ['s', &verbEndingS, nil, nil, true],
3812
        ['s/d', &verbEndingSD, nil, nil, true],
3813
        ['s/ed', &verbEndingSEd, nil, nil, true],
3814
        ['s/?ed', &verbEndingSMessageBuilder_, nil, nil, true],
3815
3816
        ['es', &verbEndingEs, nil, nil, true],
3817
        ['es/ed', &verbEndingEs, nil, nil, true],
3818
        ['ies', &verbEndingIes, nil, nil, true],
3819
        ['ies/ied', &verbEndingIes, nil, nil, true],
3820
        ['is', &verbToBe, nil, nil, true],
3821
        ['are', &verbToBe, nil, nil, true],
3822
        ['was', &verbWas, nil, nil, true],
3823
        ['were', &verbWas, nil, nil, true],
3824
        ['has', &verbToHave, nil, nil, true],
3825
        ['have', &verbToHave, nil, nil, true],
3826
        ['does', &verbToDo, nil, nil, true],
3827
        ['do', &verbToDo, nil, nil, true],
3828
        ['goes', &verbToGo, nil, nil, true],
3829
        ['go', &verbToGo, nil, nil, true],
3830
        ['comes', &verbToCome, nil, nil, true],
3831
        ['come', &verbToCome, nil, nil, true],
3832
        ['leaves', &verbToLeave, nil, nil, true],
3833
        ['leave', &verbToLeave, nil, nil, true],
3834
        ['sees', &verbToSee, nil, nil, true],
3835
        ['see', &verbToSee, nil, nil, true],
3836
        ['says', &verbToSay, nil, nil, true],
3837
        ['say', &verbToSay, nil, nil, true],
3838
        ['must', &verbMust, nil, nil, true],
3839
        ['can', &verbCan, nil, nil, true],
3840
        ['cannot', &verbCannot, nil, nil, true],
3841
        ['can\'t', &verbCant, nil, nil, true],
3842
        ['will', &verbWill, nil, nil, true],
3843
        ['won\'t', &verbWont, nil, nil, true],
3844
        ['a/he', &aName, nil, nil, true],
3845
        ['an/he', &aName, nil, nil, true],
3846
        ['a/she', &aName, nil, nil, true],
3847
        ['an/she', &aName, nil, nil, true],
3848
        ['a/him', &aNameObj, nil, &itReflexive, nil],
3849
        ['an/him', &aNameObj, nil, &itReflexive, nil],
3850
        ['a/her', &aNameObj, nil, &itReflexive, nil],
3851
        ['an/her', &aNameObj, nil, &itReflexive, nil],
3852
        ['it/he', &itNom, nil, nil, true],
3853
        ['it/she', &itNom, nil, nil, true],
3854
        ['it/him', &itObj, nil, &itReflexive, nil],
3855
        ['it/her', &itObj, nil, &itReflexive, nil],
3856
3857
        /*
3858
         *   note that we don't have its/his, because that leaves
3859
         *   ambiguous whether we want an adjective or noun form - so we
3860
         *   only use the feminine pronouns with these, to make the
3861
         *   meaning unambiguous
3862
         */
3863
        ['its/her', &itPossAdj, nil, nil, nil],
3864
        ['its/hers', &itPossNoun, nil, nil, nil],
3865
3866
        ['it\'s/he\'s', &itIsContraction, nil, nil, true],
3867
        ['it\'s/she\'s', &itIsContraction, nil, nil, true],
3868
        ['it\'s', &itIsContraction, nil, nil, true],
3869
        ['that/he', &thatNom, nil, nil, true],
3870
        ['that/she', &thatNom, nil, nil, true],
3871
        ['that/him', &thatObj, nil, &itReflexive, nil],
3872
        ['that/her', &thatObj, nil, &itReflexive, nil],
3873
        ['that\'s', &thatIsContraction, nil, nil, true],
3874
        ['itself', &itReflexive, nil, nil, nil],
3875
        ['itself/himself', &itReflexive, nil, nil, nil],
3876
        ['itself/herself', &itReflexive, nil, nil, nil],
3877
3878
        /* default preposition for standing in/on something */
3879
        ['on', &actorInName, nil, nil, nil],
3880
        ['in', &actorInName, nil, nil, nil],
3881
        ['outof', &actorOutOfName, nil, nil, nil],
3882
        ['offof', &actorOutOfName, nil, nil, nil],
3883
        ['onto', &actorIntoName, nil, nil, nil],
3884
        ['into', &actorIntoName, nil, nil, nil],
3885
3886
        /*
3887
         *   The special invisible subject marker - this can be used to
3888
         *   mark the subject in sentences that vary from the
3889
         *   subject-verb-object structure that most English sentences
3890
         *   take.  The usual SVO structure allows the message builder to
3891
         *   see the subject first in most sentences naturally, but in
3892
         *   unusual sentence forms it is sometimes useful to be able to
3893
         *   mark the subject explicitly.  This doesn't actually result in
3894
         *   any output; it's purely for marking the subject for our
3895
         *   internal book-keeping.
3896
         *
3897
         *   (The main reason the message builder wants to know the subject
3898
         *   in the first place is so that it can use a reflexive pronoun
3899
         *   if the same object ends up being used as a direct or indirect
3900
         *   object: "you can't open yourself" rather than "you can't open
3901
         *   you.")
3902
         */
3903
        ['subj', &dummyName, nil, nil, true]
3904
    ]
3905
3906
    /*
3907
     *   Add a hook to the generateMessage method, which we use to
3908
     *   pre-process the source string before expanding the substitution
3909
     *   parameters.
3910
     */
3911
    generateMessage(orig) { return inherited(processOrig(orig)); }
3912
3913
    /*
3914
     *   Pre-process a source string containing substitution parameters,
3915
     *   before generating the expanded message from it.
3916
     *
3917
     *   We use this hook to implement the special tense-switching syntax
3918
     *   {<present>|<past>}.  Although it superficially looks like an
3919
     *   ordinary substitution parameter, we actually can't use the normal
3920
     *   parameter substitution framework for that, because we want to
3921
     *   allow the <present> and <past> substrings themselves to contain
3922
     *   substitution parameters, and the normal framework doesn't allow
3923
     *   for recursive substitution.
3924
     *
3925
     *   We simply replace every sequence of the form {<present>|<past>}
3926
     *   with either <present> or <past>, depending on the current
3927
     *   narrative tense.  We then substitute braces for square brackets in
3928
     *   the resulting string.  This allows treating every bracketed tag
3929
     *   inside the tense-switching sequence as a regular substitution
3930
     *   parameter.
3931
     *
3932
     *   For example, the sequence "{take[s]|took}" appearing in the
3933
     *   message string would be replaced with "take{s}" if the current
3934
     *   narrative tense is present, and would be replaced with "took" if
3935
     *   the current narrative tense is past.  The string "take{s}", if
3936
     *   selected, would in turn be expanded to either "take" or "takes",
3937
     *   depending on the grammatical person of the subject, as per the
3938
     *   regular substitution mechanism.
3939
     */
3940
    processOrig(str)
3941
    {
3942
        local idx = 1;
3943
        local len;
3944
        local match;
3945
        local replStr;
3946
3947
        /*
3948
         *   Keep searching the string until we run out of character
3949
         *   sequences with a special meaning (specifically, we look for
3950
         *   substrings enclosed in braces, and stuttered opening braces).
3951
         */
3952
        for (;;)
3953
        {
3954
            /*
3955
             *   Find the next special sequence.
3956
             */
3957
            match = rexSearch(patSpecial, str, idx);
3958
3959
            /*
3960
             *   If there are no more special sequence, we're done
3961
             *   pre-processing the string.
3962
             */
3963
            if (match == nil) break;
3964
3965
            /*
3966
             *   Remember the starting index and length of the special
3967
             *   sequence.
3968
             */
3969
            idx = match[1];
3970
            len = match[2];
3971
3972
            /*
3973
             *   Check if this special sequence matches our tense-switching
3974
             *   syntax.
3975
             */
3976
            if (nil == rexMatch(patTenseSwitching, str, idx))
3977
            {
3978
                /*
3979
                 *   It doesn't, so forget about it and continue searching
3980
                 *   from the end of this special sequence.
3981
                 */
3982
                idx += len;
3983
                continue;
3984
            }
3985
3986
            /*
3987
             *   Extract either the first or the second embedded string,
3988
             *   depending on the current narrative tense.
3989
             */
3990
            match = rexGroup(tSel(1, 2));
3991
            replStr = match[3];
3992
3993
            /*
3994
             *   Convert all square brackets to braces in the extracted
3995
             *   string.
3996
             */
3997
            replStr = replStr.findReplace('[', '{', ReplaceAll);
3998
            replStr = replStr.findReplace(']', '}', ReplaceAll);
3999
4000
            /*
4001
             *   In the original string, replace the tense-switching
4002
             *   sequence with the extracted string.
4003
             */
4004
            str = str.substr(1, idx - 1) + replStr + str.substr(idx + len);
4005
4006
            /*
4007
             *   Move the index at the end of the substituted string.
4008
             */
4009
            idx += match[2];
4010
        }
4011
4012
        /*
4013
         *   We're done - return the result.
4014
         */
4015
        return str;
4016
    }
4017
4018
    /*
4019
     *   Pre-compiled regular expression pattern matching any sequence with
4020
     *   a special meaning in a message string.
4021
     *
4022
     *   We match either a stuttered opening brace, or a single opening
4023
     *   brace followed by any sequence of characters that doesn't contain
4024
     *   a closing brace followed by a closing brace.
4025
     */
4026
    patSpecial = static new RexPattern
4027
        ('<lbrace><lbrace>|<lbrace>(?!<lbrace>)((?:<^rbrace>)*)<rbrace>')
4028
4029
    /*
4030
     *   Pre-compiled regular expression pattern matching our special
4031
     *   tense-switching syntax.
4032
     *
4033
     *   We match a single opening brace, followed by any sequence of
4034
     *   characters that doesn't contain a closing brace or a vertical bar,
4035
     *   followed by a vertical bar, followed by any sequence of characters
4036
     *   that doesn't contain a closing brace or a vertical bar, followed
4037
     *   by a closing brace.
4038
     */
4039
    patTenseSwitching = static new RexPattern
4040
    (
4041
        '<lbrace>(?!<lbrace>)((?:<^rbrace|vbar>)*)<vbar>'
4042
                          + '((?:<^rbrace|vbar>)*)<rbrace>'
4043
    )
4044
4045
    /*
4046
     *   The most recent target object used in the nominative case.  We
4047
     *   note this so that we can supply reflexive mappings when the same
4048
     *   object is re-used in the objective case.  This allows us to map
4049
     *   things like "you can't take you" to the better-sounding "you
4050
     *   can't take yourself".
4051
     */
4052
    lastSubject_ = nil
4053
4054
    /* the parameter name of the last subject ('dobj', 'actor', etc) */
4055
    lastSubjectName_ = nil
4056
4057
    /*
4058
     *   Get the target object property mapping.  If the target object is
4059
     *   the same as the most recent subject object (i.e., the last object
4060
     *   used in the nominative case), and this parameter has a reflexive
4061
     *   form property, we'll return the reflexive form property.
4062
     *   Otherwise, we'll return the standard property mapping.
4063
     *
4064
     *   Also, if there was an exclamation mark at the end of any word in
4065
     *   the tag, we'll return a property returning a fixed-tense form of
4066
     *   the property for the tag.
4067
     */
4068
    getTargetProp(targetObj, paramObj, info)
4069
    {
4070
        local ret;
4071
4072
        /*
4073
         *   If this target object matches the last subject, and we have a
4074
         *   reflexive rendering, return the property for the reflexive
4075
         *   rendering.
4076
         *
4077
         *   Only use the reflexive rendering if the parameter name is
4078
         *   different - if the parameter name is the same, then presumably
4079
         *   the message will have been written with a reflexive pronoun or
4080
         *   not, exactly as the author wants it.  When the author knows
4081
         *   going in that these two objects are structurally the same,
4082
         *   they want the exact usage they wrote.
4083
         */
4084
        if (targetObj == lastSubject_
4085
            && paramObj != lastSubjectName_
4086
            && info[4] != nil)
4087
        {
4088
            /* use the reflexive rendering */
4089
            ret = info[4];
4090
        }
4091
        else
4092
        {
4093
            /* no special handling; inherit the default handling */
4094
            ret = inherited(targetObj, paramObj, info);
4095
        }
4096
4097
        /* if this is a nominative usage, note it as the last subject */
4098
        if (info[5])
4099
        {
4100
            lastSubject_ = targetObj;
4101
            lastSubjectName_ = paramObj;
4102
        }
4103
4104
        /*
4105
         *   If there was an exclamation mark at the end of any word in the
4106
         *   parameter string (which we remember via the fixedTenseProp_
4107
         *   property), store the original target property in
4108
         *   fixedTenseProp_ and use &propWithPresentMessageBuilder_ as the
4109
         *   target property instead.  propWithPresentMessageBuilder_ acts
4110
         *   as a wrapper for the original target property, which it
4111
         *   invokes after temporarily switching to the present tense.
4112
         */
4113
        if (fixedTenseProp_)
4114
        {
4115
            fixedTenseProp_ = ret;
4116
            ret = &propWithPresentMessageBuilder_;
4117
        }
4118
4119
        /* return the result */
4120
        return ret;
4121
    }
4122
4123
    /* end-of-sentence match pattern */
4124
    patEndOfSentence = static new RexPattern('[.;:!?]<^alphanum>')
4125
4126
    /*
4127
     *   Process result text.
4128
     */
4129
    processResult(txt)
4130
    {
4131
        /*
4132
         *   If the text contains any sentence-ending punctuation, reset
4133
         *   our internal memory of the subject of the sentence.  We
4134
         *   consider the sentence to end with a period, semicolon, colon,
4135
         *   question mark, or exclamation point followed by anything
4136
         *   other than an alpha-numeric.  (We require the secondary
4137
         *   character so that we don't confuse things like "3:00" or
4138
         *   "7.5" to contain sentence-ending punctuation.)
4139
         */
4140
        if (rexSearch(patEndOfSentence, txt) != nil)
4141
        {
4142
            /*
4143
             *   we have a sentence ending in this run of text, so any
4144
             *   saved subject object will no longer apply after this text
4145
             *   - forget our subject object
4146
             */
4147
            lastSubject_ = nil;
4148
            lastSubjectName_ = nil;
4149
        }
4150
4151
        /* return the inherited processing */
4152
        return inherited(txt);
4153
    }
4154
4155
    /* some pre-compiled search patterns we use a lot */
4156
    patIdObjSlashIdApostS = static new RexPattern(
4157
        '(<^space>+)(<space>+<^space>+)\'s(/<^space>+)$')
4158
    patIdObjApostS = static new RexPattern(
4159
        '(?!<^space>+\'s<space>)(<^space>+)(<space>+<^space>+)\'s$')
4160
    patParamWithExclam = static new RexPattern('.*(!)(?:<space>.*|/.*|$)')
4161
    patSSlashLetterEd = static new RexPattern(
4162
        's/(<alpha>ed)$|(<alpha>ed)/s$')
4163
4164
    /*
4165
     *   Rewrite a parameter string for a language-specific syntax
4166
     *   extension.
4167
     *
4168
     *   For English, we'll handle the possessive apostrophe-s suffix
4169
     *   specially, by allowing the apostrophe-s to be appended to the
4170
     *   target object name.  If we find an apostrophe-s on the target
4171
     *   object name, we'll move it to the preceding identifier name:
4172
     *
4173
     *   the dobj's -> the's dobj
4174
     *.  the dobj's/he -> the's dobj/he
4175
     *.  he/the dobj's -> he/the's dobj
4176
     *
4177
     *   We also use this method to check for the presence of an
4178
     *   exclamation mark at the end of any word in the parameter string
4179
     *   (triggering the fixed-tense handling), and to detect a parameter
4180
     *   string matching the {s/?ed} syntax, where ? is any letter, and
4181
     *   rewrite it literally as 's/?ed' literally.
4182
     */
4183
    langRewriteParam(paramStr)
4184
    {
4185
        /*
4186
         *   Check for an exclamation mark at the end of any word in the
4187
         *   parameter string, and remember the result of the test.
4188
         */
4189
        local exclam = rexMatch(patParamWithExclam, paramStr);
4190
        fixedTenseProp_ = exclam;
4191
4192
        /*
4193
         *   Remove the exclamation mark, if any.
4194
         */
4195
        if (exclam)
4196
        {
4197
            local exclamInd = rexGroup(1)[1];
4198
            paramStr = paramStr.substr(1, exclamInd - 1)
4199
                       + paramStr.substr(exclamInd + 1);
4200
        }
4201
4202
        /* look for "id obj's" and "id1 obj's/id2" */
4203
        if (rexMatch(patIdObjSlashIdApostS, paramStr) != nil)
4204
        {
4205
            /* rewrite with the "'s" moved to the preceding parameter name */
4206
            paramStr = rexGroup(1)[3] + '\'s'
4207
                       + rexGroup(2)[3] + rexGroup(3)[3];
4208
        }
4209
        else if (rexMatch(patIdObjApostS, paramStr) != nil)
4210
        {
4211
            /* rewrite with the "'s" moved to the preceding parameter name */
4212
            paramStr = rexGroup(1)[3] + '\'s' + rexGroup(2)[3];
4213
        }
4214
4215
        /*
4216
         *   Check if this parameter matches the {s/?ed} or {?ed/s} syntax.
4217
         */
4218
        if (rexMatch(patSSlashLetterEd, paramStr))
4219
        {
4220
            /*
4221
             *   It does - remember the past verb ending, and rewrite the
4222
             *   parameter literally as 's/?ed'.
4223
             */
4224
            pastEnding_ = rexGroup(1)[3];
4225
            paramStr = 's/?ed';
4226
        }
4227
4228
        /* return our (possibly modified) result */
4229
        return paramStr;
4230
    }
4231
4232
    /*
4233
     *   This property is used to temporarily store the past-tense ending
4234
     *   of a verb to be displayed by Thing.verbEndingSMessageBuilder_.
4235
     *   It's for internal use only; game authors shouldn't have any reason
4236
     *   to access it directly.
4237
     */
4238
    pastEnding_ = nil
4239
4240
    /*
4241
     *   This property is used to temporarily store either a boolean value
4242
     *   indicating whether the last encountered parameter string had an
4243
     *   exclamation mark at the end of any word, or a property to be
4244
     *   invoked by Thing.propWithPresentMessageBuilder_.  This field is
4245
     *   for internal use only; authors shouldn't have any reason to access
4246
     *   it directly.
4247
     */
4248
    fixedTenseProp_ = nil
4249
;
4250
4251
4252
/* ------------------------------------------------------------------------ */
4253
/*
4254
 *   Temporarily override the current narrative tense and invoke a callback
4255
 *   function.
4256
 */
4257
withTense(usePastTense, callback)
4258
{
4259
    /*
4260
     *   Remember the old value of the usePastTense flag.
4261
     */
4262
    local oldUsePastTense = gameMain.usePastTense;
4263
    /*
4264
     *   Set the new value.
4265
     */
4266
    gameMain.usePastTense = usePastTense;
4267
    /*
4268
     *   Invoke the callback (remembering the return value) and restore the
4269
     *   usePastTense flag on our way out.
4270
     */
4271
    local ret;
4272
    try { ret = callback(); }
4273
    finally { gameMain.usePastTense = oldUsePastTense; }
4274
    /*
4275
     *   Return the result.
4276
     */
4277
    return ret;
4278
}
4279
4280
4281
/* ------------------------------------------------------------------------ */
4282
/*
4283
 *   Functions for spelling out numbers.  These functions take a numeric
4284
 *   value as input, and return a string with the number spelled out as
4285
 *   words in English.  For example, given the number 52, we'd return a
4286
 *   string like 'fifty-two'.
4287
 *
4288
 *   These functions obviously have language-specific implementations.
4289
 *   Note also that even their interfaces might vary by language.  Some
4290
 *   languages might need additional information in the interface; for
4291
 *   example, some languages might need to know the grammatical context
4292
 *   (such as part of speech, case, or gender) of the result.
4293
 *
4294
 *   Note that some of the spellIntXxx flags might not be meaningful in all
4295
 *   languages, because most of the flags are by their very nature
4296
 *   associated with language-specific idioms.  Translations are free to
4297
 *   ignore flags that indicate variations with no local equivalent, and to
4298
 *   add their own language-specific flags as needed.
4299
 */
4300
4301
/*
4302
 *   Spell out an integer number in words.  Returns a string with the
4303
 *   spelled-out number.
4304
 *
4305
 *   Note that this simple version of the function uses the default
4306
 *   options.  If you want to specify non-default options with the
4307
 *   SpellIntXxx flags, you can call spellIntExt().
4308
 */
4309
spellInt(val)
4310
{
4311
    return spellIntExt(val, 0);
4312
}
4313
4314
/*
4315
 *   Spell out an integer number in words, but only if it's below the given
4316
 *   threshold.  It's often awkward in prose to spell out large numbers,
4317
 *   but exactly what constitutes a large number depends on context, so
4318
 *   this routine lets the caller specify the threshold.
4319
 *   
4320
 *   If the absolute value of val is less than (not equal to) the threshold
4321
 *   value, we'll return a string with the number spelled out.  If the
4322
 *   absolute value is greater than or equal to the threshold value, we'll
4323
 *   return a string representing the number in decimal digits.  
4324
 */
4325
spellIntBelow(val, threshold)
4326
{
4327
    return spellIntBelowExt(val, threshold, 0, 0);
4328
}
4329
4330
/*
4331
 *   Spell out an integer number in words if it's below a threshold, using
4332
 *   the spellIntXxx flags given in spellFlags to control the spelled-out
4333
 *   format, and using the DigitFormatXxx flags in digitFlags to control
4334
 *   the digit format.  
4335
 */
4336
spellIntBelowExt(val, threshold, spellFlags, digitFlags)
4337
{
4338
    local absval;
4339
4340
    /* compute the absolute value */
4341
    absval = (val < 0 ? -val : val);
4342
4343
    /* check the value to see whether to spell it or write it as digits */
4344
    if (absval < threshold)
4345
    {
4346
        /* it's below the threshold - spell it out in words */
4347
        return spellIntExt(val, spellFlags);
4348
    }
4349
    else
4350
    {
4351
        /* it's not below the threshold - write it as digits */
4352
        return intToDecimal(val, digitFlags);
4353
    }
4354
}
4355
4356
/*
4357
 *   Format a number as a string of decimal digits.  The DigitFormatXxx
4358
 *   flags specify how the number is to be formatted.`
4359
 */
4360
intToDecimal(val, flags)
4361
{
4362
    local str;
4363
    local sep;
4364
4365
    /* perform the basic conversion */
4366
    str = toString(val);
4367
4368
    /* add group separators as needed */
4369
    if ((flags & DigitFormatGroupComma) != 0)
4370
    {
4371
        /* explicitly use a comma as a separator */
4372
        sep = ',';
4373
    }
4374
    else if ((flags & DigitFormatGroupPeriod) != 0)
4375
    {
4376
        /* explicitly use a period as a separator */
4377
        sep = '.';
4378
    }
4379
    else if ((flags & DigitFormatGroupSep) != 0)
4380
    {
4381
        /* use the current languageGlobals separator */
4382
        sep = languageGlobals.digitGroupSeparator;
4383
    }
4384
    else
4385
    {
4386
        /* no separator */
4387
        sep = nil;
4388
    }
4389
4390
    /* if there's a separator, add it in */
4391
    if (sep != nil)
4392
    {
4393
        local i;
4394
        local len;
4395
4396
        /*
4397
         *   Insert the separator before each group of three digits.
4398
         *   Start at the right end of the string and work left: peel off
4399
         *   the last three digits and insert a comma.  Then, move back
4400
         *   four characters through the string - another three-digit
4401
         *   group, plus the comma we inserted - and repeat.  Keep going
4402
         *   until the amount we'd want to peel off the end is as long or
4403
         *   longer than the entire remaining string.
4404
         */
4405
        for (i = 3, len = str.length() ; len > i ; i += 4)
4406
        {
4407
            /* insert this comma */
4408
            str = str.substr(1, len - i) + sep + str.substr(len - i + 1);
4409
4410
            /* note the new length */
4411
            len = str.length();
4412
        }
4413
    }
4414
4415
    /* return the result */
4416
    return str;
4417
}
4418
4419
/*
4420
 *   Spell out an integer number - "extended" interface with flags.  The
4421
 *   "flags" argument is a (bitwise-OR'd) combination of SpellIntXxx
4422
 *   values, specifying the desired format of the result.
4423
 */
4424
spellIntExt(val, flags)
4425
{
4426
    local str;
4427
    local trailingSpace;
4428
    local needAnd;
4429
    local powers = [1000000000, ' billion ',
4430
                    1000000,    ' million ',
4431
                    1000,       ' thousand ',
4432
                    100,        ' hundred '];
4433
4434
    /* start with an empty string */
4435
    str = '';
4436
    trailingSpace = nil;
4437
    needAnd = nil;
4438
4439
    /* if it's zero, it's a special case */
4440
    if (val == 0)
4441
        return 'zero';
4442
4443
    /*
4444
     *   if the number is negative, note it in the string, and use the
4445
     *   absolute value
4446
     */
4447
    if (val < 0)
4448
    {
4449
        str = 'negative ';
4450
        val = -val;
4451
    }
4452
4453
    /* do each named power of ten */
4454
    for (local i = 1 ; val >= 100 && i <= powers.length() ; i += 2)
4455
    {
4456
        /*
4457
         *   if we're in teen-hundreds mode, do the teen-hundreds - this
4458
         *   only works for values from 1,100 to 9,999, since a number like
4459
         *   12,000 doesn't work this way - 'one hundred twenty hundred' is
4460
         *   no good 
4461
         */
4462
        if ((flags & SpellIntTeenHundreds) != 0
4463
            && val >= 1100 && val < 10000)
4464
        {
4465
            /* if desired, add a comma if there was a prior power group */
4466
            if (needAnd && (flags & SpellIntCommas) != 0)
4467
                str = str.substr(1, str.length() - 1) + ', ';
4468
4469
            /* spell it out as a number of hundreds */
4470
            str += spellIntExt(val / 100, flags) + ' hundred ';
4471
4472
            /* take off the hundreds */
4473
            val %= 100;
4474
4475
            /* note the trailing space */
4476
            trailingSpace = true;
4477
4478
            /* we have something to put an 'and' after, if desired */
4479
            needAnd = true;
4480
4481
            /*
4482
             *   whatever's left is below 100 now, so there's no need to
4483
             *   keep scanning the big powers of ten
4484
             */
4485
            break;
4486
        }
4487
4488
        /* if we have something in this power range, apply it */
4489
        if (val >= powers[i])
4490
        {
4491
            /* if desired, add a comma if there was a prior power group */
4492
            if (needAnd && (flags & SpellIntCommas) != 0)
4493
                str = str.substr(1, str.length() - 1) + ', ';
4494
4495
            /* add the number of multiples of this power and the power name */
4496
            str += spellIntExt(val / powers[i], flags) + powers[i+1];
4497
4498
            /* take it out of the remaining value */
4499
            val %= powers[i];
4500
4501
            /*
4502
             *   note that we have a trailing space in the string (all of
4503
             *   the power-of-ten names have a trailing space, to make it
4504
             *   easy to tack on the remainder of the value)
4505
             */
4506
            trailingSpace = true;
4507
4508
            /* we have something to put an 'and' after, if one is desired */
4509
            needAnd = true;
4510
        }
4511
    }
4512
4513
    /*
4514
     *   if we have anything left, and we have written something so far,
4515
     *   and the caller wanted an 'and' before the tens part, add the
4516
     *   'and'
4517
     */
4518
    if ((flags & SpellIntAndTens) != 0
4519
        && needAnd
4520
        && val != 0)
4521
    {
4522
        /* add the 'and' */
4523
        str += 'and ';
4524
        trailingSpace = true;
4525
    }
4526
4527
    /* do the tens */
4528
    if (val >= 20)
4529
    {
4530
        /* anything above the teens is nice and regular */
4531
        str += ['twenty', 'thirty', 'forty', 'fifty', 'sixty',
4532
                'seventy', 'eighty', 'ninety'][val/10 - 1];
4533
        val %= 10;
4534
4535
        /* if it's non-zero, we'll add the units, so add a hyphen */
4536
        if (val != 0)
4537
            str += '-';
4538
4539
        /* we no longer have a trailing space in the string */
4540
        trailingSpace = nil;
4541
    }
4542
    else if (val >= 10)
4543
    {
4544
        /* we have a teen */
4545
        str += ['ten', 'eleven', 'twelve', 'thirteen', 'fourteen',
4546
                'fifteen', 'sixteen', 'seventeen', 'eighteen',
4547
                'nineteen'][val - 9];
4548
4549
        /* we've finished with the number */
4550
        val = 0;
4551
4552
        /* there's no trailing space */
4553
        trailingSpace = nil;
4554
    }
4555
4556
    /* if we have a units value, add it */
4557
    if (val != 0)
4558
    {
4559
        /* add the units name */
4560
        str += ['one', 'two', 'three', 'four', 'five',
4561
                'six', 'seven', 'eight', 'nine'][val];
4562
4563
        /* we have no trailing space now */
4564
        trailingSpace = nil;
4565
    }
4566
4567
    /* if there's a trailing space, remove it */
4568
    if (trailingSpace)
4569
        str = str.substr(1, str.length() - 1);
4570
4571
    /* return the string */
4572
    return str;
4573
}
4574
4575
/*
4576
 *   Return a string giving the numeric ordinal representation of a number:
4577
 *   1st, 2nd, 3rd, 4th, etc.  
4578
 */
4579
intOrdinal(n)
4580
{
4581
    local s;
4582
4583
    /* start by getting the string form of the number */
4584
    s = toString(n);
4585
4586
    /* now add the appropriate suffix */
4587
    if (n >= 10 && n <= 19)
4588
    {
4589
        /* the teens all end in 'th' */
4590
        return s + 'th';
4591
    }
4592
    else
4593
    {
4594
        /*
4595
         *   for anything but a teen, a number whose last digit is 1
4596
         *   always has the suffix 'st' (for 'xxx-first', as in '141st'),
4597
         *   a number whose last digit is 2 always ends in 'nd' (for
4598
         *   'xxx-second', as in '532nd'), a number whose last digit is 3
4599
         *   ends in 'rd' (for 'xxx-third', as in '53rd'), and anything
4600
         *   else ends in 'th'
4601
         */
4602
        switch(n % 10)
4603
        {
4604
        case 1:
4605
            return s + 'st';
4606
4607
        case 2:
4608
            return s + 'nd';
4609
4610
        case 3:
4611
            return s + 'rd';
4612
4613
        default:
4614
            return s + 'th';
4615
        }
4616
    }
4617
}
4618
4619
4620
/*
4621
 *   Return a string giving a fully spelled-out ordinal form of a number:
4622
 *   first, second, third, etc.
4623
 */
4624
spellIntOrdinal(n)
4625
{
4626
    return spellIntOrdinalExt(n, 0);
4627
}
4628
4629
/*
4630
 *   Return a string giving a fully spelled-out ordinal form of a number:
4631
 *   first, second, third, etc.  This form takes the same flag values as
4632
 *   spellIntExt().
4633
 */
4634
spellIntOrdinalExt(n, flags)
4635
{
4636
    local s;
4637
4638
    /* get the spelled-out form of the number itself */
4639
    s = spellIntExt(n, flags);
4640
4641
    /*
4642
     *   If the number ends in 'one', change the ending to 'first'; 'two'
4643
     *   becomes 'second'; 'three' becomes 'third'; 'five' becomes
4644
     *   'fifth'; 'eight' becomes 'eighth'; 'nine' becomes 'ninth'.  If
4645
     *   the number ends in 'y', change the 'y' to 'ieth'.  'Zero' becomes
4646
     *   'zeroeth'.  For everything else, just add 'th' to the spelled-out
4647
     *   name
4648
     */
4649
    if (s == 'zero')
4650
        return 'zeroeth';
4651
    if (s.endsWith('one'))
4652
        return s.substr(1, s.length() - 3) + 'first';
4653
    else if (s.endsWith('two'))
4654
        return s.substr(1, s.length() - 3) + 'second';
4655
    else if (s.endsWith('three'))
4656
        return s.substr(1, s.length() - 5) + 'third';
4657
    else if (s.endsWith('five'))
4658
        return s.substr(1, s.length() - 4) + 'fifth';
4659
    else if (s.endsWith('eight'))
4660
        return s.substr(1, s.length() - 5) + 'eighth';
4661
    else if (s.endsWith('nine'))
4662
        return s.substr(1, s.length() - 4) + 'ninth';
4663
    else if (s.endsWith('y'))
4664
        return s.substr(1, s.length() - 1) + 'ieth';
4665
    else
4666
        return s + 'th';
4667
}
4668
4669
/* ------------------------------------------------------------------------ */
4670
/*
4671
 *   Parse a spelled-out number.  This is essentially the reverse of
4672
 *   spellInt() and related functions: we take a string that contains a
4673
 *   spelled-out number and return the integer value.  This uses the
4674
 *   command parser's spelled-out number rules, so we can parse anything
4675
 *   that would be recognized as a number in a command.
4676
 *
4677
 *   If the string contains numerals, we'll treat it as a number in digit
4678
 *   format: for example, if it contains '789', we'll return 789.
4679
 *
4680
 *   If the string doesn't parse as a number, we return nil.
4681
 */
4682
parseInt(str)
4683
{
4684
    try
4685
    {
4686
        /* tokenize the string */
4687
        local toks = cmdTokenizer.tokenize(str);
4688
4689
        /* parse it */
4690
        return parseIntTokens(toks);
4691
    }
4692
    catch (Exception exc)
4693
    {
4694
        /*
4695
         *   on any exception, just return nil to indicate that we couldn't
4696
         *   parse the string as a number
4697
         */
4698
        return nil;
4699
    }
4700
}
4701
4702
/*
4703
 *   Parse a spelled-out number that's given as a token list (as returned
4704
 *   from Tokenizer.tokenize).  If we can successfully parse the token list
4705
 *   as a number, we'll return the integer value.  If not, we'll return
4706
 *   nil.
4707
 */
4708
parseIntTokens(toks)
4709
{
4710
    try
4711
    {
4712
        /*
4713
         *   if the first token contains digits, treat it as a numeric
4714
         *   string value rather than a spelled-out number
4715
         */
4716
        if (toks.length() != 0
4717
            && rexMatch('<digit>+', getTokOrig(toks[1])) != nil)
4718
            return toInteger(getTokOrig(toks[1]));
4719
4720
        /* parse it using the spelledNumber production */
4721
        local lst = spelledNumber.parseTokens(toks, cmdDict);
4722
4723
        /*
4724
         *   if we got a match, return the integer value; if not, it's not
4725
         *   parseable as a number, so return nil
4726
         */
4727
        return (lst.length() != 0 ? lst[1].getval() : nil);
4728
    }
4729
    catch (Exception exc)
4730
    {
4731
        /*
4732
         *   on any exception, just return nil to indicate that it's not
4733
         *   parseable as a number
4734
         */
4735
        return nil;
4736
    }
4737
}
4738
4739
4740
/* ------------------------------------------------------------------------ */
4741
/*
4742
 *   Additional token types for US English.
4743
 */
4744
4745
/* special "apostrophe-s" token */
4746
enum token tokApostropheS;
4747
4748
/* special abbreviation-period token */
4749
enum token tokAbbrPeriod;
4750
4751
/* special "#nnn" numeric token */
4752
enum token tokPoundInt;
4753
4754
/*
4755
 *   Command tokenizer for US English.  Other language modules should
4756
 *   provide their own tokenizers to allow for differences in punctuation
4757
 *   and other lexical elements.
4758
 */
4759
cmdTokenizer: Tokenizer
4760
    rules_ = static
4761
    [
4762
        /* skip whitespace */
4763
        ['whitespace', new RexPattern('<Space>+'), nil, &tokCvtSkip, nil],
4764
4765
        /* certain punctuation marks */
4766
        ['punctuation', new RexPattern('[.,;:?!]'), tokPunct, nil, nil],
4767
4768
        /*
4769
         *   We have a special rule for spelled-out numbers from 21 to 99:
4770
         *   when we see a 'tens' word followed by a hyphen followed by a
4771
         *   digits word, we'll pull out the tens word, the hyphen, and
4772
         *   the digits word as separate tokens.
4773
         */
4774
        ['spelled number',
4775
         new RexPattern('<NoCase>(twenty|thirty|forty|fifty|sixty|'
4776
                        + 'seventy|eighty|ninety)-'
4777
                        + '(one|two|three|four|five|six|seven|eight|nine)'
4778
                        + '(?!<AlphaNum>)'),
4779
         tokWord, &tokCvtSpelledNumber, nil],
4780
4781
4782
        /*
4783
         *   Initials.  We'll look for strings of three or two initials,
4784
         *   set off by periods but without spaces.  We'll look for
4785
         *   three-letter initials first ("G.H.W. Billfold"), then
4786
         *   two-letter initials ("X.Y. Zed"), so that we find the longest
4787
         *   sequence that's actually in the dictionary.  Note that we
4788
         *   don't have a separate rule for individual initials, since
4789
         *   we'll pick that up with the regular abbreviated word rule
4790
         *   below.
4791
         *
4792
         *   Some games could conceivably extend this to allow strings of
4793
         *   initials of four letters or longer, but in practice people
4794
         *   tend to elide the periods in longer sets of initials, so that
4795
         *   the initials become an acronym, and thus would fit the
4796
         *   ordinary word token rule.
4797
         */
4798
        ['three initials',
4799
         new RexPattern('<alpha><period><alpha><period><alpha><period>'),
4800
         tokWord, &tokCvtAbbr, &acceptAbbrTok],
4801
4802
        ['two initials',
4803
         new RexPattern('<alpha><period><alpha><period>'),
4804
         tokWord, &tokCvtAbbr, &acceptAbbrTok],
4805
4806
        /*
4807
         *   Abbbreviated word - this is a word that ends in a period,
4808
         *   such as "Mr.".  This rule comes before the ordinary word rule
4809
         *   because we will only consider the period to be part of the
4810
         *   word (and not a separate token) if the entire string
4811
         *   including the period is in the main vocabulary dictionary.
4812
         */
4813
        ['abbreviation',
4814
         new RexPattern('<Alpha|-><AlphaNum|-|squote>*<period>'),
4815
         tokWord, &tokCvtAbbr, &acceptAbbrTok],
4816
4817
        /*
4818
         *   A word ending in an apostrophe-s.  We parse this as two
4819
         *   separate tokens: one for the word and one for the
4820
         *   apostrophe-s.
4821
         */
4822
        ['apostrophe-s word',
4823
         new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*<squote>[sS]'),
4824
         tokWord, &tokCvtApostropheS, nil],
4825
4826
        /*
4827
         *   Words - note that we convert everything to lower-case.  A word
4828
         *   must start with an alphabetic character, a hyphen, or an
4829
         *   ampersand; after the initial character, a word can contain
4830
         *   alphabetics, digits, hyphens, ampersands, and apostrophes.
4831
         */
4832
        ['word',
4833
         new RexPattern('<Alpha|-|&><AlphaNum|-|&|squote>*'),
4834
         tokWord, nil, nil],
4835
4836
        /* an abbreviation word starting with a number */
4837
        ['abbreviation with initial digit',
4838
         new RexPattern('<Digit>(?=<AlphaNum|-|&|squote>*<Alpha|-|&|squote>)'
4839
                        + '<AlphaNum|-|&|squote>*<period>'),
4840
         tokWord, &tokCvtAbbr, &acceptAbbrTok],
4841
4842
        /*
4843
         *   A word can start with a number, as long as there's something
4844
         *   other than numbers in the string - if it's all numbers, we
4845
         *   want to treat it as a numeric token.
4846
         */
4847
        ['word with initial digit',
4848
         new RexPattern('<Digit>(?=<AlphaNum|-|&|squote>*<Alpha|-|&|squote>)'
4849
                        + '<AlphaNum|-|&|squote>*'), tokWord, nil, nil],
4850
4851
        /* strings with ASCII "straight" quotes */
4852
        ['string ascii-quote',
4853
         new RexPattern('<min>([`\'"])(.*)%1(?!<AlphaNum>)'),
4854
         tokString, nil, nil],
4855
4856
        /* some people like to use single quotes like `this' */
4857
        ['string back-quote',
4858
         new RexPattern('<min>`(.*)\'(?!<AlphaNum>)'), tokString, nil, nil],
4859
4860
        /* strings with Latin-1 curly quotes (single and double) */
4861
        ['string curly single-quote',
4862
         new RexPattern('<min>\u2018(.*)\u2019'), tokString, nil, nil],
4863
        ['string curly double-quote',
4864
         new RexPattern('<min>\u201C(.*)\u201D'), tokString, nil, nil],
4865
4866
        /*
4867
         *   unterminated string - if we didn't just match a terminated
4868
         *   string, but we have what looks like the start of a string,
4869
         *   match to the end of the line
4870
         */
4871
        ['string unterminated',
4872
         new RexPattern('([`\'"\u2018\u201C](.*)'), tokString, nil, nil],
4873
4874
        /* integer numbers */
4875
        ['integer', new RexPattern('[0-9]+'), tokInt, nil, nil],
4876
4877
        /* numbers with a '#' preceding */
4878
        ['integer with #',
4879
         new RexPattern('#[0-9]+'), tokPoundInt, nil, nil]
4880
    ]
4881
4882
    /*
4883
     *   Handle an apostrophe-s word.  We'll return this as two separate
4884
     *   tokens: one for the word preceding the apostrophe-s, and one for
4885
     *   the apostrophe-s itself.
4886
     */
4887
    tokCvtApostropheS(txt, typ, toks)
4888
    {
4889
        local w;
4890
        local s;
4891
4892
        /*
4893
         *   pull out the part up to but not including the apostrophe, and
4894
         *   pull out the apostrophe-s part
4895
         */
4896
        w = txt.substr(1, txt.length() - 2);
4897
        s = txt.substr(txt.length() - 1);
4898
4899
        /* add the part before the apostrophe as the main token type */
4900
        toks.append([w, typ, w]);
4901
4902
        /* add the apostrophe-s as a separate special token */
4903
        toks.append([s, tokApostropheS, s]);
4904
    }
4905
4906
    /*
4907
     *   Handle a spelled-out hyphenated number from 21 to 99.  We'll
4908
     *   return this as three separate tokens: a word for the tens name, a
4909
     *   word for the hyphen, and a word for the units name.
4910
     */
4911
    tokCvtSpelledNumber(txt, typ, toks)
4912
    {
4913
        /* parse the number into its three parts with a regular expression */
4914
        rexMatch(patAlphaDashAlpha, txt);
4915
4916
        /* add the part before the hyphen */
4917
        toks.append([rexGroup(1)[3], typ, rexGroup(1)[3]]);
4918
4919
        /* add the hyphen */
4920
        toks.append(['-', typ, '-']);
4921
4922
        /* add the part after the hyphen */
4923
        toks.append([rexGroup(2)[3], typ, rexGroup(2)[3]]);
4924
    }
4925
    patAlphaDashAlpha = static new RexPattern('(<alpha>+)-(<alpha>+)')
4926
4927
    /*
4928
     *   Check to see if we want to accept an abbreviated token - this is
4929
     *   a token that ends in a period, which we use for abbreviated words
4930
     *   like "Mr." or "Ave."  We'll accept the token only if it appears
4931
     *   as given - including the period - in the dictionary.  Note that
4932
     *   we ignore truncated matches, since the only way we'll accept a
4933
     *   period in a word token is as the last character; there is thus no
4934
     *   way that a token ending in a period could be a truncation of any
4935
     *   longer valid token.
4936
     */
4937
    acceptAbbrTok(txt)
4938
    {
4939
        /* look up the word, filtering out truncated results */
4940
        return cmdDict.isWordDefined(
4941
            txt, {result: (result & StrCompTrunc) == 0});
4942
    }
4943
4944
    /*
4945
     *   Process an abbreviated token.
4946
     *
4947
     *   When we find an abbreviation, we'll enter it with the abbreviated
4948
     *   word minus the trailing period, plus the period as a separate
4949
     *   token.  We'll mark the period as an "abbreviation period" so that
4950
     *   grammar rules will be able to consider treating it as an
4951
     *   abbreviation -- but since it's also a regular period, grammar
4952
     *   rules that treat periods as regular punctuation will also be able
4953
     *   to try to match the result.  This will ensure that we try it both
4954
     *   ways - as abbreviation and as a word with punctuation - and pick
4955
     *   the one that gives us the best result.
4956
     */
4957
    tokCvtAbbr(txt, typ, toks)
4958
    {
4959
        local w;
4960
4961
        /* add the part before the period as the ordinary token */
4962
        w = txt.substr(1, txt.length() - 1);
4963
        toks.append([w, typ, w]);
4964
4965
        /* add the token for the "abbreviation period" */
4966
        toks.append(['.', tokAbbrPeriod, '.']);
4967
    }
4968
4969
    /*
4970
     *   Given a list of token strings, rebuild the original input string.
4971
     *   We can't recover the exact input string, because the tokenization
4972
     *   process throws away whitespace information, but we can at least
4973
     *   come up with something that will display cleanly and produce the
4974
     *   same results when run through the tokenizer.
4975
     */
4976
    buildOrigText(toks)
4977
    {
4978
        local str;
4979
4980
        /* start with an empty string */
4981
        str = '';
4982
4983
        /* concatenate each token in the list */
4984
        for (local i = 1, local len = toks.length() ; i <= len ; ++i)
4985
        {
4986
            /* add the current token to the string */
4987
            str += getTokOrig(toks[i]);
4988
4989
            /*
4990
             *   if this looks like a hyphenated number that we picked
4991
             *   apart into two tokens, put it back together without
4992
             *   spaces
4993
             */
4994
            if (i + 2 <= len
4995
                && rexMatch(patSpelledTens, getTokVal(toks[i])) != nil
4996
                && getTokVal(toks[i+1]) == '-'
4997
                && rexMatch(patSpelledUnits, getTokVal(toks[i+2])) != nil)
4998
            {
4999
                /*
5000
                 *   it's a hyphenated number, all right - put the three
5001
                 *   tokens back together without any intervening spaces,
5002
                 *   so ['twenty', '-', 'one'] turns into 'twenty-one'
5003
                 */
5004
                str += getTokOrig(toks[i+1]) + getTokOrig(toks[i+2]);
5005
5006
                /* skip ahead by the two extra tokens we're adding */
5007
                i += 2;
5008
            }
5009
            else if (i + 1 <= len
5010
                     && getTokType(toks[i]) == tokWord
5011
                     && getTokType(toks[i+1]) == tokApostropheS)
5012
            {
5013
                /*
5014
                 *   it's a word followed by an apostrophe-s token - these
5015
                 *   are appended together without any intervening spaces
5016
                 */
5017
                str += getTokOrig(toks[i+1]);
5018
5019
                /* skip the extra token we added */
5020
                ++i;
5021
            }
5022
5023
            /*
5024
             *   if another token follows, and the next token isn't a
5025
             *   punctuation mark, add a space before the next token
5026
             */
5027
            if (i != len && rexMatch(patPunct, getTokVal(toks[i+1])) == nil)
5028
                str += ' ';
5029
        }
5030
5031
        /* return the result string */
5032
        return str;
5033
    }
5034
5035
    /* some pre-compiled regular expressions */
5036
    patSpelledTens = static new RexPattern(
5037
        '<nocase>twenty|thirty|forty|fifty|sixty|seventy|eighty|ninety')
5038
    patSpelledUnits = static new RexPattern(
5039
        '<nocase>one|two|three|four|five|six|seven|eight|nine')
5040
    patPunct = static new RexPattern('[.,;:?!]')
5041
;
5042
5043
5044
/* ------------------------------------------------------------------------ */
5045
/*
5046
 *   Grammar Rules
5047
 */
5048
5049
/*
5050
 *   Command with explicit target actor.  When a command starts with an
5051
 *   actor's name followed by a comma followed by a verb, we take it as
5052
 *   being directed to the actor.
5053
 */
5054
grammar firstCommandPhrase(withActor):
5055
    singleNounOnly->actor_ ',' commandPhrase->cmd_
5056
    : FirstCommandProdWithActor
5057
5058
    /* "execute" the target actor phrase */
5059
    execActorPhrase(issuingActor)
5060
    {
5061
        /* flag that the actor's being addressed in the second person */
5062
        resolvedActor_.commandReferralPerson = SecondPerson;
5063
    }
5064
;
5065
5066
grammar firstCommandPhrase(askTellActorTo):
5067
    ('ask' | 'tell' | 'a' | 't') singleNounOnly->actor_
5068
    'to' commandPhrase->cmd_
5069
    : FirstCommandProdWithActor
5070
5071
    /* "execute" the target actor phrase */
5072
    execActorPhrase(issuingActor)
5073
    {
5074
        /*
5075
         *   Since our phrasing is TELL <ACTOR> TO <DO SOMETHING>, the
5076
         *   actor clearly becomes the antecedent for a subsequent
5077
         *   pronoun.  For example, in TELL BOB TO READ HIS BOOK, the word
5078
         *   HIS pretty clearly refers back to BOB.
5079
         */
5080
        if (resolvedActor_ != nil)
5081
        {
5082
            /* set the possessive anaphor object to the actor */
5083
            resolvedActor_.setPossAnaphorObj(resolvedActor_);
5084
5085
            /* flag that the actor's being addressed in the third person */
5086
            resolvedActor_.commandReferralPerson = ThirdPerson;
5087
5088
            /*
5089
             *   in subsequent commands carried out by the issuer, the
5090
             *   target actor is now the pronoun antecedent (for example:
5091
             *   after TELL BOB TO GO NORTH, the command FOLLOW HIM means
5092
             *   to follow Bob)
5093
             */
5094
            issuingActor.setPronounObj(resolvedActor_);
5095
        }
5096
    }
5097
;
5098
5099
/*
5100
 *   An actor-targeted command with a bad command phrase.  This is used as
5101
 *   a fallback if we fail to match anything on the first attempt at
5102
 *   parsing the first command on a line.  The point is to at least detect
5103
 *   the target actor phrase, if that much is valid, so that we better
5104
 *   customize error messages for the rest of the command.  
5105
 */
5106
grammar actorBadCommandPhrase(main):
5107
    singleNounOnly->actor_ ',' miscWordList
5108
    | ('ask' | 'tell' | 'a' | 't') singleNounOnly->actor_ 'to' miscWordList
5109
    : FirstCommandProdWithActor
5110
5111
    /* to resolve nouns, we merely resolve the actor */
5112
    resolveNouns(issuingActor, targetActor, results)
5113
    {
5114
        /* resolve the underlying actor phrase */
5115
        return actor_.resolveNouns(getResolver(issuingActor), results);
5116
    }
5117
;
5118
5119
5120
/*
5121
 *   Command-only conjunctions.  These words and groups of words can
5122
 *   separate commands from one another, and can't be used to separate noun
5123
 *   phrases in a noun list.  
5124
 */
5125
grammar commandOnlyConjunction(sentenceEnding):
5126
    '.'
5127
    | '!'
5128
    : BasicProd
5129
5130
    /* these conjunctions end the sentence */
5131
    isEndOfSentence() { return true; }
5132
;
5133
5134
grammar commandOnlyConjunction(nonSentenceEnding):
5135
    'then'
5136
    | 'and' 'then'
5137
    | ',' 'then'
5138
    | ',' 'and' 'then'
5139
    | ';'
5140
    : BasicProd
5141
5142
    /* these conjunctions do not end a sentence */
5143
    isEndOfSentence() { return nil; }
5144
;
5145
5146
5147
/*
5148
 *   Command-or-noun conjunctions.  These words and groups of words can be
5149
 *   used to separate commands from one another, and can also be used to
5150
 *   separate noun phrases in a noun list.
5151
 */
5152
grammar commandOrNounConjunction(main):
5153
    ','
5154
    | 'and'
5155
    | ',' 'and'
5156
    : BasicProd
5157
5158
    /* these do not end a sentence */
5159
    isEndOfSentence() { return nil; }
5160
;
5161
5162
/*
5163
 *   Noun conjunctions.  These words and groups of words can be used to
5164
 *   separate noun phrases from one another.  Note that these do not need
5165
 *   to be exclusive to noun phrases - these can occur as command
5166
 *   conjunctions as well; this list is separated from
5167
 *   commandOrNounConjunction in case there are conjunctions that can never
5168
 *   be used as command conjunctions, since such conjunctions, which can
5169
 *   appear here, would not appear in commandOrNounConjunctions.  
5170
 */
5171
grammar nounConjunction(main):
5172
    ','
5173
    | 'and'
5174
    | ',' 'and'
5175
    : BasicProd
5176
5177
    /* these conjunctions do not end a sentence */
5178
    isEndOfSentence() { return nil; }
5179
;
5180
5181
/* ------------------------------------------------------------------------ */
5182
/*
5183
 *   Noun list: one or more noun phrases connected with conjunctions.  This
5184
 *   kind of noun list can end in a terminal noun phrase.
5185
 *   
5186
 *   Note that a single noun phrase is a valid noun list, since a list can
5187
 *   simply be a list of one.  The separate production nounMultiList can be
5188
 *   used when multiple noun phrases are required.  
5189
 */
5190
5191
/*
5192
 *   a noun list can consist of a single terminal noun phrase
5193
 */
5194
grammar nounList(terminal): terminalNounPhrase->np_ : NounListProd
5195
    resolveNouns(resolver, results)
5196
    {
5197
        /* resolve the underlying noun phrase */
5198
        return np_.resolveNouns(resolver, results);
5199
    }
5200
;
5201
5202
/*
5203
 *   a noun list can consist of a list of a single complete (non-terminal)
5204
 *   noun phrase
5205
 */
5206
grammar nounList(nonTerminal): completeNounPhrase->np_ : NounListProd
5207
    resolveNouns(resolver, results)
5208
    {
5209
        /* resolve the underlying noun phrase */
5210
        return np_.resolveNouns(resolver, results);
5211
    }
5212
;
5213
5214
/*
5215
 *   a noun list can consist of a list with two or more entries
5216
 */
5217
grammar nounList(list): nounMultiList->lst_ : NounListProd
5218
    resolveNouns(resolver, results)
5219
    {
5220
        /* resolve the underlying list */
5221
        return lst_.resolveNouns(resolver, results);
5222
    }
5223
;
5224
5225
/*
5226
 *   An empty noun list is one with no words at all.  This is matched when
5227
 *   a command requires a noun list but the player doesn't include one;
5228
 *   this construct has "badness" because we only want to match it when we
5229
 *   have no choice.
5230
 */
5231
grammar nounList(empty): [badness 500] : EmptyNounPhraseProd
5232
    responseProd = nounList
5233
;
5234
5235
/* ------------------------------------------------------------------------ */
5236
/*
5237
 *   Noun Multi List: two or more noun phrases connected by conjunctions.
5238
 *   This is almost the same as the basic nounList production, but this
5239
 *   type of production requires at least two noun phrases, whereas the
5240
 *   basic nounList production more generally defines its list as any
5241
 *   number - including one - of noun phrases.
5242
 */
5243
5244
/*
5245
 *   a multi list can consist of a noun multi- list plus a terminal noun
5246
 *   phrase, separated by a conjunction
5247
 */
5248
grammar nounMultiList(multi):
5249
    nounMultiList->lst_ nounConjunction terminalNounPhrase->np_
5250
    : NounListProd
5251
    resolveNouns(resolver, results)
5252
    {
5253
        /* return a list of all of the objects from both underlying lists */
5254
        return np_.resolveNouns(resolver, results)
5255
            + lst_.resolveNouns(resolver, results);
5256
    }
5257
;
5258
5259
/*
5260
 *   a multi list can consist of a non-terminal multi list
5261
 */
5262
grammar nounMultiList(nonterminal): nonTerminalNounMultiList->lst_
5263
    : NounListProd
5264
    resolveNouns(resolver, results)
5265
    {
5266
        /* resolve the underlying list */
5267
        return lst_.resolveNouns(resolver, results);
5268
    }
5269
;
5270
5271
/* ------------------------------------------------------------------------ */
5272
/*
5273
 *   A non-terminal noun multi list is a noun list made up of at least two
5274
 *   non-terminal noun phrases, connected by conjunctions.
5275
 *
5276
 *   This is almost the same as the regular non-terminal noun list
5277
 *   production, but this production requires two or more underlying noun
5278
 *   phrases, whereas the basic non-terminal noun list matches any number
5279
 *   of underlying phrases, including one.
5280
 */
5281
5282
/*
5283
 *   a non-terminal multi-list can consist of a pair of complete noun
5284
 *   phrases separated by a conjunction
5285
 */
5286
grammar nonTerminalNounMultiList(pair):
5287
    completeNounPhrase->np1_ nounConjunction completeNounPhrase->np2_
5288
    : NounListProd
5289
    resolveNouns(resolver, results)
5290
    {
5291
        /* return the combination of the two underlying noun phrases */
5292
        return np1_.resolveNouns(resolver, results)
5293
            + np2_.resolveNouns(resolver, results);
5294
    }
5295
;
5296
5297
/*
5298
 *   a non-terminal multi-list can consist of another non-terminal
5299
 *   multi-list plus a complete noun phrase, connected by a conjunction
5300
 */
5301
grammar nonTerminalNounMultiList(multi):
5302
    nonTerminalNounMultiList->lst_ nounConjunction completeNounPhrase->np_
5303
    : NounListProd
5304
    resolveNouns(resolver, results)
5305
    {
5306
        /* return the combination of the sublist and the noun phrase */
5307
        return lst_.resolveNouns(resolver, results)
5308
            + np_.resolveNouns(resolver, results);
5309
    }
5310
;
5311
5312
5313
/* ------------------------------------------------------------------------ */
5314
/*
5315
 *   "Except" list.  This is a noun list that can contain anything that's
5316
 *   in a regular noun list plus some things that only make sense as
5317
 *   exceptions, such as possessive nouns (e.g., "mine").
5318
 */
5319
5320
grammar exceptList(single): exceptNounPhrase->np_ : ExceptListProd
5321
    resolveNouns(resolver, results)
5322
    {
5323
        return np_.resolveNouns(resolver, results);
5324
    }
5325
;
5326
5327
grammar exceptList(list):
5328
    exceptNounPhrase->np_ nounConjunction exceptList->lst_
5329
    : ExceptListProd
5330
    resolveNouns(resolver, results)
5331
    {
5332
        /* return a list consisting of all of our objects */
5333
        return np_.resolveNouns(resolver, results)
5334
            + lst_.resolveNouns(resolver, results);
5335
    }
5336
;
5337
5338
/*
5339
 *   An "except" noun phrase is a normal "complete" noun phrase or a
5340
 *   possessive noun phrase that doesn't explicitly qualify another phrase
5341
 *   (for example, "all the coins but bob's" - the "bob's" is just a
5342
 *   possessive noun phrase without another noun phrase attached, since it
5343
 *   implicitly qualifies "the coins").
5344
 */
5345
grammar exceptNounPhrase(singleComplete): completeNounPhraseWithoutAll->np_
5346
    : ExceptListProd
5347
    resolveNouns(resolver, results)
5348
    {
5349
        return np_.resolveNouns(resolver, results);
5350
    }
5351
;
5352
5353
grammar exceptNounPhrase(singlePossessive): possessiveNounPhrase->poss_
5354
    : ButPossessiveProd
5355
;
5356
5357
5358
/* ------------------------------------------------------------------------ */
5359
/*
5360
 *   A single noun is sometimes required where, structurally, a list is not
5361
 *   allowed.  Single nouns should not be used to prohibit lists where
5362
 *   there is no structural reason for the prohibition - these should be
5363
 *   used only where it doesn't make sense to use a list structurally.  
5364
 */
5365
grammar singleNoun(normal): singleNounOnly->np_ : LayeredNounPhraseProd
5366
;
5367
5368
/*
5369
 *   An empty single noun is one with no words at all.  This is matched
5370
 *   when a command requires a noun list but the player doesn't include
5371
 *   one; this construct has "badness" because we only want to match it
5372
 *   when we have no choice.
5373
 */
5374
grammar singleNoun(empty): [badness 500] : EmptyNounPhraseProd
5375
    responseProd = singleNoun
5376
;
5377
5378
/*
5379
 *   A user could attempt to use a noun list with more than one entry (a
5380
 *   "multi list") where a single noun is required.  This is not a
5381
 *   grammatical error, so we accept it grammatically; however, for
5382
 *   disambiguation purposes we score it lower than a singleNoun production
5383
 *   with only one noun phrase, and if we try to resolve it, we'll fail
5384
 *   with an error.  
5385
 */
5386
grammar singleNoun(multiple): nounMultiList->np_ : SingleNounWithListProd
5387
;
5388
5389
5390
/*
5391
 *   A *structural* single noun phrase.  This production is for use where a
5392
 *   single noun phrase (not a list of nouns) is required grammatically.
5393
 */
5394
grammar singleNounOnly(main):
5395
    terminalNounPhrase->np_
5396
    | completeNounPhrase->np_
5397
    : SingleNounProd
5398
;
5399
5400
/* ------------------------------------------------------------------------ */
5401
/*
5402
 *   Prepositionally modified single noun phrases.  These can be used in
5403
 *   indirect object responses, so allow for interactions like this:
5404
 *   
5405
 *   >unlock door
5406
 *.  What do you want to unlock it with?
5407
 *   
5408
 *   >with the key
5409
 *   
5410
 *   The entire notion of prepositionally qualified noun phrases in
5411
 *   interactive indirect object responses is specific to English, so this
5412
 *   is implemented in the English module only.  However, the general
5413
 *   notion of specialized responses to interactive indirect object queries
5414
 *   is handled in the language-independent library in some cases, in such
5415
 *   a way that the language-specific library can customize the behavior -
5416
 *   see TIAction.askIobjResponseProd.  
5417
 */
5418
class PrepSingleNounProd: SingleNounProd
5419
    resolveNouns(resolver, results)
5420
    {
5421
        return np_.resolveNouns(resolver, results);
5422
    }
5423
;
5424
5425
/*
5426
 *   Same thing for a Topic phrase
5427
 */
5428
class PrepSingleTopicProd: TopicProd
5429
    resolveNouns(resolver, results)
5430
    {
5431
        return np_.resolveNouns(resolver, results);
5432
    }
5433
;
5434
5435
grammar inSingleNoun(main):
5436
     singleNoun->np_ | ('in' | 'into' | 'in' 'to') singleNoun->np_
5437
    : PrepSingleNounProd
5438
;
5439
5440
grammar forSingleNoun(main):
5441
   singleNoun->np_ | 'for' singleNoun->np_ : PrepSingleNounProd
5442
;
5443
5444
grammar toSingleNoun(main):
5445
   singleNoun->np_ | 'to' singleNoun->np_ : PrepSingleNounProd
5446
;
5447
5448
grammar throughSingleNoun(main):
5449
   singleNoun->np_ | ('through' | 'thru') singleNoun->np_
5450
   : PrepSingleNounProd
5451
;
5452
5453
grammar fromSingleNoun(main):
5454
   singleNoun->np_ | 'from' singleNoun->np_ : PrepSingleNounProd
5455
;
5456
5457
grammar onSingleNoun(main):
5458
   singleNoun->np_ | ('on' | 'onto' | 'on' 'to') singleNoun->np_
5459
    : PrepSingleNounProd
5460
;
5461
5462
grammar withSingleNoun(main):
5463
   singleNoun->np_ | 'with' singleNoun->np_ : PrepSingleNounProd
5464
;
5465
5466
grammar atSingleNoun(main):
5467
   singleNoun->np_ | 'at' singleNoun->np_ : PrepSingleNounProd
5468
;
5469
5470
grammar outOfSingleNoun(main):
5471
   singleNoun->np_ | 'out' 'of' singleNoun->np_ : PrepSingleNounProd
5472
;
5473
5474
grammar aboutTopicPhrase(main):
5475
   topicPhrase->np_ | 'about' topicPhrase->np_
5476
   : PrepSingleTopicProd
5477
;
5478
5479
/* ------------------------------------------------------------------------ */
5480
/*
5481
 *   Complete noun phrase - this is a fully-qualified noun phrase that
5482
 *   cannot be modified with articles, quantifiers, or anything else.  This
5483
 *   is the highest-level individual noun phrase.  
5484
 */
5485
5486
grammar completeNounPhrase(main):
5487
    completeNounPhraseWithAll->np_ | completeNounPhraseWithoutAll->np_
5488
    : LayeredNounPhraseProd
5489
;
5490
5491
/*
5492
 *   Slightly better than a purely miscellaneous word list is a pair of
5493
 *   otherwise valid noun phrases connected by a preposition that's
5494
 *   commonly used in command phrases.  This will match commands where the
5495
 *   user has assumed a command with a prepositional structure that doesn't
5496
 *   exist among the defined commands.  Since we have badness, we'll be
5497
 *   ignored any time there's a valid command syntax with the same
5498
 *   prepositional structure.
5499
 */
5500
grammar completeNounPhrase(miscPrep):
5501
    [badness 100] completeNounPhrase->np1_
5502
        ('with' | 'into' | 'in' 'to' | 'through' | 'thru' | 'for' | 'to'
5503
         | 'onto' | 'on' 'to' | 'at' | 'under' | 'behind')
5504
        completeNounPhrase->np2_
5505
    : NounPhraseProd
5506
    resolveNouns(resolver, results)
5507
    {
5508
        /* note that we have an invalid prepositional phrase structure */
5509
        results.noteBadPrep();
5510
5511
        /* resolve the underlying noun phrases, for scoring purposes */
5512
        np1_.resolveNouns(resolver, results);
5513
        np2_.resolveNouns(resolver, results);
5514
5515
        /* return nothing */
5516
        return [];
5517
    }
5518
;
5519
5520
5521
/*
5522
 *   A qualified noun phrase can, all by itself, be a full noun phrase
5523
 */
5524
grammar completeNounPhraseWithoutAll(qualified): qualifiedNounPhrase->np_
5525
    : LayeredNounPhraseProd
5526
;
5527
5528
/*
5529
 *   Pronoun rules.  A pronoun is a complete noun phrase; it does not allow
5530
 *   further qualification.  
5531
 */
5532
grammar completeNounPhraseWithoutAll(it):   'it' : ItProd;
5533
grammar completeNounPhraseWithoutAll(them): 'them' : ThemProd;
5534
grammar completeNounPhraseWithoutAll(him):  'him' : HimProd;
5535
grammar completeNounPhraseWithoutAll(her):  'her' : HerProd;
5536
5537
/*
5538
 *   Reflexive second-person pronoun, for things like "bob, look at
5539
 *   yourself"
5540
 */
5541
grammar completeNounPhraseWithoutAll(yourself):
5542
    'yourself' | 'yourselves' | 'you' : YouProd
5543
;
5544
5545
/*
5546
 *   Reflexive third-person pronouns.  We accept these in places such as
5547
 *   the indirect object of a two-object verb.
5548
 */
5549
grammar completeNounPhraseWithoutAll(itself): 'itself' : ItselfProd
5550
    /* check agreement of our binding */
5551
    checkAgreement(lst)
5552
    {
5553
        /* the result is required to be singular and ungendered */
5554
        return (lst.length() == 1 && lst[1].obj_.canMatchIt);
5555
    }
5556
;
5557
5558
grammar completeNounPhraseWithoutAll(themselves):
5559
    'themself' | 'themselves' : ThemselvesProd
5560
5561
    /* check agreement of our binding */
5562
    checkAgreement(lst)
5563
    {
5564
        /*
5565
         *   For 'themselves', allow anything; we could balk at this
5566
         *   matching a single object that isn't a mass noun, but that
5567
         *   would be overly picky, and it would probably reject at least
5568
         *   a few things that really ought to be acceptable.  Besides,
5569
         *   'them' is the closest thing English has to a singular
5570
         *   gender-neutral pronoun, and some people intentionally use it
5571
         *   as such.
5572
         */
5573
        return true;
5574
    }
5575
;
5576
5577
grammar completeNounPhraseWithoutAll(himself): 'himself' : HimselfProd
5578
    /* check agreement of our binding */
5579
    checkAgreement(lst)
5580
    {
5581
        /* the result is required to be singular and masculine */
5582
        return (lst.length() == 1 && lst[1].obj_.canMatchHim);
5583
    }
5584
;
5585
5586
grammar completeNounPhraseWithoutAll(herself): 'herself' : HerselfProd
5587
    /* check agreement of our binding */
5588
    checkAgreement(lst)
5589
    {
5590
        /* the result is required to be singular and feminine */
5591
        return (lst.length() == 1 && lst[1].obj_.canMatchHer);
5592
    }
5593
;
5594
5595
/*
5596
 *   First-person pronoun, for referring to the speaker: "bob, look at me"
5597
 */
5598
grammar completeNounPhraseWithoutAll(me): 'me' | 'myself' : MeProd;
5599
5600
/*
5601
 *   "All" and "all but".
5602
 *   
5603
 *   "All" is a "complete" noun phrase, because there's nothing else needed
5604
 *   to make it a noun phrase.  We make this a special kind of complete
5605
 *   noun phrase because 'all' is not acceptable as a complete noun phrase
5606
 *   in some contexts where any of the other complete noun phrases are
5607
 *   acceptable.
5608
 *   
5609
 *   "All but" is a "terminal" noun phrase - this is a special kind of
5610
 *   complete noun phrase that cannot be followed by another noun phrase
5611
 *   with "and".  "All but" is terminal because we want any and's that
5612
 *   follow it to be part of the exception list, so that we interpret "take
5613
 *   all but a and b" as excluding a and b, not as excluding a but then
5614
 *   including b as a separate list.  
5615
 */
5616
grammar completeNounPhraseWithAll(main):
5617
    'all' | 'everything'
5618
    : EverythingProd
5619
;
5620
5621
grammar terminalNounPhrase(allBut):
5622
    ('all' | 'everything') ('but' | 'except' | 'except' 'for')
5623
        exceptList->except_
5624
    : EverythingButProd
5625
;
5626
5627
/*
5628
 *   Plural phrase with an exclusion list.  This is a terminal noun phrase
5629
 *   because it ends in an exclusion list.
5630
 */
5631
grammar terminalNounPhrase(pluralExcept):
5632
    (qualifiedPluralNounPhrase->np_ | detPluralNounPhrase->np_)
5633
    ('except' | 'except' 'for' | 'but' | 'but' 'not') exceptList->except_
5634
    : ListButProd
5635
;
5636
5637
/*
5638
 *   Qualified singular with an exception
5639
 */
5640
grammar terminalNounPhrase(anyBut):
5641
    'any' nounPhrase->np_
5642
    ('but' | 'except' | 'except' 'for' | 'but' 'not') exceptList->except_
5643
    : IndefiniteNounButProd
5644
;
5645
5646
/* ------------------------------------------------------------------------ */
5647
/*
5648
 *   A qualified noun phrase is a noun phrase with an optional set of
5649
 *   qualifiers: a definite or indefinite article, a quantifier, words such
5650
 *   as 'any' and 'all', possessives, and locational specifiers ("the box
5651
 *   on the table").
5652
 *
5653
 *   Without qualification, a definite article is implicit, so we read
5654
 *   "take box" as equivalent to "take the box."
5655
 *
5656
 *   Grammar rule instantiations in language-specific modules should set
5657
 *   property np_ to the underlying noun phrase match tree.
5658
 */
5659
5660
/*
5661
 *   A qualified noun phrase can be either singular or plural.  The number
5662
 *   is a feature of the overall phrase; the phrase might consist of
5663
 *   subphrases of different numbers (for example, "bob's coins" is plural
5664
 *   even though it contains a singular subphrase, "bob"; and "one of the
5665
 *   coins" is singular, even though its subphrase "coins" is plural).
5666
 */
5667
grammar qualifiedNounPhrase(main):
5668
    qualifiedSingularNounPhrase->np_
5669
    | qualifiedPluralNounPhrase->np_
5670
    : LayeredNounPhraseProd
5671
;
5672
5673
/* ------------------------------------------------------------------------ */
5674
/*
5675
 *   Singular qualified noun phrase.
5676
 */
5677
5678
/*
5679
 *   A singular qualified noun phrase with an implicit or explicit definite
5680
 *   article.  If there is no article, a definite article is implied (we
5681
 *   interpret "take box" as though it were "take the box").
5682
 */
5683
grammar qualifiedSingularNounPhrase(definite):
5684
    ('the' | 'the' 'one' | 'the' '1' | ) indetSingularNounPhrase->np_
5685
    : DefiniteNounProd
5686
;
5687
5688
/*
5689
 *   A singular qualified noun phrase with an explicit indefinite article.
5690
 */
5691
grammar qualifiedSingularNounPhrase(indefinite):
5692
    ('a' | 'an') indetSingularNounPhrase->np_
5693
    : IndefiniteNounProd
5694
;
5695
5696
/*
5697
 *   A singular qualified noun phrase with an explicit arbitrary
5698
 *   determiner.
5699
 */
5700
grammar qualifiedSingularNounPhrase(arbitrary):
5701
    ('any' | 'one' | '1' | 'any' ('one' | '1')) indetSingularNounPhrase->np_
5702
    : ArbitraryNounProd
5703
;
5704
5705
/*
5706
 *   A singular qualified noun phrase with a possessive adjective.
5707
 */
5708
grammar qualifiedSingularNounPhrase(possessive):
5709
    possessiveAdjPhrase->poss_ indetSingularNounPhrase->np_
5710
    : PossessiveNounProd
5711
;
5712
5713
/*
5714
 *   A singular qualified noun phrase that arbitrarily selects from a
5715
 *   plural set.  This is singular, even though the underlying noun phrase
5716
 *   is plural, because we're explicitly selecting one item.
5717
 */
5718
grammar qualifiedSingularNounPhrase(anyPlural):
5719
    'any' 'of' explicitDetPluralNounPhrase->np_
5720
    : ArbitraryNounProd
5721
;
5722
5723
/*
5724
 *   A singular object specified only by its containment, with a definite
5725
 *   article.
5726
 */
5727
grammar qualifiedSingularNounPhrase(theOneIn):
5728
    'the' 'one' ('that' ('is' | 'was') | 'that' tokApostropheS | )
5729
    ('in' | 'inside' | 'inside' 'of' | 'on' | 'from')
5730
    completeNounPhraseWithoutAll->cont_
5731
    : VagueContainerDefiniteNounPhraseProd
5732
5733
    /*
5734
     *   our main phrase is simply 'one' (so disambiguation prompts will
5735
     *   read "which one do you mean...")
5736
     */
5737
    mainPhraseText = 'one'
5738
;
5739
5740
/*
5741
 *   A singular object specified only by its containment, with an
5742
 *   indefinite article.
5743
 */
5744
grammar qualifiedSingularNounPhrase(anyOneIn):
5745
    ('anything' | 'one') ('that' ('is' | 'was') | 'that' tokApostropheS | )
5746
    ('in' | 'inside' | 'inside' 'of' | 'on' | 'from')
5747
    completeNounPhraseWithoutAll->cont_
5748
    : VagueContainerIndefiniteNounPhraseProd
5749
;
5750
5751
/* ------------------------------------------------------------------------ */
5752
/*
5753
 *   An "indeterminate" singular noun phrase is a noun phrase without any
5754
 *   determiner.  A determiner is a phrase that specifies the phrase's
5755
 *   number and indicates whether or not it refers to a specific object,
5756
 *   and if so fixes which object it refers to; determiners include
5757
 *   articles ("the", "a") and possessives.
5758
 *
5759
 *   Note that an indeterminate phrase is NOT necessarily an indefinite
5760
 *   phrase.  In fact, in most cases, we assume a definite usage when the
5761
 *   determiner is omitted: we take TAKE BOX as meaning TAKE THE BOX.  This
5762
 *   is more or less the natural way an English speaker would interpret
5763
 *   this ill-formed phrasing, but even more than that, it's the
5764
 *   Adventurese convention, taking into account that most players enter
5765
 *   commands telegraphically and are accustomed to noun phrases being
5766
 *   definite by default.
5767
 */
5768
5769
/* an indetermine noun phrase can be a simple noun phrase */
5770
grammar indetSingularNounPhrase(basic):
5771
    nounPhrase->np_
5772
    : LayeredNounPhraseProd
5773
;
5774
5775
/*
5776
 *   An indetermine noun phrase can specify a location for the object(s).
5777
 *   The location must be a singular noun phrase, but can itself be a fully
5778
 *   qualified noun phrase (so it can have possessives, articles, and
5779
 *   locational qualifiers of its own).
5780
 *   
5781
 *   Note that we take 'that are' even though the noun phrase is singular,
5782
 *   because what we consider a singular noun phrase can have plural usage
5783
 *   ("scissors", for example).  
5784
 */
5785
grammar indetSingularNounPhrase(locational):
5786
    nounPhrase->np_
5787
    ('that' ('is' | 'was')
5788
     | 'that' tokApostropheS
5789
     | 'that' ('are' | 'were')
5790
     | )
5791
    ('in' | 'inside' | 'inside' 'of' | 'on' | 'from')
5792
    completeNounPhraseWithoutAll->cont_
5793
    : ContainerNounPhraseProd
5794
;
5795
5796
/* ------------------------------------------------------------------------ */
5797
/*
5798
 *   Plural qualified noun phrase.
5799
 */
5800
5801
/*
5802
 *   A simple unqualified plural phrase with determiner.  Since this form
5803
 *   of plural phrase doesn't have any additional syntax that makes it an
5804
 *   unambiguous plural, we can only accept an actual plural for the
5805
 *   underlying phrase here - we can't accept an adjective phrase.
5806
 */
5807
grammar qualifiedPluralNounPhrase(determiner):
5808
    ('any' | ) detPluralOnlyNounPhrase->np_
5809
    : LayeredNounPhraseProd
5810
;
5811
5812
/* plural phrase qualified with a number and optional "any" */
5813
grammar qualifiedPluralNounPhrase(anyNum):
5814
    ('any' | ) numberPhrase->quant_ indetPluralNounPhrase->np_
5815
    | ('any' | ) numberPhrase->quant_ 'of' explicitDetPluralNounPhrase->np_
5816
    : QuantifiedPluralProd
5817
;
5818
5819
/* plural phrase qualified with a number and "all" */
5820
grammar qualifiedPluralNounPhrase(allNum):
5821
    'all' numberPhrase->quant_ indetPluralNounPhrase->np_
5822
    | 'all' numberPhrase->quant_ 'of' explicitDetPluralNounPhrase->np_
5823
    : ExactQuantifiedPluralProd
5824
;
5825
5826
/* plural phrase qualified with "both" */
5827
grammar qualifiedPluralNounPhrase(both):
5828
    'both' detPluralNounPhrase->np_
5829
    | 'both' 'of' explicitDetPluralNounPhrase->np_
5830
    : BothPluralProd
5831
;
5832
5833
/* plural phrase qualified with "all" */
5834
grammar qualifiedPluralNounPhrase(all):
5835
    'all' detPluralNounPhrase->np_
5836
    | 'all' 'of' explicitDetPluralNounPhrase->np_
5837
    : AllPluralProd
5838
;
5839
5840
/* vague plural phrase with location specified */
5841
grammar qualifiedPluralNounPhrase(theOnesIn):
5842
    ('the' 'ones' ('that' ('are' | 'were') | )
5843
     | ('everything' | 'all')
5844
       ('that' ('is' | 'was') | 'that' tokApostropheS | ))
5845
    ('in' | 'inside' | 'inside' 'of' | 'on' | 'from')
5846
    completeNounPhraseWithoutAll->cont_
5847
    : AllInContainerNounPhraseProd
5848
;
5849
5850
/* ------------------------------------------------------------------------ */
5851
/*
5852
 *   A plural noun phrase with a determiner.  The determiner can be
5853
 *   explicit (such as an article or possessive) or it can implied (the
5854
 *   implied determiner is the definite article, so "take boxes" is
5855
 *   understood as "take the boxes").
5856
 */
5857
grammar detPluralNounPhrase(main):
5858
    indetPluralNounPhrase->np_ | explicitDetPluralNounPhrase->np_
5859
    : LayeredNounPhraseProd
5860
;
5861
5862
/* ------------------------------------------------------------------------ */
5863
/*
5864
 *   A determiner plural phrase with an explicit underlying plural (i.e.,
5865
 *   excluding adjective phrases with no explicitly plural words).
5866
 */
5867
grammar detPluralOnlyNounPhrase(main):
5868
    implicitDetPluralOnlyNounPhrase->np_
5869
    | explicitDetPluralOnlyNounPhrase->np_
5870
    : LayeredNounPhraseProd
5871
;
5872
5873
/*
5874
 *   An implicit determiner plural phrase is an indeterminate plural phrase
5875
 *   without any extra determiner - i.e., the determiner is implicit.
5876
 *   We'll treat this the same way we do a plural explicitly determined
5877
 *   with a definite article, since this is the most natural interpretation
5878
 *   in English.
5879
 *
5880
 *   (This might seem like a pointless extra layer in the grammar, but it's
5881
 *   necessary for the resolution process to have a layer that explicitly
5882
 *   declares the phrase to be determined, even though the determiner is
5883
 *   implied in the structure.  This extra layer is important because it
5884
 *   explicitly calls results.noteMatches(), which is needed for rankings
5885
 *   and the like.)
5886
 */
5887
grammar implicitDetPluralOnlyNounPhrase(main):
5888
    indetPluralOnlyNounPhrase->np_
5889
    : DefinitePluralProd
5890
;
5891
5892
/* ------------------------------------------------------------------------ */
5893
/*
5894
 *   A plural noun phrase with an explicit determiner.
5895
 */
5896
5897
/* a plural noun phrase with a definite article */
5898
grammar explicitDetPluralNounPhrase(definite):
5899
    'the' indetPluralNounPhrase->np_
5900
    : DefinitePluralProd
5901
;
5902
5903
/* a plural noun phrase with a definite article and a number */
5904
grammar explicitDetPluralNounPhrase(definiteNumber):
5905
    'the' numberPhrase->quant_ indetPluralNounPhrase->np_
5906
    : ExactQuantifiedPluralProd
5907
;
5908
5909
/* a plural noun phrase with a possessive */
5910
grammar explicitDetPluralNounPhrase(possessive):
5911
    possessiveAdjPhrase->poss_ indetPluralNounPhrase->np_
5912
    : PossessivePluralProd
5913
;
5914
5915
/* a plural noun phrase with a possessive and a number */
5916
grammar explicitDetPluralNounPhrase(possessiveNumber):
5917
    possessiveAdjPhrase->poss_ numberPhrase->quant_
5918
    indetPluralNounPhrase->np_
5919
    : ExactQuantifiedPossessivePluralProd
5920
;
5921
5922
/* ------------------------------------------------------------------------ */
5923
/*
5924
 *   A plural noun phrase with an explicit determiner and only an
5925
 *   explicitly plural underlying phrase.
5926
 */
5927
grammar explicitDetPluralOnlyNounPhrase(definite):
5928
    'the' indetPluralOnlyNounPhrase->np_
5929
    : AllPluralProd
5930
;
5931
5932
grammar explicitDetPluralOnlyNounPhrase(definiteNumber):
5933
    'the' numberPhrase->quant_ indetPluralNounPhrase->np_
5934
    : ExactQuantifiedPluralProd
5935
;
5936
5937
grammar explicitDetPluralOnlyNounPhrase(possessive):
5938
    possessiveAdjPhrase->poss_ indetPluralOnlyNounPhrase->np_
5939
    : PossessivePluralProd
5940
;
5941
5942
grammar explicitDetPluralOnlyNounPhrase(possessiveNumber):
5943
    possessiveAdjPhrase->poss_ numberPhrase->quant_
5944
    indetPluralNounPhrase->np_
5945
    : ExactQuantifiedPossessivePluralProd
5946
;
5947
5948
5949
/* ------------------------------------------------------------------------ */
5950
/*
5951
 *   An indeterminate plural noun phrase.
5952
 *
5953
 *   For the basic indeterminate plural phrase, allow an adjective phrase
5954
 *   anywhere a plural phrase is allowed; this makes possible the
5955
 *   short-hand of omitting a plural word when the plural number is
5956
 *   unambiguous from context.
5957
 */
5958
5959
/* a simple plural noun phrase */
5960
grammar indetPluralNounPhrase(basic):
5961
    pluralPhrase->np_ | adjPhrase->np_
5962
    : LayeredNounPhraseProd
5963
;
5964
5965
/*
5966
 *   A plural noun phrase with a locational qualifier.  Note that even
5967
 *   though the overall phrase is plural (and so the main underlying noun
5968
 *   phrase is plural), the location phrase itself must always be singular.
5969
 */
5970
grammar indetPluralNounPhrase(locational):
5971
    (pluralPhrase->np_ | adjPhrase->np_) ('that' ('are' | 'were') | )
5972
    ('in' | 'inside' | 'inside' 'of' | 'on' | 'from')
5973
    completeNounPhraseWithoutAll->cont_
5974
    : ContainerNounPhraseProd
5975
;
5976
5977
/*
5978
 *   An indetermine plural noun phrase with only explicit plural phrases.
5979
 */
5980
grammar indetPluralOnlyNounPhrase(basic):
5981
    pluralPhrase->np_
5982
    : LayeredNounPhraseProd
5983
;
5984
5985
grammar indetPluralOnlyNounPhrase(locational):
5986
    pluralPhrase->np_ ('that' ('are' | 'were') | )
5987
    ('in' | 'inside' | 'inside' 'of' | 'on' | 'from')
5988
    completeNounPhraseWithoutAll->cont_
5989
    : ContainerNounPhraseProd
5990
;
5991
5992
/* ------------------------------------------------------------------------ */
5993
/*
5994
 *   Noun Phrase.  This is the basic noun phrase, which serves as a
5995
 *   building block for complete noun phrases.  This type of noun phrase
5996
 *   can be qualified with articles, quantifiers, and possessives, and can
5997
 *   be used to construct possessives via the addition of "'s" at the end
5998
 *   of the phrase.
5999
 *   
6000
 *   In most cases, custom noun phrase rules should be added to this
6001
 *   production, as long as qualification (with numbers, articles, and
6002
 *   possessives) is allowed.  For a custom noun phrase rule that cannot be
6003
 *   qualified, a completeNounPhrase rule should be added instead.  
6004
 */
6005
grammar nounPhrase(main): compoundNounPhrase->np_
6006
    : LayeredNounPhraseProd
6007
;
6008
6009
/*
6010
 *   Plural phrase.  This is the basic plural phrase, and corresponds to
6011
 *   the basic nounPhrase for plural forms.
6012
 */
6013
grammar pluralPhrase(main): compoundPluralPhrase->np_
6014
    : LayeredNounPhraseProd
6015
;
6016
6017
/* ------------------------------------------------------------------------ */
6018
/*
6019
 *   Compound noun phrase.  This is one or more noun phrases connected with
6020
 *   'of', as in "piece of paper".  The part after the 'of' is another
6021
 *   compound noun phrase.
6022
 *   
6023
 *   Note that this general rule does not allow the noun phrase after the
6024
 *   'of' to be qualified with an article or number, except that we make an
6025
 *   exception to allow a definite article.  Other cases ("a piece of four
6026
 *   papers") do not generally make sense, so we won't attempt to support
6027
 *   them; instead, games can add as special cases new nounPhrase rules for
6028
 *   specific literal sequences where more complex grammar is necessary.  
6029
 */
6030
grammar compoundNounPhrase(simple): simpleNounPhrase->np_
6031
    : NounPhraseWithVocab
6032
    getVocabMatchList(resolver, results, extraFlags)
6033
    {
6034
        return np_.getVocabMatchList(resolver, results, extraFlags);
6035
    }
6036
    getAdjustedTokens()
6037
    {
6038
        return np_.getAdjustedTokens();
6039
    }
6040
;
6041
6042
grammar compoundNounPhrase(of):
6043
    simpleNounPhrase->np1_ 'of'->of_ compoundNounPhrase->np2_
6044
    | simpleNounPhrase->np1_ 'of'->of_ 'the'->the_ compoundNounPhrase->np2_
6045
    : NounPhraseWithVocab
6046
    getVocabMatchList(resolver, results, extraFlags)
6047
    {
6048
        local lst1;
6049
        local lst2;
6050
6051
        /* resolve the two underlying lists */
6052
        lst1 = np1_.getVocabMatchList(resolver, results, extraFlags);
6053
        lst2 = np2_.getVocabMatchList(resolver, results, extraFlags);
6054
6055
        /*
6056
         *   the result is the intersection of the two lists, since we
6057
         *   want the list of objects with all of the underlying
6058
         *   vocabulary words
6059
         */
6060
        return intersectNounLists(lst1, lst2);
6061
    }
6062
    getAdjustedTokens()
6063
    {
6064
        local ofLst;
6065
6066
        /* generate the 'of the' list from the original words */
6067
        if (the_ == nil)
6068
            ofLst = [of_, &miscWord];
6069
        else
6070
            ofLst = [of_, &miscWord, the_, &miscWord];
6071
6072
        /* return the full list */
6073
        return np1_.getAdjustedTokens() + ofLst + np2_.getAdjustedTokens();
6074
    }
6075
;
6076
6077
6078
/* ------------------------------------------------------------------------ */
6079
/*
6080
 *   Compound plural phrase - same as a compound noun phrase, but involving
6081
 *   a plural part before the 'of'.  
6082
 */
6083
6084
/*
6085
 *   just a single plural phrase
6086
 */
6087
grammar compoundPluralPhrase(simple): simplePluralPhrase->np_
6088
    : NounPhraseWithVocab
6089
    getVocabMatchList(resolver, results, extraFlags)
6090
    {
6091
        return np_.getVocabMatchList(resolver, results, extraFlags);
6092
    }
6093
    getAdjustedTokens()
6094
    {
6095
        return np_.getAdjustedTokens();
6096
    }
6097
;
6098
6099
/*
6100
 *   <plural-phrase> of <noun-phrase>
6101
 */
6102
grammar compoundPluralPhrase(of):
6103
    simplePluralPhrase->np1_ 'of'->of_ compoundNounPhrase->np2_
6104
    | simplePluralPhrase->np1_ 'of'->of_ 'the'->the_ compoundNounPhrase->np2_
6105
    : NounPhraseWithVocab
6106
    getVocabMatchList(resolver, results, extraFlags)
6107
    {
6108
        local lst1;
6109
        local lst2;
6110
6111
        /* resolve the two underlying lists */
6112
        lst1 = np1_.getVocabMatchList(resolver, results, extraFlags);
6113
        lst2 = np2_.getVocabMatchList(resolver, results, extraFlags);
6114
6115
        /*
6116
         *   the result is the intersection of the two lists, since we
6117
         *   want the list of objects with all of the underlying
6118
         *   vocabulary words
6119
         */
6120
        return intersectNounLists(lst1, lst2);
6121
    }
6122
    getAdjustedTokens()
6123
    {
6124
        local ofLst;
6125
6126
        /* generate the 'of the' list from the original words */
6127
        if (the_ == nil)
6128
            ofLst = [of_, &miscWord];
6129
        else
6130
            ofLst = [of_, &miscWord, the_, &miscWord];
6131
6132
        /* return the full list */
6133
        return np1_.getAdjustedTokens() + ofLst + np2_.getAdjustedTokens();
6134
    }
6135
;
6136
6137
/* ------------------------------------------------------------------------ */
6138
/*
6139
 *   Simple noun phrase.  This is the most basic noun phrase, which is
6140
 *   simply a noun, optionally preceded by one or more adjectives.
6141
 */
6142
6143
/*
6144
 *   just a noun
6145
 */
6146
grammar simpleNounPhrase(noun): nounWord->noun_ : NounPhraseWithVocab
6147
    /* generate a list of my resolved objects */
6148
    getVocabMatchList(resolver, results, extraFlags)
6149
    {
6150
        return noun_.getVocabMatchList(resolver, results, extraFlags);
6151
    }
6152
    getAdjustedTokens()
6153
    {
6154
        return noun_.getAdjustedTokens();
6155
    }
6156
;
6157
6158
/*
6159
 *   <adjective> <simple-noun-phrase> (this allows any number of adjectives
6160
 *   to be applied) 
6161
 */
6162
grammar simpleNounPhrase(adjNP): adjWord->adj_ simpleNounPhrase->np_
6163
    : NounPhraseWithVocab
6164
6165
    /* generate a list of my resolved objects */
6166
    getVocabMatchList(resolver, results, extraFlags)
6167
    {
6168
        /*
6169
         *   return the list of objects in scope matching our adjective
6170
         *   plus the list from the underlying noun phrase
6171
         */
6172
        return intersectNounLists(
6173
            adj_.getVocabMatchList(resolver, results, extraFlags),
6174
            np_.getVocabMatchList(resolver, results, extraFlags));
6175
    }
6176
    getAdjustedTokens()
6177
    {
6178
        return adj_.getAdjustedTokens() + np_.getAdjustedTokens();
6179
    }
6180
;
6181
6182
/*
6183
 *   A simple noun phrase can also include a number or a quoted string
6184
 *   before or after a noun.  A number can be spelled out or written with
6185
 *   numerals; we consider both forms equivalent in meaning.
6186
 *   
6187
 *   A number in this type of usage is grammatically equivalent to an
6188
 *   adjective - it's not meant to quantify the rest of the noun phrase,
6189
 *   but rather is simply an adjective-like modifier.  For example, an
6190
 *   elevator's control panel might have a set of numbered buttons which we
6191
 *   want to refer to as "button 1," "button 2," and so on.  It is
6192
 *   frequently the case that numeric adjectives are equally at home before
6193
 *   or after their noun: "push 3 button" or "push button 3".  In addition,
6194
 *   we accept a number by itself as a lone adjective, as in "push 3".  
6195
 */
6196
6197
/*
6198
 *   just a numeric/string adjective (for things like "push 3", "push #3",
6199
 *   'push "G"')
6200
 */
6201
grammar simpleNounPhrase(number): literalAdjPhrase->adj_
6202
    : NounPhraseWithVocab
6203
    getVocabMatchList(resolver, results, extraFlags)
6204
    {
6205
        /*
6206
         *   note that this counts as an adjective-ending phrase, since we
6207
         *   don't have a noun involved
6208
         */
6209
        results.noteAdjEnding();
6210
6211
        /* pass through to the underlying literal adjective phrase */
6212
        local lst = adj_.getVocabMatchList(resolver, results,
6213
                                           extraFlags | EndsWithAdj);
6214
6215
        /* if in global scope, also try a noun interpretation */
6216
        if (resolver.isGlobalScope)
6217
            lst = adj_.addNounMatchList(lst, resolver, results, extraFlags);
6218
6219
        /* return the result */
6220
        return lst;
6221
    }
6222
    getAdjustedTokens()
6223
    {
6224
        /* pass through to the underlying literal adjective phrase */
6225
        return adj_.getAdjustedTokens();
6226
    }
6227
;
6228
6229
/*
6230
 *   <literal-adjective> <noun> (for things like "board 44 bus" or 'push
6231
 *   "G" button')
6232
 */
6233
grammar simpleNounPhrase(numberAndNoun):
6234
    literalAdjPhrase->adj_ nounWord->noun_
6235
    : NounPhraseWithVocab
6236
    getVocabMatchList(resolver, results, extraFlags)
6237
    {
6238
        local nounList;
6239
        local adjList;
6240
6241
        /* get the list of objects matching the rest of the noun phrase */
6242
        nounList = noun_.getVocabMatchList(resolver, results, extraFlags);
6243
6244
        /* get the list of objects matching the literal adjective */
6245
        adjList = adj_.getVocabMatchList(resolver, results, extraFlags);
6246
6247
        /* intersect the two lists and return the results */
6248
        return intersectNounLists(nounList, adjList);
6249
    }
6250
    getAdjustedTokens()
6251
    {
6252
        return adj_.getAdjustedTokens() + noun_.getAdjustedTokens();
6253
    }
6254
;
6255
6256
/*
6257
 *   <noun> <literal-adjective> (for things like "press button 3" or 'put
6258
 *   tab "A" in slot "B"')
6259
 */
6260
grammar simpleNounPhrase(nounAndNumber):
6261
    nounWord->noun_ literalAdjPhrase->adj_
6262
    : NounPhraseWithVocab
6263
    getVocabMatchList(resolver, results, extraFlags)
6264
    {
6265
        local nounList;
6266
        local adjList;
6267
6268
        /* get the list of objects matching the rest of the noun phrase */
6269
        nounList = noun_.getVocabMatchList(resolver, results, extraFlags);
6270
6271
        /* get the literal adjective matches */
6272
        adjList = adj_.getVocabMatchList(resolver, results, extraFlags);
6273
6274
        /* intersect the two lists and return the results */
6275
        return intersectNounLists(nounList, adjList);
6276
    }
6277
    getAdjustedTokens()
6278
    {
6279
        return noun_.getAdjustedTokens() + adj_.getAdjustedTokens();
6280
    }
6281
;
6282
6283
/*
6284
 *   A simple noun phrase can also end in an adjective, which allows
6285
 *   players to refer to objects using only their unique adjectives rather
6286
 *   than their full names, which is sometimes more convenient: just "take
6287
 *   gold" rather than "take gold key."
6288
 *   
6289
 *   When a particular phrase can be interpreted as either ending in an
6290
 *   adjective or ending in a noun, we will always take the noun-ending
6291
 *   interpretation - in such cases, the adjective-ending interpretation is
6292
 *   probably a weak binding.  For example, "take pizza" almost certainly
6293
 *   refers to the pizza itself when "pizza" and "pizza box" are both
6294
 *   present, but it can also refer just to the box when no pizza is
6295
 *   present.
6296
 *   
6297
 *   Equivalent to a noun phrase ending in an adjective is a noun phrase
6298
 *   ending with an adjective followed by "one," as in "the red one."  
6299
 */
6300
grammar simpleNounPhrase(adj): adjWord->adj_ : NounPhraseWithVocab
6301
    /* generate a list of my resolved objects */
6302
    getVocabMatchList(resolver, results, extraFlags)
6303
    {
6304
        /* note in the results that we end in an adjective */
6305
        results.noteAdjEnding();
6306
6307
        /* generate a list of objects matching the adjective */
6308
        local lst = adj_.getVocabMatchList(
6309
            resolver, results, extraFlags | EndsWithAdj);
6310
6311
        /* if in global scope, also try a noun interpretation */
6312
        if (resolver.isGlobalScope)
6313
            lst = adj_.addNounMatchList(lst, resolver, results, extraFlags);
6314
6315
        /* return the result */
6316
        return lst;
6317
    }
6318
    getAdjustedTokens()
6319
    {
6320
        /* return the adjusted token list for the adjective */
6321
        return adj_.getAdjustedTokens();
6322
    }
6323
;
6324
6325
grammar simpleNounPhrase(adjAndOne): adjective->adj_ 'one'
6326
    : NounPhraseWithVocab
6327
    /* generate a list of my resolved objects */
6328
    getVocabMatchList(resolver, results, extraFlags)
6329
    {
6330
        /*
6331
         *   This isn't exactly an adjective ending, but consider it as
6332
         *   such anyway, since we're not matching 'one' to a vocabulary
6333
         *   word - we're just using it as a grammatical marker that we're
6334
         *   not providing a real noun.  If there's another match for
6335
         *   which 'one' is a noun, that one is definitely preferred to
6336
         *   this one; the adj-ending marking will ensure that we choose
6337
         *   the other one.
6338
         */
6339
        results.noteAdjEnding();
6340
6341
        /* generate a list of objects matching the adjective */
6342
        return getWordMatches(adj_, &adjective, resolver,
6343
                              extraFlags | EndsWithAdj, VocabTruncated);
6344
    }
6345
    getAdjustedTokens()
6346
    {
6347
        return [adj_, &adjective];
6348
    }
6349
;
6350
6351
/*
6352
 *   In the worst case, a simple noun phrase can be constructed from
6353
 *   arbitrary words that don't appear in our dictionary.
6354
 */
6355
grammar simpleNounPhrase(misc):
6356
    [badness 200] miscWordList->lst_ : NounPhraseWithVocab
6357
    getVocabMatchList(resolver, results, extraFlags)
6358
    {
6359
        /* get the match list from the underlying list */
6360
        local lst = lst_.getVocabMatchList(resolver, results, extraFlags);
6361
6362
        /*
6363
         *   If there are no matches, note in the results that we have an
6364
         *   arbitrary word list.  Note that we do this only if there are
6365
         *   no matches, because we might match non-dictionary words to an
6366
         *   object with a wildcard in its vocabulary words, in which case
6367
         *   this is a valid, matching phrase after all.
6368
         */
6369
        if (lst == nil || lst.length() == 0)
6370
            results.noteMiscWordList(lst_.getOrigText());
6371
6372
        /* return the match list */
6373
        return lst;
6374
    }
6375
    getAdjustedTokens()
6376
    {
6377
        return lst_.getAdjustedTokens();
6378
    }
6379
;
6380
6381
/*
6382
 *   If the command has qualifiers but omits everything else, we can have
6383
 *   an empty simple noun phrase.
6384
 */
6385
grammar simpleNounPhrase(empty): [badness 600] : NounPhraseWithVocab
6386
    getVocabMatchList(resolver, results, extraFlags)
6387
    {
6388
        /* we have an empty noun phrase */
6389
        return results.emptyNounPhrase(resolver);
6390
    }
6391
    getAdjustedTokens()
6392
    {
6393
        return [];
6394
    }
6395
;
6396
6397
/* ------------------------------------------------------------------------ */
6398
/*
6399
 *   An AdjPhraseWithVocab is an English-specific subclass of
6400
 *   NounPhraseWithVocab, specifically for noun phrases that contain
6401
 *   entirely adjectives.  
6402
 */
6403
class AdjPhraseWithVocab: NounPhraseWithVocab
6404
    /* the property for the adjective literal - this is usually adj_ */
6405
    adjVocabProp = &adj_
6406
6407
    /* 
6408
     *   Add the vocabulary matches that we'd get if we were treating our
6409
     *   adjective as a noun.  This combines the noun interpretation with a
6410
     *   list of matches we got for the adjective version.  
6411
     */
6412
    addNounMatchList(lst, resolver, results, extraFlags)
6413
    {
6414
        /* get the word matches with a noun interpretation of our adjective */
6415
        local nLst = getWordMatches(
6416
            self.(adjVocabProp), &noun, resolver, extraFlags, VocabTruncated);
6417
6418
        /* combine the lists and return the result */
6419
        return combineWordMatches(lst, nLst);
6420
    }
6421
;
6422
6423
/* ------------------------------------------------------------------------ */
6424
/*
6425
 *   A "literal adjective" phrase is a number or string used as an
6426
 *   adjective.
6427
 */
6428
grammar literalAdjPhrase(number):
6429
    numberPhrase->num_ | poundNumberPhrase->num_
6430
    : AdjPhraseWithVocab
6431
6432
    adj_ = (num_.getStrVal())
6433
    getVocabMatchList(resolver, results, extraFlags)
6434
    {
6435
        local numList;
6436
6437
        /*
6438
         *   get the list of objects matching the numeral form of the
6439
         *   number as an adjective
6440
         */
6441
        numList = getWordMatches(num_.getStrVal(), &adjective,
6442
                                 resolver, extraFlags, VocabTruncated);
6443
6444
        /* add the list of objects matching the special '#' wildcard */
6445
        numList += getWordMatches('#', &adjective, resolver,
6446
                                  extraFlags, VocabTruncated);
6447
6448
        /* return the combined lists */
6449
        return numList;
6450
    }
6451
    getAdjustedTokens()
6452
    {
6453
        return [num_.getStrVal(), &adjective];
6454
    }
6455
;
6456
6457
grammar literalAdjPhrase(string): quotedStringPhrase->str_
6458
    : AdjPhraseWithVocab
6459
6460
    adj_ = (str_.getStringText().toLower())
6461
    getVocabMatchList(resolver, results, extraFlags)
6462
    {
6463
        local strList;
6464
        local wLst;
6465
6466
        /*
6467
         *   get the list of objects matching the string with the quotes
6468
         *   removed
6469
         */
6470
        strList = getWordMatches(str_.getStringText().toLower(),
6471
                                 &literalAdjective,
6472
                                 resolver, extraFlags, VocabTruncated);
6473
6474
        /* add the list of objects matching the literal-adjective wildcard */
6475
        wLst = getWordMatches('\u0001', &literalAdjective, resolver,
6476
                              extraFlags, VocabTruncated);
6477
        strList = combineWordMatches(strList, wLst);
6478
6479
        /* return the combined lists */
6480
        return strList;
6481
    }
6482
    getAdjustedTokens()
6483
    {
6484
        return [str_.getStringText().toLower(), &adjective];
6485
    }
6486
;
6487
6488
/*
6489
 *   In many cases, we might want to write what is semantically a literal
6490
 *   string qualifier without the quotes.  For example, we might want to
6491
 *   refer to an elevator button that's labeled "G" as simply "button G",
6492
 *   without any quotes around the "G".  To accommodate these cases, we
6493
 *   provide the literalAdjective part-of-speech.  We'll match these parts
6494
 *   of speech the same way we'd match them if they were quoted.
6495
 */
6496
grammar literalAdjPhrase(literalAdj): literalAdjective->adj_
6497
    : AdjPhraseWithVocab
6498
    getVocabMatchList(resolver, results, extraFlags)
6499
    {
6500
        local lst;
6501
6502
        /* get a list of objects in scope matching our literal adjective */
6503
        lst = getWordMatches(adj_, &literalAdjective, resolver,
6504
                             extraFlags, VocabTruncated);
6505
6506
        /* if the scope is global, also include ordinary adjective matches */
6507
        if (resolver.isGlobalScope)
6508
        {
6509
            /* get the ordinary adjective bindings */
6510
            local aLst = getWordMatches(adj_, &adjective, resolver,
6511
                                        extraFlags, VocabTruncated);
6512
6513
            /* global scope - combine the lists */
6514
            lst = combineWordMatches(lst, aLst);
6515
        }
6516
6517
        /* return the result */
6518
        return lst;
6519
    }
6520
    getAdjustedTokens()
6521
    {
6522
        return [adj_, &literalAdjective];
6523
    }
6524
;
6525
6526
6527
/* ------------------------------------------------------------------------ */
6528
/*
6529
 *   A noun word.  This can be either a simple 'noun' vocabulary word, or
6530
 *   it can be an abbreviated noun with a trailing abbreviation period.
6531
 */
6532
class NounWordProd: NounPhraseWithVocab
6533
    getVocabMatchList(resolver, results, extraFlags)
6534
    {
6535
        local w;
6536
        local nLst;
6537
6538
        /* get our word text */
6539
        w = getNounText();
6540
6541
        /* get the list of matches as nouns */
6542
        nLst = getWordMatches(w, &noun, resolver, extraFlags, VocabTruncated);
6543
6544
        /*
6545
         *   If the resolver indicates that we're in a "global" scope,
6546
         *   *also* include any additional matches as adjectives.
6547
         *
6548
         *   Normally, when we're operating in limited, local scopes, we
6549
         *   use the structure of the phrasing to determine whether to
6550
         *   match a noun or adjective; if we have a match for a given word
6551
         *   as a noun, we'll treat it only as a noun.  This allows us to
6552
         *   take PIZZA to refer to the pizza (for which 'pizza' is defined
6553
         *   as a noun) rather than to the PIZZA BOX (for which 'pizza' is
6554
         *   a mere adjective) when both are in scope.  It's obvious which
6555
         *   the player means in such cases, so we can be smart about
6556
         *   choosing the stronger match.
6557
         *
6558
         *   In cases of global scope, though, it's much harder to guess
6559
         *   about the player's intentions.  When the player types PIZZA,
6560
         *   they might be thinking of the box even though there's a pizza
6561
         *   somewhere else in the game.  Since the two objects might be in
6562
         *   entirely different locations, both out of view, we can't
6563
         *   assume that one or the other is more likely on the basis of
6564
         *   which is closer to the player's senses.  So, it's better to
6565
         *   allow both to match for now, and decide later, based on the
6566
         *   context of the command, which was actually meant.
6567
         */
6568
        if (resolver.isGlobalScope)
6569
        {
6570
            /* get the list of matching adjectives */
6571
            local aLst = getWordMatches(w, &adjective, resolver,
6572
                                        extraFlags, VocabTruncated);
6573
6574
            /* combine it with the noun list */
6575
            nLst = combineWordMatches(nLst, aLst);
6576
        }
6577
6578
        /* return the match list */
6579
        return nLst;
6580
    }
6581
    getAdjustedTokens()
6582
    {
6583
        /* the noun includes the period as part of the literal text */
6584
        return [getNounText(), &noun];
6585
    }
6586
6587
    /* the actual text of the noun to match to the dictionary */
6588
    getNounText() { return noun_; }
6589
;
6590
6591
grammar nounWord(noun): noun->noun_ : NounWordProd
6592
;
6593
6594
grammar nounWord(nounAbbr): noun->noun_ tokAbbrPeriod->period_
6595
    : NounWordProd
6596
6597
    /*
6598
     *   for dictionary matching purposes, include the text of our noun
6599
     *   with the period attached - the period is part of the dictionary
6600
     *   entry for an abbreviated word
6601
     */
6602
    getNounText() { return noun_ + period_; }
6603
;
6604
6605
/* ------------------------------------------------------------------------ */
6606
/*
6607
 *   An adjective word.  This can be either a simple 'adjective' vocabulary
6608
 *   word, or it can be an 'adjApostS' vocabulary word plus a 's token.  
6609
 */
6610
grammar adjWord(adj): adjective->adj_ : AdjPhraseWithVocab
6611
    /* generate a list of resolved objects */
6612
    getVocabMatchList(resolver, results, extraFlags)
6613
    {
6614
        /* return a list of objects in scope matching our adjective */
6615
        return getWordMatches(adj_, &adjective, resolver,
6616
                              extraFlags, VocabTruncated);
6617
    }
6618
    getAdjustedTokens()
6619
    {
6620
        return [adj_, &adjective];
6621
    }
6622
;
6623
6624
grammar adjWord(adjApostS): adjApostS->adj_ tokApostropheS->apost_
6625
    : AdjPhraseWithVocab
6626
    /* generate a list of resolved objects */
6627
    getVocabMatchList(resolver, results, extraFlags)
6628
    {
6629
        /* return a list of objects in scope matching our adjective */
6630
        return getWordMatches(adj_, &adjApostS, resolver,
6631
                              extraFlags, VocabTruncated);
6632
    }
6633
    getAdjustedTokens()
6634
    {
6635
        return [adj_, &adjApostS];
6636
    }
6637
;
6638
6639
grammar adjWord(adjAbbr): adjective->adj_ tokAbbrPeriod->period_
6640
    : AdjPhraseWithVocab
6641
    getVocabMatchList(resolver, results, extraFlags)
6642
    {
6643
        /*
6644
         *   return the list matching our adjective *with* the period
6645
         *   attached; the period is part of the dictionary entry for an
6646
         *   abbreviated word
6647
         */
6648
        return getWordMatches(adj_ + period_, &adjective, resolver,
6649
                              extraFlags, VocabTruncated);
6650
    }
6651
    getAdjustedTokens()
6652
    {
6653
        /* the adjective includes the period as part of the literal text */
6654
        return [adj_ + period_, &adjective];
6655
    }
6656
;
6657
6658
/* ------------------------------------------------------------------------ */
6659
/*
6660
 *   Possessive phrase.  This is a noun phrase expressing ownership of
6661
 *   another object.
6662
 *   
6663
 *   Note that all possessive phrases that can possibly be ambiguous must
6664
 *   define getOrigMainText() to return the "main noun phrase" text.  In
6665
 *   English, this means that we must omit any "'s" suffix.  This is needed
6666
 *   only when the phrase can be ambiguous, so pronouns don't need it since
6667
 *   they are inherently unambiguous.  
6668
 */
6669
grammar possessiveAdjPhrase(its): 'its' : ItsAdjProd
6670
    /* we only agree with a singular ungendered noun */
6671
    checkAnaphorAgreement(lst)
6672
        { return lst.length() == 1 && lst[1].obj_.canMatchIt; }
6673
;
6674
grammar possessiveAdjPhrase(his): 'his' : HisAdjProd
6675
    /* we only agree with a singular masculine noun */
6676
    checkAnaphorAgreement(lst)
6677
        { return lst.length() == 1 && lst[1].obj_.canMatchHim; }
6678
;
6679
grammar possessiveAdjPhrase(her): 'her' : HerAdjProd
6680
    /* we only agree with a singular feminine noun */
6681
    checkAnaphorAgreement(lst)
6682
        { return lst.length() == 1 && lst[1].obj_.canMatchHer; }
6683
;
6684
grammar possessiveAdjPhrase(their): 'their' : TheirAdjProd
6685
    /* we only agree with a single noun that has plural usage */
6686
    checkAnaphorAgreement(lst)
6687
        { return lst.length() == 1 && lst[1].obj_.isPlural; }
6688
;
6689
grammar possessiveAdjPhrase(your): 'your' : YourAdjProd
6690
    /* we are non-anaphoric */
6691
    checkAnaphorAgreement(lst) { return nil; }
6692
;
6693
grammar possessiveAdjPhrase(my): 'my' : MyAdjProd
6694
    /* we are non-anaphoric */
6695
    checkAnaphorAgreement(lst) { return nil; }
6696
;
6697
6698
grammar possessiveAdjPhrase(npApostropheS):
6699
    nounPhrase->np_ tokApostropheS->apost_ : LayeredNounPhraseProd
6700
6701
    /* get the original text without the "'s" suffix */
6702
    getOrigMainText()
6703
    {
6704
        /* return just the basic noun phrase part */
6705
        return np_.getOrigText();
6706
    }
6707
;
6708
6709
grammar possessiveAdjPhrase(ppApostropheS):
6710
    pluralPhrase->np_ tokApostropheS->apost_ : LayeredNounPhraseProd
6711
6712
    /* get the original text without the "'s" suffix */
6713
    getOrigMainText()
6714
    {
6715
        /* return just the basic noun phrase part */
6716
        return np_.getOrigText();
6717
    }
6718
6719
    resolveNouns(resolver, results)
6720
    {
6721
        /* note that we have a plural phrase, structurally speaking */
6722
        results.notePlural();
6723
6724
        /* inherit the default handling */
6725
        return inherited(resolver, results);
6726
    }
6727
6728
    /* the possessive phrase is plural */
6729
    isPluralPossessive = true
6730
;
6731
6732
/*
6733
 *   Possessive noun phrases.  These are similar to possessive phrases, but
6734
 *   are stand-alone phrases that can act as nouns rather than as
6735
 *   qualifiers for other noun phrases.  For example, for a first-person
6736
 *   player character, "mine" would be a possessive noun phrase referring
6737
 *   to an object owned by the player character.
6738
 *   
6739
 *   Note that many of the words used for possessive nouns are the same as
6740
 *   for possessive adjectives - for example "his" is the same in either
6741
 *   case, as are "'s" words.  However, we make the distinction internally
6742
 *   because the two have different grammatical uses, and some of the words
6743
 *   do differ ("her" vs "hers", for example).  
6744
 */
6745
grammar possessiveNounPhrase(its): 'its': ItsNounProd;
6746
grammar possessiveNounPhrase(his): 'his': HisNounProd;
6747
grammar possessiveNounPhrase(hers): 'hers': HersNounProd;
6748
grammar possessiveNounPhrase(theirs): 'theirs': TheirsNounProd;
6749
grammar possessiveNounPhrase(yours): 'yours' : YoursNounProd;
6750
grammar possessiveNounPhrase(mine): 'mine' : MineNounProd;
6751
6752
grammar possessiveNounPhrase(npApostropheS):
6753
    (nounPhrase->np_ | pluralPhrase->np_) tokApostropheS->apost_
6754
    : LayeredNounPhraseProd
6755
6756
    /* get the original text without the "'s" suffix */
6757
    getOrigMainText()
6758
    {
6759
        /* return just the basic noun phrase part */
6760
        return np_.getOrigText();
6761
    }
6762
;
6763
6764
6765
/* ------------------------------------------------------------------------ */
6766
/*
6767
 *   Simple plural phrase.  This is the most basic plural phrase, which is
6768
 *   simply a plural noun, optionally preceded by one or more adjectives.
6769
 *   
6770
 *   (English doesn't have any sort of adjective declension in number, so
6771
 *   there's no need to distinguish between plural and singular adjectives;
6772
 *   this equivalent rule in languages with adjective-noun agreement in
6773
 *   number would use plural adjectives here as well as plural nouns.)  
6774
 */
6775
grammar simplePluralPhrase(plural): plural->plural_ : NounPhraseWithVocab
6776
    /* generate a list of my resolved objects */
6777
    getVocabMatchList(resolver, results, extraFlags)
6778
    {
6779
        local lst;
6780
6781
        /* get the list of matching plurals */
6782
        lst = getWordMatches(plural_, &plural, resolver,
6783
                             extraFlags, PluralTruncated);
6784
6785
        /* get the list of matching 'noun' definitions */
6786
        local nLst = getWordMatches(plural_, &noun, resolver,
6787
                                    extraFlags, VocabTruncated);
6788
6789
        /* get the combined list */
6790
        local comboLst = combineWordMatches(lst, nLst);
6791
6792
        /*
6793
         *   If we're in global scope, add in the matches for just plain
6794
         *   'noun' properties as well.  This is important because we'll
6795
         *   sometimes want to define a word that's actually a plural
6796
         *   usage (in terms of the real-world English) under the 'noun'
6797
         *   property.  This occurs particularly when a single game-world
6798
         *   object represents a multiplicity of real-world objects.  When
6799
         *   the scope is global, it's hard to anticipate all of the
6800
         *   possible interactions with vocabulary along these lines, so
6801
         *   it's easiest just to include the 'noun' matches.
6802
         */
6803
        if (resolver.isGlobalScope)
6804
        {
6805
            /* keep the combined list */
6806
            lst = comboLst;
6807
        }
6808
        else if (comboLst.length() > lst.length())
6809
        {
6810
            /*
6811
             *   ordinary scope, so don't include the noun matches; but
6812
             *   since we found extra items to add, at least mark the
6813
             *   plural matches as potentially ambiguous
6814
             */
6815
            lst.forEach({x: x.flags_ |= UnclearDisambig});
6816
        }
6817
6818
        /* return the result list */
6819
        return lst;
6820
    }
6821
    getAdjustedTokens()
6822
    {
6823
        return [plural_, &plural];
6824
    }
6825
;
6826
6827
grammar simplePluralPhrase(adj): adjWord->adj_ simplePluralPhrase->np_ :
6828
    NounPhraseWithVocab
6829
6830
    /* resolve my object list */
6831
    getVocabMatchList(resolver, results, extraFlags)
6832
    {
6833
        /*
6834
         *   return the list of objects in scope matching our adjective
6835
         *   plus the list from the underlying noun phrase
6836
         */
6837
        return intersectNounLists(
6838
            adj_.getVocabMatchList(resolver, results, extraFlags),
6839
            np_.getVocabMatchList(resolver, results, extraFlags));
6840
    }
6841
    getAdjustedTokens()
6842
    {
6843
        return adj_.getAdjustedTokens() + np_.getAdjustedTokens();
6844
    }
6845
;
6846
6847
grammar simplePluralPhrase(poundNum):
6848
    poundNumberPhrase->num_ simplePluralPhrase->np_
6849
    : NounPhraseWithVocab
6850
6851
    /* resolve my object list */
6852
    getVocabMatchList(resolver, results, extraFlags)
6853
    {
6854
        local baseList;
6855
        local numList;
6856
6857
        /* get the base list for the rest of the phrase */
6858
        baseList = np_.getVocabMatchList(resolver, results, extraFlags);
6859
6860
        /* get the numeric matches, including numeric wildcards */
6861
        numList = getWordMatches(num_.getStrVal(), &adjective,
6862
                                 resolver, extraFlags, VocabTruncated)
6863
                  + getWordMatches('#', &adjective,
6864
                                   resolver, extraFlags, VocabTruncated);
6865
6866
        /* return the intersection of the lists */
6867
        return intersectNounLists(numList, baseList);
6868
    }
6869
    getAdjustedTokens()
6870
    {
6871
        return [num_.getStrVal(), &adjective] + np_.getAdjustedTokens();
6872
    }
6873
;
6874
6875
/*
6876
 *   A simple plural phrase can end with an adjective and "ones," as in
6877
 *   "the red ones."
6878
 */
6879
grammar simplePluralPhrase(adjAndOnes): adjective->adj_ 'ones'
6880
    : NounPhraseWithVocab
6881
    getVocabMatchList(resolver, results, extraFlags)
6882
    {
6883
        /* generate a list of objects matching the adjective */
6884
        return getWordMatches(adj_, &adjective, resolver,
6885
                              extraFlags | EndsWithAdj, VocabTruncated);
6886
    }
6887
    getAdjustedTokens()
6888
    {
6889
        return [adj_, &adjective];
6890
    }
6891
;
6892
6893
/*
6894
 *   If the command has qualifiers that require a plural, but omits
6895
 *   everything else, we can have an empty simple noun phrase.
6896
 */
6897
grammar simplePluralPhrase(empty): [badness 600] : NounPhraseWithVocab
6898
    getVocabMatchList(resolver, results, extraFlags)
6899
    {
6900
        /* we have an empty noun phrase */
6901
        return results.emptyNounPhrase(resolver);
6902
    }
6903
    getAdjustedTokens()
6904
    {
6905
        return [];
6906
    }
6907
;
6908
6909
/*
6910
 *   A simple plural phrase can match unknown words as a last resort.
6911
 */
6912
grammar simplePluralPhrase(misc):
6913
    [badness 300] miscWordList->lst_ : NounPhraseWithVocab
6914
    getVocabMatchList(resolver, results, extraFlags)
6915
    {
6916
        /* get the match list from the underlying list */
6917
        local lst = lst_.getVocabMatchList(resolver, results, extraFlags);
6918
6919
        /*
6920
         *   if there are no matches, note in the results that we have an
6921
         *   arbitrary word list that doesn't correspond to any object
6922
         */
6923
        if (lst == nil || lst.length() == 0)
6924
            results.noteMiscWordList(lst_.getOrigText());
6925
6926
        /* return the vocabulary match list */
6927
        return lst;
6928
    }
6929
    getAdjustedTokens()
6930
    {
6931
        return lst_.getAdjustedTokens();
6932
    }
6933
;
6934
6935
6936
/* ------------------------------------------------------------------------ */
6937
/*
6938
 *   An "adjective phrase" is a phrase made entirely of adjectives.
6939
 */
6940
grammar adjPhrase(adj): adjective->adj_ : AdjPhraseWithVocab
6941
    getVocabMatchList(resolver, results, extraFlags)
6942
    {
6943
        /* note the adjective ending */
6944
        results.noteAdjEnding();
6945
6946
        /* return the match list */
6947
        local lst = getWordMatches(adj_, &adjective, resolver,
6948
                                   extraFlags | EndsWithAdj, VocabTruncated);
6949
6950
        /* if in global scope, also try a noun interpretation */
6951
        if (resolver.isGlobalScope)
6952
            lst = addNounMatchList(lst, resolver, results, extraFlags);
6953
6954
        /* return the result */
6955
        return lst;
6956
    }
6957
6958
    getAdjustedTokens()
6959
    {
6960
        return [adj_, &adjective];
6961
    }
6962
;
6963
6964
grammar adjPhrase(adjAdj): adjective->adj_ adjPhrase->ap_
6965
    : NounPhraseWithVocab
6966
    /* generate a list of my resolved objects */
6967
    getVocabMatchList(resolver, results, extraFlags)
6968
    {
6969
        /*
6970
         *   return the list of objects in scope matching our adjective
6971
         *   plus the list from the underlying adjective phrase
6972
         */
6973
        return intersectWordMatches(
6974
            adj_, &adjective, resolver, extraFlags, VocabTruncated,
6975
            ap_.getVocabMatchList(resolver, results, extraFlags));
6976
    }
6977
    getAdjustedTokens()
6978
    {
6979
        return [adj_, &adjective] + ap_.getAdjustedTokens();
6980
    }
6981
;
6982
6983
6984
/* ------------------------------------------------------------------------ */
6985
/*
6986
 *   A "topic" is a special type of noun phrase used in commands like "ask
6987
 *   <actor> about <topic>."  We define a topic as simply an ordinary
6988
 *   single-noun phrase.  We distinguish this in the grammar to allow games
6989
 *   to add special syntax for these.  
6990
 */
6991
grammar topicPhrase(main): singleNoun->np_ : TopicProd
6992
;
6993
6994
/*
6995
 *   Explicitly match a miscellaneous word list as a topic.
6996
 *
6997
 *   This might seem redundant with the ordinary topicPhrase that accepts a
6998
 *   singleNoun, because singleNoun can match a miscellaneous word list.
6999
 *   The difference is that singleNoun only matches a miscWordList with a
7000
 *   "badness" value, whereas we match a miscWordList here without any
7001
 *   badness.  We want to be more tolerant of unrecognized input in topic
7002
 *   phrases than in ordinary noun phrases, because it's in the nature of
7003
 *   topic phrases to go outside of what's implemented directly in the
7004
 *   simulation model.  At a grammatical level, we don't want to treat
7005
 *   topic phrases that we can resolve to the simulation model any
7006
 *   differently than we treat those we can't resolve, so we must add this
7007
 *   rule to eliminate the badness that singleNoun associated with a
7008
 *   miscWordList match.
7009
 *
7010
 *   Note that we do prefer resolvable noun phrase matches to miscWordList
7011
 *   matches, but we handle this preference with the resolver's scoring
7012
 *   mechanism rather than with badness.
7013
 */
7014
grammar topicPhrase(misc): miscWordList->np_ : TopicProd
7015
   resolveNouns(resolver, results)
7016
   {
7017
       /* note in the results that we have an arbitrary word list */
7018
       results.noteMiscWordList(np_.getOrigText());
7019
7020
       /* inherit the default TopicProd behavior */
7021
       return inherited(resolver, results);
7022
   }
7023
;
7024
7025
/* ------------------------------------------------------------------------ */
7026
/*
7027
 *   A "quoted string" phrase is a literal enclosed in single or double
7028
 *   quotes.
7029
 *
7030
 *   Note that this is a separate production from literalPhrase.  This
7031
 *   production can be used when *only* a quoted string is allowed.  The
7032
 *   literalPhrase production allows both quoted and unquoted text.
7033
 */
7034
grammar quotedStringPhrase(main): tokString->str_ : LiteralProd
7035
    /*
7036
     *   get my string, with the quotes trimmed off (so we return simply
7037
     *   the contents of the string)
7038
     */
7039
    getStringText() { return stripQuotesFrom(str_); }
7040
;
7041
7042
/*
7043
 *   Service routine: strip quotes from a *possibly* quoted string.  If the
7044
 *   string starts with a quote, we'll remove the open quote.  If it starts
7045
 *   with a quote and it ends with a corresponding close quote, we'll
7046
 *   remove that as well.  
7047
 */
7048
stripQuotesFrom(str)
7049
{
7050
    local hasOpen;
7051
    local hasClose;
7052
7053
    /* presume we won't find open or close quotes */
7054
    hasOpen = hasClose = nil;
7055
7056
    /*
7057
     *   Check for quotes.  We'll accept regular ASCII "straight" single
7058
     *   or double quotes, as well as Latin-1 curly single or double
7059
     *   quotes.  The curly quotes must be used in their normal
7060
     */
7061
    if (str.startsWith('\'') || str.startsWith('"'))
7062
    {
7063
        /* single or double quote - check for a matching close quote */
7064
        hasOpen = true;
7065
        hasClose = (str.length() > 2 && str.endsWith(str.substr(1, 1)));
7066
    }
7067
    else if (str.startsWith('`'))
7068
    {
7069
        /* single in-slanted quote - check for either type of close */
7070
        hasOpen = true;
7071
        hasClose = (str.length() > 2
7072
                    && (str.endsWith('`') || str.endsWith('\'')));
7073
    }
7074
    else if (str.startsWith('\u201C'))
7075
    {
7076
        /* it's a curly double quote */
7077
        hasOpen = true;
7078
        hasClose = str.endsWith('\u201D');
7079
    }
7080
    else if (str.startsWith('\u2018'))
7081
    {
7082
        /* it's a curly single quote */
7083
        hasOpen = true;
7084
        hasClose = str.endsWith('\u2019');
7085
    }
7086
7087
    /* trim off the quotes */
7088
    if (hasOpen)
7089
    {
7090
        if (hasClose)
7091
            str = str.substr(2, str.length() - 2);
7092
        else
7093
            str = str.substr(2);
7094
    }
7095
7096
    /* return the modified text */
7097
    return str;
7098
}
7099
7100
/* ------------------------------------------------------------------------ */
7101
/*
7102
 *   A "literal" is essentially any phrase.  This can include a quoted
7103
 *   string, a number, or any set of word tokens.
7104
 */
7105
grammar literalPhrase(string): quotedStringPhrase->str_ : LiteralProd
7106
    getLiteralText(results, action, which)
7107
    {
7108
        /* get the text from our underlying quoted string */
7109
        return str_.getStringText();
7110
    }
7111
7112
    getTentativeLiteralText()
7113
    {
7114
        /*
7115
         *   our result will never change, so our tentative text is the
7116
         *   same as our regular literal text
7117
         */
7118
        return str_.getStringText();
7119
    }
7120
7121
    resolveLiteral(results)
7122
    {
7123
        /* flag the literal text */
7124
        results.noteLiteral(str_.getOrigText());
7125
    }
7126
;
7127
7128
grammar literalPhrase(miscList): miscWordList->misc_ : LiteralProd
7129
    getLiteralText(results, action, which)
7130
    {
7131
        /* get my original text */
7132
        local txt = misc_.getOrigText();
7133
7134
        /*
7135
         *   if our underlying miscWordList has only one token, strip
7136
         *   quotes, in case that token is a quoted string token
7137
         */
7138
        if (misc_.getOrigTokenList().length() == 1)
7139
            txt = stripQuotesFrom(txt);
7140
7141
        /* return the text */
7142
        return txt;
7143
    }
7144
7145
    getTentativeLiteralText()
7146
    {
7147
        /* our regular text is permanent, so simply use it now */
7148
        return misc_.getOrigText();
7149
    }
7150
7151
    resolveLiteral(results)
7152
    {
7153
        /*
7154
         *   note the length of our literal phrase - when we have a choice
7155
         *   of interpretations, we prefer to choose shorter literal
7156
         *   phrases, since this means that we'll have more of our tokens
7157
         *   being fully interpreted rather than bunched into an
7158
         *   uninterpreted literal
7159
         */
7160
        results.noteLiteral(misc_.getOrigText());
7161
    }
7162
;
7163
7164
/*
7165
 *   In case we have a verb grammar rule that calls for a literal phrase,
7166
 *   but the player enters a command with nothing in that slot, match an
7167
 *   empty token list as a last resort.  Since this phrasing has a badness,
7168
 *   we won't match it unless we don't have any better structural match.
7169
 */
7170
grammar literalPhrase(empty): [badness 400]: EmptyLiteralPhraseProd
7171
    resolveLiteral(results) { }
7172
;
7173
7174
/* ------------------------------------------------------------------------ */
7175
/*
7176
 *   An miscellaneous word list is a list of one or more words of any kind:
7177
 *   any word, any integer, or any apostrophe-S token will do.  Note that
7178
 *   known and unknown words can be mixed in an unknown word list; we care
7179
 *   only that the list is made up of tokWord, tokInt, tokApostropheS,
7180
 *   and/or abbreviation-period tokens.
7181
 *
7182
 *   Note that this kind of phrase is often used with a 'badness' value.
7183
 *   However, we don't assign any badness here, because a miscellaneous
7184
 *   word list might be perfectly valid in some contexts; instead, any
7185
 *   productions that include a misc word list should specify badness as
7186
 *   desired.
7187
 */
7188
grammar miscWordList(wordOrNumber):
7189
    tokWord->txt_ | tokInt->txt_ | tokApostropheS->txt_
7190
    | tokPoundInt->txt_ | tokString->txt_ | tokAbbrPeriod->txt_
7191
    : NounPhraseWithVocab
7192
    getVocabMatchList(resolver, results, extraFlags)
7193
    {
7194
        /* we don't match anything directly with our vocabulary */
7195
        return [];
7196
    }
7197
    getAdjustedTokens()
7198
    {
7199
        /* our token type is the special miscellaneous word type */
7200
        return [txt_, &miscWord];
7201
    }
7202
;
7203
7204
grammar miscWordList(list):
7205
    (tokWord->txt_ | tokInt->txt_ | tokApostropheS->tok_ | tokAbbrPeriod->txt_
7206
     | tokPoundInt->txt_ | tokString->txt_) miscWordList->lst_
7207
    : NounPhraseWithVocab
7208
    getVocabMatchList(resolver, results, extraFlags)
7209
    {
7210
        /* we don't match anything directly with our vocabulary */
7211
        return [];
7212
    }
7213
    getAdjustedTokens()
7214
    {
7215
        /* our token type is the special miscellaneous word type */
7216
        return [txt_, &miscWord] + lst_.getAdjustedTokens();
7217
    }
7218
;
7219
7220
/* ------------------------------------------------------------------------ */
7221
/*
7222
 *   A main disambiguation phrase consists of a disambiguation phrase,
7223
 *   optionally terminated with a period.
7224
 */
7225
grammar mainDisambigPhrase(main):
7226
    disambigPhrase->dp_
7227
    | disambigPhrase->dp_ '.'
7228
    : BasicProd
7229
    resolveNouns(resolver, results)
7230
    {
7231
        return dp_.resolveNouns(resolver, results);
7232
    }
7233
    getResponseList() { return dp_.getResponseList(); }
7234
;
7235
7236
/*
7237
 *   A "disambiguation phrase" is a phrase that answers a disambiguation
7238
 *   question ("which book do you mean...").
7239
 *   
7240
 *   A disambiguation question can be answered with several types of
7241
 *   syntax:
7242
 *   
7243
 *.  all/everything/all of them
7244
 *.  both/both of them
7245
 *.  any/any of them
7246
 *.  <disambig list>
7247
 *.  the <ordinal list> ones
7248
 *.  the former/the latter
7249
 *   
7250
 *   Note that we assign non-zero badness to all of the ordinal
7251
 *   interpretations, so that we will take an actual vocabulary
7252
 *   interpretation instead of an ordinal interpretation whenever possible.
7253
 *   For example, if an object's name is actually "the third button," this
7254
 *   will give us greater affinity for using "third" as an adjective than
7255
 *   as an ordinal in our own list.  
7256
 */
7257
grammar disambigPhrase(all):
7258
    'all' | 'everything' | 'all' 'of' 'them' : DisambigProd
7259
    resolveNouns(resolver, results)
7260
    {
7261
        /* they want everything we proposed - return the whole list */
7262
        return removeAmbigFlags(resolver.getAll(self));
7263
    }
7264
7265
    /* there's only me in the response list */
7266
    getResponseList() { return [self]; }
7267
;
7268
7269
grammar disambigPhrase(both): 'both' | 'both' 'of' 'them' : DisambigProd
7270
    resolveNouns(resolver, results)
7271
    {
7272
        /*
7273
         *   they want two items - return the whole list (if it has more
7274
         *   than two items, we'll simply act as though they wanted all of
7275
         *   them)
7276
         */
7277
        return removeAmbigFlags(resolver.getAll(self));
7278
    }
7279
7280
    /* there's only me in the response list */
7281
    getResponseList() { return [self]; }
7282
;
7283
7284
grammar disambigPhrase(any): 'any' | 'any' 'of' 'them' : DisambigProd
7285
    resolveNouns(resolver, results)
7286
    {
7287
        local lst;
7288
7289
        /* they want any item - arbitrarily pick the first one */
7290
        lst = resolver.matchList.sublist(1, 1);
7291
7292
        /*
7293
         *   add the "unclear disambiguation" flag to the item we picked,
7294
         *   to indicate that the selection was arbitrary
7295
         */
7296
        if (lst.length() > 0)
7297
            lst[1].flags_ |= UnclearDisambig;
7298
7299
        /* return the result */
7300
        return lst;
7301
    }
7302
7303
    /* there's only me in the response list */
7304
    getResponseList() { return [self]; }
7305
;
7306
7307
grammar disambigPhrase(list): disambigList->lst_ : DisambigProd
7308
    resolveNouns(resolver, results)
7309
    {
7310
        return removeAmbigFlags(lst_.resolveNouns(resolver, results));
7311
    }
7312
7313
    /* there's only me in the response list */
7314
    getResponseList() { return lst_.getResponseList(); }
7315
;
7316
7317
grammar disambigPhrase(ordinalList):
7318
    disambigOrdinalList->lst_ 'ones'
7319
    | 'the' disambigOrdinalList->lst_ 'ones'
7320
    : DisambigProd
7321
7322
    resolveNouns(resolver, results)
7323
    {
7324
        /* return the list with the ambiguity flags removed */
7325
        return removeAmbigFlags(lst_.resolveNouns(resolver, results));
7326
    }
7327
7328
    /* the response list consists of my single ordinal list item */
7329
    getResponseList() { return [lst_]; }
7330
;
7331
7332
/*
7333
 *   A disambig list consists of one or more disambig list items, connected
7334
 *   by noun phrase conjunctions.  
7335
 */
7336
grammar disambigList(single): disambigListItem->item_ : DisambigProd
7337
    resolveNouns(resolver, results)
7338
    {
7339
        return item_.resolveNouns(resolver, results);
7340
    }
7341
7342
    /* the response list consists of my single item */
7343
    getResponseList() { return [item_]; }
7344
;
7345
7346
grammar disambigList(list):
7347
    disambigListItem->item_ commandOrNounConjunction disambigList->lst_
7348
    : DisambigProd
7349
7350
    resolveNouns(resolver, results)
7351
    {
7352
        return item_.resolveNouns(resolver, results)
7353
            + lst_.resolveNouns(resolver, results);
7354
    }
7355
7356
    /* my response list consists of each of our list items */
7357
    getResponseList() { return [item_] + lst_.getResponseList(); }
7358
;
7359
7360
/*
7361
 *   Base class for ordinal disambiguation items
7362
 */
7363
class DisambigOrdProd: DisambigProd
7364
    resolveNouns(resolver, results)
7365
    {
7366
        /* note the ordinal match */
7367
        results.noteDisambigOrdinal();
7368
7369
        /* select the result by the ordinal */
7370
        return selectByOrdinal(ord_, resolver, results);
7371
    }
7372
7373
    selectByOrdinal(ordTok, resolver, results)
7374
    {
7375
        local idx;
7376
        local matchList = resolver.ordinalMatchList;
7377
7378
        /*
7379
         *   look up the meaning of the ordinal word (note that we assume
7380
         *   that each ordinalWord is unique, since we only create one of
7381
         *   each)
7382
         */
7383
        idx = cmdDict.findWord(ordTok, &ordinalWord)[1].numval;
7384
7385
        /*
7386
         *   if it's the special value -1, it indicates that we should
7387
         *   select the *last* item in the list
7388
         */
7389
        if (idx == -1)
7390
            idx = matchList.length();
7391
7392
        /* if it's outside the limits of the match list, it's an error */
7393
        if (idx > matchList.length())
7394
        {
7395
            /* note the problem */
7396
            results.noteOrdinalOutOfRange(ordTok);
7397
7398
            /* no results */
7399
            return [];
7400
        }
7401
7402
        /* return the selected item as a one-item list */
7403
        return matchList.sublist(idx, 1);
7404
    }
7405
;
7406
7407
/*
7408
 *   A disambig vocab production is the base class for disambiguation
7409
 *   phrases that involve vocabulary words.
7410
 */
7411
class DisambigVocabProd: DisambigProd
7412
;
7413
7414
/*
7415
 *   A disambig list item consists of:
7416
 *
7417
 *.  first/second/etc
7418
 *.  the first/second/etc
7419
 *.  first one/second one/etc
7420
 *.  the first one/the second one/etc
7421
 *.  <compound noun phrase>
7422
 *.  possessive
7423
 */
7424
7425
grammar disambigListItem(ordinal):
7426
    ordinalWord->ord_
7427
    | ordinalWord->ord_ 'one'
7428
    | 'the' ordinalWord->ord_
7429
    | 'the' ordinalWord->ord_ 'one'
7430
    : DisambigOrdProd
7431
;
7432
7433
grammar disambigListItem(noun):
7434
    completeNounPhraseWithoutAll->np_
7435
    | terminalNounPhrase->np_
7436
    : DisambigVocabProd
7437
    resolveNouns(resolver, results)
7438
    {
7439
        /* get the matches for the underlying noun phrase */
7440
        local lst = np_.resolveNouns(resolver, results);
7441
7442
        /* note the matches */
7443
        results.noteMatches(lst);
7444
7445
        /* return the match list */
7446
        return lst;
7447
    }
7448
;
7449
7450
grammar disambigListItem(plural):
7451
    pluralPhrase->np_
7452
    : DisambigVocabProd
7453
    resolveNouns(resolver, results)
7454
    {
7455
        local lst;
7456
7457
        /*
7458
         *   get the underlying match list; since we explicitly have a
7459
         *   plural, the result doesn't need to be unique, so simply
7460
         *   return everything we find
7461
         */
7462
        lst = np_.resolveNouns(resolver, results);
7463
7464
        /*
7465
         *   if we didn't get anything, it's an error; otherwise, take
7466
         *   everything, since we explicitly wanted a plural usage
7467
         */
7468
        if (lst.length() == 0)
7469
            results.noMatch(resolver.getAction(), np_.getOrigText());
7470
        else
7471
            results.noteMatches(lst);
7472
7473
        /* return the list */
7474
        return lst;
7475
    }
7476
;
7477
7478
grammar disambigListItem(possessive): possessiveNounPhrase->poss_
7479
    : DisambigPossessiveProd
7480
;
7481
7482
/*
7483
 *   A disambig ordinal list consists of two or more ordinal words
7484
 *   separated by noun phrase conjunctions.  Note that there is a minimum
7485
 *   of two entries in the list.
7486
 */
7487
grammar disambigOrdinalList(tail):
7488
    ordinalWord->ord1_ ('and' | ',') ordinalWord->ord2_ : DisambigOrdProd
7489
    resolveNouns(resolver, results)
7490
    {
7491
        /* note the pair of ordinal matches */
7492
        results.noteDisambigOrdinal();
7493
        results.noteDisambigOrdinal();
7494
7495
        /* combine the selections of our two ordinals */
7496
        return selectByOrdinal(ord1_, resolver, results)
7497
            + selectByOrdinal(ord2_, resolver, results);
7498
    }
7499
;
7500
7501
grammar disambigOrdinalList(head):
7502
    ordinalWord->ord_ ('and' | ',') disambigOrdinalList->lst_
7503
    : DisambigOrdProd
7504
    resolveNouns(resolver, results)
7505
    {
7506
        /* note the ordinal match */
7507
        results.noteDisambigOrdinal();
7508
7509
        /* combine the selections of our ordinal and the sublist */
7510
        return selectByOrdinal(ord_, resolver, results)
7511
            + lst_.resolveNouns(resolver, results);
7512
    }
7513
;
7514
7515
7516
/* ------------------------------------------------------------------------ */
7517
/*
7518
 *   Ordinal words.  We define a limited set of these, since we only use
7519
 *   them in a few special contexts where it would be unreasonable to need
7520
 *   even as many as define here.
7521
 */
7522
#define defOrdinal(str, val) object ordinalWord=#@str numval=val
7523
7524
defOrdinal(former, 1);
7525
defOrdinal(first, 1);
7526
defOrdinal(second, 2);
7527
defOrdinal(third, 3);
7528
defOrdinal(fourth, 4);
7529
defOrdinal(fifth, 5);
7530
defOrdinal(sixth, 6);
7531
defOrdinal(seventh, 7);
7532
defOrdinal(eighth, 8);
7533
defOrdinal(ninth, 9);
7534
defOrdinal(tenth, 10);
7535
defOrdinal(eleventh, 11);
7536
defOrdinal(twelfth, 12);
7537
defOrdinal(thirteenth, 13);
7538
defOrdinal(fourteenth, 14);
7539
defOrdinal(fifteenth, 15);
7540
defOrdinal(sixteenth, 16);
7541
defOrdinal(seventeenth, 17);
7542
defOrdinal(eighteenth, 18);
7543
defOrdinal(nineteenth, 19);
7544
defOrdinal(twentieth, 20);
7545
defOrdinal(1st, 1);
7546
defOrdinal(2nd, 2);
7547
defOrdinal(3rd, 3);
7548
defOrdinal(4th, 4);
7549
defOrdinal(5th, 5);
7550
defOrdinal(6th, 6);
7551
defOrdinal(7th, 7);
7552
defOrdinal(8th, 8);
7553
defOrdinal(9th, 9);
7554
defOrdinal(10th, 10);
7555
defOrdinal(11th, 11);
7556
defOrdinal(12th, 12);
7557
defOrdinal(13th, 13);
7558
defOrdinal(14th, 14);
7559
defOrdinal(15th, 15);
7560
defOrdinal(16th, 16);
7561
defOrdinal(17th, 17);
7562
defOrdinal(18th, 18);
7563
defOrdinal(19th, 19);
7564
defOrdinal(20th, 20);
7565
7566
/*
7567
 *   the special 'last' ordinal - the value -1 is special to indicate the
7568
 *   last item in a list
7569
 */
7570
defOrdinal(last, -1);
7571
defOrdinal(latter, -1);
7572
7573
7574
/* ------------------------------------------------------------------------ */
7575
/*
7576
 *   A numeric production.  These can be either spelled-out numbers (such
7577
 *   as "fifty-seven") or numbers entered in digit form (as in "57").
7578
 */
7579
class NumberProd: BasicProd
7580
    /* get the numeric (integer) value */
7581
    getval() { return 0; }
7582
7583
    /*
7584
     *   Get the string version of the numeric value.  This should return
7585
     *   a string, but the string should be in digit form.  If the
7586
     *   original entry was in digit form, then the original entry should
7587
     *   be returned; otherwise, a string should be constructed from the
7588
     *   integer value.  By default, we'll do the latter.
7589
     */
7590
    getStrVal() { return toString(getval()); }
7591
;
7592
7593
/*
7594
 *   A quantifier is simply a number, entered with numerals or spelled out.
7595
 */
7596
grammar numberPhrase(digits): tokInt->num_ : NumberProd
7597
    /* get the numeric value */
7598
    getval() { return toInteger(num_); }
7599
7600
    /*
7601
     *   get the string version of the numeric value - since the token was
7602
     *   an integer to start with, return the actual integer value
7603
     */
7604
    getStrVal() { return num_; }
7605
;
7606
7607
grammar numberPhrase(spelled): spelledNumber->num_ : NumberProd
7608
    /* get the numeric value */
7609
    getval() { return num_.getval(); }
7610
;
7611
7612
/*
7613
 *   A number phrase preceded by a pound sign.  We distinguish this kind of
7614
 *   number phrase from plain numbers, since this kind has a somewhat more
7615
 *   limited set of valid contexts.  
7616
 */
7617
grammar poundNumberPhrase(main): tokPoundInt->num_ : NumberProd
7618
    /*
7619
     *   get the numeric value - a tokPoundInt token has a pound sign
7620
     *   followed by digits, so the numeric value is the value of the
7621
     *   substring following the '#' sign
7622
     */
7623
    getval() { return toInteger(num_.substr(2)); }
7624
7625
    /*
7626
     *   get the string value - we have a number token following the '#',
7627
     *   so simply return the part after the '#'
7628
     */
7629
    getStrVal() { return num_.substr(2); }
7630
;
7631
7632
7633
/*
7634
 *   Number literals.  We'll define a set of special objects for numbers:
7635
 *   each object defines a number and a value for the number.
7636
 */
7637
#define defDigit(num, val) object digitWord=#@num numval=val
7638
#define defTeen(num, val)  object teenWord=#@num numval=val
7639
#define defTens(num, val)  object tensWord=#@num numval=val
7640
7641
defDigit(one, 1);
7642
defDigit(two, 2);
7643
defDigit(three, 3);
7644
defDigit(four, 4);
7645
defDigit(five, 5);
7646
defDigit(six, 6);
7647
defDigit(seven, 7);
7648
defDigit(eight, 8);
7649
defDigit(nine, 9);
7650
defTeen(ten, 10);
7651
defTeen(eleven, 11);
7652
defTeen(twelve, 12);
7653
defTeen(thirteen, 13);
7654
defTeen(fourteen, 14);
7655
defTeen(fifteen, 15);
7656
defTeen(sixteen, 16);
7657
defTeen(seventeen, 17);
7658
defTeen(eighteen, 18);
7659
defTeen(nineteen, 19);
7660
defTens(twenty, 20);
7661
defTens(thirty, 30);
7662
defTens(forty, 40);
7663
defTens(fifty, 50);
7664
defTens(sixty, 60);
7665
defTens(seventy, 70);
7666
defTens(eighty, 80);
7667
defTens(ninety, 90);
7668
7669
grammar spelledSmallNumber(digit): digitWord->num_ : NumberProd
7670
    getval()
7671
    {
7672
        /*
7673
         *   Look up the units word - there should be only one in the
7674
         *   dictionary, since these are our special words.  Return the
7675
         *   object's numeric value property 'numval', which gives the
7676
         *   number for the name.
7677
         */
7678
        return cmdDict.findWord(num_, &digitWord)[1].numval;
7679
    }
7680
;
7681
7682
grammar spelledSmallNumber(teen): teenWord->num_ : NumberProd
7683
    getval()
7684
    {
7685
        /* look up the dictionary word for the number */
7686
        return cmdDict.findWord(num_, &teenWord)[1].numval;
7687
    }
7688
;
7689
7690
grammar spelledSmallNumber(tens): tensWord->num_ : NumberProd
7691
    getval()
7692
    {
7693
        /* look up the dictionary word for the number */
7694
        return cmdDict.findWord(num_, &tensWord)[1].numval;
7695
    }
7696
;
7697
7698
grammar spelledSmallNumber(tensAndUnits):
7699
    tensWord->tens_ '-'->sep_ digitWord->units_
7700
    | tensWord->tens_ digitWord->units_
7701
    : NumberProd
7702
    getval()
7703
    {
7704
        /* look up the words, and add up the values */
7705
        return cmdDict.findWord(tens_, &tensWord)[1].numval
7706
            + cmdDict.findWord(units_, &digitWord)[1].numval;
7707
    }
7708
;
7709
7710
grammar spelledSmallNumber(zero): 'zero' : NumberProd
7711
    getval() { return 0; }
7712
;
7713
7714
grammar spelledHundred(small): spelledSmallNumber->num_ : NumberProd
7715
    getval() { return num_.getval(); }
7716
;
7717
7718
grammar spelledHundred(hundreds): spelledSmallNumber->hun_ 'hundred'
7719
    : NumberProd
7720
    getval() { return hun_.getval() * 100; }
7721
;
7722
7723
grammar spelledHundred(hundredsPlus):
7724
    spelledSmallNumber->hun_ 'hundred' spelledSmallNumber->num_
7725
    | spelledSmallNumber->hun_ 'hundred' 'and'->and_ spelledSmallNumber->num_
7726
    : NumberProd
7727
    getval() { return hun_.getval() * 100 + num_.getval(); }
7728
;
7729
7730
grammar spelledHundred(aHundred): 'a' 'hundred' : NumberProd
7731
    getval() { return 100; }
7732
;
7733
7734
grammar spelledHundred(aHundredPlus):
7735
    'a' 'hundred' 'and' spelledSmallNumber->num_
7736
    : NumberProd
7737
    getval() { return 100 + num_.getval(); }
7738
;
7739
7740
grammar spelledThousand(thousands): spelledHundred->thou_ 'thousand'
7741
    : NumberProd
7742
    getval() { return thou_.getval() * 1000; }
7743
;
7744
7745
grammar spelledThousand(thousandsPlus):
7746
    spelledHundred->thou_ 'thousand' spelledHundred->num_
7747
    : NumberProd
7748
    getval() { return thou_.getval() * 1000 + num_.getval(); }
7749
;
7750
7751
grammar spelledThousand(thousandsAndSmall):
7752
    spelledHundred->thou_ 'thousand' 'and' spelledSmallNumber->num_
7753
    : NumberProd
7754
    getval() { return thou_.getval() * 1000 + num_.getval(); }
7755
;
7756
7757
grammar spelledThousand(aThousand): 'a' 'thousand' : NumberProd
7758
    getval() { return 1000; }
7759
;
7760
7761
grammar spelledThousand(aThousandAndSmall):
7762
    'a' 'thousand' 'and' spelledSmallNumber->num_
7763
    : NumberProd
7764
    getval() { return 1000 + num_.getval(); }
7765
;
7766
7767
grammar spelledMillion(millions): spelledHundred->mil_ 'million': NumberProd
7768
    getval() { return mil_.getval() * 1000000; }
7769
;
7770
7771
grammar spelledMillion(millionsPlus):
7772
    spelledHundred->mil_ 'million'
7773
    (spelledThousand->nxt_ | spelledHundred->nxt_)
7774
    : NumberProd
7775
    getval() { return mil_.getval() * 1000000 + nxt_.getval(); }
7776
;
7777
7778
grammar spelledMillion(aMillion): 'a' 'million' : NumberProd
7779
    getval() { return 1000000; }
7780
;
7781
7782
grammar spelledMillion(aMillionAndSmall):
7783
    'a' 'million' 'and' spelledSmallNumber->num_
7784
    : NumberProd
7785
    getval() { return 1000000 + num_.getval(); }
7786
;
7787
7788
grammar spelledMillion(millionsAndSmall):
7789
    spelledHundred->mil_ 'million' 'and' spelledSmallNumber->num_
7790
    : NumberProd
7791
    getval() { return mil_.getval() * 1000000 + num_.getval(); }
7792
;
7793
7794
grammar spelledNumber(main):
7795
    spelledHundred->num_
7796
    | spelledThousand->num_
7797
    | spelledMillion->num_
7798
    : NumberProd
7799
    getval() { return num_.getval(); }
7800
;
7801
7802
7803
/* ------------------------------------------------------------------------ */
7804
/*
7805
 *   "OOPS" command syntax
7806
 */
7807
grammar oopsCommand(main):
7808
    oopsPhrase->oops_ | oopsPhrase->oops_ '.' : BasicProd
7809
    getNewTokens() { return oops_.getNewTokens(); }
7810
;
7811
7812
grammar oopsPhrase(main):
7813
    'oops' miscWordList->lst_
7814
    | 'oops' ',' miscWordList->lst_
7815
    | 'o' miscWordList->lst_
7816
    | 'o' ',' miscWordList->lst_
7817
    : BasicProd
7818
    getNewTokens() { return lst_.getOrigTokenList(); }
7819
;
7820
7821
grammar oopsPhrase(missing):
7822
    'oops' | 'o'
7823
    : BasicProd
7824
    getNewTokens() { return nil; }
7825
;
7826
7827
/* ------------------------------------------------------------------------ */
7828
/*
7829
 *   finishGame options.  We provide descriptions and keywords for the
7830
 *   option objects here, because these are inherently language-specific.
7831
 *   
7832
 *   Note that we provide hyperlinks for our descriptions when possible.
7833
 *   When we're in plain text mode, we can't show links, so we'll instead
7834
 *   show an alternate form with the single-letter response highlighted in
7835
 *   the text.  We don't highlight the single-letter response in the
7836
 *   hyperlinked version because (a) if the user wants a shortcut, they can
7837
 *   simply click the hyperlink, and (b) most UI's that show hyperlinks
7838
 *   show a distinctive appearance for the hyperlink itself, so adding even
7839
 *   more highlighting within the hyperlink starts to look awfully busy.  
7840
 */
7841
modify finishOptionQuit
7842
    desc = "<<aHrefAlt('quit', 'QUIT', '<b>Q</b>UIT', 'Leave the story')>>"
7843
    responseKeyword = 'quit'
7844
    responseChar = 'q'
7845
;
7846
7847
modify finishOptionRestore
7848
    desc = "<<aHrefAlt('restore', 'RESTORE', '<b>R</b>ESTORE',
7849
            'Restore a saved position')>> a saved position"
7850
    responseKeyword = 'restore'
7851
    responseChar = 'r'
7852
;
7853
7854
modify finishOptionRestart
7855
    desc = "<<aHrefAlt('restart', 'RESTART', 'RE<b>S</b>TART',
7856
            'Start the story over from the beginning')>> the story"
7857
    responseKeyword = 'restart'
7858
    responseChar = 's'
7859
;
7860
7861
modify finishOptionUndo
7862
    desc = "<<aHrefAlt('undo', 'UNDO', '<b>U</b>NDO',
7863
            'Undo the last move')>> the last move"
7864
    responseKeyword = 'undo'
7865
    responseChar = 'u'
7866
;
7867
7868
modify finishOptionCredits
7869
    desc = "see the <<aHrefAlt('credits', 'CREDITS', '<b>C</b>REDITS',
7870
            'Show credits')>>"
7871
    responseKeyword = 'credits'
7872
    responseChar = 'c'
7873
;
7874
7875
modify finishOptionFullScore
7876
    desc = "see your <<aHrefAlt('full score', 'FULL SCORE',
7877
            '<b>F</b>ULL SCORE', 'Show full score')>>"
7878
    responseKeyword = 'full score'
7879
    responseChar = 'f'
7880
;
7881
7882
modify finishOptionAmusing
7883
    desc = "see some <<aHrefAlt('amusing', 'AMUSING', '<b>A</b>MUSING',
7884
            'Show some amusing things to try')>> things to try"
7885
    responseKeyword = 'amusing'
7886
    responseChar = 'a'
7887
;
7888
7889
modify restoreOptionStartOver
7890
    desc = "<<aHrefAlt('start', 'START', '<b>S</b>TART',
7891
            'Start from the beginning')>> the game from the beginning"
7892
    responseKeyword = 'start'
7893
    responseChar = 's'
7894
;
7895
7896
modify restoreOptionRestoreAnother
7897
    desc = "<<aHrefAlt('restore', 'RESTORE', '<b>R</b>ESTORE',
7898
            'Restore a saved position')>> a different saved position"
7899
;
7900
7901
/* ------------------------------------------------------------------------ */
7902
/*
7903
 *   Context for Action.getVerbPhrase().  This keeps track of pronoun
7904
 *   antecedents in cases where we're stringing together a series of verb
7905
 *   phrases.
7906
 */
7907
class GetVerbPhraseContext: object
7908
    /* get the objective form of an object, using a pronoun as appropriate */
7909
    objNameObj(obj)
7910
    {
7911
        /*
7912
         *   if it's the pronoun antecedent, use the pronoun form;
7913
         *   otherwise, use the full name
7914
         */
7915
        if (obj == pronounObj)
7916
            return obj.itObj;
7917
        else
7918
            return obj.theNameObj;
7919
    }
7920
7921
    /* are we showing the given object pronomially? */
7922
    isObjPronoun(obj) { return (obj == pronounObj); }
7923
7924
    /* set the pronoun antecedent */
7925
    setPronounObj(obj) { pronounObj = obj; }
7926
7927
    /* the pronoun antecedent */
7928
    pronounObj = nil
7929
;
7930
7931
/*
7932
 *   Default getVerbPhrase context.  This can be used when no other context
7933
 *   is needed.  This context instance has no state - it doesn't track any
7934
 *   antecedents.  
7935
 */
7936
defaultGetVerbPhraseContext: GetVerbPhraseContext
7937
    /* we don't remember any antecedents */
7938
    setPronounObj(obj) { }
7939
;
7940
7941
/* ------------------------------------------------------------------------ */
7942
/*
7943
 *   Implicit action context.  This is passed to the message methods that
7944
 *   generate implicit action announcements, to indicate the context in
7945
 *   which the message is to be used.
7946
 */
7947
class ImplicitAnnouncementContext: object
7948
    /*
7949
     *   Should we use the infinitive form of the verb, or the participle
7950
     *   form for generating the announcement?  By default, use use the
7951
     *   participle form: "(first OPENING THE BOX)".
7952
     */
7953
    useInfPhrase = nil
7954
7955
    /* is this message going in a list? */
7956
    isInList = nil
7957
7958
    /*
7959
     *   Are we in a sublist of 'just trying' or 'just asking' messages?
7960
     *   (We can only have sublist groupings one level deep, so we don't
7961
     *   need to worry about what kind of sublist we're in.)
7962
     */
7963
    isInSublist = nil
7964
7965
    /* our getVerbPhrase context - by default, don't use one */
7966
    getVerbCtx = nil
7967
7968
    /* generate the announcement message given the action description */
7969
    buildImplicitAnnouncement(txt)
7970
    {
7971
        /* if we're not in a list, make it a full, stand-alone message */
7972
        if (!isInList)
7973
            txt = '<./p0>\n<.assume>first ' + txt + '<./assume>\n';
7974
7975
        /* return the result */
7976
        return txt;
7977
    }
7978
;
7979
7980
/* the standard implicit action announcement context */
7981
standardImpCtx: ImplicitAnnouncementContext;
7982
7983
/* the "just trying" implicit action announcement context */
7984
tryingImpCtx: ImplicitAnnouncementContext
7985
    /*
7986
     *   The action was merely attempted, so use the infinitive phrase in
7987
     *   the announcement: "(first trying to OPEN THE BOX)".
7988
     */
7989
    useInfPhrase = true
7990
7991
    /* build the announcement */
7992
    buildImplicitAnnouncement(txt)
7993
    {
7994
        /*
7995
         *   If we're not in a list of 'trying' messages, add the 'trying'
7996
         *   prefix message to the action description.  This isn't
7997
         *   necessary if we're in a 'trying' list, since the list itself
7998
         *   will have the 'trying' part.
7999
         */
8000
        if (!isInSublist)
8001
            txt = 'trying to ' + txt;
8002
8003
        /* now build the message into the full text as usual */
8004
        return inherited(txt);
8005
    }
8006
;
8007
8008
/*
8009
 *   The "asking question" implicit action announcement context.  By
8010
 *   default, we generate the message exactly the same way we do for the
8011
 *   'trying' case.
8012
 */
8013
askingImpCtx: tryingImpCtx;
8014
8015
/*
8016
 *   A class for messages appearing in a list.  Within a list, we want to
8017
 *   keep track of the last direct object, so that we can refer to it with
8018
 *   a pronoun later in the list.
8019
 */
8020
class ListImpCtx: ImplicitAnnouncementContext, GetVerbPhraseContext
8021
    /*
8022
     *   Set the appropriate base context for the given implicit action
8023
     *   announcement report (an ImplicitActionAnnouncement object).
8024
     */
8025
    setBaseCtx(ctx)
8026
    {
8027
        /*
8028
         *   if this is a failed attempt, use a 'trying' context;
8029
         *   otherwise, use a standard context
8030
         */
8031
        if (ctx.justTrying)
8032
            baseCtx = tryingImpCtx;
8033
        else if (ctx.justAsking)
8034
            baseCtx = askingImpCtx;
8035
        else
8036
            baseCtx = standardImpCtx;
8037
    }
8038
8039
    /* we're in a list */
8040
    isInList = true
8041
8042
    /* we are our own getVerbPhrase context */
8043
    getVerbCtx = (self)
8044
8045
    /* delegate the phrase format to our underlying announcement context */
8046
    useInfPhrase = (delegated baseCtx)
8047
8048
    /* build the announcement using our underlying context */
8049
    buildImplicitAnnouncement(txt) { return delegated baseCtx(txt); }
8050
8051
    /* our base context - we delegate some unoverridden behavior to this */
8052
    baseCtx = nil
8053
;
8054
8055
/* ------------------------------------------------------------------------ */
8056
/*
8057
 *   Language-specific Action modifications.
8058
 */
8059
modify Action
8060
    /*
8061
     *   In the English grammar, all 'predicate' grammar definitions
8062
     *   (which are usually made via the VerbRule macro) are associated
8063
     *   with Action match tree objects; in fact, each 'predicate' grammar
8064
     *   match tree is the specific Action subclass associated with the
8065
     *   grammar for the predicate.  This means that the Action associated
8066
     *   with a grammar match is simply the grammar match object itself.
8067
     *   Hence, we can resolve the action for a 'predicate' match simply
8068
     *   by returning the match itself: it is the Action as well as the
8069
     *   grammar match.
8070
     *
8071
     *   This approach ('grammar predicate' matches are based on Action
8072
     *   subclasses) works well for languages like English that encode the
8073
     *   role of each phrase in the word order of the sentence.
8074
     *
8075
     *   Languages that encode phrase roles using case markers or other
8076
     *   devices tend to be freer with word order.  As a result,
8077
     *   'predicate' grammars for such languages should generally not
8078
     *   attempt to capture all possible word orderings for a given
8079
     *   action, but should instead take the complementary approach of
8080
     *   capturing the possible overall sentence structures independently
8081
     *   of verb phrases, and plug in a verb phrase as a component, just
8082
     *   like noun phrases plug into the English grammar.  In these cases,
8083
     *   the match objects will NOT be Action subclasses; the Action
8084
     *   objects will instead be buried down deeper in the match tree.
8085
     *   Hence, resolveAction() must be defined on whatever class is used
8086
     *   to construct 'predicate' grammar matches, instead of on Action,
8087
     *   since Action will not be a 'predicate' match.
8088
     */
8089
    resolveAction(issuingActor, targetActor) { return self; }
8090
8091
    /*
8092
     *   Return the interrogative pronoun for a missing object in one of
8093
     *   our object roles.  In most cases, this is simply "what", but for
8094
     *   some actions, "whom" is more appropriate (for example, the direct
8095
     *   object of "ask" is implicitly a person, so "whom" is most
8096
     *   appropriate for this role).
8097
     */
8098
    whatObj(which)
8099
    {
8100
        /* intransitive verbs have no objects, so there's nothing to show */
8101
    }
8102
8103
    /*
8104
     *   Translate an interrogative word for whatObj.  If the word is
8105
     *   'whom', translate to the library message for 'whom'; this allows
8106
     *   authors to use 'who' rather than 'whom' as the objective form of
8107
     *   'who', which sounds less stuffy to many people.
8108
     */
8109
    whatTranslate(txt)
8110
    {
8111
        /*
8112
         *   if it's 'whom', translate to the library message for 'whom';
8113
         *   otherwise, just show the word as is
8114
         */
8115
        return (txt == 'whom' ? gLibMessages.whomPronoun : txt);
8116
    }
8117
8118
    /*
8119
     *   Return a string with the appropriate pronoun (objective form) for
8120
     *   a list of object matches, with the given resolved cardinality.
8121
     *   This list is a list of ResolveInfo objects.
8122
     */
8123
    objListPronoun(objList)
8124
    {
8125
        local himCnt, herCnt, themCnt;
8126
        local FirstPersonCnt, SecondPersonCnt;
8127
        local resolvedNumber;
8128
8129
        /* if there's no object list at all, just use 'it' */
8130
        if (objList == nil || objList == [])
8131
            return 'it';
8132
8133
        /* note the number of objects in the resolved list */
8134
        resolvedNumber = objList.length();
8135
8136
        /*
8137
         *   In the tentatively resolved object list, we might have hidden
8138
         *   away ambiguous matches.  Expand those back into the list so
8139
         *   we have the full list of in-scope matches.
8140
         */
8141
        foreach (local cur in objList)
8142
        {
8143
            /*
8144
             *   if this one has hidden ambiguous objects, add the hidden
8145
             *   objects back into our list
8146
             */
8147
            if (cur.extraObjects != nil)
8148
                objList += cur.extraObjects;
8149
        }
8150
8151
        /*
8152
         *   if the desired cardinality is plural and the object list has
8153
         *   more than one object, simply say 'them'
8154
         */
8155
        if (objList.length() > 1 && resolvedNumber > 1)
8156
            return 'them';
8157
8158
        /*
8159
         *   singular cardinality - count masculine and feminine objects,
8160
         *   and count the referral persons
8161
         */
8162
        himCnt = herCnt = themCnt = 0;
8163
        FirstPersonCnt = SecondPersonCnt = 0;
8164
        foreach (local cur in objList)
8165
        {
8166
            /* if it's masculine, count it */
8167
            if (cur.obj_.isHim)
8168
                ++himCnt;
8169
8170
            /* if it's feminine, count it */
8171
            if (cur.obj_.isHer)
8172
                ++herCnt;
8173
8174
            /* if it has plural usage, count it */
8175
            if (cur.obj_.isPlural)
8176
                ++themCnt;
8177
8178
            /* if it's first person usage, count it */
8179
            if (cur.obj_.referralPerson == FirstPerson)
8180
                ++FirstPersonCnt;
8181
8182
            /* if it's second person usage, count it */
8183
            if (cur.obj_.referralPerson == SecondPerson)
8184
                ++SecondPersonCnt;
8185
        }
8186
8187
        /*
8188
         *   if they all have plural usage, show "them"; if they're all of
8189
         *   one gender, show "him" or "her" as appropriate; if they're
8190
         *   all neuter, show "it"; otherwise, show "them"
8191
         */
8192
        if (themCnt == objList.length())
8193
            return 'them';
8194
        else if (FirstPersonCnt == objList.length())
8195
            return 'myself';
8196
        else if (SecondPersonCnt == objList.length())
8197
            return 'yourself';
8198
        else if (himCnt == objList.length() && herCnt == 0)
8199
            return 'him';
8200
        else if (herCnt == objList.length() && himCnt == 0)
8201
            return 'her';
8202
        else if (herCnt == 0 && himCnt == 0)
8203
            return 'it';
8204
        else
8205
            return 'them';
8206
    }
8207
8208
    /*
8209
     *   Announce a default object used with this action.
8210
     *
8211
     *   'resolvedAllObjects' indicates where we are in the command
8212
     *   processing: this is true if we've already resolved all of the
8213
     *   other objects in the command, nil if not.  We use this
8214
     *   information to get the phrasing right according to the situation.
8215
     */
8216
    announceDefaultObject(obj, whichObj, resolvedAllObjects)
8217
    {
8218
        /*
8219
         *   the basic action class takes no objects, so there can be no
8220
         *   default announcement
8221
         */
8222
        return '';
8223
    }
8224
8225
    /*
8226
     *   Announce all defaulted objects in the action.  By default, we
8227
     *   show nothing.
8228
     */
8229
    announceAllDefaultObjects(allResolved) { }
8230
8231
    /*
8232
     *   Return a phrase describing the action performed implicitly, as a
8233
     *   participle phrase.  'ctx' is an ImplicitAnnouncementContext object
8234
     *   describing the context in which we're generating the phrase.
8235
     *
8236
     *   This comes in two forms: if the context indicates we're only
8237
     *   attempting the action, we'll return an infinitive phrase ("open
8238
     *   the box") for use in a larger participle phrase describing the
8239
     *   attempt ("trying to...").  Otherwise, we'll be describing the
8240
     *   action as actually having been performed, so we'll return a
8241
     *   present participle phrase ("opening the box").
8242
     */
8243
    getImplicitPhrase(ctx)
8244
    {
8245
        /*
8246
         *   Get the phrase.  Use the infinitive or participle form, as
8247
         *   indicated in the context.
8248
         */
8249
        return getVerbPhrase(ctx.useInfPhrase, ctx.getVerbCtx);
8250
    }
8251
8252
    /*
8253
     *   Get the infinitive form of the action.  We are NOT to include the
8254
     *   infinitive complementizer (i.e., "to") as part of the result,
8255
     *   since the complementizer isn't used in all contexts in which we
8256
     *   might want to use the infinitive; for example, we don't want a
8257
     *   "to" in phrases involving an auxiliary verb, such as "he can open
8258
     *   the box."
8259
     */
8260
    getInfPhrase()
8261
    {
8262
        /* return the verb phrase in infinitive form */
8263
        return getVerbPhrase(true, nil);
8264
    }
8265
8266
    /*
8267
     *   Get the root infinitive form of our verb phrase as part of a
8268
     *   question in which one of the verb's objects is the "unknown" of
8269
     *   the interrogative.  'which' is one of the role markers
8270
     *   (DirectObject, IndirectObject, etc), indicating which object is
8271
     *   the subject of the interrogative.
8272
     *
8273
     *   For example, for the verb UNLOCK <dobj> WITH <iobj>, if the
8274
     *   unknown is the direct object, the phrase we'd return would be
8275
     *   "unlock": this would plug into contexts such as "what do you want
8276
     *   to unlock."  If the indirect object is the unknown for the same
8277
     *   verb, the phrase would be "unlock it with", which would plug in as
8278
     *   "what do you want to unlock it with".
8279
     *
8280
     *   Note that we are NOT to include the infinitive complementizer
8281
     *   (i.e., "to") as part of the phrase we generate, since the
8282
     *   complementizer isn't used in some contexts where the infinitive
8283
     *   conjugation is needed (for example, "what should I <infinitive>").
8284
     */
8285
    getQuestionInf(which)
8286
    {
8287
        /*
8288
         *   for a verb without objects, this is the same as the basic
8289
         *   infinitive
8290
         */
8291
        return getInfPhrase();
8292
    }
8293
8294
    /*
8295
     *   Get a string describing the full action in present participle
8296
     *   form, using the current command objects: "taking the watch",
8297
     *   "putting the book on the shelf"
8298
     */
8299
    getParticiplePhrase()
8300
    {
8301
        /* return the verb phrase in participle form */
8302
        return getVerbPhrase(nil, nil);
8303
    }
8304
8305
    /*
8306
     *   Get the full verb phrase in either infinitive or participle
8307
     *   format.  This is a common handler for getInfinitivePhrase() and
8308
     *   getParticiplePhrase().
8309
     *
8310
     *   'ctx' is a GetVerbPhraseContext object, which lets us keep track
8311
     *   of antecedents when we're stringing together multiple verb
8312
     *   phrases.  'ctx' can be nil if the verb phrase is being used in
8313
     *   isolation.
8314
     */
8315
    getVerbPhrase(inf, ctx)
8316
    {
8317
        /*
8318
         *   parse the verbPhrase into the parts before and after the
8319
         *   slash, and any additional text following the slash part
8320
         */
8321
        rexMatch('(.*)/(<alphanum|-|squote>+)(.*)', verbPhrase);
8322
8323
        /* return the appropriate parts */
8324
        if (inf)
8325
        {
8326
            /*
8327
             *   infinitive - we want the part before the slash, plus the
8328
             *   extra prepositions (or whatever) after the switched part
8329
             */
8330
            return rexGroup(1)[3] + rexGroup(3)[3];
8331
        }
8332
        else
8333
        {
8334
            /* participle - it's the part after the slash */
8335
            return rexGroup(2)[3] + rexGroup(3)[3];
8336
        }
8337
    }
8338
8339
    /*
8340
     *   Show the "noMatch" library message.  For most verbs, we use the
8341
     *   basic "you can't see that here".  Verbs that are mostly used with
8342
     *   intangible objects, such as LISTEN TO and SMELL, might want to
8343
     *   override this to use a less visually-oriented message.
8344
     */
8345
    noMatch(msgObj, actor, txt) { msgObj.noMatchCannotSee(actor, txt); }
8346
8347
    /*
8348
     *   Verb flags - these are used to control certain aspects of verb
8349
     *   formatting.  By default, we have no special flags.
8350
     */
8351
    verbFlags = 0
8352
8353
    /* add a space prefix/suffix to a string if the string is non-empty */
8354
    spPrefix(str) { return (str == '' ? str : ' ' + str); }
8355
    spSuffix(str) { return (str == '' ? str : str + ' '); }
8356
;
8357
8358
/*
8359
 *   English-specific additions for single-object verbs.
8360
 */
8361
modify TAction
8362
    /* return an interrogative word for an object of the action */
8363
    whatObj(which)
8364
    {
8365
        /*
8366
         *   Show the interrogative for our direct object - this is the
8367
         *   last word enclosed in parentheses in our verbPhrase string.
8368
         */
8369
        rexSearch('<lparen>.*?(<alpha>+)<rparen>', verbPhrase);
8370
        return whatTranslate(rexGroup(1)[3]);
8371
    }
8372
8373
    /* announce a default object used with this action */
8374
    announceDefaultObject(obj, whichObj, resolvedAllObjects)
8375
    {
8376
        local prep;
8377
        local nm = obj.getAnnouncementDistinguisher().theName(obj);
8378
8379
        /*
8380
         *   get any direct object preposition - this is the part inside
8381
         *   the "(what)" specifier parens, excluding the last word
8382
         */
8383
        rexSearch('<lparen>(.*<space>+)?<alpha>+<rparen>', verbPhrase);
8384
        prep = (rexGroup(1) == nil ? '' : rexGroup(1)[3]);
8385
8386
        /* do any verb-specific adjustment of the preposition */
8387
        if (prep != nil)
8388
            prep = adjustDefaultObjectPrep(prep, obj);
8389
8390
        /* show the preposition (if any) and the object */
8391
        return (prep == '' ? nm : prep + nm);
8392
    }
8393
8394
    /*
8395
     *   Adjust the preposition.  In some cases, the verb will want to vary
8396
     *   the preposition according to the object.  This method can return a
8397
     *   custom preposition in place of the one in the verbPhrase.  By
8398
     *   default, we just use the fixed preposition from the verbPhrase,
8399
     *   which is passed in to us in 'prep'.  
8400
     */
8401
    adjustDefaultObjectPrep(prep, obj) { return prep; }
8402
8403
    /* announce all defaulted objects */
8404
    announceAllDefaultObjects(allResolved)
8405
    {
8406
        /* announce a defaulted direct object if appropriate */
8407
        maybeAnnounceDefaultObject(dobjList_, DirectObject, allResolved);
8408
    }
8409
8410
    /* show the verb's basic infinitive form for an interrogative */
8411
    getQuestionInf(which)
8412
    {
8413
        /*
8414
         *   Show the present-tense verb form (removing the participle
8415
         *   part - the "/xxxing" part).  Include any prepositions
8416
         *   attached to the verb itself or to the direct object (inside
8417
         *   the "(what)" parens).
8418
         */
8419
        rexSearch('(.*)/<alphanum|-|squote>+(.*?)<space>+'
8420
                  + '<lparen>(.*?)<space>*?<alpha>+<rparen>',
8421
                  verbPhrase);
8422
        return rexGroup(1)[3] + spPrefix(rexGroup(2)[3])
8423
            + spPrefix(rexGroup(3)[3]);
8424
    }
8425
8426
    /* get the verb phrase in infinitive or participle form */
8427
    getVerbPhrase(inf, ctx)
8428
    {
8429
        local dobj;
8430
        local dobjText;
8431
        local dobjIsPronoun;
8432
        local ret;
8433
8434
        /* use the default pronoun context if one wasn't supplied */
8435
        if (ctx == nil)
8436
            ctx = defaultGetVerbPhraseContext;
8437
8438
        /* get the direct object */
8439
        dobj = getDobj();
8440
8441
        /* note if it's a pronoun */
8442
        dobjIsPronoun = ctx.isObjPronoun(dobj);
8443
8444
        /* get the direct object name */
8445
        dobjText = ctx.objNameObj(dobj);
8446
8447
        /* get the phrasing */
8448
        ret = getVerbPhrase1(inf, verbPhrase, dobjText, dobjIsPronoun);
8449
8450
        /* set the pronoun antecedent to my direct object */
8451
        ctx.setPronounObj(dobj);
8452
8453
        /* return the result */
8454
        return ret;
8455
    }
8456
8457
    /*
8458
     *   Given the text of the direct object phrase, build the verb phrase
8459
     *   for a one-object verb.  This is a class method that can be used by
8460
     *   other kinds of verbs (i.e., non-TActions) that use phrasing like a
8461
     *   single object.
8462
     *
8463
     *   'inf' is a flag indicating whether to use the infinitive form
8464
     *   (true) or the present participle form (nil); 'vp' is the
8465
     *   verbPhrase string; 'dobjText' is the direct object phrase's text;
8466
     *   and 'dobjIsPronoun' is true if the dobj text is rendered as a
8467
     *   pronoun.
8468
     */
8469
    getVerbPhrase1(inf, vp, dobjText, dobjIsPronoun)
8470
    {
8471
        local ret;
8472
        local dprep;
8473
        local vcomp;
8474
8475
        /*
8476
         *   parse the verbPhrase: pick out the 'infinitive/participle'
8477
         *   part, the complementizer part up to the '(what)' direct
8478
         *   object placeholder, and any preposition within the '(what)'
8479
         *   specifier
8480
         */
8481
        rexMatch('(.*)/(<alphanum|-|squote>+)(.*) '
8482
                 + '<lparen>(.*?)<space>*?<alpha>+<rparen>(.*)',
8483
                 vp);
8484
8485
        /* start off with the infinitive or participle, as desired */
8486
        if (inf)
8487
            ret = rexGroup(1)[3];
8488
        else
8489
            ret = rexGroup(2)[3];
8490
8491
        /* get the prepositional complementizer */
8492
        vcomp = rexGroup(3)[3];
8493
8494
        /* get the direct object preposition */
8495
        dprep = rexGroup(4)[3];
8496
8497
        /* do any verb-specific adjustment of the preposition */
8498
        if (dprep != nil)
8499
            dprep = adjustDefaultObjectPrep(dprep, getDobj());
8500
8501
        /*
8502
         *   if the direct object is not a pronoun, put the complementizer
8503
         *   BEFORE the direct object (the 'up' in "PICKING UP THE BOX")
8504
         */
8505
        if (!dobjIsPronoun)
8506
            ret += spPrefix(vcomp);
8507
8508
        /* add the direct object preposition */
8509
        ret += spPrefix(dprep);
8510
8511
        /* add the direct object, using the pronoun form if applicable */
8512
        ret += ' ' + dobjText;
8513
8514
        /*
8515
         *   if the direct object is a pronoun, put the complementizer
8516
         *   AFTER the direct object (the 'up' in "PICKING IT UP")
8517
         */
8518
        if (dobjIsPronoun)
8519
            ret += spPrefix(vcomp);
8520
8521
        /*
8522
         *   if there's any suffix following the direct object
8523
         *   placeholder, add it at the end of the phrase
8524
         */
8525
        ret += rexGroup(5)[3];
8526
8527
        /* return the complete phrase string */
8528
        return ret;
8529
    }
8530
;
8531
8532
/*
8533
 *   English-specific additions for two-object verbs.
8534
 */
8535
modify TIAction
8536
    /*
8537
     *   Flag: omit the indirect object in a query for a missing direct
8538
     *   object.  For many verbs, if we already know the indirect object
8539
     *   and we need to ask for the direct object, the query sounds best
8540
     *   when it includes the indirect object: "what do you want to put in
8541
     *   it?"  or "what do you want to take from it?".  This is the
8542
     *   default phrasing.
8543
     *
8544
     *   However, the corresponding query for some verbs sounds weird:
8545
     *   "what do you want to dig in with it?" or "whom do you want to ask
8546
     *   about it?".  For such actions, this property should be set to
8547
     *   true to indicate that the indirect object should be omitted from
8548
     *   the queries, which will change the phrasing to "what do you want
8549
     *   to dig in", "whom do you want to ask", and so on.
8550
     */
8551
    omitIobjInDobjQuery = nil
8552
8553
    /*
8554
     *   For VerbRules: does this verb rule have a prepositional or
8555
     *   structural phrasing of the direct and indirect object slots?  That
8556
     *   is, are the object slots determined by a prepositional marker, or
8557
     *   purely by word order?  For most English verbs with two objects,
8558
     *   the indirect object is marked by a preposition: GIVE BOOK TO BOB,
8559
     *   PUT BOOK IN BOX.  There are a few English verbs that don't include
8560
     *   any prespositional markers for the objects, though, and assign the
8561
     *   noun phrase roles purely by the word order: GIVE BOB BOOK, SHOW
8562
     *   BOB BOOK, THROW BOB BOOK.  We define these phrasings with separate
8563
     *   verb rules, which we mark with this property.
8564
     *
8565
     *   We use this in ranking verb matches.  Non-prepositional verb
8566
     *   structures are especially prone to matching where they shouldn't,
8567
     *   because we can often find a way to pick out words to fill the
8568
     *   slots in the absence of any marker words.  For example, GIVE GREEN
8569
     *   BOOK could be interpreted as GIVE BOOK TO GREEN, where GREEN is
8570
     *   assumed to be an adjective-ending noun phrase; but the player
8571
     *   probably means to give the green book to someone who they assumed
8572
     *   would be filled in as a default.  So, whenever we find an
8573
     *   interpretation that involves a non-prespositional phrasing, we'll
8574
     *   use this flag to know we should be suspicious of it and try
8575
     *   alternative phrasing first.
8576
     *
8577
     *   Most two-object verbs in English use prepositional markers, so
8578
     *   we'll set this as the default.  Individual VerbRules that use
8579
     *   purely structural phrasing should override this.
8580
     */
8581
    isPrepositionalPhrasing = true
8582
8583
    /* resolve noun phrases */
8584
    resolveNouns(issuingActor, targetActor, results)
8585
    {
8586
        /*
8587
         *   If we're a non-prepositional phrasing, it means that we have
8588
         *   the VERB IOBJ DOBJ word ordering (as in GIVE BOB BOX or THROW
8589
         *   BOB COIN).  For grammar match ranking purposes, give these
8590
         *   phrasings a lower match probability when the dobj phrase
8591
         *   doesn't have a clear qualifier.  If the dobj phrase starts
8592
         *   with 'the' or a qualifier word like that (GIVE BOB THE BOX),
8593
         *   then it's pretty clear that the structural phrasing is right
8594
         *   after all; but if there's no qualifier, we could reading too
8595
         *   much into the word order.  We could have something like GIVE
8596
         *   GREEN BOX, where we *could* treat this as two objects, but we
8597
         *   could just as well have a missing indirect object phrase.
8598
         */
8599
        if (!isPrepositionalPhrasing)
8600
        {
8601
            /*
8602
             *   If the direct object phrase starts with 'a', 'an', 'the',
8603
             *   'some', or 'any', the grammar is pretty clearly a good
8604
             *   match for the non-prepositional phrasing.  Otherwise, it's
8605
             *   suspect, so rank it accordingly.
8606
             */
8607
            if (rexMatch('(a|an|the|some|any)<space>',
8608
                         dobjMatch.getOrigText()) == nil)
8609
            {
8610
                /* note this as weak phrasing level 100 */
8611
                results.noteWeakPhrasing(100);
8612
            }
8613
        }
8614
8615
        /* inherit the base handling */
8616
        inherited(issuingActor, targetActor, results);
8617
    }
8618
8619
    /* get the interrogative for one of our objects */
8620
    whatObj(which)
8621
    {
8622
        switch (which)
8623
        {
8624
        case DirectObject:
8625
            /*
8626
             *   the direct object interrogative is the first word in
8627
             *   parentheses in our verbPhrase string
8628
             */
8629
            rexSearch('<lparen>.*?(<alpha>+)<rparen>', verbPhrase);
8630
            break;
8631
8632
        case IndirectObject:
8633
            /*
8634
             *   the indirect object interrogative is the second
8635
             *   parenthesized word in our verbPhrase string
8636
             */
8637
            rexSearch('<rparen>.*<lparen>.*?(<alpha>+)<rparen>', verbPhrase);
8638
            break;
8639
        }
8640
8641
        /* show the group match */
8642
        return whatTranslate(rexGroup(1)[3]);
8643
    }
8644
8645
    /* announce a default object used with this action */
8646
    announceDefaultObject(obj, whichObj, resolvedAllObjects)
8647
    {
8648
        local verb;
8649
        local prep;
8650
8651
        /* presume we won't have a verb or preposition */
8652
        verb = '';
8653
        prep = '';
8654
8655
        /*
8656
         *   Check the full phrasing - if we're showing the direct object,
8657
         *   but an indirect object was supplied, use the verb's
8658
         *   participle form ("asking bob") in the default string, since
8659
         *   we must clarify that we're not tagging the default string on
8660
         *   to the command line.  Don't include the participle form if we
8661
         *   don't know all the objects yet, since in this case we are in
8662
         *   fact tagging the default string onto the command so far, as
8663
         *   there's nothing else in the command to get in the way.
8664
         */
8665
        if (whichObj == DirectObject && resolvedAllObjects)
8666
        {
8667
            /*
8668
             *   extract the verb's participle form (including any
8669
             *   complementizer phrase)
8670
             */
8671
            rexSearch('/(<^lparen>+) <lparen>', verbPhrase);
8672
            verb = rexGroup(1)[3] + ' ';
8673
        }
8674
8675
        /* get the preposition to use, if any */
8676
        switch(whichObj)
8677
        {
8678
        case DirectObject:
8679
            /* use the preposition in the first "(what)" phrase */
8680
            rexSearch('<lparen>(.*?)<space>*<alpha>+<rparen>', verbPhrase);
8681
            prep = rexGroup(1)[3];
8682
            break;
8683
8684
        case IndirectObject:
8685
            /* use the preposition in the second "(what)" phrase */
8686
            rexSearch('<rparen>.*<lparen>(.*?)<space>*<alpha>+<rparen>',
8687
                      verbPhrase);
8688
            prep = rexGroup(1)[3];
8689
            break;
8690
        }
8691
8692
        /* build and return the complete phrase */
8693
        return spSuffix(verb) + spSuffix(prep)
8694
            + obj.getAnnouncementDistinguisher().theName(obj);
8695
    }
8696
8697
    /* announce all defaulted objects */
8698
    announceAllDefaultObjects(allResolved)
8699
    {
8700
        /* announce a defaulted direct object if appropriate */
8701
        maybeAnnounceDefaultObject(dobjList_, DirectObject, allResolved);
8702
8703
        /* announce a defaulted indirect object if appropriate */
8704
        maybeAnnounceDefaultObject(iobjList_, IndirectObject, allResolved);
8705
    }
8706
8707
    /* show the verb's basic infinitive form for an interrogative */
8708
    getQuestionInf(which)
8709
    {
8710
        local ret;
8711
        local vcomp;
8712
        local dprep;
8713
        local iprep;
8714
        local pro;
8715
8716
        /*
8717
         *   Our verb phrase can one of three formats, depending on which
8718
         *   object role we're asking about (the part in <angle brackets>
8719
         *   is the part we're responsible for generating).  In these
8720
         *   formats, 'verb' is the verb infinitive; 'comp' is the
8721
         *   complementizer, if any (e.g., the 'up' in 'pick up'); 'dprep'
8722
         *   is the direct object preposition (the 'in' in 'dig in x with
8723
         *   y'); and 'iprep' is the indirect object preposition (the
8724
         *   'with' in 'dig in x with y').
8725
         *
8726
         *   asking for dobj: verb vcomp dprep iprep it ('what do you want
8727
         *   to <open with it>?', '<dig in with it>', '<look up in it>').
8728
         *
8729
         *   asking for dobj, but suppressing the iobj part: verb vcomp
8730
         *   dprep ('what do you want to <turn>?', '<look up>?', '<dig
8731
         *   in>')
8732
         *
8733
         *   asking for iobj: verb dprep it vcomp iprep ('what do you want
8734
         *   to <open it with>', '<dig in it with>', '<look it up in>'
8735
         */
8736
8737
        /* parse the verbPhrase into its component parts */
8738
        rexMatch('(.*)/<alphanum|-|squote>+(?:<space>+(<^lparen>*))?'
8739
                 + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>'
8740
                 + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>',
8741
                 verbPhrase);
8742
8743
        /* pull out the verb */
8744
        ret = rexGroup(1)[3];
8745
8746
        /* pull out the verb complementizer */
8747
        vcomp = (rexGroup(2) == nil ? '' : rexGroup(2)[3]);
8748
8749
        /* pull out the direct and indirect object prepositions */
8750
        dprep = rexGroup(3)[3];
8751
        iprep = rexGroup(4)[3];
8752
8753
        /* get the pronoun for the other object phrase */
8754
        pro = getOtherMessageObjectPronoun(which);
8755
8756
        /* check what we're asking about */
8757
        if (which == DirectObject)
8758
        {
8759
            /* add the <vcomp dprep> part in all cases */
8760
            ret += spPrefix(vcomp) + spPrefix(dprep);
8761
8762
            /* add the <iprep it> part if we want the indirect object part */
8763
            if (!omitIobjInDobjQuery && pro != nil)
8764
                ret += spPrefix(iprep) + ' ' + pro;
8765
        }
8766
        else
8767
        {
8768
            /* add the <dprep it> part if appropriate */
8769
            if (pro != nil)
8770
                ret += spPrefix(dprep) + ' ' + pro;
8771
8772
            /* add the <vcomp iprep> part */
8773
            ret += spPrefix(vcomp) + spPrefix(iprep);
8774
        }
8775
8776
        /* return the result */
8777
        return ret;
8778
    }
8779
8780
    /*
8781
     *   Get the pronoun for the message object in the given role.
8782
     */
8783
    getOtherMessageObjectPronoun(which)
8784
    {
8785
        local lst;
8786
8787
        /*
8788
         *   Get the resolution list (or tentative resolution list) for the
8789
         *   *other* object, since we want to show a pronoun representing
8790
         *   the other object.  If we don't have a fully-resolved list for
8791
         *   the other object, use the tentative resolution list, which
8792
         *   we're guaranteed to have by the time we start resolving
8793
         *   anything (and thus by the time we need to ask for objects).
8794
         */
8795
        lst = (which == DirectObject ? iobjList_ : dobjList_);
8796
        if (lst == nil || lst == [])
8797
            lst = (which == DirectObject
8798
                   ? tentativeIobj_ : tentativeDobj_);
8799
8800
        /* if we found an object list, use the pronoun for the list */
8801
        if (lst != nil && lst != [])
8802
        {
8803
            /* we got a list - return a suitable pronoun for this list */
8804
            return objListPronoun(lst);
8805
        }
8806
        else
8807
        {
8808
            /* there's no object list, so there's no pronoun */
8809
            return nil;
8810
        }
8811
    }
8812
8813
    /* get the verb phrase in infinitive or participle form */
8814
    getVerbPhrase(inf, ctx)
8815
    {
8816
        local dobj, dobjText, dobjIsPronoun;
8817
        local iobj, iobjText;
8818
        local ret;
8819
8820
        /* use the default context if one wasn't supplied */
8821
        if (ctx == nil)
8822
            ctx = defaultGetVerbPhraseContext;
8823
8824
        /* get the direct object information */
8825
        dobj = getDobj();
8826
        dobjText = ctx.objNameObj(dobj);
8827
        dobjIsPronoun = ctx.isObjPronoun(dobj);
8828
8829
        /* get the indirect object information */
8830
        iobj = getIobj();
8831
        iobjText = (iobj != nil ? ctx.objNameObj(iobj) : nil);
8832
8833
        /* get the phrasing */
8834
        ret = getVerbPhrase2(inf, verbPhrase,
8835
                             dobjText, dobjIsPronoun, iobjText);
8836
8837
        /*
8838
         *   Set the antecedent for the next verb phrase.  Our direct
8839
         *   object is normally the antecedent; however, if the indirect
8840
         *   object matches the current antecedent, keep the current
8841
         *   antecedent, so that 'it' (or whatever) remains the same for
8842
         *   the next verb phrase.
8843
         */
8844
        if (ctx.pronounObj != iobj)
8845
            ctx.setPronounObj(dobj);
8846
8847
        /* return the result */
8848
        return ret;
8849
    }
8850
8851
    /*
8852
     *   Get the verb phrase for a two-object (dobj + iobj) phrasing.  This
8853
     *   is a class method, so that it can be reused by unrelated (i.e.,
8854
     *   non-TIAction) classes that also use two-object syntax but with
8855
     *   other internal structures.  This is the two-object equivalent of
8856
     *   TAction.getVerbPhrase1().
8857
     */
8858
    getVerbPhrase2(inf, vp, dobjText, dobjIsPronoun, iobjText)
8859
    {
8860
        local ret;
8861
        local vcomp;
8862
        local dprep, iprep;
8863
8864
        /* parse the verbPhrase into its component parts */
8865
        rexMatch('(.*)/(<alphanum|-|squote>+)(?:<space>+(<^lparen>*))?'
8866
                 + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>'
8867
                 + '<space>+<lparen>(.*?)<space>*<alpha>+<rparen>',
8868
                 vp);
8869
8870
        /* start off with the infinitive or participle, as desired */
8871
        if (inf)
8872
            ret = rexGroup(1)[3];
8873
        else
8874
            ret = rexGroup(2)[3];
8875
8876
        /* get the complementizer */
8877
        vcomp = (rexGroup(3) == nil ? '' : rexGroup(3)[3]);
8878
8879
        /* get the direct and indirect object prepositions */
8880
        dprep = rexGroup(4)[3];
8881
        iprep = rexGroup(5)[3];
8882
8883
        /*
8884
         *   add the complementizer BEFORE the direct object, if the
8885
         *   direct object is being shown as a full name ("PICK UP BOX")
8886
         */
8887
        if (!dobjIsPronoun)
8888
            ret += spPrefix(vcomp);
8889
8890
        /*
8891
         *   add the direct object and its preposition, using a pronoun if
8892
         *   applicable
8893
         */
8894
        ret += spPrefix(dprep) + ' ' + dobjText;
8895
8896
        /*
8897
         *   add the complementizer AFTER the direct object, if the direct
8898
         *   object is shown as a pronoun ("PICK IT UP")
8899
         */
8900
        if (dobjIsPronoun)
8901
            ret += spPrefix(vcomp);
8902
8903
        /* if we have an indirect object, add it with its preposition */
8904
        if (iobjText != nil)
8905
            ret += spPrefix(iprep) + ' ' + iobjText;
8906
8907
        /* return the result phrase */
8908
        return ret;
8909
    }
8910
;
8911
8912
/*
8913
 *   English-specific additions for verbs taking a literal phrase as the
8914
 *   sole object.
8915
 */
8916
modify LiteralAction
8917
    /* provide a base verbPhrase, in case an instance leaves it out */
8918
    verbPhrase = 'verb/verbing (what)'
8919
8920
    /* get an interrogative word for an object of the action */
8921
    whatObj(which)
8922
    {
8923
        /* use the same processing as TAction */
8924
        return delegated TAction(which);
8925
    }
8926
8927
    getVerbPhrase(inf, ctx)
8928
    {
8929
        /* handle this as though the literal were a direct object phrase */
8930
        return TAction.getVerbPhrase1(inf, verbPhrase, gLiteral, nil);
8931
    }
8932
8933
    getQuestionInf(which)
8934
    {
8935
        /* use the same handling as for a regular one-object action */
8936
        return delegated TAction(which);
8937
    }
8938
8939
;
8940
8941
/*
8942
 *   English-specific additions for verbs of a direct object and a literal
8943
 *   phrase.
8944
 */
8945
modify LiteralTAction
8946
    announceDefaultObject(obj, whichObj, resolvedAllObjects)
8947
    {
8948
        /*
8949
         *   Use the same handling as for a regular two-object action.  We
8950
         *   can only default the actual object in this kind of verb; the
8951
         *   actual object always fills the DirectObject slot, but in
8952
         *   message generation it might use a different slot, so use the
8953
         *   message generation slot here.
8954
         */
8955
        return delegated TIAction(obj, whichMessageObject,
8956
                                  resolvedAllObjects);
8957
    }
8958
8959
    whatObj(which)
8960
    {
8961
        /* use the same handling we use for a regular two-object action */
8962
        return delegated TIAction(which);
8963
    }
8964
8965
    getQuestionInf(which)
8966
    {
8967
        /*
8968
         *   use the same handling as for a two-object action (but note
8969
         *   that we override getMessageObjectPronoun(), which will affect
8970
         *   the way we present the verb infinitive in some cases)
8971
         */
8972
        return delegated TIAction(which);
8973
    }
8974
8975
    /*
8976
     *   When we want to show a verb infinitive phrase that involves a
8977
     *   pronoun for the literal phrase, refer to the literal as 'that'
8978
     *   rather than 'it' or anything else.
8979
     */
8980
    getOtherMessageObjectPronoun(which)
8981
    {
8982
        /*
8983
         *   If we're asking about the literal phrase, then the other
8984
         *   pronoun is for the resolved object: so, return the pronoun
8985
         *   for the direct object phrase, because we *always* store the
8986
         *   non-literal in the direct object slot, regardless of the
8987
         *   actual phrasing of the action.
8988
         *
8989
         *   If we're asking about the resolved object (i.e., not the
8990
         *   literal phrase), then return 'that' as the pronoun for the
8991
         *   literal phrase.
8992
         */
8993
        if (which == whichMessageLiteral)
8994
        {
8995
            /*
8996
             *   we're asking about the literal, so the other pronoun is
8997
             *   for the resolved object, which is always in the direct
8998
             *   object slot (so the 'other' slot is effectively the
8999
             *   indirect object)
9000
             */
9001
            return delegated TIAction(IndirectObject);
9002
        }
9003
        else
9004
        {
9005
            /*
9006
             *   We're asking about the resolved object, so the other
9007
             *   pronoun is for the literal phrase: always use 'that' to
9008
             *   refer to the literal phrase.
9009
             */
9010
            return 'that';
9011
        }
9012
    }
9013
9014
    getVerbPhrase(inf, ctx)
9015
    {
9016
        local dobj, dobjText, dobjIsPronoun;
9017
        local litText;
9018
        local ret;
9019
9020
        /* use the default context if one wasn't supplied */
9021
        if (ctx == nil)
9022
            ctx = defaultGetVerbPhraseContext;
9023
9024
        /* get the direct object information */
9025
        dobj = getDobj();
9026
        dobjText = ctx.objNameObj(dobj);
9027
        dobjIsPronoun = ctx.isObjPronoun(dobj);
9028
9029
        /* get our literal text */
9030
        litText = gLiteral;
9031
9032
        /*
9033
         *   Use the standard two-object phrasing.  The order of the
9034
         *   phrasing depends on whether our literal phrase is in the
9035
         *   direct or indirect object slot.
9036
         */
9037
        if (whichMessageLiteral == DirectObject)
9038
            ret = TIAction.getVerbPhrase2(inf, verbPhrase,
9039
                                          litText, nil, dobjText);
9040
        else
9041
            ret = TIAction.getVerbPhrase2(inf, verbPhrase,
9042
                                          dobjText, dobjIsPronoun, litText);
9043
9044
        /* use the direct object as the antecedent for the next phrase */
9045
        ctx.setPronounObj(dobj);
9046
9047
        /* return the result */
9048
        return ret;
9049
    }
9050
;
9051
9052
/*
9053
 *   English-specific additions for verbs taking a topic phrase as the sole
9054
 *   object.  
9055
 */
9056
modify TopicAction
9057
    /* get an interrogative word for an object of the action */
9058
    whatObj(which)
9059
    {
9060
        /* use the same processing as TAction */
9061
        return delegated TAction(which);
9062
    }
9063
9064
    getVerbPhrase(inf, ctx)
9065
    {
9066
        /* handle this as though the topic text were a direct object phrase */
9067
        return TAction.getVerbPhrase1(
9068
            inf, verbPhrase, getTopic().getTopicText().toLower(), nil);
9069
    }
9070
9071
    getQuestionInf(which)
9072
    {
9073
        /* use the same handling as for a regular one-object action */
9074
        return delegated TAction(which);
9075
    }
9076
9077
;
9078
9079
/*
9080
 *   English-specific additions for verbs with topic phrases.
9081
 */
9082
modify TopicTAction
9083
    announceDefaultObject(obj, whichObj, resolvedAllObjects)
9084
    {
9085
        /*
9086
         *   Use the same handling as for a regular two-object action.  We
9087
         *   can only default the actual object in this kind of verb; the
9088
         *   actual object always fills the DirectObject slot, but in
9089
         *   message generation it might use a different slot, so use the
9090
         *   message generation slot here.
9091
         */
9092
        return delegated TIAction(obj, whichMessageObject,
9093
                                  resolvedAllObjects);
9094
    }
9095
9096
    whatObj(which)
9097
    {
9098
        /* use the same handling we use for a regular two-object action */
9099
        return delegated TIAction(which);
9100
    }
9101
9102
    getQuestionInf(which)
9103
    {
9104
        /* use the same handling as for a regular two-object action */
9105
        return delegated TIAction(which);
9106
    }
9107
9108
    getOtherMessageObjectPronoun(which)
9109
    {
9110
        /*
9111
         *   If we're asking about the topic, then the other pronoun is
9112
         *   for the resolved object, which is always in the direct object
9113
         *   slot.  If we're asking about the resolved object, then return
9114
         *   a pronoun for the topic.
9115
         */
9116
        if (which == whichMessageTopic)
9117
        {
9118
            /*
9119
             *   we want the pronoun for the resolved object, which is
9120
             *   always in the direct object slot (so the 'other' slot is
9121
             *   effectively the indirect object)
9122
             */
9123
            return delegated TIAction(IndirectObject);
9124
        }
9125
        else
9126
        {
9127
            /* return a generic pronoun for the topic */
9128
            return 'that';
9129
        }
9130
    }
9131
9132
    getVerbPhrase(inf, ctx)
9133
    {
9134
        local dobj, dobjText, dobjIsPronoun;
9135
        local topicText;
9136
        local ret;
9137
9138
        /* use the default context if one wasn't supplied */
9139
        if (ctx == nil)
9140
            ctx = defaultGetVerbPhraseContext;
9141
9142
        /* get the direct object information */
9143
        dobj = getDobj();
9144
        dobjText = ctx.objNameObj(dobj);
9145
        dobjIsPronoun = ctx.isObjPronoun(dobj);
9146
9147
        /* get our topic phrase */
9148
        topicText = getTopic().getTopicText().toLower();
9149
9150
        /*
9151
         *   Use the standard two-object phrasing.  The order of the
9152
         *   phrasing depends on whether our topic phrase is in the direct
9153
         *   or indirect object slot.
9154
         */
9155
        if (whichMessageTopic == DirectObject)
9156
            ret = TIAction.getVerbPhrase2(inf, verbPhrase,
9157
                                          topicText, nil, dobjText);
9158
        else
9159
            ret = TIAction.getVerbPhrase2(inf, verbPhrase,
9160
                                          dobjText, dobjIsPronoun, topicText);
9161
9162
        /* use the direct object as the antecedent for the next phrase */
9163
        ctx.setPronounObj(dobj);
9164
9165
        /* return the result */
9166
        return ret;
9167
    }
9168
;
9169
9170
/* ------------------------------------------------------------------------ */
9171
/*
9172
 *   Verbs.
9173
 *
9174
 *   The actual body of each of our verbs is defined in the main
9175
 *   language-independent part of the library.  We only define the
9176
 *   language-specific grammar rules here.
9177
 */
9178
9179
VerbRule(Take)
9180
    ('take' | 'pick' 'up' | 'get') dobjList
9181
    | 'pick' dobjList 'up'
9182
    : TakeAction
9183
    verbPhrase = 'take/taking (what)'
9184
;
9185
9186
VerbRule(TakeFrom)
9187
    ('take' | 'get') dobjList
9188
        ('from' | 'out' 'of' | 'off' | 'off' 'of') singleIobj
9189
    | 'remove' dobjList 'from' singleIobj
9190
    : TakeFromAction
9191
    verbPhrase = 'take/taking (what) (from what)'
9192
;
9193
9194
VerbRule(Remove)
9195
    'remove' dobjList
9196
    : RemoveAction
9197
    verbPhrase = 'remove/removing (what)'
9198
;
9199
9200
VerbRule(Drop)
9201
    ('drop' | 'put' 'down' | 'set' 'down') dobjList
9202
    | ('put' | 'set') dobjList 'down'
9203
    : DropAction
9204
    verbPhrase = 'drop/dropping (what)'
9205
;
9206
9207
VerbRule(Examine)
9208
    ('examine' | 'inspect' | 'x' | 'look' 'at' | 'l' 'at') dobjList
9209
    : ExamineAction
9210
    verbPhrase = 'examine/examining (what)'
9211
;
9212
9213
VerbRule(Read)
9214
    'read' dobjList
9215
    : ReadAction
9216
    verbPhrase = 'read/reading (what)'
9217
;
9218
9219
VerbRule(LookIn)
9220
    ('look' | 'l') ('in' | 'inside') dobjList
9221
    : LookInAction
9222
    verbPhrase = 'look/looking (in what)'
9223
;
9224
9225
VerbRule(Search)
9226
    'search' dobjList
9227
    : SearchAction
9228
    verbPhrase = 'search/searching (what)'
9229
;
9230
9231
VerbRule(LookThrough)
9232
    ('look' | 'l') ('through' | 'thru' | 'out') dobjList
9233
    : LookThroughAction
9234
    verbPhrase = 'look/looking (through what)'
9235
;
9236
9237
VerbRule(LookUnder)
9238
    ('look' | 'l') 'under' dobjList
9239
    : LookUnderAction
9240
    verbPhrase = 'look/looking (under what)'
9241
;
9242
9243
VerbRule(LookBehind)
9244
    ('look' | 'l') 'behind' dobjList
9245
    : LookBehindAction
9246
    verbPhrase = 'look/looking (behind what)'
9247
;
9248
9249
VerbRule(Feel)
9250
    ('feel' | 'touch') dobjList
9251
    : FeelAction
9252
    verbPhrase = 'touch/touching (what)'
9253
;
9254
9255
VerbRule(Taste)
9256
    'taste' dobjList
9257
    : TasteAction
9258
    verbPhrase = 'taste/tasting (what)'
9259
;
9260
9261
VerbRule(Smell)
9262
    ('smell' | 'sniff') dobjList
9263
    : SmellAction
9264
    verbPhrase = 'smell/smelling (what)'
9265
9266
    /*
9267
     *   use the "not aware" version of the no-match message - the object
9268
     *   of SMELL is often intangible, so the default "you can't see that"
9269
     *   message is often incongruous for this verb
9270
     */
9271
    noMatch(msgObj, actor, txt) { msgObj.noMatchNotAware(actor, txt); }
9272
;
9273
9274
VerbRule(SmellImplicit)
9275
    'smell' | 'sniff'
9276
    : SmellImplicitAction
9277
    verbPhrase = 'smell/smelling'
9278
;
9279
9280
VerbRule(ListenTo)
9281
    ('hear' | 'listen' 'to' ) dobjList
9282
    : ListenToAction
9283
    verbPhrase = 'listen/listening (to what)'
9284
9285
    /*
9286
     *   use the "not aware" version of the no-match message - the object
9287
     *   of LISTEN TO is often intangible, so the default "you can't see
9288
     *   that" message is often incongruous for this verb
9289
     */
9290
    noMatch(msgObj, actor, txt) { msgObj.noMatchNotAware(actor, txt); }
9291
;
9292
9293
VerbRule(ListenImplicit)
9294
    'listen' | 'hear'
9295
    : ListenImplicitAction
9296
    verbPhrase = 'listen/listening'
9297
;
9298
9299
VerbRule(PutIn)
9300
    ('put' | 'place' | 'set') dobjList
9301
        ('in' | 'into' | 'in' 'to' | 'inside' | 'inside' 'of') singleIobj
9302
    : PutInAction
9303
    verbPhrase = 'put/putting (what) (in what)'
9304
    askIobjResponseProd = inSingleNoun
9305
;
9306
9307
VerbRule(PutOn)
9308
    ('put' | 'place' | 'drop' | 'set') dobjList
9309
        ('on' | 'onto' | 'on' 'to' | 'upon') singleIobj
9310
    | 'put' dobjList 'down' 'on' singleIobj
9311
    : PutOnAction
9312
    verbPhrase = 'put/putting (what) (on what)'
9313
    askIobjResponseProd = onSingleNoun
9314
;
9315
9316
VerbRule(PutUnder)
9317
    ('put' | 'place' | 'set') dobjList 'under' singleIobj
9318
    : PutUnderAction
9319
    verbPhrase = 'put/putting (what) (under what)'
9320
;
9321
9322
VerbRule(PutBehind)
9323
    ('put' | 'place' | 'set') dobjList 'behind' singleIobj
9324
    : PutBehindAction
9325
    verbPhrase = 'put/putting (what) (behind what)'
9326
;
9327
9328
VerbRule(PutInWhat)
9329
    [badness 500] ('put' | 'place') dobjList
9330
    : PutInAction
9331
    verbPhrase = 'put/putting (what) (in what)'
9332
    construct()
9333
    {
9334
        /* set up the empty indirect object phrase */
9335
        iobjMatch = new EmptyNounPhraseProd();
9336
        iobjMatch.responseProd = inSingleNoun;
9337
    }
9338
;
9339
9340
VerbRule(Wear)
9341
    ('wear' | 'don' | 'put' 'on') dobjList
9342
    | 'put' dobjList 'on'
9343
    : WearAction
9344
    verbPhrase = 'wear/wearing (what)'
9345
;
9346
9347
VerbRule(Doff)
9348
    ('doff' | 'take' 'off') dobjList
9349
    | 'take' dobjList 'off'
9350
    : DoffAction
9351
    verbPhrase = 'take/taking off (what)'
9352
;
9353
9354
VerbRule(Kiss)
9355
    'kiss' singleDobj
9356
    : KissAction
9357
    verbPhrase = 'kiss/kissing (whom)'
9358
;
9359
9360
VerbRule(AskFor)
9361
    ('ask' | 'a') singleDobj 'for' singleTopic
9362
    | ('ask' | 'a') 'for' singleTopic 'from' singleDobj
9363
    : AskForAction
9364
    verbPhrase = 'ask/asking (whom) (for what)'
9365
    omitIobjInDobjQuery = true
9366
    askDobjResponseProd = singleNoun
9367
    askIobjResponseProd = forSingleNoun
9368
;
9369
9370
VerbRule(AskWhomFor)
9371
    ('ask' | 'a') 'for' singleTopic
9372
    : AskForAction
9373
    verbPhrase = 'ask/asking (whom) (for what)'
9374
    omitIobjInDobjQuery = true
9375
    construct()
9376
    {
9377
        /* set up the empty direct object phrase */
9378
        dobjMatch = new EmptyNounPhraseProd();
9379
        dobjMatch.responseProd = singleNoun;
9380
    }
9381
;
9382
9383
VerbRule(AskAbout)
9384
    ('ask' | 'a') singleDobj 'about' singleTopic
9385
    : AskAboutAction
9386
    verbPhrase = 'ask/asking (whom) (about what)'
9387
    omitIobjInDobjQuery = true
9388
    askDobjResponseProd = singleNoun
9389
;
9390
9391
VerbRule(AskAboutImplicit)
9392
    'a' singleTopic
9393
    : AskAboutAction
9394
    verbPhrase = 'ask/asking (whom) (about what)'
9395
    omitIobjInDobjQuery = true
9396
    construct()
9397
    {
9398
        /* set up the empty direct object phrase */
9399
        dobjMatch = new EmptyNounPhraseProd();
9400
        dobjMatch.responseProd = singleNoun;
9401
    }
9402
;
9403
9404
VerbRule(AskAboutWhat)
9405
    [badness 500] 'ask' singleDobj
9406
    : AskAboutAction
9407
    verbPhrase = 'ask/asking (whom) (about what)'
9408
    askDobjResponseProd = singleNoun
9409
    omitIobjInDobjQuery = true
9410
    construct()
9411
    {
9412
        /* set up the empty topic phrase */
9413
        topicMatch = new EmptyNounPhraseProd();
9414
        topicMatch.responseProd = aboutTopicPhrase;
9415
    }
9416
;
9417
9418
9419
VerbRule(TellAbout)
9420
    ('tell' | 't') singleDobj 'about' singleTopic
9421
    : TellAboutAction
9422
    verbPhrase = 'tell/telling (whom) (about what)'
9423
    askDobjResponseProd = singleNoun
9424
    omitIobjInDobjQuery = true
9425
;
9426
9427
VerbRule(TellAboutImplicit)
9428
    't' singleTopic
9429
    : TellAboutAction
9430
    verbPhrase = 'tell/telling (whom) (about what)'
9431
    omitIobjInDobjQuery = true
9432
    construct()
9433
    {
9434
        /* set up the empty direct object phrase */
9435
        dobjMatch = new EmptyNounPhraseProd();
9436
        dobjMatch.responseProd = singleNoun;
9437
    }
9438
;
9439
9440
VerbRule(TellAboutWhat)
9441
    [badness 500] 'tell' singleDobj
9442
    : TellAboutAction
9443
    verbPhrase = 'tell/telling (whom) (about what)'
9444
    askDobjResponseProd = singleNoun
9445
    omitIobjInDobjQuery = true
9446
    construct()
9447
    {
9448
        /* set up the empty topic phrase */
9449
        topicMatch = new EmptyNounPhraseProd();
9450
        topicMatch.responseProd = aboutTopicPhrase;
9451
    }
9452
;
9453
9454
VerbRule(AskVague)
9455
    [badness 500] 'ask' singleDobj singleTopic
9456
    : AskVagueAction
9457
    verbPhrase = 'ask/asking (whom)'
9458
;
9459
9460
VerbRule(TellVague)
9461
    [badness 500] 'tell' singleDobj singleTopic
9462
    : AskVagueAction
9463
    verbPhrase = 'ask/asking (whom)'
9464
;
9465
9466
VerbRule(TalkTo)
9467
    ('greet' | 'say' 'hello' 'to' | 'talk' 'to') singleDobj
9468
    : TalkToAction
9469
    verbPhrase = 'talk/talking (to whom)'
9470
    askDobjResponseProd = singleNoun
9471
;
9472
9473
VerbRule(TalkToWhat)
9474
    [badness 500] 'talk'
9475
    : TalkToAction
9476
    verbPhrase = 'talk/talking (to whom)'
9477
    askDobjResponseProd = singleNoun
9478
9479
    construct()
9480
    {
9481
        /* set up the empty direct object phrase */
9482
        dobjMatch = new EmptyNounPhraseProd();
9483
        dobjMatch.responseProd = onSingleNoun;
9484
    }
9485
;
9486
9487
VerbRule(Topics)
9488
    'topics'
9489
    : TopicsAction
9490
    verbPhrase = 'show/showing topics'
9491
;
9492
9493
VerbRule(Hello)
9494
    ('say' | ) ('hello' | 'hallo' | 'hi')
9495
    : HelloAction
9496
    verbPhrase = 'say/saying hello'
9497
;
9498
9499
VerbRule(Goodbye)
9500
    ('say' | ()) ('goodbye' | 'good-bye' | 'good' 'bye' | 'bye')
9501
    : GoodbyeAction
9502
    verbPhrase = 'say/saying goodbye'
9503
;
9504
9505
VerbRule(Yes)
9506
    'yes' | 'affirmative' | 'say' 'yes'
9507
    : YesAction
9508
    verbPhrase = 'say/saying yes'
9509
;
9510
9511
VerbRule(No)
9512
    'no' | 'negative' | 'say' 'no'
9513
    : NoAction
9514
    verbPhrase = 'say/saying no'
9515
;
9516
9517
VerbRule(Yell)
9518
    'yell' | 'scream' | 'shout' | 'holler'
9519
    : YellAction
9520
    verbPhrase = 'yell/yelling'
9521
;
9522
9523
VerbRule(GiveTo)
9524
    ('give' | 'offer') dobjList 'to' singleIobj
9525
    : GiveToAction
9526
    verbPhrase = 'give/giving (what) (to whom)'
9527
    askIobjResponseProd = toSingleNoun
9528
;
9529
9530
VerbRule(GiveToType2)
9531
    ('give' | 'offer') singleIobj dobjList
9532
    : GiveToAction
9533
    verbPhrase = 'give/giving (what) (to whom)'
9534
    askIobjResponseProd = toSingleNoun
9535
9536
    /* this is a non-prepositional phrasing */
9537
    isPrepositionalPhrasing = nil
9538
;
9539
9540
VerbRule(GiveToWhom)
9541
    ('give' | 'offer') dobjList
9542
    : GiveToAction
9543
    verbPhrase = 'give/giving (what) (to whom)'
9544
    construct()
9545
    {
9546
        /* set up the empty indirect object phrase */
9547
        iobjMatch = new ImpliedActorNounPhraseProd();
9548
        iobjMatch.responseProd = toSingleNoun;
9549
    }
9550
;
9551
9552
VerbRule(ShowTo)
9553
    'show' dobjList 'to' singleIobj
9554
    : ShowToAction
9555
    verbPhrase = 'show/showing (what) (to whom)'
9556
    askIobjResponseProd = toSingleNoun
9557
;
9558
9559
VerbRule(ShowToType2)
9560
    'show' singleIobj dobjList
9561
    : ShowToAction
9562
    verbPhrase = 'show/showing (what) (to whom)'
9563
    askIobjResponseProd = toSingleNoun
9564
9565
    /* this is a non-prepositional phrasing */
9566
    isPrepositionalPhrasing = nil
9567
;
9568
9569
VerbRule(ShowToWhom)
9570
    'show' dobjList
9571
    : ShowToAction
9572
    verbPhrase = 'show/showing (what) (to whom)'
9573
    construct()
9574
    {
9575
        /* set up the empty indirect object phrase */
9576
        iobjMatch = new ImpliedActorNounPhraseProd();
9577
        iobjMatch.responseProd = toSingleNoun;
9578
    }
9579
;
9580
9581
VerbRule(Throw)
9582
    ('throw' | 'toss') dobjList
9583
    : ThrowAction
9584
    verbPhrase = 'throw/throwing (what)'
9585
;
9586
9587
VerbRule(ThrowAt)
9588
    ('throw' | 'toss') dobjList 'at' singleIobj
9589
    : ThrowAtAction
9590
    verbPhrase = 'throw/throwing (what) (at what)'
9591
    askIobjResponseProd = atSingleNoun
9592
;
9593
9594
VerbRule(ThrowTo)
9595
    ('throw' | 'toss') dobjList 'to' singleIobj
9596
    : ThrowToAction
9597
    verbPhrase = 'throw/throwing (what) (to whom)'
9598
    askIobjResponseProd = toSingleNoun
9599
;
9600
9601
VerbRule(ThrowToType2)
9602
    'throw' singleIobj dobjList
9603
    : ThrowToAction
9604
    verbPhrase = 'throw/throwing (what) (to whom)'
9605
    askIobjResponseProd = toSingleNoun
9606
9607
    /* this is a non-prepositional phrasing */
9608
    isPrepositionalPhrasing = nil
9609
;
9610
9611
VerbRule(ThrowDir)
9612
    ('throw' | 'toss') dobjList ('to' ('the' | ) | ) singleDir
9613
    : ThrowDirAction
9614
    verbPhrase = ('throw/throwing (what) ' + dirMatch.dir.name)
9615
;
9616
9617
/* a special rule for THROW DOWN <dobj> */
9618
VerbRule(ThrowDirDown)
9619
    'throw' ('down' | 'd') dobjList
9620
    : ThrowDirAction
9621
    verbPhrase = ('throw/throwing (what) down')
9622
9623
    /* the direction is fixed as 'down' for this phrasing */
9624
    getDirection() { return downDirection; }
9625
;
9626
9627
VerbRule(Follow)
9628
    'follow' singleDobj
9629
    : FollowAction
9630
    verbPhrase = 'follow/following (whom)'
9631
    askDobjResponseProd = singleNoun
9632
;
9633
9634
VerbRule(Attack)
9635
    ('attack' | 'kill' | 'hit' | 'kick' | 'punch') singleDobj
9636
    : AttackAction
9637
    verbPhrase = 'attack/attacking (whom)'
9638
    askDobjResponseProd = singleNoun
9639
;
9640
9641
VerbRule(AttackWith)
9642
    ('attack' | 'kill' | 'hit' | 'kick' | 'punch' | 'strike')
9643
        singleDobj
9644
        'with' singleIobj
9645
    : AttackWithAction
9646
    verbPhrase = 'attack/attacking (whom) (with what)'
9647
    askDobjResponseProd = singleNoun
9648
    askIobjResponseProd = withSingleNoun
9649
;
9650
9651
VerbRule(Inventory)
9652
    'i' | 'inventory' | 'take' 'inventory'
9653
    : InventoryAction
9654
    verbPhrase = 'take/taking inventory'
9655
;
9656
9657
VerbRule(InventoryTall)
9658
    'i' 'tall' | 'inventory' 'tall'
9659
    : InventoryTallAction
9660
    verbPhrase = 'take/taking "tall" inventory'
9661
;
9662
9663
VerbRule(InventoryWide)
9664
    'i' 'wide' | 'inventory' 'wide'
9665
    : InventoryWideAction
9666
    verbPhrase = 'take/taking "wide" inventory'
9667
;
9668
9669
VerbRule(Wait)
9670
    'z' | 'wait'
9671
    : WaitAction
9672
    verbPhrase = 'wait/waiting'
9673
;
9674
9675
VerbRule(Look)
9676
    'look' | 'look' 'around' | 'l' | 'l' 'around'
9677
    : LookAction
9678
    verbPhrase = 'look/looking around'
9679
;
9680
9681
VerbRule(Quit)
9682
    'quit' | 'q'
9683
    : QuitAction
9684
    verbPhrase = 'quit/quitting'
9685
;
9686
9687
VerbRule(Again)
9688
    'again' | 'g'
9689
    : AgainAction
9690
    verbPhrase = 'repeat/repeating the last command'
9691
;
9692
9693
VerbRule(Footnote)
9694
    ('footnote' | 'note') singleNumber
9695
    : FootnoteAction
9696
    verbPhrase = 'show/showing a footnote'
9697
;
9698
9699
VerbRule(FootnotesFull)
9700
    'footnotes' 'full'
9701
    : FootnotesFullAction
9702
    verbPhrase = 'enable/enabling all footnotes'
9703
;
9704
9705
VerbRule(FootnotesMedium)
9706
    'footnotes' 'medium'
9707
    : FootnotesMediumAction
9708
    verbPhrase = 'enable/enabling new footnotes'
9709
;
9710
9711
VerbRule(FootnotesOff)
9712
    'footnotes' 'off'
9713
    : FootnotesOffAction
9714
    verbPhrase = 'hide/hiding footnotes'
9715
;
9716
9717
VerbRule(FootnotesStatus)
9718
    'footnotes'
9719
    : FootnotesStatusAction
9720
    verbPhrase = 'show/showing footnote status'
9721
;
9722
9723
VerbRule(TipsOn)
9724
    ('tips' | 'tip') 'on'
9725
    : TipModeAction
9726
9727
    stat_ = true
9728
9729
    verbPhrase = 'turn/turning tips on'
9730
;
9731
9732
VerbRule(TipsOff)
9733
    ('tips' | 'tip') 'off'
9734
    : TipModeAction
9735
9736
    stat_ = nil
9737
9738
    verbPhrase = 'turn/turning tips off'
9739
;
9740
9741
VerbRule(Verbose)
9742
    'verbose'
9743
    : VerboseAction
9744
    verbPhrase = 'enter/entering VERBOSE mode'
9745
;
9746
9747
VerbRule(Terse)
9748
    'terse' | 'brief'
9749
    : TerseAction
9750
    verbPhrase = 'enter/entering BRIEF mode'
9751
;
9752
9753
VerbRule(Score)
9754
    'score' | 'status'
9755
    : ScoreAction
9756
    verbPhrase = 'show/showing score'
9757
;
9758
9759
VerbRule(FullScore)
9760
    'full' 'score' | 'fullscore' | 'full'
9761
    : FullScoreAction
9762
    verbPhrase = 'show/showing full score'
9763
;
9764
9765
VerbRule(Notify)
9766
    'notify'
9767
    : NotifyAction
9768
    verbPhrase = 'show/showing notification status'
9769
;
9770
9771
VerbRule(NotifyOn)
9772
    'notify' 'on'
9773
    : NotifyOnAction
9774
    verbPhrase = 'turn/turning on score notification'
9775
;
9776
9777
VerbRule(NotifyOff)
9778
    'notify' 'off'
9779
    : NotifyOffAction
9780
    verbPhrase = 'turn/turning off score notification'
9781
;
9782
9783
VerbRule(Save)
9784
    'save'
9785
    : SaveAction
9786
    verbPhrase = 'save/saving'
9787
;
9788
9789
VerbRule(SaveString)
9790
    'save' quotedStringPhrase->fname_
9791
    : SaveStringAction
9792
    verbPhrase = 'save/saving'
9793
;
9794
9795
VerbRule(Restore)
9796
    'restore'
9797
    : RestoreAction
9798
    verbPhrase = 'restore/restoring'
9799
;
9800
9801
VerbRule(RestoreString)
9802
    'restore' quotedStringPhrase->fname_
9803
    : RestoreStringAction
9804
    verbPhrase = 'restore/restoring'
9805
;
9806
9807
VerbRule(SaveDefaults)
9808
    'save' 'defaults'
9809
    : SaveDefaultsAction
9810
    verbPhrase = 'save/saving defaults'
9811
;
9812
9813
VerbRule(RestoreDefaults)
9814
    'restore' 'defaults'
9815
    : RestoreDefaultsAction
9816
    verbPhrase = 'restore/restoring defaults'
9817
;
9818
9819
VerbRule(Restart)
9820
    'restart'
9821
    : RestartAction
9822
    verbPhrase = 'restart/restarting'
9823
;
9824
9825
VerbRule(Pause)
9826
    'pause'
9827
    : PauseAction
9828
    verbPhrase = 'pause/pausing'
9829
;
9830
9831
VerbRule(Undo)
9832
    'undo'
9833
    : UndoAction
9834
    verbPhrase = 'undo/undoing'
9835
;
9836
9837
VerbRule(Version)
9838
    'version'
9839
    : VersionAction
9840
    verbPhrase = 'show/showing version'
9841
;
9842
9843
VerbRule(Credits)
9844
    'credits'
9845
    : CreditsAction
9846
    verbPhrase = 'show/showing credits'
9847
;
9848
9849
VerbRule(About)
9850
    'about'
9851
    : AboutAction
9852
    verbPhrase = 'show/showing story information'
9853
;
9854
9855
VerbRule(Script)
9856
    'script' | 'script' 'on'
9857
    : ScriptAction
9858
    verbPhrase = 'start/starting scripting'
9859
;
9860
9861
VerbRule(ScriptString)
9862
    'script' quotedStringPhrase->fname_
9863
    : ScriptStringAction
9864
    verbPhrase = 'start/starting scripting'
9865
;
9866
9867
VerbRule(ScriptOff)
9868
    'script' 'off' | 'unscript'
9869
    : ScriptOffAction
9870
    verbPhrase = 'end/ending scripting'
9871
;
9872
9873
VerbRule(Record)
9874
    'record' | 'record' 'on'
9875
    : RecordAction
9876
    verbPhrase = 'start/starting command recording'
9877
;
9878
9879
VerbRule(RecordString)
9880
    'record' quotedStringPhrase->fname_
9881
    : RecordStringAction
9882
    verbPhrase = 'start/starting command recording'
9883
;
9884
9885
VerbRule(RecordEvents)
9886
    'record' 'events' | 'record' 'events' 'on'
9887
    : RecordEventsAction
9888
    verbPhrase = 'start/starting event recording'
9889
;
9890
9891
VerbRule(RecordEventsString)
9892
    'record' 'events' quotedStringPhrase->fname_
9893
    : RecordEventsStringAction
9894
    verbPhrase = 'start/starting command recording'
9895
;
9896
9897
VerbRule(RecordOff)
9898
    'record' 'off'
9899
    : RecordOffAction
9900
    verbPhrase = 'end/ending command recording'
9901
;
9902
9903
VerbRule(ReplayString)
9904
    'replay' ('quiet'->quiet_ | 'nonstop'->nonstop_ | )
9905
        (quotedStringPhrase->fname_ | )
9906
    : ReplayStringAction
9907
    verbPhrase = 'replay/replaying command recording'
9908
9909
    /* set the appropriate option flags */
9910
    scriptOptionFlags = ((quiet_ != nil ? ScriptFileQuiet : 0)
9911
                         | (nonstop_ != nil ? ScriptFileNonstop : 0))
9912
;
9913
VerbRule(ReplayQuiet)
9914
    'rq' (quotedStringPhrase->fname_ | )
9915
    : ReplayStringAction
9916
9917
    scriptOptionFlags = ScriptFileQuiet
9918
;
9919
9920
VerbRule(VagueTravel) 'go' | 'walk' : VagueTravelAction
9921
    verbPhrase = 'go'
9922
;
9923
9924
VerbRule(Travel)
9925
    'go' singleDir | singleDir
9926
    : TravelAction
9927
    verbPhrase = ('go/going ' + dirMatch.dir.name)
9928
;
9929
9930
/*
9931
 *   Create a TravelVia subclass merely so we can supply a verbPhrase.
9932
 *   (The parser looks for subclasses of each specific Action class to find
9933
 *   its verb phrase, since the language-specific Action definitions are
9934
 *   always in the language module's 'grammar' subclasses.  We don't need
9935
 *   an actual grammar rule, since this isn't an input-able verb, so we
9936
 *   merely need to create a regular subclass in order for the verbPhrase
9937
 *   to get found.)  
9938
 */
9939
class EnTravelVia: TravelViaAction
9940
    verbPhrase = 'use/using (what)'
9941
;
9942
9943
VerbRule(Port)
9944
    'go' 'to' ('port' | 'p')
9945
    : PortAction
9946
    dirMatch: DirectionProd { dir = portDirection }
9947
    verbPhrase = 'go/going to port'
9948
;
9949
9950
VerbRule(Starboard)
9951
    'go' 'to' ('starboard' | 'sb')
9952
    : StarboardAction
9953
    dirMatch: DirectionProd { dir = starboardDirection }
9954
    verbPhrase = 'go/going to starboard'
9955
;
9956
9957
VerbRule(In)
9958
    'enter'
9959
    : InAction
9960
    dirMatch: DirectionProd { dir = inDirection }
9961
    verbPhrase = 'enter/entering'
9962
;
9963
9964
VerbRule(Out)
9965
    'exit' | 'leave'
9966
    : OutAction
9967
    dirMatch: DirectionProd { dir = outDirection }
9968
    verbPhrase = 'exit/exiting'
9969
;
9970
9971
VerbRule(GoThrough)
9972
    ('walk' | 'go' ) ('through' | 'thru')
9973
        singleDobj
9974
    : GoThroughAction
9975
    verbPhrase = 'go/going (through what)'
9976
    askDobjResponseProd = singleNoun
9977
;
9978
9979
VerbRule(Enter)
9980
    ('enter' | 'in' | 'into' | 'in' 'to'
9981
     | ('walk' | 'go') ('to' | 'in' | 'in' 'to' | 'into'))
9982
    singleDobj
9983
    : EnterAction
9984
    verbPhrase = 'enter/entering (what)'
9985
    askDobjResponseProd = singleNoun
9986
;
9987
9988
VerbRule(GoBack)
9989
    'back' | 'go' 'back' | 'return'
9990
    : GoBackAction
9991
    verbPhrase = 'go/going back'
9992
;
9993
9994
VerbRule(Dig)
9995
    ('dig' | 'dig' 'in') singleDobj
9996
    : DigAction
9997
    verbPhrase = 'dig/digging (in what)'
9998
    askDobjResponseProd = inSingleNoun
9999
;
10000
10001
VerbRule(DigWith)
10002
    ('dig' | 'dig' 'in') singleDobj 'with' singleIobj
10003
    : DigWithAction
10004
    verbPhrase = 'dig/digging (in what) (with what)'
10005
    omitIobjInDobjQuery = true
10006
    askDobjResponseProd = inSingleNoun
10007
    askIobjResponseProd = withSingleNoun
10008
;
10009
10010
VerbRule(Jump)
10011
    'jump'
10012
    : JumpAction
10013
    verbPhrase = 'jump/jumping'
10014
;
10015
10016
VerbRule(JumpOffI)
10017
    'jump' 'off'
10018
    : JumpOffIAction
10019
    verbPhrase = 'jump/jumping off'
10020
;
10021
10022
VerbRule(JumpOff)
10023
    'jump' 'off' singleDobj
10024
    : JumpOffAction
10025
    verbPhrase = 'jump/jumping (off what)'
10026
    askDobjResponseProd = singleNoun
10027
;
10028
10029
VerbRule(JumpOver)
10030
    ('jump' | 'jump' 'over') singleDobj
10031
    : JumpOverAction
10032
    verbPhrase = 'jump/jumping (over what)'
10033
    askDobjResponseProd = singleNoun
10034
;
10035
10036
VerbRule(Push)
10037
    ('push' | 'press') dobjList
10038
    : PushAction
10039
    verbPhrase = 'push/pushing (what)'
10040
;
10041
10042
VerbRule(Pull)
10043
    'pull' dobjList
10044
    : PullAction
10045
    verbPhrase = 'pull/pulling (what)'
10046
;
10047
10048
VerbRule(Move)
10049
    'move' dobjList
10050
    : MoveAction
10051
    verbPhrase = 'move/moving (what)'
10052
;
10053
10054
VerbRule(MoveTo)
10055
    ('push' | 'move') dobjList ('to' | 'under') singleIobj
10056
    : MoveToAction
10057
    verbPhrase = 'move/moving (what) (to what)'
10058
    askIobjResponseProd = toSingleNoun
10059
    omitIobjInDobjQuery = true
10060
;
10061
10062
VerbRule(MoveWith)
10063
    'move' singleDobj 'with' singleIobj
10064
    : MoveWithAction
10065
    verbPhrase = 'move/moving (what) (with what)'
10066
    askDobjResponseProd = singleNoun
10067
    askIobjResponseProd = withSingleNoun
10068
    omitIobjInDobjQuery = true
10069
;
10070
10071
VerbRule(Turn)
10072
    ('turn' | 'twist' | 'rotate') dobjList
10073
    : TurnAction
10074
    verbPhrase = 'turn/turning (what)'
10075
;
10076
10077
VerbRule(TurnWith)
10078
    ('turn' | 'twist' | 'rotate') singleDobj 'with' singleIobj
10079
    : TurnWithAction
10080
    verbPhrase = 'turn/turning (what) (with what)'
10081
    askDobjResponseProd = singleNoun
10082
    askIobjResponseProd = withSingleNoun
10083
;
10084
10085
VerbRule(TurnTo)
10086
    ('turn' | 'twist' | 'rotate') singleDobj
10087
        'to' singleLiteral
10088
    : TurnToAction
10089
    verbPhrase = 'turn/turning (what) (to what)'
10090
    askDobjResponseProd = singleNoun
10091
    omitIobjInDobjQuery = true
10092
;
10093
10094
VerbRule(Set)
10095
    'set' dobjList
10096
    : SetAction
10097
    verbPhrase = 'set/setting (what)'
10098
;
10099
10100
VerbRule(SetTo)
10101
    'set' singleDobj 'to' singleLiteral
10102
    : SetToAction
10103
    verbPhrase = 'set/setting (what) (to what)'
10104
    askDobjResponseProd = singleNoun
10105
    omitIobjInDobjQuery = true
10106
;
10107
10108
VerbRule(TypeOn)
10109
    'type' 'on' singleDobj
10110
    : TypeOnAction
10111
    verbPhrase = 'type/typing (on what)'
10112
;
10113
10114
VerbRule(TypeLiteralOn)
10115
    'type' singleLiteral 'on' singleDobj
10116
    : TypeLiteralOnAction
10117
    verbPhrase = 'type/typing (what) (on what)'
10118
    askDobjResponseProd = singleNoun
10119
;
10120
10121
VerbRule(TypeLiteralOnWhat)
10122
    [badness 500] 'type' singleLiteral
10123
    : TypeLiteralOnAction
10124
    verbPhrase = 'type/typing (what) (on what)'
10125
    construct()
10126
    {
10127
        /* set up the empty direct object phrase */
10128
        dobjMatch = new EmptyNounPhraseProd();
10129
        dobjMatch.responseProd = onSingleNoun;
10130
    }
10131
;
10132
10133
VerbRule(EnterOn)
10134
    'enter' singleLiteral
10135
        ('on' | 'in' | 'in' 'to' | 'into' | 'with') singleDobj
10136
    : EnterOnAction
10137
    verbPhrase = 'enter/entering (what) (on what)'
10138
    askDobjResponseProd = singleNoun
10139
;
10140
10141
VerbRule(EnterOnWhat)
10142
    'enter' singleLiteral
10143
    : EnterOnAction
10144
    verbPhrase = 'enter/entering (what) (on what)'
10145
    construct()
10146
    {
10147
        /*
10148
         *   ENTER <text> is a little special, because it could mean ENTER
10149
         *   <text> ON <keypad>, or it could mean GO INTO <object>.  It's
10150
         *   hard to tell which based on the grammar alone, so we have to
10151
         *   do some semantic analysis to make a good decision about it.
10152
         *
10153
         *   We'll start by assuming it's the ENTER <text> ON <iobj> form
10154
         *   of the command, and we'll look for a suitable default object
10155
         *   to serve as the iobj.  If we can't find a suitable default, we
10156
         *   won't prompt for the missing object as we usually would.
10157
         *   Instead, we'll try re-parsing the command as GO INTO.  To do
10158
         *   this, use our custom "asker" - this won't actually prompt for
10159
         *   the missing object, but will instead retry the command as a GO
10160
         *   INTO command.
10161
         */
10162
        dobjMatch = new EmptyNounPhraseProd();
10163
        dobjMatch.setPrompt(onSingleNoun, enterOnWhatAsker);
10164
    }
10165
;
10166
10167
/* our custom "asker" for the missing iobj in an "ENTER <text>" command */
10168
enterOnWhatAsker: ResolveAsker
10169
    askMissingObject(targetActor, action, which)
10170
    {
10171
        /*
10172
         *   This method is called when the resolver has failed to find a
10173
         *   suitable default for the missing indirect object of ENTER
10174
         *   <text> ON <iobj>.
10175
         *
10176
         *   Instead of issuing the prompt that we'd normally issue under
10177
         *   these circumstances, assume that we're totally wrong about the
10178
         *   way we've been interpreting the command: assume that it's not
10179
         *   meant as ENTER <text> ON <iobj> after all, but was actually
10180
         *   meant as GO IN <object>.  So, rephrase the command as such and
10181
         *   start over with the new phrasing.
10182
         */
10183
        throw new ReplacementCommandStringException(
10184
            'get in ' + action.getLiteral(), gIssuingActor, gActor);
10185
    }
10186
;
10187
10188
VerbRule(Consult)
10189
    'consult' singleDobj : ConsultAction
10190
    verbPhrase = 'consult/consulting (what)'
10191
    askDobjResponseProd = singleNoun
10192
;
10193
10194
VerbRule(ConsultAbout)
10195
    'consult' singleDobj ('on' | 'about') singleTopic
10196
    | 'search' singleDobj 'for' singleTopic
10197
    | (('look' | 'l') ('up' | 'for')
10198
       | 'find'
10199
       | 'search' 'for'
10200
       | 'read' 'about')
10201
         singleTopic 'in' singleDobj
10202
    | ('look' | 'l') singleTopic 'up' 'in' singleDobj
10203
    : ConsultAboutAction
10204
    verbPhrase = 'consult/consulting (what) (about what)'
10205
    omitIobjInDobjQuery = true
10206
    askDobjResponseProd = singleNoun
10207
;
10208
10209
VerbRule(ConsultWhatAbout)
10210
    (('look' | 'l') ('up' | 'for')
10211
     | 'find'
10212
     | 'search' 'for'
10213
     | 'read' 'about')
10214
    singleTopic
10215
    | ('look' | 'l') singleTopic 'up'
10216
    : ConsultAboutAction
10217
    verbPhrase = 'look/looking up (what) (in what)'
10218
    whichMessageTopic = DirectObject
10219
    construct()
10220
    {
10221
        /* set up the empty direct object phrase */
10222
        dobjMatch = new EmptyNounPhraseProd();
10223
        dobjMatch.responseProd = inSingleNoun;
10224
    }
10225
;
10226
10227
VerbRule(Switch)
10228
    'switch' dobjList
10229
    : SwitchAction
10230
    verbPhrase = 'switch/switching (what)'
10231
;
10232
10233
VerbRule(Flip)
10234
    'flip' dobjList
10235
    : FlipAction
10236
    verbPhrase = 'flip/flipping (what)'
10237
;
10238
10239
VerbRule(TurnOn)
10240
    ('activate' | ('turn' | 'switch') 'on') dobjList
10241
    | ('turn' | 'switch') dobjList 'on'
10242
    : TurnOnAction
10243
    verbPhrase = 'turn/turning on (what)'
10244
;
10245
10246
VerbRule(TurnOff)
10247
    ('deactivate' | ('turn' | 'switch') 'off') dobjList
10248
    | ('turn' | 'switch') dobjList 'off'
10249
    : TurnOffAction
10250
    verbPhrase = 'turn/turning off (what)'
10251
;
10252
10253
VerbRule(Light)
10254
    'light' dobjList
10255
    : LightAction
10256
    verbPhrase = 'light/lighting (what)'
10257
;
10258
10259
DefineTAction(Strike);
10260
VerbRule(Strike)
10261
    'strike' dobjList
10262
    : StrikeAction
10263
    verbPhrase = 'strike/striking (what)'
10264
;
10265
10266
VerbRule(Burn)
10267
    ('burn' | 'ignite' | 'set' 'fire' 'to') dobjList
10268
    : BurnAction
10269
    verbPhrase = 'light/lighting (what)'
10270
;
10271
10272
VerbRule(BurnWith)
10273
    ('light' | 'burn' | 'ignite' | 'set' 'fire' 'to') singleDobj
10274
        'with' singleIobj
10275
    : BurnWithAction
10276
    verbPhrase = 'light/lighting (what) (with what)'
10277
    omitIobjInDobjQuery = true
10278
    askDobjResponseProd = singleNoun
10279
    askIobjResponseProd = withSingleNoun
10280
;
10281
10282
VerbRule(Extinguish)
10283
    ('extinguish' | 'douse' | 'put' 'out' | 'blow' 'out') dobjList
10284
    | ('blow' | 'put') dobjList 'out'
10285
    : ExtinguishAction
10286
    verbPhrase = 'extinguish/extinguishing (what)'
10287
;
10288
10289
VerbRule(Break)
10290
    ('break' | 'ruin' | 'destroy' | 'wreck') dobjList
10291
    : BreakAction
10292
    verbPhrase = 'break/breaking (what)'
10293
;
10294
10295
VerbRule(CutWithWhat)
10296
    [badness 500] 'cut' singleDobj
10297
    : CutWithAction
10298
    verbPhrase = 'cut/cutting (what) (with what)'
10299
    construct()
10300
    {
10301
        /* set up the empty indirect object phrase */
10302
        iobjMatch = new EmptyNounPhraseProd();
10303
        iobjMatch.responseProd = withSingleNoun;
10304
    }
10305
;
10306
10307
VerbRule(CutWith)
10308
    'cut' singleDobj 'with' singleIobj
10309
    : CutWithAction
10310
    verbPhrase = 'cut/cutting (what) (with what)'
10311
    askDobjResponseProd = singleNoun
10312
    askIobjResponseProd = withSingleNoun
10313
;
10314
10315
VerbRule(Eat)
10316
    ('eat' | 'consume') dobjList
10317
    : EatAction
10318
    verbPhrase = 'eat/eating (what)'
10319
;
10320
10321
VerbRule(Drink)
10322
    ('drink' | 'quaff' | 'imbibe') dobjList
10323
    : DrinkAction
10324
    verbPhrase = 'drink/drinking (what)'
10325
;
10326
10327
VerbRule(Pour)
10328
    'pour' dobjList
10329
    : PourAction
10330
    verbPhrase = 'pour/pouring (what)'
10331
;
10332
10333
VerbRule(PourInto)
10334
    'pour' dobjList ('in' | 'into' | 'in' 'to') singleIobj
10335
    : PourIntoAction
10336
    verbPhrase = 'pour/pouring (what) (into what)'
10337
    askIobjResponseProd = inSingleNoun
10338
;
10339
10340
VerbRule(PourOnto)
10341
    'pour' dobjList ('on' | 'onto' | 'on' 'to') singleIobj
10342
    : PourOntoAction
10343
    verbPhrase = 'pour/pouring (what) (onto what)'
10344
    askIobjResponseProd = onSingleNoun
10345
;
10346
10347
VerbRule(Climb)
10348
    'climb' singleDobj
10349
    : ClimbAction
10350
    verbPhrase = 'climb/climbing (what)'
10351
    askDobjResponseProd = singleNoun
10352
;
10353
10354
VerbRule(ClimbUp)
10355
    ('climb' | 'go' | 'walk') 'up' singleDobj
10356
    : ClimbUpAction
10357
    verbPhrase = 'climb/climbing (up what)'
10358
    askDobjResponseProd = singleNoun
10359
;
10360
10361
VerbRule(ClimbUpWhat)
10362
    [badness 200] ('climb' | 'go' | 'walk') 'up'
10363
    : ClimbUpAction
10364
    verbPhrase = 'climb/climbing (up what)'
10365
    askDobjResponseProd = singleNoun
10366
    construct()
10367
    {
10368
        dobjMatch = new EmptyNounPhraseProd();
10369
        dobjMatch.responseProd = onSingleNoun;
10370
    }
10371
;
10372
10373
VerbRule(ClimbDown)
10374
    ('climb' | 'go' | 'walk') 'down' singleDobj
10375
    : ClimbDownAction
10376
    verbPhrase = 'climb/climbing (down what)'
10377
    askDobjResponseProd = singleNoun
10378
;
10379
10380
VerbRule(ClimbDownWhat)
10381
    [badness 200] ('climb' | 'go' | 'walk') 'down'
10382
    : ClimbDownAction
10383
    verbPhrase = 'climb/climbing (down what)'
10384
    askDobjResponseProd = singleNoun
10385
    construct()
10386
    {
10387
        dobjMatch = new EmptyNounPhraseProd();
10388
        dobjMatch.responseProd = onSingleNoun;
10389
    }
10390
;
10391
10392
VerbRule(Clean)
10393
    'clean' dobjList
10394
    : CleanAction
10395
    verbPhrase = 'clean/cleaning (what)'
10396
;
10397
10398
VerbRule(CleanWith)
10399
    'clean' dobjList 'with' singleIobj
10400
    : CleanWithAction
10401
    verbPhrase = 'clean/cleaning (what) (with what)'
10402
    askIobjResponseProd = withSingleNoun
10403
    omitIobjInDobjQuery = true
10404
;
10405
10406
VerbRule(AttachTo)
10407
    ('attach' | 'connect') dobjList 'to' singleIobj
10408
    : AttachToAction
10409
    askIobjResponseProd = toSingleNoun
10410
    verbPhrase = 'attach/attaching (what) (to what)'
10411
;
10412
10413
VerbRule(AttachToWhat)
10414
    [badness 500] ('attach' | 'connect') dobjList
10415
    : AttachToAction
10416
    verbPhrase = 'attach/attaching (what) (to what)'
10417
    construct()
10418
    {
10419
        /* set up the empty indirect object phrase */
10420
        iobjMatch = new EmptyNounPhraseProd();
10421
        iobjMatch.responseProd = toSingleNoun;
10422
    }
10423
;
10424
10425
VerbRule(DetachFrom)
10426
    ('detach' | 'disconnect') dobjList 'from' singleIobj
10427
    : DetachFromAction
10428
    verbPhrase = 'detach/detaching (what) (from what)'
10429
    askIobjResponseProd = fromSingleNoun
10430
;
10431
10432
VerbRule(Detach)
10433
    ('detach' | 'disconnect') dobjList
10434
    : DetachAction
10435
    verbPhrase = 'detach/detaching (what)'
10436
;
10437
10438
VerbRule(Open)
10439
    'open' dobjList
10440
    : OpenAction
10441
    verbPhrase = 'open/opening (what)'
10442
;
10443
10444
VerbRule(Close)
10445
    ('close' | 'shut') dobjList
10446
    : CloseAction
10447
    verbPhrase = 'close/closing (what)'
10448
;
10449
10450
VerbRule(Lock)
10451
    'lock' dobjList
10452
    : LockAction
10453
    verbPhrase = 'lock/locking (what)'
10454
;
10455
10456
VerbRule(Unlock)
10457
    'unlock' dobjList
10458
    : UnlockAction
10459
    verbPhrase = 'unlock/unlocking (what)'
10460
;
10461
10462
VerbRule(LockWith)
10463
    'lock' singleDobj 'with' singleIobj
10464
    : LockWithAction
10465
    verbPhrase = 'lock/locking (what) (with what)'
10466
    omitIobjInDobjQuery = true
10467
    askDobjResponseProd = singleNoun
10468
    askIobjResponseProd = withSingleNoun
10469
;
10470
10471
VerbRule(UnlockWith)
10472
    'unlock' singleDobj 'with' singleIobj
10473
    : UnlockWithAction
10474
    verbPhrase = 'unlock/unlocking (what) (with what)'
10475
    omitIobjInDobjQuery = true
10476
    askDobjResponseProd = singleNoun
10477
    askIobjResponseProd = withSingleNoun
10478
;
10479
10480
VerbRule(SitOn)
10481
    'sit' ('on' | 'in' | 'down' 'on' | 'down' 'in')
10482
        singleDobj
10483
    : SitOnAction
10484
    verbPhrase = 'sit/sitting (on what)'
10485
    askDobjResponseProd = singleNoun
10486
10487
    /* use the actorInPrep, if there's a direct object available */
10488
    adjustDefaultObjectPrep(prep, obj)
10489
        { return (obj != nil ? obj.actorInPrep + ' ' : prep); }
10490
;
10491
10492
VerbRule(Sit)
10493
    'sit' ( | 'down') : SitAction
10494
    verbPhrase = 'sit/sitting down'
10495
;
10496
10497
VerbRule(LieOn)
10498
    'lie' ('on' | 'in' | 'down' 'on' | 'down' 'in')
10499
        singleDobj
10500
    : LieOnAction
10501
    verbPhrase = 'lie/lying (on what)'
10502
    askDobjResponseProd = singleNoun
10503
10504
    /* use the actorInPrep, if there's a direct object available */
10505
    adjustDefaultObjectPrep(prep, obj)
10506
        { return (obj != nil ? obj.actorInPrep + ' ' : prep); }
10507
;
10508
10509
VerbRule(Lie)
10510
    'lie' ( | 'down') : LieAction
10511
    verbPhrase = 'lie/lying down'
10512
;
10513
10514
VerbRule(StandOn)
10515
    ('stand' ('on' | 'in' | 'onto' | 'on' 'to' | 'into' | 'in' 'to')
10516
     | 'climb' ('on' | 'onto' | 'on' 'to'))
10517
    singleDobj
10518
    : StandOnAction
10519
    verbPhrase = 'stand/standing (on what)'
10520
    askDobjResponseProd = singleNoun
10521
10522
    /* use the actorInPrep, if there's a direct object available */
10523
    adjustDefaultObjectPrep(prep, obj)
10524
        { return (obj != nil ? obj.actorInPrep + ' ' : prep); }
10525
;
10526
10527
VerbRule(Stand)
10528
    'stand' | 'stand' 'up' | 'get' 'up'
10529
    : StandAction
10530
    verbPhrase = 'stand/standing up'
10531
;
10532
10533
VerbRule(GetOutOf)
10534
    ('out' 'of' | 'get' 'out' 'of' | 'climb' 'out' 'of' | 'leave' | 'exit')
10535
    singleDobj
10536
    : GetOutOfAction
10537
    verbPhrase = 'get/getting (out of what)'
10538
    askDobjResponseProd = singleNoun
10539
10540
    /* use the actorOutOfPrep, if there's a direct object available */
10541
    adjustDefaultObjectPrep(prep, obj)
10542
        { return (obj != nil ? obj.actorOutOfPrep + ' ' : prep); }
10543
;
10544
10545
VerbRule(GetOffOf)
10546
    'get' ('off' | 'off' 'of' | 'down' 'from') singleDobj
10547
    : GetOffOfAction
10548
    verbPhrase = 'get/getting (off of what)'
10549
    askDobjResponseProd = singleNoun
10550
10551
    /* use the actorOutOfPrep, if there's a direct object available */
10552
    adjustDefaultObjectPrep(prep, obj)
10553
        { return (obj != nil ? obj.actorOutOfPrep + ' ' : prep); }
10554
;
10555
10556
VerbRule(GetOut)
10557
    'get' 'out'
10558
    | 'get' 'off'
10559
    | 'get' 'down'
10560
    | 'disembark'
10561
    | 'climb' 'out'
10562
    : GetOutAction
10563
    verbPhrase = 'get/getting out'
10564
;
10565
10566
VerbRule(Board)
10567
    ('board'
10568
     | ('get' ('in' | 'into' | 'in' 'to' | 'on' | 'onto' | 'on' 'to'))
10569
     | ('climb' ('in' | 'into' | 'in' 'to')))
10570
    singleDobj
10571
    : BoardAction
10572
    verbPhrase = 'get/getting (in what)'
10573
    askDobjResponseProd = singleNoun
10574
;
10575
10576
VerbRule(Sleep)
10577
    'sleep'
10578
    : SleepAction
10579
    verbPhrase = 'sleep/sleeping'
10580
;
10581
10582
VerbRule(Fasten)
10583
    ('fasten' | 'buckle' | 'buckle' 'up') dobjList
10584
    : FastenAction
10585
    verbPhrase = 'fasten/fastening (what)'
10586
;
10587
10588
VerbRule(FastenTo)
10589
    ('fasten' | 'buckle') dobjList 'to' singleIobj
10590
    : FastenToAction
10591
    verbPhrase = 'fasten/fastening (what) (to what)'
10592
    askIobjResponseProd = toSingleNoun
10593
;
10594
10595
VerbRule(Unfasten)
10596
    ('unfasten' | 'unbuckle') dobjList
10597
    : UnfastenAction
10598
    verbPhrase = 'unfasten/unfastening (what)'
10599
;
10600
10601
VerbRule(UnfastenFrom)
10602
    ('unfasten' | 'unbuckle') dobjList 'from' singleIobj
10603
    : UnfastenFromAction
10604
    verbPhrase = 'unfasten/unfastening (what) (from what)'
10605
    askIobjResponseProd = fromSingleNoun
10606
;
10607
10608
VerbRule(PlugInto)
10609
    'plug' dobjList ('in' | 'into' | 'in' 'to') singleIobj
10610
    : PlugIntoAction
10611
    verbPhrase = 'plug/plugging (what) (into what)'
10612
    askIobjResponseProd = inSingleNoun
10613
;
10614
10615
VerbRule(PlugIntoWhat)
10616
    [badness 500] 'plug' dobjList
10617
    : PlugIntoAction
10618
    verbPhrase = 'plug/plugging (what) (into what)'
10619
    construct()
10620
    {
10621
        /* set up the empty indirect object phrase */
10622
        iobjMatch = new EmptyNounPhraseProd();
10623
        iobjMatch.responseProd = inSingleNoun;
10624
    }
10625
;
10626
10627
VerbRule(PlugIn)
10628
    'plug' dobjList 'in'
10629
    | 'plug' 'in' dobjList
10630
    : PlugInAction
10631
    verbPhrase = 'plug/plugging (what)'
10632
;
10633
10634
VerbRule(UnplugFrom)
10635
    'unplug' dobjList 'from' singleIobj
10636
    : UnplugFromAction
10637
    verbPhrase = 'unplug/unplugging (what) (from what)'
10638
    askIobjResponseProd = fromSingleNoun
10639
;
10640
10641
VerbRule(Unplug)
10642
    'unplug' dobjList
10643
    : UnplugAction
10644
    verbPhrase = 'unplug/unplugging (what)'
10645
;
10646
10647
VerbRule(Screw)
10648
    'screw' dobjList
10649
    : ScrewAction
10650
    verbPhrase = 'screw/screwing (what)'
10651
;
10652
10653
VerbRule(ScrewWith)
10654
    'screw' dobjList 'with' singleIobj
10655
    : ScrewWithAction
10656
    verbPhrase = 'screw/screwing (what) (with what)'
10657
    omitIobjInDobjQuery = true
10658
    askIobjResponseProd = withSingleNoun
10659
;
10660
10661
VerbRule(Unscrew)
10662
    'unscrew' dobjList
10663
    : UnscrewAction
10664
    verbPhrase = 'unscrew/unscrewing (what)'
10665
;
10666
10667
VerbRule(UnscrewWith)
10668
    'unscrew' dobjList 'with' singleIobj
10669
    : UnscrewWithAction
10670
    verbPhrase = 'unscrew/unscrewing (what) (with what)'
10671
    omitIobjInDobjQuery = true
10672
    askIobjResponseProd = withSingleNoun
10673
;
10674
10675
VerbRule(PushTravelDir)
10676
    ('push' | 'pull' | 'drag' | 'move') singleDobj singleDir
10677
    : PushTravelDirAction
10678
    verbPhrase = ('push/pushing (what) ' + dirMatch.dir.name)
10679
;
10680
10681
VerbRule(PushTravelThrough)
10682
    ('push' | 'pull' | 'drag' | 'move') singleDobj
10683
    ('through' | 'thru') singleIobj
10684
    : PushTravelThroughAction
10685
    verbPhrase = 'push/pushing (what) (through what)'
10686
;
10687
10688
VerbRule(PushTravelEnter)
10689
    ('push' | 'pull' | 'drag' | 'move') singleDobj
10690
    ('in' | 'into' | 'in' 'to') singleIobj
10691
    : PushTravelEnterAction
10692
    verbPhrase = 'push/pushing (what) (into what)'
10693
;
10694
10695
VerbRule(PushTravelGetOutOf)
10696
    ('push' | 'pull' | 'drag' | 'move') singleDobj
10697
    'out' ('of' | ) singleIobj
10698
    : PushTravelGetOutOfAction
10699
    verbPhrase = 'push/pushing (what) (out of what)'
10700
;
10701
10702
10703
VerbRule(PushTravelClimbUp)
10704
    ('push' | 'pull' | 'drag' | 'move') singleDobj
10705
    'up' singleIobj
10706
    : PushTravelClimbUpAction
10707
    verbPhrase = 'push/pushing (what) (up what)'
10708
    omitIobjInDobjQuery = true
10709
;
10710
10711
VerbRule(PushTravelClimbDown)
10712
    ('push' | 'pull' | 'drag' | 'move') singleDobj
10713
    'down' singleIobj
10714
    : PushTravelClimbDownAction
10715
    verbPhrase = 'push/pushing (what) (down what)'
10716
;
10717
10718
VerbRule(Exits)
10719
    'exits'
10720
    : ExitsAction
10721
    verbPhrase = 'exits/showing exits'
10722
;
10723
10724
VerbRule(ExitsMode)
10725
    'exits' ('on'->on_ | 'all'->on_
10726
             | 'off'->off_ | 'none'->off_
10727
             | ('status' ('line' | ) | 'statusline') 'look'->on_
10728
             | 'look'->on_ ('status' ('line' | ) | 'statusline')
10729
             | 'status'->stat_ ('line' | ) | 'statusline'->stat_
10730
             | 'look'->look_)
10731
    : ExitsModeAction
10732
    verbPhrase = 'turn/turning off exits display'
10733
;
10734
10735
VerbRule(HintsOff)
10736
    'hints' 'off'
10737
    : HintsOffAction
10738
    verbPhrase = 'disable/disabling hints'
10739
;
10740
10741
VerbRule(Hint)
10742
    'hint' | 'hints'
10743
    : HintAction
10744
    verbPhrase = 'show/showing hints'
10745
;
10746
10747
VerbRule(Oops)
10748
    ('oops' | 'o') singleLiteral
10749
    : OopsAction
10750
    verbPhrase = 'oops/correcting (what)'
10751
;
10752
10753
VerbRule(OopsOnly)
10754
    ('oops' | 'o')
10755
    : OopsIAction
10756
    verbPhrase = 'oops/correcting'
10757
;
10758
10759
/* ------------------------------------------------------------------------ */
10760
/*
10761
 *   "debug" verb - special verb to break into the debugger.  We'll only
10762
 *   compile this into the game if we're compiling a debug version to begin
10763
 *   with, since a non-debug version can't be run under the debugger.  
10764
 */
10765
#ifdef __DEBUG
10766
10767
VerbRule(Debug)
10768
    'debug'
10769
    : DebugAction
10770
    verbPhrase = 'debug/debugging'
10771
;
10772
10773
#endif /* __DEBUG */
10774