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

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