parser.t

documentation
#charset "us-ascii"
#include "advlite.h"

/* ------------------------------------------------------------------------ */
/*
 *   Temporary scaffolding for the game world.  This gives us information
 *   on scope, pronoun antecedents, and other information the parser needs
 *   from the game world.  
 */
World: PreinitObject
    /*
     *   Get the list of objects in scope 
     */
    scope()
    {
        local s = scope_;

        if (s == nil)
            scope_ = s = Q.scopeList(gPlayerChar);

        return s;
    }

    /* cached scope list */
    scope_ = nil

    /* 
     *   A list of all Mentionables in the game, useful for building scope lists
     *   for resolving Topics.
     */
    universalScope = nil
    
    buildUniversalScope()
    {
        local vec = new Vector(100);
        forEachInstance(Mentionable, {o: vec.append(o) });
        universalScope = vec.toList;
    }
    
    execute()
    {
        buildUniversalScope();
    }
    
    
;


/* ------------------------------------------------------------------------ */
/*
 *   Parser is the class that implements the main parsing procedure, namely
 *   taking a line of text from the player, figuring out what it means, and
 *   executing it.
 *   
 *   The conventional IF parsing loop simply consists of reading a line of
 *   text from the player, calling Parser.parse() on the string, and
 *   repeating.
 *   
 *   In most cases you'll just need a single Parser instance.  The Parser
 *   object keeps track of unfinished commands, such as when we need to ask
 *   for disambiguation help or for a missing object.  If for some reason
 *   you want to keep multiple sets of this kind of state (reading input
 *   from more than one player, for example), you can create as many Parser
 *   instances as needed.  
 */
class Parser: object
    /*
     *   Auto-Look: Should we treat an empty command line (i.e., the user
     *   just presses Return) as a LOOK AROUND command?
     *   
     *   The traditional handling since the Infocom era has always been to
     *   treat an empty command line as a parsing error, and display an
     *   error message along the lines of "I beg your pardon?".  Given that
     *   an empty command line has no conflicting meaning, though, we
     *   *could* assign it a meaning.
     *   
     *   But what meaning should that be?  A blank line is the simplest
     *   possible command for a player to enter, so it would make sense to
     *   define it as some very commonly used command.  It's also fairly
     *   easy to enter a blank line accidentally (which is partly why the
     *   traditional reply is an error message), so the command should be
     *   benign - it shouldn't be a problem to enter it unintentionally.
     *   It can't be anything with parallel verbs, like NORTH, since then
     *   there'd be no good reason to pick NORTH instead of, say, SOUTH.
     *   Finally, it has to be intransitive, since it obviously won't
     *   involve an object name.  The obvious candidates that fit all of
     *   these criteria are LOOK and INVENTORY.  LOOK is probably the more
     *   useful and the more frequently used of the two, so it's the one we
     *   choose by default.
     *   
     *   If this property is set to true, we'll perform a LOOK AROUND
     *   command when the player enters a blank command line.  If nil,
     *   we'll show an error message.  
     */
    autoLook = true

    /*
     *   Default Actions: Should we treat a command line that consists entirely
     *   of a single noun phrase to be a "Default Action" on the named object?
     *   The precise meaning of the default action varies by object.  For most
     *   objects, it's EXAMINE.  For locations, it's GO TO.
     *
     *   We make the default value nil since setting it to true can result in
     *   some rather odd parser behaviour.
     */
    
    defaultActions = true

    /*
     *   Should we attempt automatic spelling correction?  If this is true, whenever a command
     *   fails, we'll check for a word that we don't recognize; if we find one, we'll try applying
     *   spelling correction to see if we can come up with a working command.
     *
     *   Our spelling correction algorithm is designed to be quite conservative.  In particular, we
     *   generally limit candidates for "correct" words to the vocabulary for objects that are
     *   actually in scope, which avoids revealing the existence of objects that haven't been seen
     *   yet; and we only apply a correction when it yields a command that parses and resolves
     *   correctly.  When we can't correct a command and get something resolvable, we don't even
     *   mention that we tried.  This avoids the bizarre, random guesses at "corrections" that often
     *   show up in other applications, and more importantly avoids giving away information that the
     *   player shouldn't know yet.
     *
     *   We set this to true by default, in an attempt to reduce the player's typing workload by
     *   automatically correcting simple typos when possible.  If for some reason the spelling
     *   corrector is problematic in a particular game, you can disable it by setting this property
     *   to nil.
     *
     *   As an experiment, change the default value to be nil when we're in a conversation and true
     *   otherwise, since over-zealous spelling corrections can be particularly troublesome in a
     *   conversational context.
     */
    autoSpell = (gPlayerChar.currentInterlocutor == nil)

    /*
     *   Maximum spelling correction time, in milliseconds.  The spelling
     *   correction process is iterative, and each iteration involves a new
     *   parsing attempt.  On a fast machine this doesn't tend to be
     *   noticeable, but it's conceivable that a pathological case could
     *   involve a large number of attempts that could be noticeably slow
     *   on an older machine.  To avoid stalling the game while we
     *   overanalyze the spelling possibilities, we set an upper bound to
     *   the actual elapsed time for spelling correction.  Each time we
     *   consider a new correction candidate, we'll check the elapsed time,
     *   and abort the process if it exceeds this limit.  Note that this
     *   limit doesn't limit the parsing time itself - we'll never
     *   interrupt that mid-stream.  
     */
    spellTimeLimit = 250

    /*
     *   When the parser doesn't recognize a word, should it say so?  If
     *   this property is set to true, when parsing fails, we'll scan the
     *   command line for a word that's not in the dictionary and show a
     *   message such as "I don't know the word <foo>."  If this property
     *   is nil, the parser will instead simply say that it doesn't
     *   recognize the syntax, or that the object in question isn't
     *   present, without saying specifically which word wasn't recognized,
     *   or indeed even admitting that there was such a thing.
     *   
     *   There are two schools of thought on this, both concerned with
     *   optimizing the user experience.
     *   
     *   The first school holds that the parser's job is to be as helpful
     *   as possible.  First and foremost, that means we should understand
     *   the user's input as often as possible.  But when we can't, it
     *   means that we should be do our best to explain what we didn't
     *   understand, to help the user formulate a working command next
     *   time.  In the case of a word the parser doesn't recognize, we can
     *   be pretty sure that the unknown word is the reason we can't
     *   understand the input.  The best way to help the user correct the
     *   problem is to let them know exactly which word we didn't know,
     *   rather than make them guess at what we didn't understand.  This is
     *   the way the classic Infocom games worked, and it's the traditional
     *   TADS default as well.
     *   
     *   The second school holds that the user's overriding interest is
     *   maintaining suspension of disbelief, and that the parser should do
     *   its best not to interfere with that.  A major aspect of this in IF
     *   the illusion that the game world is as boundless as the real
     *   world.  Missing dictionary words tend to break this illusion: if
     *   the user types EXAMINE ZEBRA, and the parser replies that it
     *   doesn't know the word "zebra", we've suddenly exposed a limit of
     *   the game world.  If we instead play coy and simply say that
     *   there's no zebra currently present, we allow the player to imagine
     *   that a zebra might yet turn up.  This is the way Inform games
     *   typically work.
     *   
     *   Each approach has its advantages and disadvantages, adherents and
     *   detractors, and it seems that neither one is objectively "right".
     *   It comes down to taste.  But there seems to be a clear preference
     *   among the majority of players in the modern era for the second
     *   approach.  The key factor is probably that typical IF commands are
     *   so short that it's easy enough to spot a typo without help from
     *   the parser, so the clarity benefits of "unknown word" messages
     *   seem considerably outweighed by the harm they do to the illusion
     *   of boundlessness.  So, our default is the second option, playing
     *   coy.  
     */
    showUnknownWords = nil

    /*
     *   Parse and execute a command line.  This is the main parsing
     *   routine.  We take the text of a command line, parse it against the
     *   grammar defined in the language module, resolve the noun phrases
     *   to game-world objects, and execute the action.  If the command
     *   line has more than one verb phrase, we repeat the process for each
     *   one.
     *   
     *   'str' is the text of the command line, as entered by the player.
     */
    parse(str)
    {
        /* Make sure our current SpecialVerb is set to nil before we start parsing a new command. */
        specialVerbMgr.currentSV = nil;
        
        /* tokenize the input */
        local toks;
        try
        {
            /* run the command tokenizer over the input string */
            toks = cmdTokenizer.tokenize(str);
            
            /* Dispose of any unwanted terminal punctuation */
            while(toks.length > 0 && getTokType(toks[toks.length]) == tokPunct)
                toks = toks.removeElementAt(toks.length);
            
        }
        catch (TokErrorNoMatch err)
        {
            /* 
             *   The tokenizer found a character (usually a punctuation
             *   mark) that doesn't fit any of the token rules.  
             */
            DMsg(token error, 'I don\'t understand the punctuation {1}',
                 err.curChar_);

           
            
            
            /* give up on the parse */
            return;
        }

        /* 
         *   Assume initially that the actor is the player character, but only
         *   if we don't have a question, since if the player is replying to a
         *   question the actor may already have been resolved.
         */
        if(question == nil)
            gActor = gPlayerChar;        
        
        /* no spelling corrections have been attempted yet */
        local history = new transient SpellingHistory(self);

        /* we're starting with the first command in the string */
        local firstCmd = true;

        /* parse the tokens */
        try
        {
            /* if there are no tokens, simply perform the empty command */
            if (toks.length() == 0)
            {
                /* 
                 *   this counts as a new command, so forget any previous
                 *   question or typo information 
                 */
                question = nil;
                lastTokens = nil;

                /* process an empty command */
                emptyCommand();

                /* we're done */
                return;
            }

            /* check for an OOPS command */
            local lst = oopsCommand.parseTokens(toks, cmdDict);
            if (lst.length() != 0)
            {
                /* this only works if we have an error to correct */
                local ui;
                if (lastTokens == nil
                    || (ui = spellingCorrector.findUnknownWord(lastTokens))
                        == nil)
                {
                    /* OOPS isn't available - throw an error */
                    throw new CantOopsError();
                }
                
                /* apply the correction, and proceed to parse the result */
                toks = OopsProduction.applyCorrection(lst[1], lastTokens, ui);
            }
            
             /* Update the vocabulary of any game objects with alternating/changing vocab. */
            updateVocab();
            
             /* Allow the specialVerb Manager to adjust our toks */            
            toks = specialVerbMgr.matchSV(toks);  
            
            /*   
             *   Parse each predicate in the command line, until we run out
             *   of tokens.  The beginning of a whole new command line is
             *   definitely the beginning of a sentence, so start parsing
             *   with firstCommandPhrase.  
             */
            for (local root = firstCommandPhrase ; toks.length() != 0 ; )
            {
                /* we don't have a parse list yet */
                local cmdLst = nil;

                /* 
                 *   we haven't found a resolution error in a non-command
                 *   parsing yet 
                 */
                local qErr = nil, defErr = nil;

                /* 
                 *   If we have an outstanding question, and it takes
                 *   priority over interpreting input as a new command, try
                 *   parsing the input against the question.  Only do this
                 *   on the first command on the line - a question answer
                 *   has to be the entire input, so if we've already parsed
                 *   earlier commands on the same line, this definitely
                 *   isn't an answer to a past question.  
                 */
                if (firstCmd && question != nil && question.priority)
                {
                    /* try parsing against the Question */
                    local l = question.parseAnswer(toks, cmdDict);

                    /* if it parsed and resolved, this is our command */
                    if (l != nil && l.cmd != nil)
                        cmdLst = l;

                    /* if it parsed but didn't resolved, note the error */
                    if (l != nil)
                        qErr = l.getResErr();
                }

                /* 
                 *   if the question didn't grab it, try parsing as a whole
                 *   new command against the ordinary command grammar
                 */
                if (cmdLst == nil || cmdLst.cmd == nil)
                {
                    cmdLst = new CommandList(
                        root, toks, cmdDict, { p: new Command(p) });
                }

                /* 
                 *   If we didn't find any resolvable commands, and this is
                 *   the first command, check to see if it's an answer to
                 *   an outstanding query.  We only check this if the
                 *   regular grammar parsing fails, because anything that
                 *   looks like a valid new command overrides a past query.
                 *   This is important because some of the short, common
                 *   commands sometimes can look like noun phrases, so we
                 *   explicitly give preference to interpreting these as
                 *   brand new commands.  
                 */
                if (cmdLst.cmd == nil
                    && firstCmd
                    && question != nil
                    && !question.priority)
                {
                    /* try parsing against the Question */
                    local l = question.parseAnswer(toks, cmdDict);

                    /* if it parsed and resolved, this is our command */
                    if (l != nil && l.cmd != nil)
                        cmdLst = l;

                    /* if it parsed but didn't resolved, note the error */
                    if (l != nil)
                        qErr = l.getResErr();
                }

                /*
                 *   If we don't have a command yet, and this is the first
                 *   command on the line, handle it as a conversational command
                 *   if conversation is in progress; otherwise if default
                 *   actions are enabled, check to see if the command looks like
                 *   a single noun phrase.  If so, handle it as the default
                 *   action on the noun.
                 */
                if (cmdLst.cmd == nil
                    && firstCmd)
                {
                    local l;                   
                    
                    
                    /* 
                     *   If a conversation is in progress parse the command line
                     *   as the single topic object phrase of a Say command,
                     *   provided that the first word on the command line
                     *   doesn't match a possible action.
                     */
                    
                    if(gPlayerChar.currentInterlocutor != nil
                       && cmdLst.length == 0 
                       && Q.canTalkTo(gPlayerChar,
                                      gPlayerChar.currentInterlocutor)
                       && str.find(',') == nil
                       && gPlayerChar.currentInterlocutor.allowImplicitSay())
                    {
                         l = new CommandList(
                            topicPhrase, toks, cmdDict,
                            { p: new Command(SayAction, p) });
                        
                        libGlobal.lastCommandForUndo = str;
                        savepoint();
                    }
                    /* 
                     *   If the player char is not in conversation with anyone,
                     *   or the first word of the command matches a possible
                     *   command verb, then try parsing the command line as a
                     *   single direct object phrase for the DefaultAction verb,
                     *   provided defaultActions are enabled (which they aren't
                     *   by default).
                     */
                    else if(defaultActions)                                                
                        l = new CommandList(
                            defaultCommandPhrase, toks, cmdDict,
                            { p: new Command(p) });                       
                    
                    
                       
                    /* accept a curable reply */
                    if (l != nil && l.acceptCurable() != nil)
                    {
                        cmdLst = l;
                        
                        /* note any resolution error */
                        defErr = l.getResErr();
                    }
                }
                
                /*
                 *   If we've applied a spelling correction, and the
                 *   command match didn't consume the entire input, make
                 *   sure what's left of the input has a valid parsing as
                 *   another command.  This ensures that we don't get a
                 *   false positive by excessively shortening a command,
                 *   which we can sometimes do by substituting a word like
                 *   "then" for another word.  
                 */
                if (cmdLst.length() != nil
                    && history.hasCorrections())
                {
                    /* get the best available parsing */
                    local c = cmdLst.getBestCmd();

                    /* if it doesn't use all the tokens, check what's left */
                    if (c != nil && c.tokenLen < toks.length())
                    {
                        /* try parsing the next command */
                        local l = commandPhrase.parseTokens(
                            c.nextTokens, cmdDict);

                        /* 
                         *   if that didn't work, invalidate the command by
                         *   substituting an empty command list 
                         */
                        if (l.length() == 0)
                            cmdLst = new CommandList();
                    }
                }
                
                /* 
                 *   If we didn't find a parsing at all, it's a generic "I
                 *   don't understand" error.  If we found a parsing, but
                 *   not a resolution, reject it if it's a spelling
                 *   correction.  We only want completely clean spelling
                 *   corrections, without any errors.
                 */
                if (cmdLst.length() == 0
                    || (history.hasCorrections()
                        && cmdLst.getResErr() != nil
                        && !cmdLst.getResErr().allowOnRespell))
                {
                    /* 
                     *   If we were able to parse the input using one of
                     *   the non-command interpretations, use the
                     *   resolution error from that parsing.  Otherwise, we
                     *   simply can't make any sense of this input, so use
                     *   the generic "I don't understand" error. 
                     */
                    local err = (qErr != nil ? qErr :
                                 defErr != nil ? defErr :
                                 new NotUnderstoodError());
                    
                    /* look for a spelling correction */
                    local newToks = history.checkSpelling(toks, err);
                    if (newToks != nil)
                    {
                        /* parse again with the new tokens */
                        toks = newToks;
                        continue;
                    }

                    /* 
                     *   There's no spelling correction available.  If we've 
                     *   settled on an auto-examine or question error, skip 
                     *   that and go back to "I don't understand" after 
                     *   all.  We don't want to assume Auto-Examine unless we
                     *   actually have something to examine, since we can 
                     *   parse noun phrase grammar out of practically any 
                     *   input.  
                     */
                    if (err is in (defErr, qErr))
                    {
                        /* return to the not-understood error */
                        err = new NotUnderstoodError();
                        
                        /* check spelling again with this error */
                        newToks = history.checkSpelling(toks, err);
                        if (newToks != nil)
                        {
                            /* parse again with the new tokens */
                            toks = newToks;
                            continue;
                        }
                    
                        
                        /* 
                         *   We didn't find any spelling corrections this time
                         *   through.  Since we're rolling back to the
                         *   not-understood error, discard any spelling
                         *   corrections we attempted with other
                         *   interpretations.
                         */
                        history.clear();                   
                    }
                
                    /* fail with the error */
                    throw err;
                }

                /* if we found a resolvable command, execute it */
                if (cmdLst.cmd != nil)
                {
                    /* get the winning Command */
                    local cmd = cmdLst.cmd;
                    
                    /* 
                     *   We next have to ensure that the player hasn't entered
                     *   multiple nouns in a slot that only allows a single noun
                     *   in the grammar. If the player has entered two objects
                     *   like "the bat and the ball" in such a case, the
                     *   badMulti flag will be set on the command object, so we
                     *   first test for that and abort the command with a
                     *   suitable error message if badMulti is not nil (by
                     *   throwing a BadMultiError
                     *
                     *   Unfortunately the badMulti flag doesn't get set if the
                     *   player enters a multiple object as a plural (e.g.
                     *   "bats"), so we need to trap this case too. We do that
                     *   by checking whether there's multiple objects in the
                     *   direct, indirect and accessory object slots at the same
                     *   time as the grammar tag matching the slot in question
                     *   is 'normal', which it is only for a single noun match.
                     */
                     
                    if(cmd && cmd.verbProd != nil &&                        
                        (cmd.badMulti != nil 
                       || (cmd.verbProd.dobjMatch != nil &&
                           cmd.verbProd.dobjMatch.grammarTag == 'normal'
                           && cmd.dobjs.length > 1)
                       ||
                       (cmd.verbProd.iobjMatch != nil &&
                           cmd.verbProd.iobjMatch.grammarTag == 'normal'
                           && cmd.iobjs.length > 1)                          
                        ||
                       (cmd.verbProd.accMatch != nil &&
                           cmd.verbProd.accMatch.grammarTag == 'normal'
                           && cmd.accs.length > 1)
                           ))
                        cmd.cmdErr = new BadMultiError(cmd.np);
                    
                    /* if this command has a pending error, throw it */
                    if (cmd.cmdErr != nil)
                        throw cmd.cmdErr;

                    /* 
                     *   Forget any past question and typo information.
                     *   The new command is either an answer to this
                     *   question, or it's simply ignoring the question; in
                     *   either case, the question is no longer in play for
                     *   future input.  
                     */
                    question = nil;
                    lastTokens = nil;
                    
                    /* note any spelling changes */
                    history.noteSpelling(toks);
                    
                    /* execute the command */
                    cmd.exec();
                    
                    /* start over with a new spelling correction history */
                    history = new transient SpellingHistory(self);
                    
                    /* 
                     *   Set the root grammar production for the next
                     *   predicate.  If the previous command ended the
                     *   sentence, start a new sentence; otherwise, use the
                     *   additional clause syntax. 
                     */
                    root = cmd.endOfSentence
                        ? firstCommandPhrase : commandPhrase;
                    
                    /* we're no longer on the first command in the string */
                    firstCmd = nil;
                    
                    /* go back and parse the remainder of the command line */
                    toks = cmd.nextTokens;
                    continue;
                }

                /*
                 *   We weren't able to resolve any of the parse trees.  If
                 *   one of the errors is "curable", meaning that the
                 *   player can fix it by answering a question, pick the
                 *   first of those, in predicate priority order.
                 *   Otherwise, just pick the first command overall in
                 *   predicate priority order.  In either case, since we
                 *   didn't find any working alternatives, it's time to
                 *   actually show the error and fail the command.  
                 */
                local c = cmdLst.acceptAny();

                /* 
                 *   If the error isn't curable, check for spelling errors,
                 *   time permitting.  Don't bother doing this with a
                 *   curable error, since that will have its own way of
                 *   solving the problem that reflects a better
                 *   understanding of the input than considering it a
                 *   simple typo.  
                 */
                if (!c.cmdErr.curable)
                {
                    /*
                     *   For spelling correction purposes, if this is an
                     *   unmatched noun error, but the command has a misc
                     *   word list and an empty noun phrase, treat this as
                     *   a "not understood" error.  The combination of noun
                     *   phrase errors suggests that we took a word that
                     *   was meant to be part of the verb, and incorrectly
                     *   parsed it as part of a noun phrase, leaving the
                     *   verb structure and other noun phrase incomplete.
                     *   This is really a verb syntax error, not a noun
                     *   phrase error.  
                     */
                    local spellErr = c.cmdErr;
                    if (c.cmdErr.ofKind(UnmatchedNounError)
                        && c.miscWordLists.length() > 0
                        && c.missingNouns > 0)
                        spellErr = new NotUnderstoodError();

                    /* try spelling correction */
                    local newToks = history.checkSpelling(toks, spellErr);

                    /* if that worked, try the corrected command */
                    if (newToks != nil)
                    {
                        /* parse again with the new tokens */
                        toks = newToks;
                        continue;
                    }
                }

                /* re-throw the error that caused the resolution to fail */
                throw c.cmdErr;
            }
        }
        catch (ParseError err)
        {
            /* 
             *   roll back any spelling changes to the last one that
             *   improved matters 
             */
            local h = history.rollback(toks, err);
            toks = h.oldToks;
            err = h.parseError;

            /* 
             *   if this is a curable error, it poses a question, which the
             *   player can answer on the next input 
             */
            if (err.curable)
                question = new ParseErrorQuestion(err);
            
            /* 
             *   If the current error isn't curable, and unknown word
             *   disclosure is enabled, and there's a word in the command
             *   that's not in the dictionary, replace the parsing error
             *   with an unknown word error.  
             */
            local ui;
            if (!err.curable
                && showUnknownWords
                && (ui = spellingCorrector.findUnknownWord(toks)) != nil)
            {
                /* find the misspelled word in the original tokens */
                err = new UnknownWordError(getTokOrig(toks[ui]));
            }
            
            /* 
             *   If the new error isn't an error in an OOPS command, save
             *   the token list for an OOPS command next time out. 
             */
            if (!err.ofKind(OopsError))
                lastTokens = toks;
            
            /* log any spelling changes we kept */
            history.noteSpelling(toks);

            /* display the error we finally decided upon */
            err.display();
        }
        catch (CommandSignal sig)
        {
            /* 
             *   On any command signal we haven't caught so far, simply
             *   stop processing this command line.  
             */
        }
    }

    /*
     *   The token list from the last command, if an error occurred.  This
     *   is the token list that we'll retry if the player enters an OOPS
     *   command.  
     */
    lastTokens = nil

    /*
     *   The outstanding Question object.  When we ask an interactive
     *   question (such as a disambiguation query, a missing noun phrase
     *   query, or a custom question from the game), this is set to the
     *   Question waiting to be answered.  We parse the next command
     *   against the Question to see if it's a reply, and if so we execute
     *   the reply.  
     */
    question = nil

    /*
     *   Execute an empty command line.  The parse() routine calls this
     *   when given a blank command line (i.e., the user simply pressed the
     *   Return key).  By default, we execute a Look Around command if
     *   autoLook is enabled, otherwise we show the "I beg your pardon"
     *   error.
     */
    emptyCommand()
    {
        if (autoLook)
            new Command(Look).exec();
        else
        {
            /* 
             *   The player entered an empty command line (i.e., pressed
             *   Return at the command prompt, without typing anything else
             *   first).  Note that this error can only occur if Auto-Look
             *   is disabled, since otherwise an empty command implicitly
             *   means LOOK AROUND.  
             */
            DMsg(empty command line, 'I beg your pardon?');

        }
    }
    
    /* 
     *   The action to be tried if the parser can't find a verb in the command
     *   line and tries to parse the command line as the single object of a
     *   DefaultAction command instead.
     */
    
    DefaultAction = ExamineOrGoTo
    
    /*  Return an rmcXXXX enum code depending on the state of Parser.question */
    rmcType()
    {
        if(Parser.question != nil && Parser.question.err != nil)
        {
            /* 
             *   If the Parser error is an EmptyNounError then we're asking for
             *   an object.
             */
            if(Parser.question.err.ofKind(EmptyNounError))
                return rmcAskObject;
            
            /* 
             *   If the Parser error is an AmbiguousError then we're requesting
             *   disambiguation.
             */
            if(Parser.question.err.ofKind(AmbiguousError))
                return rmcDisambig;            
        }
        
        /* 
         *   If there's no special situation, assume we're reading a standard
         *   command.
         */
        return rmcCommand;
    }
    
    /* 
     *   Update the vocabulary of items in the game for which it might vary; specifically those with
     *   an altVocab defined.
     */
    updateVocab()
    {
        /* Retrieve the list of items that have alternating vocabulary. */
        local lst = libGlobal.altVocabLst;
        
        /* 
         *   If the list is longer than a certain amount, it may become more efficient to iterate
         *   over only those items in the list that are already in scope.
         */
        if(lst.length > 30)
        {
            /* Get a list of items in scope. */
            local scope = Q.scopeList(gPlayerChar).toList();
            
            /* Reduce our list of variable vocab items to include only those in scope. */
            lst = lst.intersect(scope);
            
        }
        
        /* Call updateVocab() on every item in our list. */
        lst.forEach({ x: x.updateVocab() });
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Base class for command execution signals.  These allow execution
 *   handlers to terminate execution for the current command line or
 *   portion of the command line.  
 */
class CommandSignal: Exception
;


/* 
 *   Terminate the entire command line.
 */
class ExitCommandLineSignal: CommandSignal
;
    

/* ------------------------------------------------------------------------ */
/*
 *   A CommandList is a set of potential parsings for a given input with a
 *   given grammar.  
 */
class CommandList: object
    /*
     *   new CommandList(prod, toks, dict, wrapper) - construct a new
     *   CommandList object by parsing an input token list.  'prod' is the
     *   GrammarProd to parse against; 'toks' is the token list; 'dict' is
     *   the main parser dictionary; 'wrapper' is a callback function that
     *   maps a parse tree to a Command object.
     *   
     *   new CommandList(Command) - construct a CommandList containing a
     *   single pre-resolved Command object.  
     */
    construct([args])
    {
        /* check which argument list we have */        
        if (args.matchProto([GrammarProd, Collection, Dictionary, TypeFuncPtr]))
        {
            /* retrieve the arguments */
            local prod = args[1], toks = args[2], dict = args[3],
                wrapper = args[4];

            /* parse the token list, and map the list to Command objects  */
            cmdLst = prod.parseTokens(toks, dict).mapAll(wrapper);
            
            /* sort in priority order */
            cmdLst = Command.sortList(cmdLst);
            
            /* 
             *   Go through the list, looking for an item with noun phrases we
             *   can resolve.  Take the first item that we can properly
             *   resolve.  
             */
            foreach (local c in cmdLst)
            {
                try
                {                    
                    /* resolve this phrase */
                    c.resolveNouns();
                    
                    /* success - take this as the result; look no further */
                    /* 
                     *   But only if we haven't yet got a command or the new
                     *   command didn't have to create a new Topic object.
                     */
                    if(cmd == nil || !c.madeTopic)
                        cmd = c;
                    
                    /* 
                     *   But if this command had to create a new Topic object,
                     *   let's go on to see if we can find a better match.
                     */
                    if(!cmd.madeTopic)
                       break;
                }
                catch(InsufficientNounsError err)
                {
                    c.cmdErr = err;
                    throw err;
                }
                catch(NoneInOwnerError err)
                {
                    c.cmdErr = err;
                    throw err;
                }
                
                catch (ParseError err)
                {
                    /* save the error with the command */
                    c.cmdErr = err;
                    
                    /* 
                     *   That didn't resolve correctly.  But don't actually
                     *   show the error message yet; instead, continue through
                     *   the list to see if we can find another alternative
                     *   that we can resolve.
                     *   
                     *   If it's the first curable error we've seen, note it. 
                     */
                    if (err.curable && curable == nil)
                        curable = c;
                }
            }
        }
        else if (args.matchProto([Command]))
        {
            /* get the command - it's the single list entry */
            cmd = args[1];
            cmdLst = [cmd];
        }
        else if (args.matchProto([]))
        {
            /* empty command list */
            cmd = nil;
            cmdLst = [];
        }
        else
            throw new ArgumentMismatchError();
    }

    /* number of parsings in the list */
    length() { return cmdLst.length(); }

    /*
     *   Accept a curable resolution as the actual resolution.  If we don't
     *   have an error-free resolution, we'll set 'cmd' to the curable
     *   resolution.  Returns true if we have any resolution, nil if not.  
     */
    acceptCurable()
    {
        /* if we don't have an error-free resolution, accept a curable one */
        if (cmd == nil)
            cmd = curable;

        /* indicate whether we have a resolution now */
        return cmd;
    }

    /*
     *   Accept ANY command, with or without a resolution error, curable or
     *   not.  We'll take the error-free resolution if we have one,
     *   otherwise the resolution with a curable error, otherwise just the
     *   first parsing in priority order. 
     */
    acceptAny()
    {
        /* accept the best command, and return it */
        return cmd = getBestCmd();
    }

    /*
     *   Get the most promising command from the available parsings.  This
     *   returns the first successfully resolved command in priority order,
     *   if any; otherwise the first command with a curable error, if any;
     *   otherwise the first command in priority order.  
     */
    getBestCmd()
    {
        /* if we have a parsed and resolved command, return it */
        if (cmd != nil)
            return cmd;

        /* if we have a curable command, return it */
        if (curable != nil)
            return curable;

        /* if we have any parsing at all, return the first one */
        if (cmdLst.length() > 0)
            return cmdLst[1];

        /* we don't have any parsing */
        return nil;
    }

    /*
     *   Get the resolution error, if any.  If we parsed but didn't
     *   resolve, this returns the error from the first parsing in priority
     *   order.  
     */
    getResErr()
    {
        /* if we resolved a command, return any error from it */
        if (cmd != nil)
            return cmd.cmdErr;

        /* if we have a curable error, return it */
        if (curable != nil)
            return curable.cmdErr;

        /* otherwise, return the first item with a cmdErr */
        local c = cmdLst.valWhich({ c: c.cmdErr != nil });
        return (c != nil ? c.cmdErr : nil);
    }

    /* our list of Command objects */
    cmdLst = []

    /* 
     *   Our resolved Command.  This is the first parsing in our list that
     *   (in priority order) we were able to resolve with no errors.  
     */
    cmd = nil

    /* 
     *   Our semi-resolved Command.  When we can't find a command that
     *   resolves without errors, we'll set this to the first one (in
     *   priority order) that resolves with a curable error.  
     */
    curable = nil
;


/* ------------------------------------------------------------------------ */
/*
 *   A Question is an interactive question we ask the player via the
 *   regular command line.  The player then has the option to answer the
 *   question, or to ignore the question and enter a new command.
 *   
 *   The parser uses Question objects internally to handle certain errors
 *   that the player can fix by entering additional information, such as
 *   disambiguation queries and missing noun phrase queries.  Games can use
 *   Question objects for other, custom interactions.
 *   
 *   The basic Question object is incomplete - you have to subclass it to
 *   get a functional question handler.  In particular, you must provide a
 *   parseAnswer() routine that parses the reply and creates a Command to
 *   carry out the action of answering the question.  
 */
class Question: object
    /*
     *   Priority: Should the answer be parsed before checking for a
     *   regular command entry?  If this is true, the parser will try
     *   parsing the player's input as an answer to this question BEFORE it
     *   tries parsing the input as a regular command.  If the answer
     *   parses, we'll assume it really is an answer to the question, and
     *   we won't even try parsing it as a new command.
     *   
     *   For disambiguation and missing noun queries, the parser only
     *   parses question replies AFTER parsing regular commands.  Replies
     *   to these questions are frequently very short, abbreviated noun
     *   phrases - maybe just a single adjective or noun.  It's fairly
     *   common for there be at least a few nouns that are the same as
     *   verbs in the game, so the input after a disambiguation or missing
     *   noun reply can often be interpreted equally well as a new verb or
     *   as a reply to the question.  There's probably no theoretical basis
     *   for choosing one over the other when this happens, but in practice
     *   it seems that it's usually better to treat the reply as a new
     *   command.  So, by default we set this property to nil, to give
     *   priority to a new command.
     *   
     *   Custom questions posed by the game might want to give higher
     *   priority to the answer interpretation, though.  Yes/No questions
     *   in particular will probably want to do this, because otherwise the
     *   parser would take the answer as a conversational overture to any
     *   nearby NPC.  
     */
    priority = nil
    
    /*
     *   Parse the answer.  'toks' is the token list of the user's input,
     *   and 'dict' is the main parser Dictionary object.
     *   
     *   If the input does look like a valid answer to the question,
     *   returns a CommandList with the parsed reply.  If not, returns nil,
     *   in which case the parser will continue trying to parse the input
     *   as a whole new command.
     *   
     *   By default, we simply return nil.  Subclasses/instances must
     *   override this to provide the custom answer parsing.  
     */
    parseAnswer(toks, dict) { return nil; }

    /* the answer template */
    answerTemplate = nil
;

/*
 *   A GramQuestion is a question handler that parses an answer using a
 *   grammar rule.
 */
class GramQuestion: Question
    /*
     *   Create a simple question.  'prod' is the root GrammarProd to use
     *   for parsing the reply.  'func' is a callback function that carries
     *   out the action of the answering the question.  'func' is invoked
     *   with a single argument giving the Command object representing the
     *   answer; you can get the match tree from the Command if you need
     *   the parsed form of the answer input.  
     */
    construct(prod, func)
    {
        answerProd = prod;
        answerFunc = func;
    }

    /*
     *   Parse the answer.  We'll match the token list against the grammar
     *   rule.  If we find a match, we'll call makeCommand() to create the
     *   command to carry out the action of answering the question.  
     */
    parseAnswer(toks, dict)
    {
        /* try parsing against our grammar rule */
        return new CommandList(
            answerProd, toks, dict, { p: makeCommand(p) });
    }

    /*
     *   Create a Command object for a successful grammar match.  'prod' is
     *   the root match object of the grammar match.  This returns a
     *   suitable Command that carries out the action of answering the
     *   question.  
     */
    makeCommand(prod) { return new FuncCommand(prod, answerFunc); }

    /* the GrammarProd rule that we use to parse the answer */
    answerProd = nil

    /* the callback function that carries out the reply action */
    answerFunc = nil
;

/*
 *   A YesNoQuestion is a simple subclass of Question for asking
 *   interactive questions with Yes or No answers. 
 */
class YesNoQuestion: GramQuestion
    /*
     *   Create - 'func' is the callback function to invoke on answering
     *   the question.  This is invoked with one argument, true if the
     *   answer was Yes, nil if the answer was No.  
     */
    construct(func)
    {
        /* 
         *   Parse our answer against the simple yes-or-no grammar.  To
         *   execute the answer, call the user's callback, providing the
         *   yes/no result from the Command.  The yes-or-no production sets
         *   the yesOrNoAnswer property in the Command during the build
         *   process.  
         */
        inherited(yesOrNoPhrase, { cmd: func(cmd.yesOrNoAnswer) });
    }

    /* 
     *   parse Yes/No replies ahead of new commands, since we'd otherwise
     *   never get an answer - the parser would always match the reply to a
     *   conversational verb instead 
     */
    priority = true
;

/*
 *   A RexQuestion is a simple subclass of Question for parsing answers
 *   with regular expressions. 
 */
class RexQuestion: Question
    /*
     *   Create - 'pat' is the regular expression pattern, as either a
     *   string or a RexPattern object.  We'll parse an answer simply by
     *   matching it against the regular expression; if we match, we'll
     *   take it as an answer.  'func' is a callback function that we'll
     *   call to carry out the action of answering the question.  We'll
     *   invoke this with one argument giving the literal text of the
     *   input.  
     */
    construct(pat, func)
    {
        answerPat = pat;
        answerFunc = func;
    }

    parseAnswer(toks, dict)
    {
        /* reconstruct the string input */
        local str = cmdTokenizer.buildOrigText(toks);

        /* check for a match to our pattern */
        if (rexMatch(answerPat, str,) != nil)
        {
            /* set up a single-entry command list for the answer */
            return new CommandList(new FuncCommand(
                nil, { cmd: answerFunc(str) }));
        }
        else
        {
            /* no match */
            return nil;
        }
    }

    /* the regular expression pattern to match */
    answerPat = nil

    /* the callback to invoke on answering */
    answerFunc = nil
;

/*
 *   An ErrorQuestion is a subclass of Question for curable parsing errors.
 */
class ParseErrorQuestion: Question
    construct(err)
    {
        /* remember the ParseError object */
        self.err = err;
    }

    parseAnswer(toks, dict)
    {
        /* ask the error to parse the response */
        return err.tryCuring(toks, dict);
    }

    /* the curable ParseError that posed the question */
    err = nil
    
    /* 
     *   Should we prioritize interpreting player input to a parser query as a response to that
     *   query over interpreting it as a nea command (where the latter is possible)? By default we
     *   do (since this seems more likely to reflect player intention in this case), although game
     *   code can override if desired.
     */
    priority = true
;


/* ------------------------------------------------------------------------ */
/*
 *   A Distinguisher is an abstract parser object that represents one way
 *   that we can tell two objects apart, both in the name we display and in
 *   command input.
 *   
 *   Note that this class is designed primarily for the parser's internal
 *   use, to facilitate some bookkeeping that we have to do during
 *   disambiguation.  It's not really designed as an extensibility
 *   mechanism, because it's not usually enough to just add a new instance:
 *   you usually also have to add grammar for whatever new phrasing the new
 *   distinguisher represents, plus object resolution code to handle the
 *   new form of qualification.  
 */
class Distinguisher: object
    /* 
     *   Sorting order.  The parser sorts the master list of distinguishers
     *   in ascending order of this value to determine the order of
     *   application.  
     */
    sortOrder = 0

    /*
     *   Compare two objects for equivalence under this distinguisher.
     *   Returns true if the objects are equivalent, nil others. 
     */
    equal(a, b) { return nil; }

    /* 
     *   Is this distinguisher applicable to the given object?  Some
     *   distinguishers can only apply to certain objects.  For example, a
     *   Lit/Unlit distinguisher can only be applied to objects with that
     *   state variable, because there's no vocabulary that we can add to
     *   an object without the variable.  (We can talk about "lit" and
     *   "unlit" matches, but we don't have any standard vocabulary to talk
     *   about "unlightable" matches.)  
     */
    appliesTo(obj) { return true; }

    /*
     *   Apply the distinguisher.  Returns a DistResult object with the
     *   results.  
     */
    apply(lst)
    {
        /* create the results object */
        local r = new DistResult(self);

        /* set up the list of applicable objects */
        r.appliesTo = lst.subset({ obj: appliesTo(obj) });

        /* make a to-do vector, starting with all applicable items */
        local toDo = new Vector(10, r.appliesTo);

        /* process each item in the to-do list */
        while (toDo.length() > 0)
        {
            /* pop the last item */
            local obj = toDo.pop();

            /* start a partition list for this item and its equivalents */
            local sv = new Vector(10);

            /* start the partition with the current object */
            sv.append(obj);

            /* add the new partition to the partition list */
            r.partitioned.append(sv);

            /* scan the to-do list for items equivalent to obj */
            for (local i = toDo.length() ; i > 0 ; --i)
            {
                /* get this item and check for equivalence to obj */
                local obj2 = toDo[i];
                if (equal(obj, obj2))
                {
                    /* it's equivalent to obj, so add it to obj's partition */
                    sv.append(obj2);

                    /* it's been processed; remove it from the to-do list */
                    toDo.removeElementAt(i);
                }
            }
        }

        /* return the result object */
        return r;
    }

    /* class property: master list of all distinguishers */
    all = []

    /* during initialization, build the master list */
    classInit()
    {
        /* add each instance to the master list */
        forEachInstance(Distinguisher, {d: all += d});

        /* arrange by sortOrder */
        all = all.sort(SortAsc, {a, b: a.sortOrder - b.sortOrder});
    }

    /* make sure the StateDistinguisher instances are constructed first */
    classInitFirst = [StateDistinguisher]

    /*
     *   Class method: generate distinguishing names for a list of objects.
     *   This generates names that distinguish the objects from one
     *   another, by applying as many Distinguishers as needed to come up
     *   with unique names.
     *   
     *   If 'article' is true, we'll use a definite or indefinite article,
     *   as appropriate: definite if the name we settle upon uniquely
     *   identifies the object within the list, indefinite if not.  If
     *   'article' is nil, the names don't have articles at all.
     *   
     *   Returns a list of [name, [objects]] sublists.  The name is a
     *   string giving the distinguished name; the [objects] sub-sublist is
     *   a list of the objects known under that name.  
     */
    getNames(objs, article)
    {
        /* start with an empty result list */
        local names = new Vector(objs.length());

        /* start with an empty list of distinguisher results */
        local dres = new Vector(Distinguisher.all.length());

        /* apply each distinguisher to our objects, saving the results */
        foreach (local d in Distinguisher.all)
            dres.append(d.apply(objs));

        /* find the distinguishing characteristics for each object */
        while (objs.length() != 0)
        {
            /* treat the to-do list as a stack: pop an object */
            local obj = objs.pop();

            /* get the subset of dist results that apply to this object */
            local ores = dres.subset({ r: r.appliesTo.indexOf(obj) != nil });

            /*
             *   What we're after is the minimum set of distinguishers that
             *   can tell this object apart from as many of the others as
             *   possible.
             *   
             *   In the best case, there's a single distinguisher that can
             *   pick out this object uniquely.  For example, if this is
             *   the lit match, and all the other matches are unlit, the
             *   Lit/Unlit State distinguisher is all we need: 'lit'
             *   uniquely identifies this object among our set.  If that's
             *   the case, obj will appear in the Lit/Unlit list in its own
             *   partition, with no other objects.
             *   
             *   It might be that there's no one distinguisher that can
             *   tell obj apart from all of the others.  But perhaps a
             *   combination of two distinguishers can: perhaps obj is one
             *   of two lit matches, and one of two matches belonging to
             *   Bob, but it's the only lit match belonging to Bob.  We'd
             *   thus find obj in two partitions of two objects each;
             *   intersecting the two will yield a list with just one
             *   element, so we'd get down to a unique object again.
             *   
             *   We might not ever get down to a single-object partition,
             *   even after combining several distinguishers, since we
             *   might be truly indistinguishable from one or more other
             *   objects.  In such a case, we'll have to group the objects
             *   and ask if the player means "*a* lit match of Bob's", say.
             *   
             *   In any case, our fastest route to the smallest set is to
             *   start with the smallest set, and intersect against larger
             *   sets until we get down to one object or run out of other
             *   sets to intersect.  So start by sorting the results by the
             *   size of the partition in which obj appears, smallest to
             *   largest.  
             */
            ores.sort(SortAsc, new function(a, b)
            {
                /* sort by partition size */
                local asiz = a.partSize(obj);
                local bsiz = b.partSize(obj);

                /* if they differ, sort according to the relative sizes */
                if (asiz != bsiz)
                    return asiz - bsiz;

                /* same size partitions; sort by Distinguisher order */
                return a.distinguisher.sortOrder - b.distinguisher.sortOrder;
            });

            /* we haven't used any distinguishers yet */
            local used = new Vector(10);

            /* 
             *   Start with the first distinguisher in the list, and its
             *   initial set.  Note that we're guaranteed to have at least
             *   one result in the list, since the basic name distinguisher
             *   applies to everything.  
             */
            used.append(ores[1].distinguisher);
            local rem = ores[1].partition(obj).toList();

            /* 
             *   now apply additional distinguishers until we get the set
             *   down to one object, or run out of distinguishers 
             */
            for (local i = 2, local olen = ores.length() ;
                 rem.length() > 1 && i <= olen ; ++i)
            {
                /* 
                 *   figure the intersection of this distinguisher
                 *   partition with the remaining set 
                 */
                local rcur = ores[i];
                local isect = rem.intersect(rcur.partition(obj).toList());

                /* 
                 *   If that reduced the set size, keep the result.  Ignore
                 *   distinguishers that don't reduce the set size, because
                 *   they don't help - they'd just add words to the
                 *   generated name without helping to tell things apart.  
                 */
                if (isect.length() < rem.length())
                {
                    /* keep the reduced list */
                    rem = isect;

                    /* note that we used this distinguisher */
                    used.append(rcur.distinguisher);
                }
            }

            /* 
             *   We've thrown out as many distinguishable items as
             *   possible, and in the process we've built a list of the
             *   distinguishing characteristics that we needed in order to
             *   tell this object apart from the others.  Generate the
             *   name, based on that list of characteristics.  If we
             *   whittled the set size down to a single object, use a
             *   definite article; otherwise, we have a group of
             *   indistinguishable objects, so use an indefinite article to
             *   refer to an arbitrary one of these.  
             */
            local rlen = rem.length();
            local nm = obj.distinguishedName(
                article ? (rlen == 1 ? Definite : Indefinite) : Unqualified,
                used);

            /* 
             *   This name covers the whole group, so remove all of the of
             *   additional objects from the to-do list.  (We've already
             *   removed the first one, so don't waste time looking for it
             *   again.)  
             */
            for (local i = 2 ; i <= rlen ; ++i)
                objs.removeElement(rem[i]);

            /* append this name result */
            names.append([nm, rem]);
        }

        /* return the name list */
        return names;
    }
;

/*
 *   Result object from applying a Distinguisher to a set of objects. 
 */
class DistResult: object
    construct(dist)
    {
        /* remember the distinguisher */
        distinguisher = dist;

        /* set up a vector for the partition list */
        partitioned = new Vector(10);
    }

    /* get the partition in which 'obj' appears */
    partition(obj)
    {
        return partitioned.valWhich({ p: p.indexOf(obj) != nil });
    }

    /* get the size of the partition in which 'obj' appears */
    partSize(obj)
    {
        return partition(obj).length();
    }

    /* the objects that the distinguisher applies to */
    appliesTo = []

    /* 
     *   The partitioned list of objects.  This is a list of lists.  Each
     *   sublist is a group of objects we can't distinguish from one
     *   another.  Each object in appliesTo appears once in a sublist, and
     *   each object in a sublist appears in appliesTo.  
     */
    partitioned = []

    /* the Distinguisher that these results come from */
    distinguisher = nil
;

/*
 *   The basic name distinguisher distinguishes objects by their base names.
 *   This is the first distinguisher we apply, since the name is always the
 *   easiest way to tell objects apart in parsing. However since one name could
 *   be entirely contained within another (e.g. 'ball' and 'red ball') we
 *   consider the names as equal for this purpose if one of them is part of the
 *   other.
 */
nameDistinguisher: Distinguisher
    sortOrder = 100
    equal(a, b) { return a.name.find(b.name) || b.name.find(a.name); }
;

/*
 *   The disambiguation name distinguisher.  This distinguishes objects by
 *   their disambiguation names.  We apply this immediately after the basic
 *   name distinguisher, since the disambiguation name is a custom name
 *   provided by the author for the express purpose of distinguishing the
 *   object in parsing.  
 */
disambigNameDistinguisher: Distinguisher
    sortOrder = 200
    equal(a, b) { return a.disambigName == b.disambigName; }
;

/*
 *   The class for state distinguishers.  A state distinguisher tells
 *   objects apart based on their having distinct current values for a
 *   given state.  During preinit, we create a separate instance of this
 *   for each State object in the game.  
 */
class StateDistinguisher: Distinguisher
    sortOrder = 300

    /* we distinguish based on each object's current value for the state */
    equal(a, b) { return a.(state.stateProp) == b.(state.stateProp); }

    /* we only apply to objects that have our state variable */
    appliesTo(obj) { return state.appliesTo(obj); }
    
    /* build from a State */
    construct(st)
    {
        /* remember the state object */
        state = st;
    }

    /* during preinit, build an instance for each State */
    classInit()
    {
        forEachInstance(State, {st: stateList += new StateDistinguisher(st)});
    }

    /* the State this distinguisher tests */
    state = nil

    /* class property: the list of state distinguisher instances */
    stateList = []
;


/*
 *   Owner distinguisher.  This tells objects apart based on their nominal
 *   owners (and only applies to objects with nominal owners at all).  
 */
ownerDistinguisher: Distinguisher
    sortOrder = 400
    appliesTo(obj) { return obj.nominalOwner() != nil; }
    equal(a, b) { return a.nominalOwner() == b.nominalOwner(); }
;

/*
 *   Location distinguisher.  This tells objects apart based on their
 *   immediate containers. 
 */
locationDistinguisher: Distinguisher
    sortOrder = 500
    equal(a, b) { return a.location == b.location; }
;

/*
 *   Contents distinguisher.  This tells objects apart based on their
 *   nominal contents (and only applies to objects with nominal contents at
 *   all).  Note that we're interested in the *names* of the contents, so
 *   even if two objects have different contents objects, they're still
 *   considered equal if the contents' names match.  (E.g., two "buckets of
 *   water" are indistinguishable, even if the contents are two distinct
 *   "water" objects.  But "bucket of water" and "bucket of fish" are
 *   distinguishable.)  
 */
contentsDistinguisher: Distinguisher
    sortOrder = 600
    appliesTo(obj) { return obj.distinguishByContents != nil; }
    equal(a, b)
    {
        local ac = a.nominalContents(), bc = b.nominalContents();
        return (ac != nil ? ac.name : nil) == (bc != nil ? bc.name : nil);
    }
;
    

/* ------------------------------------------------------------------------ */
/*
 *   A NounPhrase object represents a noun phrase within a command line.
 *   This class handles the mapping from the text of the noun phrase in the
 *   input to the game-world objects that the noun phrase refers to.
 *   
 *   This object encompasses a core noun phrase plus all of its qualifiers.
 *   Qualifiers can themselves be noun phrases: possessives, locationals,
 *   and contents phrases contain subsidiary noun phrases, so we represent
 *   these qualifiers with subsidiary NounPhrase objects.  
 */
class NounPhrase: object
    /* create */
    construct(parent, prod)
    {
        /* 
         *   remember the parent NounPhrase and the grammar production
         *   match object that's the source of the noun phrase
         */
        self.parent = parent;
        self.prod = self.coreProd = prod;
    }

    /* clone - create a modifiable copy based on this original noun phrase */
    clone()
    {
        /* create a new object with my same property values */
        local cl = createClone();

        /* make safe copies of any vectors */
        foreach (local p in cl.getPropList())
        {
            local v;
            if (cl.propType(p) == TypeObject && (v = cl.(p)).ofKind(Vector))
                cl.(p) = new Vector(v.length(), v);
        }

        /* return the clone */
        return cl;
    }

    /* 
     *   By default, use the original input text of my "core" production as
     *   the name we show for this noun phrase in error messages.  The core
     *   production is the noun phrase minus any qualifiers (articles,
     *   possessives, locational phrases, etc).
     *   
     *   As we successfully resolve qualifiers, we'll expand this to
     *   include the qualifying phrases.  Any error we find after resolving
     *   a qualifier will necessary apply to the qualified form, so we want
     *   to include the qualifier in any error message.
     *   
     *   For example, if the original phrase is BUCKET OF FISH ON TABLE,
     *   we'll start out with the core phrase of BUCKET.  We'll next
     *   resolve the contents qualifier, OF FISH.  Assuming that we find a
     *   BUCKET OF FISH, that becomes the new error name.  If we then fail
     *   to find such an object ON TABLE, we'll be able to report that
     *   there's no BUCKET OF FISH on the table.  This is better than
     *   reporting that we don't see any BUCKET on the table, because there
     *   could in fact be a different bucket on the table.  
     */
    errName = (errNameProd.getText())

    /* the source of the error name is initially the core production */
    errNameProd = (coreProd)

    /*
     *   Expand the error-message name to include the given qualifier.
     *   We'll find the common parent of the core production and the given
     *   qualifier's production, and use its text as the new error name. 
     */
    expandErrName(np)
    {
        /* 
         *   look for the common parent of 'np' and the current error name
         *   source 
         */
        for (local prod = np.prod ; prod != nil ; prod = prod.parent)
        {
            /* 
             *   if this is also a parent of errNameProd, we've found the
             *   common parent 
             */
            if (prod == errNameProd || errNameProd.isChildOf(prod))
            {
                /* establish this as the new error name source */
                errNameProd = prod;

                /* no need to keep looking */
                break;
            }
        }
    }

    /*
     *   Does this NounPhrase contain the given NounPhrase?  Returns true
     *   if NounPhrase is self, or one of our qualifier noun phrases
     *   contains it. 
     */
    contains(np)
    {
        return (np == self
                || (possQual != nil && possQual.contains(np))
                || (locQual != nil && locQual.contains(np))
                || (exclusions != nil
                    && exclusions.indexWhich({ x: x.contains(np) }) != nil));
    }
    
    /*
     *   Get the list of objects matching the vocabulary words in our noun
     *   phrase.  Populates our 'matches' property with a vector of matching
     *   objects.  This doesn't look at any of our qualifiers, or attempt
     *   to disambiguate contextually; it simply finds everything in scope
     *   that the noun phrase could refer to.  
     */
    matchVocab(cmd)
    {
        /* start with an empty vector */
        local v = new Vector(32);

        /* get the current scope list */        
        cmd.action.buildScopeList();
        local scope = cmd.action.scopeList;

        /* check what kind of phrase we have */
        if (pronoun != nil)
        {
            /* it's a pronoun - resolved based on the antecedent */
            addMatches(v, pronoun.resolve(), 0);

            /* if there are no antecedents, flag the error */
            if (v.length() == 0)
                throw new NoAntecedentError(self, pronoun);

            /* filter for in-scope objects (or reflexive placeholders) */
            v = v.subset(
                { m: m.obj.ofKind(Pronoun) || scope.find(m.obj) });

            /* if that leaves nothing, flag the error */
            if (v.length() == 0)
                throw new AntecedentScopeError(cmd, self, pronoun);

        }
        else if (determiner == All && tokens == [])
        {
            /* ALL - use everything in scope applicable to the verb */
            addMatches(v, cmd.action.getAllUnhidden(cmd, role), 0);
            cmd.matchedAll = true;
        }
        else
        {
            /* 
             *   It's a named object.  Our 'tokens' property is a list of
             *   the words in the noun phrase in the user input.  Match it
             *   against the objects in physical scope.
             */
            v.appendAll(matchNameScope(cmd, scope));
        }

        /* save the match list so far */
        matches = v;

        /* if we have a contents qualifier, match its vocabulary */
        if (contQual != nil)
        {
            /* match vocabulary */
            contQual.matchVocab(cmd);

            /* apply the qualifier to keep only matching items */
            contQual.applyContQual();

            /* if that empties our list, flag it */
            if (matches.length() == 0)
                throw new NoneWithContentsError(cmd, self, contQual);

            /* 
             *   Expand the error text name to include the contents
             *   qualifier, since any subsequent failure to match will be
             *   against the result of this qualification.  For example, if
             *   the phrase is BUCKET OF FISH ON TABLE, we've now limited
             *   the scope to just BUCKET OF FISH, so if we fail to find
             *   such an object on the table it'll be because there's no
             *   BUCKET OF FISH on the table, not because there's simply no
             *   BUCKET.  
             */
            expandErrName(contQual);
        }

        /* if we have a possessive qualifier, apply it */
        if (possQual != nil)
        {
            /* match vocabulary for the possessive phrase */
            possQual.matchVocabPoss(cmd);

            /* 
             *   apply the qualifier, filtering out things not owned by the
             *   object named in the qualifier 
             */
            possQual.applyPossessive();

            /* if that empties our list, flag it */
            if (matches.length() == 0)
                throw new NoneInOwnerError(cmd, self, possQual);

            /* expand the error text to include the possessive qualifier */
            expandErrName(possQual);
        }

        /* if we have a locational qualifier, match its vocabulary */
        if (locQual != nil)
        {
            /* match vocabulary */
            locQual.matchVocab(cmd);

            /* apply the qualifier to keep only properly located items */
            locQual.applyLocational();

            /* if that empties our list, flag it */
            if (matches.length() == 0)
                throw new NoneInLocationError(cmd, self, locQual);

            /* expand the error name to include the locational */
            expandErrName(locQual);
        }

        /* if there's an exclusion list, apply it */
        if (exclusions != nil)
            exclusions.forEach({ x: x.applyExclusion(cmd) });
    }

    /*
     *   Add matching objects to a match vector.  'lst' can be a list or
     *   vector of objects, or a single object.  'match' is the MatchXxx
     *   flag value returned from the object name match, if applicable.  
     */
    addMatches(vec, lst, match)
    {
        /* ignore nil */
        if (lst == nil)
            return;

        /* wrap each item in an NPMatch object and add it to the vector */
        vec.appendAll(valToList(lst).mapAll({ obj: new NPMatch(self, obj, match) }));
    }

    /*
     *   Find the vocabulary matches for a given noun phrase within a given
     *   scope list.  Add all of the matches to the given vector.  
     */
    matchNameScope(cmd, scope)
    {
        /* set up a vector for the results */
        local v = new Vector(32);
        
        /*
         *   Run through the scope list and ask each object if it matches
         *   the noun phrase.  Keep the ones that match.  
         */
        foreach (local obj in scope)
        {
            /* ask this object if it matches */
            local match = obj.matchName(tokens);
            
            /* if it matches, include it in the results */
            if (match)
                v.append(new NPMatch(self, obj, match));
        }

        /*
         *   Now narrow the list according to the match strength.  Only
         *   keep the matches that have the maximum strength of the list.
         */
        if (v.length() > 0)
        {
            /* sort in descending order of strength */
            v.sort(SortDesc, { a, b: a.strength - b.strength });

            /* 
             *   discard everything that doesn't match the highest strength
             *   (which is the first element's strength, since we've sorted
             *   in descending order) 
             */
            v = v.subset({ a: a.strength == v[1].strength });
        }
        else
        {
            /* the list is empty - complain about it */
            throw new UnmatchedNounError(cmd, self);
        }

        /* return the list */
        return v;
    }

    /*
     *   Match vocabulary for a possessive qualifier phrase.
     *   
     *   Possessive matching has somewhat different rules than for ordinary
     *   noun phrases.
     *   
     *   First, possessive pronouns (HIS, HER, ITS, THEIR) *can* act like
     *   reflexives, in that they can refer back to earlier clauses in the
     *   same predicate: ASK BOB ABOUT HIS MOTHER.  However, they can also
     *   refer to previous commands: SEARCH BOB; TAKE HIS WALLET.  The
     *   deciding factor is whether or not there's an earlier noun phrase
     *   in the command that matches in gender and number; if so, we use
     *   the reflexive meaning, otherwise we use the external referent.
     *   
     *   Second, the scope for ordinary noun phrases has to be expanded to
     *   include the owners of the objects in scope.  If we have a wallet
     *   that we know belongs to Bob, we should be able to refer to it as
     *   "Bob's wallet" whether or not Bob himself is in scope.  So, for
     *   the purposes of the possessive, Bob is in scope even if he
     *   wouldn't be for an ordinary noun phrase.  
     */
    matchVocabPoss(cmd)
    {
        /* start with an empty vector */
        local v = matches = new Vector(32);

        /* check what kind of phrase we have */
        if (pronoun != nil)
        {
            /* 
             *   It's a possessive pronoun (HIS, HER, ITS, etc).
             *   Possessive pronouns can refer back to something in the
             *   same sentence (ASK BOB ABOUT HIS WALLET) or to earlier
             *   commands (SEARCH BOB; TAKE HIS WALLET).
             *   
             *   If it's a second-person possessive (YOUR), and there's an
             *   addressee actor, it refers to the actor.  For example,
             *   "Bob, give me your wallet".
             *   
             *   Look at the preceding noun phrases in the predicate to see
             *   if we can find an object that matches this pronoun.  If
             *   so, use it as the antecedent.  If not, use the regular
             *   antecedent.  
             */
            local done = nil;
            cmd.forEachNP(new function(np)
            {
                /* if we've already finished, ignore this noun phrase */
                if (done)
                    return;

                /* 
                 *   if this is our parent, stop looking - pronoun
                 *   references of this sort are always back references 
                 */
                if (np == parent)
                {
                    done = true;
                    return;
                }

                /* 
                 *   If this is the addressee actor, and we have a
                 *   second-person possessive pronoun (YOUR), the pronoun
                 *   refers to the actor. 
                 */
                if (np.role == ActorRole && pronoun.person == 2)
                {
                    v.appendAll(np.matches);
                    done = true;
                    return;
                }

                /* check for matches to this pronoun in this match list */
                local s = np.matches.subset(
                    { o: pronoun.matchObj(o.obj) });

                /* 
                 *   if we found any matches, use them, and stop looking -
                 *   we only want to use the nearest match 
                 */
                if (s.length() != 0)
                {
                    v.appendAll(s);
                    done = true;
                }
            });

            /* 
             *   if we didn't find any matches, use the antecedent from a
             *   previous command, if available 
             */
            if (v.length() == 0)
                v.appendAll(pronoun.resolve().mapAll({
                    x: new NPMatch(self, x, 0) }));
        }
        else
        {
            /* 
             *   It's a named object.  We need an expanded scope list that
             *   includes the owners of the objects referred to by the
             *   underlying noun phrase that we qualify.  
             */
            local expScope = new Vector(32);
            foreach (local obj in parent.matches)
            {
                /* 
                 *   if this object has an owner or owners, add it/them to
                 *   the expanded scope list 
                 */
                local owner = obj.obj.owner;
                if (owner != nil)
                    expScope.appendAll(owner);
            }

            /* add the objects in scope, filtering out duplicates */
            expScope.appendUnique(World.scope.toList());

            /* now build the match list using the expanded scope */
            v.appendAll(matchNameScope(cmd, expScope));
        }

        /* 
         *   a possessive qualifier can itself have a possessive qualifier
         *   (BOB'S MOTHER'S HAT), so go resolve that as well 
         */
        if (possQual != nil)
            possQual.matchVocabPoss(cmd);
    }

    /*
     *   Apply this possessive phrase's qualification.  This filters the
     *   underlying (parent) noun list to keep only objects owned by the
     *   object(s) named in this noun phrase.  
     */
    applyPossessive()
    {
        /* 
         *   First, do "reverse" filtering: consider only owners who own
         *   something that's in the underlying noun list.  For example, if
         *   there are two guards present, but only one of them is carrying
         *   a sword, GUARD'S SWORD must refer to the guard who's carrying
         *   a sword.
         *   
         *   (Take a subset of our own objects, keeping each object 'o'
         *   where there's something in the parent list owned by 'o'.  That
         *   is, there's an object 'p' in the parent list where 'p' is
         *   owned by 'o'.)  
         */
        local m = matches.subset(
            { o: parent.matches.indexWhich(
                { p: p.obj.ownedBy(o.obj) }) != nil });

        /* if the reverse filter didn't rule everything out, apply it */
        if (m.length() > 0)
            matches = m;
        
        /* next, apply any possessive qualifier of my own */
        if (possQual != nil)
            possQual.applyPossessive();

        /* filter parent objects not owned by anything in my list */
        parent.matches = parent.matches.subset(
            { p: matches.indexWhich(
                { o: p.obj.ownedBy(o.obj) }) != nil });
    }

    /*
     *   Apply this contents qualifier phrase's qualification.  This
     *   filters the underlying (parent) noun list to keep only objects
     *   that contain the object(s) named in this noun list.  
     */
    applyContQual()
    {
        /* 
         *   First, do the reverse filtering, to keep only objects that
         *   could be inside objects in the underlying list.  
         */
        local m = matches.subset(
            { o: parent.matches.indexWhich(
                { p: o.obj.isChild(p.obj, nil) }) != nil });

        /* if that didn't rule everything out, apply the filter */
        if (m.length() > 0)
            matches = m;

        /* next, apply any locational qualifier of my own */
        if (contQual != nil)
            contQual.applyContQual();

        /* filter parent objects not containing anything in my list */
        parent.matches = parent.matches.subset(
            { p: matches.indexWhich(
                { o: o.obj.isChild(p.obj, nil) }) != nil });
    }
    
    /*
     *   Apply this locational phrase's qualification.  This filters the
     *   underlying (parent) noun list to keep only objects located within
     *   the object(s) named in this noun phrase.  
     */
    applyLocational()
    {
        /* 
         *   First, do the reverse filtering, to keep only objects that
         *   could contain objects in the underlying list. 
         */
        local m = matches.subset(
            { o: parent.matches.indexWhich(
                { p: p.obj.isChild(o.obj, locType) }) != nil });

        /* if that didn't rule everything out, apply the filter */
        if (m.length() > 0)
            matches = m;

        /* next, apply any locational qualifier of my own */
        if (locQual != nil)
            locQual.applyLocational();

        /* first, try limiting to items DIRECTLY in the location */
        m = parent.matches.subset(
            { p: matches.indexWhich(
                { o: p.obj.isDirectChild(o.obj, locType) }) != nil });

        /* if there weren't any, try items indirectly in the location */
        if (m.length() == 0)
            m = parent.matches.subset(
                { p: matches.indexWhich(
                    { o: p.obj.isChild(o.obj, locType) }) != nil });

        /* save the filtered list in the parent */
        parent.matches = m;
    }
    
    /*
     *   Apply an exclusion item.  This resolves the vocabulary for the
     *   exclusion phrase and filters the matching item(s) noun phrase out
     *   of the parent list.  
     */
    applyExclusion(cmd)
    {
        /* do our vocabulary matching */
        matchVocab(cmd);

        /* filter the parent list to exclude everything we matched */
        parent.matches = parent.matches.subset(
            { p: matches.indexWhich(
                { o: o.obj == p.obj }) == nil });
    }

    /*
     *   Select the objects from among the vocabulary matches.  This
     *   narrows the list of possible vocabulary matches for our noun
     *   phrase to find the actual object or objects the player is
     *   referencing.
     *   
     *   When this is called, we've already filled in the match list with
     *   all objects in scope that match the vocabulary of the core noun
     *   phrase (including non-reflexive pronouns and ALL), and we've
     *   applied any possessive, locational, and exclusion qualifiers.
     *   What we're left with is the list of in-scope objects that meet all
     *   of the specifications contained in the entire noun phrase.  In
     *   other words, we've squeezed all available information out of the
     *   noun phrase itself.  If the result is ambiguous, then, we'll have
     *   to look beyond the noun phrase, to the broader semantic content of
     *   the overall command.  
     *   
     *   There are three possible "goals" for what our final object list
     *   should look like after disambiguation.  Only one goal applies to
     *   each particular noun phrase; which it is depends on the grammar of
     *   the phrase:
     *   
     *   1.  Definite mode: TAKE BOOK, TAKE THE BOOK, TAKE BOTH BOOKS, TAKE
     *   THE THREE BOOKS.  The goal in definite mode is to choose the given
     *   number of objects, *and* to make sure that the player could *only*
     *   have meant those precise objects.  In other words, we're not
     *   allowed to make an arbitrary choice: in natural language, the
     *   definite mode says that the speaker believes the listener knows
     *   which *particular* object or objects the speaker is referring to.
     *   If we're not absolutely sure which objects the player is talking
     *   about, we have a disagreement with the player's apparent
     *   expectations and must ask for clarification.
     *   
     *   2.  Indefinite mode: TAKE A BOOK, TAKE ANY BOOK, TAKE TWO BOOKS.
     *   The goal is to choose the given number of objects from the
     *   possible matches, arbitrarily choosing from the available objects.
     *   
     *   3. Plural mode: TAKE BOOKS, TAKE THE BOOKS, TAKE ALL BOOKS.  The
     *   goal here is to choose all of the matching objects.  
     */
    selectObjects(cmd)
    {
        /* take the mode from the determiner */
        local mode = determiner;
        
        /* 
         *   If the player typed ALL or ANY or some such response in response to
         *   a disambiguation respose, that response will have set a determiner
         *   we need to use instead.
         */
        if(cmd.disambig.length > 0)
            mode = cmd.disambig[1][1].determiner;
        

        /* 
         *   Sort the matches by listing order.  If we have a plural, this
         *   will make the order of processing more logical when there's
         *   some kind of natural order (such as alphabetized or numbered
         *   items).  
         */
        matches.groupSort(
            { ent, idx: [ent.obj.disambigGroup, ent.obj.disambigOrder] });

        /* if there's no determiner, assume definite */
        if (mode == nil)
            mode = Definite;

        /*
         *   If we're in Definite mode and we don't have a quantifier,
         *   check for a plural noun phrase.  A plural without a specific
         *   number puts us in Plural mode.  Consider the noun phrase to be
         *   plural if it matched any of our objects with plural usage.
         *   Even if some objects were singular in usage, one plural is
         *   enough to suggest that the name has plural usage. 
         */
        if (mode == Definite
            && quantifier == nil
            && matches.indexWhich({ m: m.match & MatchPlural }) != nil)
            mode = All;

        /* 
         *   Figure the target quantity.  If there's a quantifier, it's the
         *   quantifier value; otherwise we implicitly want a singleton.
         *   Note that the quantifier is ignored in Plural mode, since we
         *   simply want to match all of the objects in this case.  
         */
        local num = (quantifier != nil ? quantifier : 1);

        /*
         *   If we don't have enough objects to satisfy the request,
         *   complain about it. 
         */
        if (num > matches.length() 
            && objs.indexWhich({o: o.canSupply}) == nil)
            throw new InsufficientNounsError(cmd, self);
        
        if (mode == Definite && isAllEquivalent(matches) && matches.length > 1)
            mode = Indefinite;

        
        for(local cur in objs)
            cur.filterResolveList(self, cmd, mode);
        
        /* select the goal based on the mode */
        switch (mode)
        {
        case Definite:        
            /* 
             *   Definite mode.  If we have more than the desired number,
             *   we must disambiguate.  
             */
            if (matches.length() > num)
                disambiguate(cmd, num, cmd.action);
            else
                /* 
                 *   If we don't disambiguate we don't run the verify routine
                 *   that stores a reference to the object selected for this
                 *   role so that it can be available to the verify routine for
                 *   the other role; so we store a reference now.
                 */
                cmd.action.(role.objProp) = cmd.(role.objListProp)[1].obj;
            break;

        case Indefinite:
            /*
             *   Indefinite mode - we must select the desired number, but
             *   we can do so arbitrarily.  Simply select the first 'num'
             *   in the list.
             */
            
            /* start by getting object rankings from the verb */
            cmd.action.scoreObjects(cmd, role, matches);
            
            /* sort by score (highest to lowest), except for the ActorRole */
            matches.sort(SortDesc, { a, b: a.score - b.score });
            
            if (matches.length() > num)
                matches.removeRange(num + 1, matches.length());

            /* flag the objects as arbitrarily chosen */
            matches.forEach({ m: m.flags |= SelArbitrary });
            break;

        case All:
            /* 
             *   Plural mode - select all of the objects.  Simply use the
             *   set we've already built.  Flag the selections as plural.  
             */
            matches.forEach({ m: m.flags |= SelPlural });
            cmd.matchedMulti = true;
            break;
        }
    }

    /* 
     *   Determine whether all matches in the matchList are impossible to
     *   disambiguate.
     */
    isAllEquivalent(matchList){
        local names = Distinguisher.getNames(
            matchList.mapAll({ x: x.obj }), nil);
        return (names.length() == 1);
    }
    
    /*
     *   Disambiguate the match list to select the given target number of
     *   objects.  
     */
    disambiguate(cmd, num, action)
    {
        /* start by getting object rankings from the verb */
        action.scoreObjects(cmd, role, matches);

        /* sort by score (highest to lowest) */
        matches.sort(SortDesc, { a, b: a.score - b.score });

        /* 
         *   To pick automatically, we need exactly 'num' objects at the
         *   highest score.  The sort put the object with the highest score
         *   at the start of the list, so the high-scoring subset is the
         *   subset that matches the first object's score.
         *   
         *   We only disambiguate in definite mode, and definite mode in
         *   input is the player's way of telling us that they think there
         *   are exactly this many objects that obviously apply to their
         *   command.  The score tells us which objects *we* think are the
         *   obvious choices for the command.  If we come up with the same
         *   set size that the player expressed, then we presumably
         *   understand what the player is saying.  If our set size differs
         *   from theirs, we're not thinking alike, so we need to ask for
         *   clarification.  
         */
        local sub = matches.subset({x: x.score == matches[1].score});
        
        if (isAllEquivalent(sub))
            sub.setLength(num);
        
        if (sub.length() == num)
        {
            /* 
             *   We have the desired number of most obvious matches, so
             *   we've successfully disambiguated it.  We might want to
             *   announce the selection, since we're making a guess, so
             *   generate a name for each that distinguishes it from the
             *   other items we could have matched.  
             */
            local names = Distinguisher.getNames(
                matches.mapAll({ x: x.obj }), nil);            

            /* save the disambiguated subset */
            matches = sub;

            /* mark each as disambiguated, and save the distinctive name */
            foreach (local m in matches)
            {
                m.flags |= SelDisambig;
                m.name = names.valWhich({ n: n[2].indexOf(m.obj) != nil })[1];
            }
            
            /* we've successfully disambiguated the phrase */
            return;
        }        
        else if (sub.length() > num && num > 1)                    
        {
            /*            
             *   We've asked for a definite number of 2 or more items, but there
             *   are different options among the best matches.  This poses a
             *   problem because there's no good disambiguation we can possibly
             *   ask here. So we just give up and ask them to be more specific.
             *   I can imagine someone responding to this message by just
             *   retyping a more specific form of the noun phrase. For example,
             *   after GET THE TWO MATCHES doesn't work, they might just want to
             *   type THE LIT MATCH AND AN UNLIT MATCH, but currently the parser
             *   does not do this.  Probably the DMsg should also refer to the
             *   specific noun phrase that is ambiguous in its message.
             */
            throw new AmbiguousMultiDefiniteError(cmd,self);
        }
    
        /*
         *   We have more than what we need in the highest scoring section,
         *   so make this the matches and proceed with disambiguation.
         *   This line makes it so that when you say something like "drop match"
         *   it will only try to disambiguate among the things you are holding.
         *   I think this behavior is more desirable, but am not certain.
         */
        matches = sub;
        

        /* it's still ambiguous, so throw an error to ask for help */
        ambigError(cmd);
        matches.setLength(num);
    }

    /*
     *   Throw an ambiguous noun phrase error for the current match list. 
     */
    ambigError(cmd)
    {
        /* if we have a reply to a past disambiguation question, apply it */
        local disambig = cmd.fetchDisambigReply();
        if (disambig != nil)
        {
            /* start a list for the result */
            local dmatches = new Vector(matches.length());

            /* add the object(s) selected by each noun phrase in the reply */
            foreach (local dnp in disambig)
            {
                /* 
                 *   resolve this noun phrase of the reply and add its
                 *   selections to the master list 
                 */
                dmatches.appendAll(
                    dnp.applyDisambig(cmd, matches, disambigNameList));
            }

            /* 
             *   If we get this far, we have a valid result set now, even
             *   if it's not of the original size.  The reply can select a
             *   different number of objects than originally implied, since
             *   the reply can say something like "the red one and the blue
             *   one" to pick more than one of the offered objects.  
             */
            matches = dmatches;
            return;
        }

        /* 
         *   Still ambiguous, so we need to ask for clarification, with a
         *   question like "Which do you mean, the red book, or the blue
         *   book?"  Generate the list of names.  
         */
        local nameList = Distinguisher.getNames(
            matches.mapAll({ x: x.obj }), true);

        /* 
         *   Sort by disambigGroup and disambigOrder.  Note that it's
         *   possible for a set to have more than one item with a
         *   disambigGroup, and these might not match.  We can't break up a
         *   set, though, so we'll just have to pick one group arbitrarily
         *   for the whole set. 
         */
        nameList.groupSort(new function(entry, idx)
        {
            /* find an arbitrary entry in the set with a disambigGroup */
            local gobj = entry[2].valWhich({ x: x.disambigGroup != nil });
            
            /* if we found one, use its group and order; otherwise use nil */
            return gobj != nil
                ? [gobj.disambigGroup, gobj.disambigOrder]
                : [nil, idx];
        });

        /* 
         *   remember this list, since it determines the meanings of
         *   ordinals ("the second one") in the reply 
         */
        disambigNameList = nameList;       
        
        /* throw the still-ambiguous error */
        throw new AmbiguousError(cmd, self, nameList);
        
        
    }

    /*
     *   Apply this noun phrase as a disambiguation reply to the given
     *   original list of matches to an ambiguous noun phrase.  
     */
    applyDisambig(cmd, ambigMatches, nameList)
    {
        /* 
         *   Start with the ambiguous list.  Our goal is to narrow the list
         *   that the original noun phrase matched, so start with the list
         *   and remove items that don't match the additional vocabulary
         *   and/or qualifiers in the reply.  
         */
        matches = ambigMatches;

        /* if there's an ordinal, pick out the selected item */
        if (ordinal != nil)
        {
            /* pick the name list entry based on the list position */
            local n;
            if (ordinal == -1 && nameList.length() > 0)
                n = nameList[nameList.length()];
            else if (ordinal >= 1 && ordinal <= nameList.length())
                n = nameList[ordinal];
            else
                throw new OrdinalRangeError(self, ordinal);

            /* 
             *   take the first object from the group, and build a resolved
             *   object list with this object 
             */
            n = n[2][1];
            matches = matches.subset({ m: m.obj == n });
        }

        /* if there's a locational qualifier, apply it */
        if (locQual != nil)
        {
            /* apply the locational qualifier */
            locQual.matchVocab(cmd);
            locQual.applyLocational();

            /* if that empties our list, flag it */
            if (matches.length() == 0)
                throw new NoneInLocationError(cmd, parent, locQual);
        }
                
        /* if there's a possessive qualifier, apply it */
        if (possQual != nil)
        {
            /* apply the possessive qualifier */
            possQual.matchVocabPoss(cmd);
            possQual.applyPossessive();

            /* if that empties our list, flag it */
            if (matches.length() == 0)
                throw new NoneInOwnerError(cmd, parent, possQual);
        }

        /* if there's a contents qualifier, apply it */
        if (contQual != nil)
        {
            /* apply the qualifier */
            contQual.matchVocab(cmd);
            contQual.applyContQual();

            /* if that empties our list, flag it */
            if (matches.length() == 0)
                throw new NoneWithContentsError(cmd, parent, contQual);
        }

        /* if there's vocabulary, apply it */
        if (tokens.length() > 0)
        {
            /*
             *   The basic vocabulary of the reply isn't as simple to
             *   handle as it might seem, since the words could refer to
             *   the object itself, a container, an owner, or the nominal
             *   contents.  Consider:
             *   
             *   Which bucket do you mean, the empty bucket, the bucket on
             *   the shelf, the tall fisherman's bucket, or the bucket of
             *   water?
             *   
             *   Players are accustomed to being able to answer these
             *   questions by entering just a word or two from the prompt,
             *   so we'd want to accept EMPTY, SHELF, TALL, and WATER for
             *   the respective choices.
             *   
             *   So: first assume that the reply refers to the object
             *   itself.  If we don't find any matches, try the other
             *   possibilities: containers, owners, and nominal contents.  
             */
            if (tokens.length() > 0 && matches.length() > 0)
            {
                /* first try applying the words to the object itself */
                local m = matches.subset(
                    { o: o.obj.matchNameDisambig(tokens) != 0 });
                
                /* if that didn't match anything, try containers */
                if (m.length() == 0)
                {
                    /* get the list of in-scope objects matching the tokens */
                    local locs = World.scope.subset(
                        { o: o.matchNameDisambig(tokens) != 0 });

                    /* get the subset of 'matches' that are in 'locs' */
                    m = matches.subset(
                        { o: locs.indexWhich(
                            { l: o.obj.isChild(l, nil) }) != nil });
                }
                
                /* if we still don't have any matches, try owners */
                if (m.length() == 0)
                {
                    /* get the list of scope objects plus potential owners */
                    local owners = new Vector(50, World.scope.toList());
                    foreach (local obj in matches)
                    {
                        local owner = obj.obj.owner;
                        if (owner != nil)
                            owners.appendAll(owner);
                    }

                    /* get the subset matching the tokens */
                    owners = owners.subset(
                        { o: o.matchNameDisambig(tokens) != 0 });

                    /* get the subset of 'matches' with owners in 'owners' */
                    m = matches.subset(
                        { o: owners.indexWhich(
                            { l: o.obj.ownedBy(l) }) != nil });
                }
                
                /* if we still don't have any matches, try nominal contents */
                if (m.length() == 0)
                {
                    /* get the list of in-scope objects matching the tokens */
                    local conts = World.scope.subset(
                        { c: c.matchNameDisambig(tokens) != 0 });

                    /* get the subset of 'matches' with contents in 'conts' */
                    m = matches.subset(
                        { o: conts.indexOf(o.obj.nominalContents) != nil });
                }
                
                /* if we've eliminated everything, it's an error */
                if (m.length() == 0)
                    throw new UnmatchedNounError(cmd, self);

                /* we're out of things to try, so take what we have */
                matches = m;
            }
        }

        /* determine how many the reply *wants* to select */
        local num = (quantifier != nil ? quantifier : 1);

        /* if we don't have enough, it's an error */
        if (matches.length() < num)
            throw new InsufficientNounsError(cmd, self);
        
        if (determiner == Definite && isAllEquivalent(matches))
            determiner = Indefinite;
        
        /* determine the actual number available */
        switch (determiner)
        {
        case Definite:
            /* 
             *   They want exactly the number indicated.  If we still have
             *   more than this, it's *still* ambiguous, so throw another
             *   ambiguity error to further disambiguate the new list.
             *   Otherwise, we're set.  
             */
            if (num < matches.length())
                ambigError(cmd);
            break;
            
        case Indefinite:
            /* indefinite - arbitrarily select any 'num' of the items */
            if (matches.length() > num)
                matches.removeRange(num + 1, matches.length());

            /* mark them as arbitrary */
            matches.forEach({ m: m.flags |= SelArbitrary });
            break;
            
        case All:
            /* all - keep all of the items */
            break;
        }

        /* return our final match list */
        return matches;
    }

    /*
     *   Resolve ALL.  This is called on a separate pass after
     *   selectObjects(), because two-object verbs sometimes resolve ALL in
     *   one slot according to the selection in the other slot.  
     */
    resolveAll(cmd)
    {
        /* if this is a simple ALL phrase, re-resolve it */
        if (determiner == All)
        {
            /* 
             *   If it's just ALL, re-match the vocabulary to pick up the
             *   final ALL list.  Otherwise, take the subset of the ALL
             *   list that matches the vocabulary. 
             */
            if (tokens == [])
            {
                /* it's just ALL - start over with the final ALL list */
                matchVocab(cmd);
            }
            else
            {
                /* 
                 *   it's ALL <somethings> - take the subset that's in the
                 *   final ALL list 
                 */
                local all = cmd.action.getAll(cmd, role);
                local sub = matches.subset({ m: all.indexOf(m.obj) != nil });

                /* 
                 *   If we found any objects in the intersection, keep only
                 *   the subset, since this is the set that actually makes
                 *   sense for this command.  If the subset is empty,
                 *   though, ignore it and stick with the objects we've
                 *   already selected based on the vocabulary; these will
                 *   probably fail the command, but that'll make more sense
                 *   to the player than claiming there's nothing matching
                 *   the description they gave.  
                 */
                if (sub.length() != 0)
                    matches = sub;
            }
        }
    }
    
    /*
     *   Resolve reflexive pronouns.  Our Command calls this AFTER
     *   resolving all of the regular noun phrases, because reflexives
     *   refer back to other nouns in the same command.  
     */
    resolveReflexives(cmd)
    {
        /* if we have a reflexive pronoun object in the list, resolve it */
        if (matches.length() == 1 && matches[1].obj.ofKind(Pronoun))
        {
            /* 
             *   We have a reflexive pronoun pending.  Ask the command for
             *   the current meaning of the reflexive.  
             */
            matches = cmd.resolveReflexive(matches[1].obj).mapAll(
                { x: new NPMatch(self, x, 0) });

            /* it's an error if that didn't yield anything */
            if (matches.length() == 0)
                throw new NoAntecedentError(self, pronoun);
        }
        else
        {
            /*
             *   This isn't a reflexive, so it's a candidate to be an
             *   antecedent of a reflexive later in the phrase.  Pass it
             *   along to the command to note for future use.  Since we
             *   visit the noun phrases in their order of appearance in the
             *   command, we'll naturally have the latest one before the
             *   pronoun when we encounter a pronoun, which is exactly what
             *   we want: a reflexive pronoun binds to the nearest
             *   preceding noun that matches in gender, number, etc.  
             */
            matches.forEach({ match: cmd.saveReflexiveAnte(match.obj) });

            /* also save the entire list, for THEMSELVES */
            cmd.saveReflexiveAnte(matches.mapAll({ x: x.obj }));
        }
    }

    /* Build the 'objs' list from the match list */
    buildObjList()
    {
        objs = matches.mapAll({x: x.obj});
    }

    /* 
     *   List of NPMatch objects.  This is populated during the matchName
     *   phase with the list of possible vocabulary matches, and then
     *   reduced during disambiguation to the final set.  
     */
    matches = []

    /* 
     *   List of resolved objects.  This is populated after disambiguation
     *   from the 'matches' set - it contains the same objects, but simply
     *   the objects rather than the NPMatch wrappers. 
     */
    objs = []

    /* add a literal to this phrase */
    addLiteral(tok) { tokens += tok; }

    /* add a possessive qualifier, returning the new noun phrase */
    addPossessive(prod)
    {
        /* create, store, and return a new noun phrase for the possessor */
        return possQual = new NounPhrase(self, prod);
    }

    /* add a contents qualifier, returning the new noun phrase */
    addContents(prep, prod)
    {
        /* remember the contents preposition */
        contPrep = prep;

        /* create, store, and return a new noun phrase for the contents */
        return contQual = new NounPhrase(self, prod);
    }

    /* add a locational qualifier, returning the new noun phrase */
    addLocation(locType, prod)
    {
        /* create a new noun phrase for the location */
        locQual = new NounPhrase(self, prod);

        /* set its location type */
        locQual.locType = locType;

        /* return the new phrase */
        return locQual;
    }

    /* add a quantifier, given as an integer value */
    addQuantifier(num)
    {
        quantifier = num;
    }

    /* add an ordinal, given as an integer value */
    addOrdinal(num)
    {
        ordinal = num;
    }

    /* add an exclusion list item */
    addExclusionItem(prod)
    {
        /* if we don't already have an exclusion list, create one */
        if (exclusions == nil)
            exclusions = [];

        /* create a new NounPhrase */
        local np = new NounPhrase(self, prod);

        /* add it to the exclusion list */
        exclusions += np;

        /* return the new noun phrase */
        return np;
    }

    /*
     *   Does this noun phrase refer to multiple objects structurally?
     *   This is true if any the matches used plural words, or the
     *   determiner is All, or we have a quantifier greater than 1.  
     */
    isMultiple()
    {
        return determiner == All
            || (quantifier != nil && quantifier > 1)
            || matches.indexWhich({ m: m.match & MatchPlural }) != nil;
    }

    /* 
     *   the Command list we're a part of (&dobjNPs, &iobjNPs, etc: the
     *   Command overrides this to the actual list property for a primary
     *   noun phrase, and for qualifiers such as possessives, this
     *   inherited version looks it up via the parent) 
     */
    role = (parent.role)

    /* the NounPhrase we qualify, if we're a possessive or locational */
    parent = nil

    /* the grammar production match object for this noun phrase */
    prod = nil

    /* 
     *   the grammar match for the core noun phrase; this is the part that
     *   names a single object, stripped of all qualifiers (such as
     *   possessives, articles, quantifiers, and locational phrases) 
     */
    coreProd = nil

    /* the literal tokens making up the noun phrase */
    tokens = []

    /* the pronoun, if any, as a Pronoun object */
    pronoun = nil

    /* the possessive qualifier, if any ("BOB'S box") */
    possQual = nil

    /* the locational qualifier phrase, if any ("the box ON THE SHELF") */
    locQual = nil

    /* 
     *   The locational qualifier relationship, as a LocType object.  (This
     *   is stored on the locational qualifier noun phrase itself, not on
     *   the underlying noun phrase it qualifies.)  
     */
    locType = nil

    /* the contents qualifier phrase, if any ("the bucket OF WATER") */
    contQual = nil

    /* the preposition of the contents qualifier */
    contPrep = nil

    /* the quantifier, if any, as a number: for "five books", this is 5 */
    quantifier = nil

    /* 
     *   The ordinal value, if any, as a number: for "the third one", this
     *   is 3.  This is intended for use in disambiguation replies, to let
     *   the user pick out an item by its position in the offered list.  
     */
    ordinal = nil

    /* the determiner, if any, as a Determiner object */
    determiner = nil

    /* 
     *   the exclusion list, if any (this is the list following EXCEPT or
     *   BUT in a phrase like ALL EXCEPT THE RED ONES) 
     */
    exclusions = nil

    /* the name list from the disambiguation query */
    disambigNameList = nil
;

/*
 *   TopicPhrase is a special kind of NounPhrase for topics (ASK ABOUT,
 *   TELL ABOUT, TALK ABOUT, LOOK UP, etc).  These phrases aren't resolved
 *   to game-world objects the way ordinary noun phrases are, but instead
 *   are resolved to conversation topic objects.  
 */
class TopicPhrase: NounPhrase
     /*
     *   Get the list of objects matching the vocabulary words in our noun
     *   phrase.  Populates our 'matches' property with a vector of matching
     *   objects.  This doesn't look at any of our qualifiers, or attempt
     *   to disambiguate contextually; it simply finds everything in scope
     *   that the noun phrase could refer to.  
     */
    matchVocab(cmd)
    {
        /* start with an empty vector */
        local v = new Vector(32);
        
        /* get the current scope list */
        //        local scope = World.scope;
        
        
        local scope = Q.topicScopeList;
        
        /* check what kind of phrase we have */
        if (pronoun != nil)
        {
            /* it's a pronoun - resolved based on the antecedent */
            addMatches(v, pronoun.resolve(), 0);
            
            /* if there are no antecedents, flag the error */
            if (v.length() == 0)
                throw new NoAntecedentError(self, pronoun);
            
            /* filter for in-scope objects (or reflexive placeholders) */
            v = v.subset(
                { m: m.obj.ofKind(Pronoun) || scope.find(m.obj) });
            
            /* if that leaves nothing, flag the error */
            if (v.length() == 0)
                throw new AntecedentScopeError(cmd, self, pronoun);
            
        }
        
        else
        {
            /* 
             *   It's a named object.  Our 'tokens' property is a list of the
             *   words in the noun phrase in the user input.  Match it against
             *   the objects in physical scope.
             */
             v.appendAll(matchNameScope(cmd, scope));
            
            /* 
             *   Create a dummy object to represent the literal text; we may
             *   need this even if there are other matches to ensure that a
             *   regular expression is matched on a TopicEntry.
             */
            local obj = new Topic(tokens.join(' ').trim());
            
            /* 
             *   Note that the dummy object has been newly created so it can defer to any
             *   user-defined topics when it comes to selecting the best match.
             */
            obj.newlyCreated = true;
            
            /* Wrap the dummy object in am NPMatch object */
            local lst = [obj];

            addMatches(v, lst, 1);
            
            matches = v;  
            
            cmd.madeTopic = true;
            

        }
        
        /* save the match list so far */
        matches = v;
        
        
        
        /* 
         *   if we have a possessive qualifier, apply it, but only if it is to be applied to a Thing
         *   (rather than a Topic)
         */
        if (possQual != nil && matches.indexWhich({x: x.obj.ofKind(Thing)}))
        {
            /* match vocabulary for the possessive phrase */
            possQual.matchVocabPoss(cmd);
            
            /* 
             *   apply the qualifier, filtering out things not owned by the
             *   object named in the qualifier
             */
            possQual.applyPossessive();
            
            /* if that empties our list, flag it */
            if (matches.length() == 0)
                throw new NoneInOwnerError(cmd, self, possQual);
            
            /* expand the error text to include the possessive qualifier */
            expandErrName(possQual);
        }
        
      
        
//        if(matches.length == 0)
        {        
           
        }
        
        
        local res = new ResolvedTopic(matches.mapAll({o: o.obj}).toList, tokens);
        
        res = new NPMatch(v[1].np, res, v[1].match);
        
        matches = new Vector([res]);
        
        
    }
    
    matchNameScope(cmd, scope)
    {
        /* set up a vector for the results */
        local v = new Vector(32);
        
        /*
         *   Run through the scope list and ask each object if it matches
         *   the noun phrase.  Keep the ones that match.  
         */
        foreach (local obj in scope)
        {
            /* ask this object if it matches */
            local match = obj.matchName(tokens);
            
            /* if it matches, include it in the results */
            if (match)
                v.append(new NPMatch(self, obj, match));
        }


        /* return the list */
        return v;
    }
    
    selectObjects(cmd)
    {
        filterResolveList(self, cmd, All);
    }
;

class ResolvedTopic: object
    construct(lst, toks)
    {
        /* 
         *   if our list of topics has more than one entry, sort it in ascending
         *   order of length of name. That's because the shorter the name, the
         *   closer it may be to what the player actually typed.
         */
        if(lst != nil && lst.length > 1)
            topicList = lst.sort(SortAsc, {a, b: a.name.length - b.name.length});
        else       
            topicList = lst;    
        tokens = toks;
    }    
    
    topicList = nil
    tokens = nil
    
    /* Get the object representing this ResolvedTopic's best match. */
    getBestMatch()
    {
        if(topicList == nil)
            return nil;
        
        /* 
         *   If possible, find an object that hasn't just been newly created by the parser as a
         *   fallback wrapper (because we'd prefer to use Thing or Topic defined by the user).
         */
        local top = topicList.valWhich({t:!t.newlyCreated});
        
        /*   
         *   If we find a user-defined topic, return that; otherwise simply return the first item in
         *   the list.
         */
        return top ?? topicList[1];
    }    
    
    getTopicText = tokens.join(' ').trim()
    theName = (topicList != nil ? topicList[1].theName : getTopicText)
    aName = (topicList != nil ? topicList[1].aName : getTopicText)
    name = (topicList != nil ? topicList[1].name : getTopicText)
    person = 3
;

/*
     *   LiteralPhrase is a special kind of NounPhrase for literals (TYPE,
     *   WRITE, SET TO, etc).  These phrases aren't resolved to game-world
     *   objects, but instead are just treated as literal text.  
 */
class LiteralPhrase: NounPhrase
    matchVocab(cmd)
    {
        local v = new Vector(2);
        
        /* Recreate the literal text */
        local litName = tokens.join(' ');
               
        /* Create a dummy object to represent the literal text */
        local obj = new LiteralObject(litName.trim());
        
        /* Wrap the dummy object in am NPMatch object */
        local lst = [obj];
        addMatches(v, lst, MatchNoApprox);
        
        matches = v;   
    }
    
    selectObjects(cmd)
    {
        /* do nothing; there's only one object */
    }
;

/* object to hold the result of a literal input */

class LiteralObject: object
    construct(name_)
    {
        name = name_;
    }
    
    name = nil
    theName = (name)
    person = 3
;
    
/*
 *   NumberPhrase is a special kind of NounPhrase for numeric literals
 *   (e.g., FOOTNOTE n).  These phrases aren't resolved to game-world
 *   objects, but are simply taken as numeric values. 
 */
class NumberPhrase: NounPhrase
     matchVocab(cmd)
    {
        local v = new Vector(2);
        
        /* Get the number just entered*/        
        local val = prod.numval;
               
        /* Create a dummy object to represent the literal text */
        local obj = new NumericObject(tokens, val);
        
        /* Wrap the dummy object in am NPMatch object */
        local lst = [obj];
        addMatches(v, lst, MatchNoApprox);
        
        matches = v;   
    }
    
    selectObjects(cmd)
    {
        /* do nothing; there's only one object */
    }
;

/* An object to hold a numerical value. */
class NumericObject: object
    construct(toks, val)
    {   
        numToks = toks;
        numVal = val;
    }
    
    numToks = nil
    numVal = nil
    numStr = (numToks.join(' '))
;
    

/* ------------------------------------------------------------------------ */
/*
 *   NPMatch is an object that describes one object matching a noun phrase.
 */
class NPMatch: object
    construct(np, obj, match)
    {
        /* save the NounPhrase, the object we matched, and the match flags */
        self.np = np;
        self.obj = obj;
        self.match = match;

        /* 
         *   set the name initially to the object name; the Command will
         *   replace this before execution with a name from the
         *   Distinguishers that's unique relative to the other objects in
         *   the list 
         */
        self.name = obj.name;

        /*
         *   Calculate the match strength for sorting purposes.
         *   
         *   The strength tells us how well the vocabulary matched the
         *   object.  Matches without truncation are stronger than those
         *   that include truncated words; likewise character
         *   approximation.  Matches that consist of entirely adjectives
         *   are weaker than those that contain nouns or plurals (for
         *   example, if we have a TOY CAR and a TOY CAR REMOTE CONTROL in
         *   scope, we'd consider CAR to be an unambiguous match to the TOY
         *   CAR, since the match to the REMOTE is weak by virtue of being
         *   all adjectives).
         *   
         *   The MatchXxx bit flags are arranged in arithmetic order of
         *   match strength, so the 'match' value basically equals the
         *   strength.  However, for the strength calculation, plurals and
         *   nouns are equivalent.  So the strength value is the match
         *   value with any plural flag replaced by the noun flag. 
         */
        self.strength = (match & ~MatchPlural)
            | (match & MatchPlural ? MatchNoun : 0);
    }

    /* the NounPhrase we matched */
    np = nil

    /* the matching object */
    obj = nil

    /* 
     *   the match flags - this is a combination of MatchXxx flags as
     *   returned from Mentionable.matchName()
     */
    match = 0

    /* the match strength, for sorting the match list */
    strength = 0

    /* the selection/disambiguation flags (SelXxx) */
    flags = 0

    /*
     *   Disambiguation score.  This is a number assigned by the action in
     *   scoreObjects().
     */
    score = 0

    /*
     *   The name, for announcement purposes.  This is filled in by the
     *   Command during execution.  The Command figures the name so that
     *   it's distinguished from all of the other objects in the same noun
     *   role in the command.  
     */
    name = ''
;


/* ------------------------------------------------------------------------ */
/*
 *   Our root class for grammar productions.  (A "production" represents a
 *   match to a syntax rule, as defined with a 'grammar' template.)
 *   
 *   The language module's grammar rules can define certain special
 *   properties on any production match object, and we'll find them in the
 *   course of building the command from the match tree:
 *   
 *   endOfSentence=true - define this on a production for a sentence-ending
 *   verb conjunction.  In English (and most Western languages), this can
 *   be used with rules that match punctuation marks like periods,
 *   exclamation points, and question marks, since these marks typically
 *   end a sentence.  The parser distinguishes between the grammar rules
 *   for the first clause in a sentence vs subsequent clauses.  It starts a
 *   new input line with the first-in-sentence rule, then uses the
 *   additional clause rule for each subsequent clause.  When a clause ends
 *   with a sentence-ending mark, though, we'll treat the next clause as a
 *   sentence opener again.  
 */
class Production: object
    /* get the original text of the command for this match */
    getText()
    {
        /* if we have no token list, return an empty string */
        if (tokenList == nil)
            return '';
        
        /* build the string based on my original token list */
        return cmdTokenizer.buildOrigText(getTokens());
    }

    /* get my original token list, in canonical tokenizer format */
    getTokens()
    {
        /* 
         *   return the subset of the full token list from my first token
         *   to my last token
         */
        return nilToList(tokenList).sublist(
            firstTokenIndex, lastTokenIndex - firstTokenIndex + 1);
    }

    /*
     *   Build the command for this production and its children.  By
     *   default, we'll simply traverse into our children.  
     */
    build(cmd, np)
    {
        /* if this is a sentence-ending mark, note it */
        if (endOfSentence)
            noteEndOfSentence(cmd, self);

        /* run through our list of matches */
        local info = grammarInfoForBuild();
        for (local i = 2, local len = info.length() ; i <= len ; ++i)
        {
            /* get the current match */
            local cur = info[i];

            /* process it based on its type */
            switch (dataType(cur))
            {
            case TypeSString:
                /* it's a literal token match item */
                visitLiteral(cmd, np, cur);
                break;

            case TypeObject:
                /* 
                 *   An object is a production sub-tree.  Set its parent
                 *   pointer to point back at us. 
                 */
                cur.parent = self;

                /* visit the production */
                visitProd(cmd, np, cur);
                break;
            }
        }

        /* 
         *   If there's a determiner, apply it the noun phrase.  Apply the
         *   determiner after building out the subtree, so that the parent
         *   determiner overrides any found in the subtree. 
         */
        if (determiner != nil)
            np.determiner = determiner;
    }

    /*
     *   Get the grammar match list for build() purposes.  By default, this
     *   simply returns the grammarInfo() results, which are automatically
     *   generated by the compiler to return a list of the "->prop" values from
     *   the matched grammar rule.  Some rules might want to modify that default
     *   value list, so we provide this routine as an override hook.
     */
    grammarInfoForBuild()
    {
        return grammarInfo();
    }
    
    
    /*
     *   Add a new NounPhrase item to the list under construction.  Certain
     *   productions are associated with specific functional slots in the
     *   abstract command - direct object, indirect object, EXCEPT list,
     *   etc.  This routine is for such production subclasses to override,
     *   to direct new noun phrases into the appropriate slot lists.  In a
     *   grammar, the functional role is typically at a higher level in the
     *   tree, with ordinary noun phrases plugged in underneath.
     *   
     *   Our default handling is to first check our nounPhraseRole
     *   property; if it's set, it tells us the role that this sub-tree
     *   plays in the predicate (direct object, indirect object,
     *   accessory).  We use that information to add the new NounPhrase to
     *   the Command list that we're building for our assigned role.
     *   
     *   If nounPhraseRole is nil, then we simply pass the request up to
     *   our parent.  Eventually we should reach a node encoding the
     *   function slot.  
     */
    addNounListItem(cmd, prod)
    {
        if (nounPhraseRole != nil)
            return cmd.addNounListItem(nounPhraseRole, prod);
        else
            return parent.addNounListItem(cmd, prod);
    }

    /* 
     *   Note an end-of-sentence marker.  We'll simply notify our parent by
     *   default. 
     */
    noteEndOfSentence(cmd, prod)
    {
        if (parent != nil)
            parent.noteEndOfSentence(cmd, prod);
    }

    /*
     *   The NounPhrase subclass to use for noun phrases within this
     *   sub-tree.  By default, we look to our parent; if we don't have a
     *   parent, we use the base NounPhrase class.
     *   
     *   Special phrase types (topics, literals, and numbers) have their
     *   own NounPhrase subclasses.  This is important because the
     *   resolution rules for these phrase types differ from the regular
     *   object resolution rules.  
     */
    npClass = (parent != nil ? parent.npClass : NounPhrase)

    /*
     *   My assigned noun phrase role, as a NounRole object.  This must be
     *   explicitly set for the top node in a noun slot (which can be a
     *   noun list production, a single noun production, a topic, etc).
     *   
     *   In a positional language grammar, the predicate production will
     *   mark its immediate child in each noun phrase slot by setting this
     *   according to the role that the sub-tree plays in the predicate
     *   grammar.  Non-positional languages that use grammatical case or
     *   other ways of encoding the role information must set this some
     *   other way.  
     */
    nounPhraseRole = nil

    /*
     *   Get our noun phrase role.  If we don't have a role defined
     *   directly, we'll inherit the role from our parent node. 
     */
    getNounPhraseRole()
    {
        return nounPhraseRole != nil
            ? nounPhraseRole : parent.nounPhraseRole;
    }

    /* 
     *   Visit a literal token child in our sub-tree.  This is called
     *   during the build process for each literal token in our child list.
     *   By default, we add the token to the command's current noun phrase.
     */
    visitLiteral(cmd, np, tok)
    {
        /* add the literal to the current noun phrase */
        if(np != nil)
           np.addLiteral(tok);
    }

    /* 
     *   Visit a production object in our list.  This is called during the
     *   build process for each production object in our child list.  By
     *   default, we simply build the child production recursively.  
     */
    visitProd(cmd, np, prod)
    {
        /* build out this production recursively */
        prod.build(cmd, np);
    }

    /*
     *   The determiner that this production applies to the noun phrase
     *   it's part of, as a Determiner object.  If this is non-nil, this
     *   Determiner will be set in the current NounPhrase when we visit
     *   this production in the build process.  
     */
    determiner = nil

    /* 
     *   My parent production.  The low-level GrammarProd mechanism doesn't
     *   set this up, so we set it up ourselves in the course of building
     *   out the tree.  In build(), just before we visit each
     *   sub-production, we set the sub-production's 'parent' property to
     *   point back to the parent production.  This property is therefore
     *   always set while we're traversing the child's tree, but won't
     *   necessarily be set yet if we're not currently working somewhere
     *   within the child's tree.  That means that you can always look at
     *   'parent' within your own build() routine or a child build()
     *   routine, but you can't necessarily look at it across the tree or
     *   within your own children.  
     */
    parent = nil

    /* 
     *   Find a parent matching a given test.  We'll scan up the parent
     *   tree, looking for the nearest parent p for which func(p) returns
     *   true, returning p.  If we can't find one, we return nil.  
     */
    findParent(func)
    {
        /* find the nearest parent that passes the callback test */
        local par;
        for (par = parent ; par != nil && !func(par) ; par = par.parent) ;

        /* return what we found */
        return par;
    }

    /* Is this production a child of the given production? */
    isChildOf(prod)
    {
        /* look up my parent tree for the given parent */
        for (local par = parent ; par != nil ; par = par.parent)
        {
            /* if this is the one we're looking for, we're a child */
            if (par == prod)
                return true;
        }

        /* didn't find it */
        return nil;
    }

    /*
     *   Find the action.  This finds the child of type VerbProduction,
     *   then retrieves the action from the verb production.  
     */
    findAction()
    {
        /* find the VerbProduction among our children */
        local vp = findChild(VerbProduction);

        /* return the action from the VerbProduction, if we found it */
        return (vp != nil ? vp.action : nil);
    }

    /*
     *   Find a child of a given class. 
     */
    findChild(cls)
    {
        /* if I'm of the desired class, we're done */
        if (ofKind(cls))
            return self;

        /* recursively scan my children */
        for (local gi = grammarInfo(), local i = 2, local len = gi.length() ;
             i <= len ; ++i)
        {
            /* try this child */
            local chi = gi[i].findChild(cls);
            if (chi != nil)
                return chi;
        }

        /* didn't find it */
        return nil;
    }
;

/*
 *   CommandProduction is a special Production subclass for the top-level
 *   grammar rule for the overall command. 
 *   
 *   Each instance of this type of production must define the following
 *   '->' properties in its syntax template:
 *   
 *   actor_ is the noun phrase giving the addressee of the command, if any.
 *   A command such as TELL ACTOR TO DO X or (using the long-standing IF
 *   convention) ACTOR, DO X addresses a command to an actor; i.e., it
 *   tells the actor to carry out the command, rather than the player's
 *   avatar.  A command that isn't addressed to an actor can leave actor_
 *   as nil.  
 *   
 *   cmd_ is the *first* predicate phrase (see below), in the desired order
 *   of execution.  For example, for "open the door and go north", cmd_
 *   should be set to the match tree for the "open the door" predicate.
 *   
 *   conj_ is any conjunction or punctuation ending the first predicate
 *   phrase.  This might be a period at the end of the sentence, or a word
 *   like 'and' or 'then' that can separate multiple commands.  This can be
 *   nil if there's no conjunction at all (such as when the whole command
 *   is just the first predicate).  The reason we need conj_ is that it
 *   tells us where any subsequent command on the same command line starts.
 *   If cmd2_ is not nil, we'll ignore conj_ and use cmd2_ instead for this
 *   purpose.
 *   
 *   cmd2_ is optional: it's the *second* predicate phrase.  If this is not
 *   nil, it tells the parser where to start parsing the next predicate on
 *   the same command line after finishing with the first one.  This is
 *   optional, even if the command line really does have more than one
 *   predicate, because the parser can use conj_ instead to infer where the
 *   second predicate must start.
 *   
 *   (It's probably intuitively obvious what "first predicate" means, but
 *   for the sake of translators, here's a more thorough analysis.  Some
 *   command productions can match more than one predicate phrase, but this
 *   is only for the sake of determining where the first one ends,
 *   syntactically.  The execution engine actually only carries out the
 *   first predicate matched for a given parse tree - it simply ignores any
 *   others in the same tree.  After we finish executing the first
 *   predicate from the match, we go back and re-parse the remaining text
 *   from scratch, as raw text; at that point, the next predicate in the
 *   text becomes the first predicate in the new parse tree and gets
 *   executed.  We repeat this until we run out of text.  So we do
 *   eventually execute everything the player types in - but not on the
 *   first parse; we have to do one parse per predicate.  We have to repeat
 *   the parsing because carrying out the first action could change the
 *   game state in such a way that we'll find a different match to the next
 *   predicate than we would have if we'd parsed everything up front.  By
 *   "first predicate phrase", then, we mean the one that gets executed
 *   first.  The point is to carry out the user's wishes as expressed in
 *   the command, so we want the first predicate we execute to be the one
 *   that the player *intends* to be carried out first; so by "first" we
 *   really mean the one that a speaker of the natural language would
 *   expect to be performed first, given the structure of the sentence and
 *   the rules of the language.  In English, this is easy: X THEN Y or X,Y
 *   or X AND Y all mean "first do X, then do Y" - the reading order is the
 *   same as the execution order.)  
 */
class CommandProduction: Production
    /* -> property: the match tree for the addressee, if any */
    actor_ = nil

    /*
     *   The grammatical person of the actor to whom we're giving orders.
     *   This is 2 for second person and 3 for third person.  (It's not
     *   meaningful to give orders in the first person.)
     *   
     *   In English (and probably most languages), commands of the form
     *   ACTOR, DO SOMETHING address ACTOR in the second person.  In
     *   contrast, TELL ACTOR TO DO SOMETHING gives orders to ACTOR, but in
     *   the third person.
     *   
     *   In the second-person form of giving orders, second-person pronouns
     *   (YOU, YOURSELF) within the command will refer back to the actor
     *   being addressed: BOB, EXAMINE YOURSELF tells Bob to look at Bob.
     *   In the indirect form, YOU refers to the player character: TELL BOB
     *   TO EXAMINE YOU tells Bob to look at the PC.
     *   
     *   The default is 2, since the long-standing IF convention is the
     *   ACTOR, DO SOMETHING format.  Override this (to 3) for TELL TO
     *   grammar rules.  
     */
    actorPerson = 2
    
    /* build the tree */
    build(cmd, np)
    {
        /* if we're giving orders, tell the command which person they're in */
        if (actor_ != nil)
            cmd.actorPerson = actorPerson;

        /* 
         *   if we have a second predicate or a conjunction, note where the
         *   second predicate starts 
         */
        if (cmd2_ != nil)
        {
            /* 
             *   we have a second predicate production, so the second
             *   predicate starts with the first token of that production 
             */
            cmd.nextTokens = tokenList.sublist(cmd2_.firstTokenIndex);
        }
        else if (conj_ != nil)
        {
            /* 
             *   we don't have an explicit second predicate production, but
             *   we do have a conjunction, so the second predicate must
             *   start at the next token after the conjunction 
             */
            cmd.nextTokens = tokenList.sublist(conj_.lastTokenIndex + 1);
        }

        /* do the normal work */
        inherited(cmd, np);
    }

    /* visit a production */
    visitProd(cmd, np, prod)
    {
        /* if this is the actor, create a NounPhrase for the actor role */
        if (prod == actor_)
            np = cmd.addNounListItem(ActorRole, prod);

        /*
         *   If this is the first predicate, actor, or conjunction, build
         *   it out as usual; otherwise ignore it.  We specifically don't
         *   want to build out any command processing for a second or
         *   subsequent predicate, because we only execute the first
         *   predicate in a parse tree.  
         */
        if (prod is in (cmd_, actor_, conj_))
        {
            /* expand the token extent to include this phrase */
            if (prod.lastTokenIndex > cmd.tokenLen)
                cmd.tokenLen = prod.lastTokenIndex;

            /* do the normal work */
            inherited(cmd, np, prod);
        }
    }

    /* note the end of the sentence */
    noteEndOfSentence(cmd, prod)
    {
        /* 
         *   if the production is within the conjunction, the command ends
         *   the sentence 
         */
        if (prod == conj_ || prod.isChildOf(conj_))
            cmd.endOfSentence = true;
    }
;

/*
 *   A NounRole is a internal parser object that provides information on a
 *   given noun role in a predicate.
 *   
 *   A noun role is one of the standard semantic roles that a noun phrase
 *   can play in a natural language predicate.  A predicate is a
 *   combination of an action and the objects that it applies to.  Any
 *   given verb has a set of assigned roles that need to be filled to make
 *   a complete thought.  (Sometimes the same verb word has multiple senses
 *   with different numbers of slots to fill, but you can think of the
 *   different senses as actually being different actions at some abstract
 *   level, which all happen to share the same verb word.)  For example,
 *   TAKE requires a noun phrase telling us which object is to be taken;
 *   this is called the direct object of the verb.  PUT X IN Y has a direct
 *   object (the thing to be put somewhere) and an indirect object (the
 *   place to put it).
 *   
 *   Natural languages use a fairly small number of these noun roles.  Most
 *   predicates in most languages have just one role: TAKE, DROP, OPEN,
 *   CLOSE.  We call this first-and-only noun role the direct object.  A
 *   few predicates have two roles: PUT IN, GIVE TO, UNLOCK WITH.  We call
 *   the second role the indirect object.  A very few predicates have three
 *   roles: TRADE BOB AN APPLE FOR AN ORANGE, PUT PLUTONIUM IN REACTOR WITH
 *   TONGS.  We call the third role the "accessory" object (which is
 *   something we made up - there doesn't seem to be an agreed-upon word
 *   among linguists for this role).  And it appears that there's simply no
 *   such thing as a "tetratransitive" verb in any natural human language,
 *   so we don't bother defining a fourth slot.
 *   
 *   (It would be easy for a game to add an object defining a fourth slot,
 *   analogous with these others, and use it to include a fourth noun
 *   phrase in the grammar for applicable verbs.  The rest of the parser
 *   will pick it up automatically if you do.  However, the practical
 *   utility of this seems minimal.  *Three*-noun verbs are incredibly rare
 *   in IF, in part because situations requiring them are rare, and in part
 *   because they're almost guaranteed to vex players and be panned as
 *   guess-the-syntax puzzles.  One can only imagine how a *four*-noun
 *   command would be received.)  
 */
class NounRole: object
    /* 
     *   The -> property slot in the predicate grammar that's assigned to
     *   this role.  This is the property that predicate grammar rules
     *   assign for the match tree for a noun phrase taking this role.
     */
    matchProp = nil

    /* the NounPhrase list property in the Command object for this role */
    npListProp = nil

    /* the object match list property in the Command object for this role */
    objListProp = nil

    /* the property in the Command for the *current* item being executed */
    objProp = nil

    /* the property in the Command for the current item's NPMatch */
    objMatchProp = nil

    /*
     *   Is this a predicate noun phrase role?  This is true for roles that
     *   serve as objects of a verb: direct object, indirect object,
     *   accessory.  This is nil for non-predicate roles, such as the
     *   addressee actor. 
     */
    isPredicate = true

    /* 
     *   the predicate match object property that gives the grammar rule
     *   for parsing a reply to a missing noun question for this role 
     */
    missingReplyProp = nil

    /* 
     *   name - this is an ID string that we use internally for embedding
     *   the role in things like verb template strings 
     */
    name = ''

    /*
     *   Internal sequence number.  This tells us the order in which this
     *   role appears in lists (including argument lists) when we store
     *   lists of roles.  
     */
    order = 1000

    /* class property: master list of all roles */
    all = []

    /* class property: master list of all predicate roles */
    allPredicate = []

    /* on construction, populate the various maps */
    construct()
    {
        /* add it to our master list of roles */
        NounRole.all += self;

        /* add it to the master list of predicate roles, if applicable */
        if (isPredicate)
            NounRole.allPredicate += self;
        
        /* keep the lists in sorted order */
        NounRole.all = NounRole.all.sort(SortAsc, { a, b: a.order - b.order });
        NounRole.allPredicate = NounRole.allPredicate.sort(
            SortAsc, { a, b: a.order - b.order });
    }
;

/*
 *   The DirectObject role is the role of the object being most directly
 *   acted upon in the command.  The is the only role in a verb that has
 *   only one object.  In a verb with two objects, this is the object most
 *   directly affected.  For example, UNLOCK DOOR WITH KEY directly acts
 *   upon the door, so the door is the direct object; the key isn't the
 *   direct object because it's merely a tool used to effect the change on
 *   the door.  
 */
DirectObject: NounRole
    matchProp = &dobjMatch
    npListProp = &dobjNPs
    objListProp = &dobjs
    objProp = &dobj
    objMatchProp = &dobjInfo
    missingReplyProp = &dobjReply
    curObjProp = &curDobj
    name = 'dobj'
    order = 1
;

/*
 *   The IndirectObject role is the role of a secondary object that is used
 *   in the command, but isn't the primary object being acted upon.  This
 *   is usually a tool (UNLOCK dobj WITH iobj), a destination (PUT dobj IN
 *   iobj), or a topic (ASK dobj ABOUT iobj). 
 */
IndirectObject: NounRole
    matchProp = &iobjMatch
    npListProp = &iobjNPs
    objListProp = &iobjs
    objProp = &iobj
    objMatchProp = &iobjInfo
    missingReplyProp = &iobjReply
    curObjProp = &curIobj
    name = 'iobj'
    order = 2
;

/*
 *   The AccessoryObject role is for a *third* object, beyond the direct
 *   and indirect objects, involved in the command.  This might be a peer
 *   to the indirect object in an exchange (TRADE dobj AN iobj FOR AN
 *   accessory), but the canonical IF use is as a tool in a two-object
 *   operation (PUT dobj IN iobj WITH accessory, WRITE dobj ON iobj WITH
 *   accessory).  
 */
AccessoryObject: NounRole
    matchProp = &accMatch
    npListProp = &accNPs
    objListProp = &accs
    objProp = &acc
    objMatchProp = &accInfo
    missingReplyProp = &accReply
    curObjProp = &curAobj
    name = 'acc'
    order = 3
;

/*
 *   ActorRole is a special role for the addressee of a command (BOB, GO
 *   NORTH, or TELL BOB TO GO NORTH).  This doesn't appear as part of a
 *   predicate structure, so there's no matchProp, but it is used within
 *   the Command.  
 */
ActorRole: NounRole
    npListProp = &actorNPs
    objListProp = &actors
    objProp = &actor
    isPredicate = nil
;


/*
 *   VerbProduction is a special Production subclass for verb (predicate)
 *   rules.  This production has special processing for building out the
 *   object phrases making up the verb.
 *   
 *   Each instance should have an 'action' property giving the Action
 *   object associated with the verb rule.  This is the Action that will be
 *   performed when the parser matches the command input to the rule.
 *   
 *   Some languages, such as English, have "positional" predicate grammars.
 *   This means that the position of a noun phrase in the sentence
 *   determines its role (direct object, indirect object, etc).  In the
 *   grammar for a positional language, each predicate rule simply needs to
 *   plug in a singleNoun or nounList production (as appropriate) in each
 *   noun phrase position, with its '->' property set to correspond to the
 *   role: ->dobjMatch for a direct object, ->iobjMatch for an indirect
 *   object, and ->accMatch for an accessory object.
 *   
 *   Some languages, such as German and Latin, identify the role of a noun
 *   phrase using grammatical case.  This means that the articles change
 *   form in the different roles, or that the nouns themselves are
 *   inflected (they have different forms, such as added suffixes)
 *   according to role.  Case languages tend to have flexible predicate
 *   word order, because the case markers tell you the role of each noun
 *   even if the nouns are rearranged.  For this reason, it can be tedious
 *   to write a grammar for a case language the way we do for English,
 *   where the word ordering for a given verb is so rigid that we can
 *   easily just write out each possible phrasing manually.  For a case
 *   language, you'll probably instead want to write a set of generic verb
 *   rules that cover *all* verbs (i.e., you leave the verb word itself as
 *   a sub-production) in all of the different word orders, and use the
 *   case tagging in the language to determine the role of each noun
 *   phrase.  For this style of grammar, the grammar must set the property
 *   nounPhraseRole in the top-level rule for each noun phrase case; set
 *   this to DirectObject, IndirectObject, AccessoryObject, etc., according
 *   to the role denoted by the case.
 *   
 *   Still other languages, such as Japanese, use particles (grammar
 *   function words) to denote the role of each noun phrase in the
 *   sentence.  This is similar to grammatical case, but the role
 *   information is encoded in separate words (the particles) rather than
 *   in noun affixes, so the nouns themselves aren't inflected.  You can
 *   handle this type of language roughly the same way you'd handle a case
 *   language.  Create generic rules that cover all verbs, then create a
 *   grammar rule for each particle-plus-noun structure.  In each
 *   particle-plus-noun phrase's top-level rule, set the nounPhraseRole
 *   property to the appropriate role object (DirectObject, etc).  
 */
class VerbProduction: Production
    /*
     *   The "priority" of this grammar rule.  This is a contributor to the
     *   Command priority - see Command.priority for an explanation of how
     *   that's used.
     *   
     *   The predicate priority is a small number, 0-99.  The default is
     *   50, which should apply to most normal, complete verb phrases.  For
     *   incomplete phrases (with a missing object, which will force the
     *   parser to assume a default or ask the player for the missing
     *   information), use 25.  Other values are for fine-tuning as needed
     *   in the individual grammar rules.  A higher value means higher
     *   priority.  
     */
    priority = 50

    /* 
     *   Do we want to consider this production to be active; we may want some
     *   VerbRules to be active only under certain circumstanes.
     */
    isActive = true
    
    /* build the command */
    build(cmd, np)
    {
        /* set the action and the predicate phrase priority in the command */
        cmd.action = action;
        cmd.verbProd = self;
        cmd.predPriority = priority;
        cmd.predActive = isActive;;

        /* do the standard work */
        inherited(cmd, np);

        /* if we have a structurally empty slot, note it in the command */
        if (missingRole != nil)
            cmd.emptyNounRole(missingRole);
    }

    /*
     *   Visit a production during the build process.  If this is one of
     *   our noun phrase slots, we tell the command to add a new noun
     *   phrase of this type, and make it the current phrase; then we
     *   recursively build out this child to populate the new noun phrase.
     */
    visitProd(cmd, np, prod)
    {
        /* 
         *   If this is a special phrase slot, mark the child with its
         *   role.  This is necessary for positional languages, where the
         *   role of a noun phrase is determined by its position in the
         *   predicate.  Grammars for these languages use generic rules
         *   that apply to any noun phrases, so the match objects don't
         *   know what role they have until they find out their position in
         *   the parent grammar.  We're the parent grammar, so when we see
         *   one of these special positional markers, we need to pass the
         *   information down to the sub-tree here.  
         */
        local r;
        if ((r = NounRole.all.valWhich(
            {x: x.matchProp != nil && self.(x.matchProp) == prod})) != nil)
        {
            /* set the role in the sub-tree */
            prod.nounPhraseRole = r;

            /* start a new noun phrase for the role */
            np = prod.addNounListItem(cmd, prod);
        }

        /* 
         *   build out the child, in the context of the noun phrase we
         *   decided upon 
         */
        prod.build(cmd, np);
    }

    /*
     *   The parser calls answerMissing() when the player answers a query
     *   for a missing noun phrase in the last command.  There's nothing
     *   that needs to happen here, and by default we do nothing; this is
     *   purely advisory.  This routine gives the language module a chance
     *   to alter the command according to the reply, if necessary.  
     */
    answerMissing(cmd, np) { }
;

/*
 *   NounListProduction is a special Production subclass for lists
 *   including more than one noun.
 *   
 *   Each instance should have two '->' properties: np1_ and np2_.  These
 *   should be set to the match sub-tree for the first and second elements
 *   of the noun list (respectively).  Note that we assume there are only
 *   two elements in each list grammar item - this isn't because we want to
 *   limit noun lists to two elements, but rather because we assume that
 *   the grammars for longer lists will be constructed recursively out of
 *   two-element nodes: A, B, C, D becomes something along the lines of
 *   List(a, List(b, List(c, List(d)))).  
 */
class NounListProduction: Production
    /*
     *   Visit a production during the build process.  When parsing the
     *   second element, we'll add a new NounPhrase to the current slot's
     *   list.  
     */
    visitProd(cmd, np, prod)
    {
        /* if this is the second list item, add a new NounPhrase for it */
        if (prod == np2_)
            np = prod.addNounListItem(cmd, prod);

        /* for noun phrases, tell the noun phrase about the production */
        if (prod is in (np1_, np2_))
            np.prod = prod;

        /* do the normal work */
        inherited(cmd, np, prod);
    }
;

/*
 *   A BadListProduction is a Production subclass for a noun list written
 *   in a slot intended for a single noun only.  This isn't really a
 *   grammatical error, so the language grammar will probably want to
 *   include a rule for this.  However, it *is* a semantic error; rules
 *   that are written for single objects are written that way because we
 *   can't make sense of a list there.  For example, PUT BOOK IN BOX AND
 *   BAG would be nonsensical to us, because we can't put something in two
 *   places at once.  This class can be used for a grammar rule that parses
 *   a list where a single noun is required; we'll flag it with an
 *   explanatory error message for the user.  
 */
class BadListProduction: Production
    build(cmd, np)
    {
        /* mark the command with the list-in-single-slot error */
        cmd.badMulti = getNounPhraseRole();

        /* do the normal work */
        inherited(cmd, np);
    }
;

/*
 *   ExceptListProduction is a Production subclass for EXCEPT lists.  This
 *   is a slot in the grammar that holds a list of objects excepted from
 *   some set, as in ALL BUT THE RED BOOK or THE COINS EXCEPT THE PENNIES.
 */
class ExceptListProduction: Production
    /*
     *   Build this phrase.  Our sub-tree is a noun list that's to be
     *   excluded from the current noun phrase under construction, 'np';
     *   this exclusion list is a type of qualifier.  So, we (a) start an
     *   exception qualifier for 'np', (b) make that list the current noun
     *   phrase within our sub-tree, then (c) do the normal work to build
     *   out our sub-tree, but using the new context.  
     */
    build(cmd, np)
    {
        /* 
         *   remember the noun phrase that we qualify - this is simply the
         *   parent noun phrase, np 
         */
        qualifiedNP = np;

        /* 
         *   start an exclusion list for the noun phrase; make the first
         *   element of the new list the active noun phrase for our
         *   sub-tree 
         */
        np = np.addExclusionItem(self);

        /* do the normal work */
        inherited(cmd, np);
    }

    /*
     *   Add a noun list item.  List items within our sub-tree go into the
     *   exclusion list for the parent noun phrase that we qualify.  
     */
    addNounListItem(cmd, prod)
    {
        /* add a new noun phrase to the qualified NP's exclusion list */
        return qualifiedNP.addExclusionItem(prod);
    }

    /* the noun phrase we qualify */
    qualifiedNP = nil
;

/*
 *   CoreNounPhraseProduction is a Production subclass for the "core" noun
 *   phrase grammar for noun phrases with object vocabulary.  This is the
 *   part of the grammar that matches the basic name of an object, after
 *   all qualifiers (such as articles, possessives, and quantifiers) have
 *   already been dealt with.  This is the part of the phrase that contains
 *   the vocabulary words for game-world objects.
 *   
 *   This class is only needed for noun phrases with object vocabulary
 *   words.  It captures the unqualified core of the phrase as entered by
 *   the user, mostly for reiteration in error messages from the parser.
 *   It's not necessary to define rules of this class for noun phrases that
 *   don't have object vocabulary words (e.g., pronouns, ALL).  
 */
class CoreNounPhraseProduction: Production
    build(cmd, np)
    {
        /* note in the NounPhrase that this is the core noun phrase */
        np.coreProd = self;

        /* do the normal work */
        inherited(cmd, np);
    }
;

/*
 *   EmptyNounProduction is a Production subclass for a grammar rule that
 *   matches no tokens where a noun phrase would ordinarily go.  
 */
class EmptyNounProduction: Production
    build(cmd, np)
    {
        /* mark the noun phrase role as empty in the command */
        cmd.emptyNounRole(np.role);
    }
;

/*
 *   NumberNounProduction is a Production subclass for a number that serves
 *   as a direct, indirect, or accessory object.  
 */
class NumberNounProduction: Production
    /* we use the NumberPhrase subclass for a noun phrase entries */
    npClass = NumberPhrase
;

/*
 *   TopicNounProduction is a Production subclass for a topic that serves
 *   as a direct, indirect, or accessory object.  
 */
class TopicNounProduction: Production
    /* we use the TopicPhrase subclass for a topic phrase entry */
    npClass = TopicPhrase
;

/*
 *   LiteralNounProduction is a Production subclass for a literal phrase
 *   that serves as a direct, indirect, or accessory object. 
 */
class LiteralNounProduction: Production
    /* we use the LiteralPhrase subclass for a noun phrase entries */
    npClass = LiteralPhrase
;

/*
 *   PronounProduction is a Production subclass for pronoun phrases.
 *   Each instance should set the property 'pronoun' to a Pronoun object
 *   giving the pronoun role for the phrase.  
 */
class PronounProduction: Production
    /*
     *   Build the phrase.  We'll add our pronoun association to the
     *   current noun phrase.  (We'll also build out any sub-tree, although
     *   in nearly all cases a pronoun phrase is just a literal and won't
     *   have a sub-tree.) 
     */
    build(cmd, np)
    {
        /* set the pronoun association in the noun phrase */
        np.pronoun = pronoun;

        /* do the normal work */
        inherited(cmd, np);
    }
;

/*
 *   A PossessiveProduction is a production subclass for possessive
 *   qualifier phrases ("John's", "my").  When we build out this
 *   production's contribution to the command, we add a separate NounPhrase
 *   object for it, as a possessive qualifier to the current noun phrase.  
 */
class PossessiveProduction: Production
    /*
     *   Build the phrase.  We'll build out our sub-tree as normal, except
     *   that we'll assign its output to a new NounPhrase, which we attach
     *   as a possessive qualifier to the encompassing noun phrase under
     *   construction.  
     */
    build(cmd, np)
    {
        /* 
         *   create a new noun phrase to serve as a possessive qualifier to
         *   the current noun phrase 
         */
        np = np.addPossessive(self);

        /* if I have a pronoun, set it in the noun phrase */
        np.pronoun = pronoun;

        /* do the normal work in the context of the possessive qualifier */
        inherited(cmd, np);
    }
;

/*
 *   ContentsQualifierProduction is a subclass of Production for phrases
 *   that involve contents qualifiers, as in "the bucket of water".
 *   
 *   Each grammar rule of this type needs to define two special '->'
 *   associations in its template:
 *   
 *   cont_ is the contents qualifier.  This is also just an ordinary noun
 *   phrase.  This is the "water" part in "bucket of water".
 *   
 *   prep_ is the preposition giving the relationship. 
 */
class ContentsQualifierProduction: Production
    /*
     *   Visit a production.  When we process the contents qualifier
     *   phrase, we'll build out the sub-tree in the context of a new
     *   NounPhrase, which we attach as a contents qualifier to the
     *   encompassing noun phrase under construction.  
     */
    visitProd(cmd, np, prod)
    {
        /* 
         *   if this sub-production is the contents qualifier phrase,
         *   create a new locational qualifier for it 
         */
        if (prod == cont_)
            np = np.addContents(prep_.getText(), prod);

        /* do the normal work in the context of the noun phrase we set up */
        inherited(cmd, np, prod);
    }
;
    

/*
 *   LocationalProduction is a subclass of Production for phrases that
 *   involve locational qualifiers, as in "the book on the table".
 *   
 *   Each grammar rule of this type needs to define two special '->'
 *   associations in its template:
 *   
 *   cont_ is the locational qualifier.  This is also just an ordinary noun
 *   phrase.  This is the "the table" part in "the book on the table".
 *   
 *   prep_ is the preposition production.  This should be *or* contain a
 *   LocationTypeProduction match, which tells us the type of containment
 *   relationship specified by the grammar.  *Alternatively*, you can
 *   define locType directly on this production.  This specifies a LocType
 *   object giving the containment relationship.  
 */
class LocationalProduction: Production
    /*
     *   Visit a production.  When we process the locational qualifier
     *   phrase, we'll build out the sub-tree in the context of a new
     *   NounPhrase, which we attach as a locational qualifier to the
     *   encompassing noun phrase under construction.  
     */
    visitProd(cmd, np, prod)
    {
        /* 
         *   if this sub-production is the locational qualifier phrase,
         *   create a new locational qualifier for it 
         */
        if (prod == cont_)
        {
            /* add the location to the noun phrase */
            np = np.addLocation(locType, prod);

            /* 
             *   explicitly build out the preposition sub-tree in the
             *   context of the locational noun phrase - this ensures that
             *   the locType gets set for the locational rather than the
             *   base noun phrase 
             */
            if (prep_ != nil)
                prep_.build(cmd, np);
        }            

        /* 
         *   we already built the prep_ subtree explicitly in build(), so
         *   we can ignore it if we're hitting it again here 
         */
        if (prod == prep_)
            return;

        /* do the normal work in the context of the noun phrase we set up */
        inherited(cmd, np, prod);
    }

    /*
     *   Our location type.  This is a LocType object giving the location
     *   relationship specified by this locational phrase.  For languages
     *   that special locational phrases prepositionally, this will be set
     *   by the LocationPrepProduction in our sub-tree.  For languages that
     *   use case inflection to specify the type of relationship, this must
     *   be set by the noun phrase sub-tree instead.  
     */
    locType = nil
;

/*
 *   A LocationPrepProduction is a special Production type for phrases that
 *   encode the preposition of a locational phrase.  This is only needed in
 *   languages that use prepositional grammar to express location
 *   relationships.  For languages that use noun case inflection, the
 *   relationship will have to be inferred from the case grammar of the
 *   noun phrase (such as noun affixes or articles), and the noun phrase
 *   production will have to set the locType in the LocationalProduction.
 *   
 *   Set the locType property to the LocType object corresponding to the
 *   location relationship of the preposition.  
 */
class LocationPrepProduction: Production
    /* our location relationship type, as a LocType object */
    locType = nil

    /* 
     *   on building the production, set the locType in our
     *   LocationalProduction parent 
     */
    build(cmd, np)
    {
        /* set the location type on the noun phrase */
        np.locType = locType;
        
        /* do the normal work */
        inherited(cmd, np);
    }
;    

/*
 *   QuantifierProduction is a subclass of Production for phrases that add
 *   a number qualifier, as in "five books".
 *   
 *   Each grammar rule of this type needs to define a special '->quant_'
 *   association in its template, giving the quantity phrase production.
 *   This phrase must in turn provide a 'numval' property giving its
 *   numeric value. 
 *   
 *   Alternatively, this production can itself simply provide a 'numval'
 *   property with the correct number.  This is convenient for
 *   adjective-like qualifier phrases that imply a number without stating
 *   one directly, such as BOTH BOOKS.  
 */
class QuantifierProduction: Production
    /*
     *   Build out the subtree.  If we have a numval embedded in this
     *   production, we'll use it as the quantifier.  Otherwise, we'll
     *   expect to find a separate quant_ sub-production among our
     *   children, and that it provides the quantity.  
     */
    build(cmd, np)
    {
        /* if we have our own numval value, apply it as the quantifier */
        if (numval != nil)
            np.addQuantifier(numval);

        /* do the normal work */
        inherited(cmd, np);
    }

    /*
     *   Visit a production.  When we visit the quantifier phrase, we'll
     *   handle it specially: we'll add the quantifier value to the main
     *   noun phrase, and then we *won't* parse into the subtree.  There's
     *   no need to parse the quantifier subtree, as its entire meaning is
     *   captured in its numeric value.  Parsing into it is undesirable
     *   because that would add the numeric tokens to the noun phrase -
     *   they don't belong there, since their qualification is captured in
     *   the quantifier and shouldn't also be added as adjectives.  
     */
    visitProd(cmd, np, prod)
    {
        /* if this is the quantifier, handle it specially */
        if (prod == quant_)
        {
            /* add the quantifier to the noun phrase */
            np.addQuantifier(prod.numval);

            /* note that we explicitly DON'T parse into the subtree */
        }
        else
        {
            /* do the inherited work */
            inherited(cmd, np, prod);
        }
    }
;

/*
 *   OrdinalProduction is a subclass of Production for ordinal phrases
 *   ("first", "second", etc).  The match object must define a method
 *   ordval() that returns the integer value of the ordinal (1 for "first",
 *   2 for "second", etc).  
 */
class OrdinalProduction: Production
    build(cmd, np)
    {
        /* apply the ordinal value to the noun phrase */
        np.addOrdinal(ordval);

        /* 
         *   we don't want to build out the subtree, since we don't want to
         *   treat literals as vocabulary words for this type of phrase 
         */
    }
;

/*
 *   MiscWordListProduction is a subclass of Production for miscellaneous
 *   word list rules.  These are grammar rules of last resort, for matching
 *   text that's positionally where a noun phrase ought to be, but which
 *   doesn't match any of our other rules for constructing a valid noun
 *   phrase.  These rules let us still recognize the overall verb-phrase
 *   structure of a command, even though we can't make sense of what goes
 *   where the nouns ought to be.  
 */
class MiscWordListProduction: Production
    build(cmd, np)
    {
        /* tell the Command that it contains a misc word list */
        cmd.noteMiscWords(np);

        /* do the normal work */
        inherited(cmd, np);
    }
;

/*
 *   An OopsProduction is a subclass for the word list part of an OOPS
 *   command.  This must have a ->toks_ property that holds the sub-tree
 *   for the literal token list of the correction. 
 */
class OopsProduction: Production
    /* 
     *   Class method: apply the correction for an OOPS command to an
     *   original token list.
     */
    applyCorrection(prod, toks, typoIdx)
    {
        /* build the tree into an OopsCommand object */
        local cmd = new OopsCommand();
        prod.build(cmd, nil);

        /* 
         *   splice the corrected tokens into the original token list,
         *   replacing the unknown word token, and return the result 
         */
        return toks.splice(typoIdx, 1, cmd.tokens...);
    }

    /* build the command */
    build(cmd, np)
    {
        /* add the token list of the correction to the command */
        cmd.tokens += toks_.getTokens();
    }
;

/*
 *   An OopsCommand is a fake Command object for building out an Oops tree.
 */
class OopsCommand: object
    /* the token list: this is filled in when we build the Oops nodes */
    tokens = []
;

/*
 *   DisambigProduction is a subclass of Production for the root of a
 *   disambiguation reply tree grammar. 
 */
class DisambigProduction: Production
    addNounListItem(cmd, prod)
    {
        /* 
         *   add the new item to the disambig reply list for the original
         *   noun phrase 
         */
        return cmd.addDisambigNP(prod);
    }
;

/*
 *   Production class for Yes-or-No phrases 
 */
property yesOrNoAnswer;
class YesOrNoProduction: Production
    build(cmd, np)
    {
        /* set the yes/no answer property in the Command */
        cmd.yesOrNoAnswer = answer;

        /* do the normal work */
        inherited(cmd, np);
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   Base class for pronouns.  We represent each type of pronoun with an
 *   object, to abstract pronouns away from the vocabulary.
 *   
 *   The base library defines a set of pronouns that are common to most
 *   languages: It, Him, Her, Them, You, Y'all, Me, and Us, plus reflexive
 *   forms of It, Him, Her, and Them.  Some languages might not employ all
 *   of these (French, for example, has no neuter gender, so there's no
 *   equivalent of It), and some might need additional pronouns (e.g.,
 *   French needs a feminine third-person plural).  If a pronoun we define
 *   here has no equivalent in a given language, the language module should
 *   simply omit any grammar mentioning it.  If the language has pronouns
 *   that aren't in the basic set, the language module can provide
 *   definitions for its own additional Pronoun objects, along with the
 *   corresponding grammar rules.
 *   
 *   The library itself only directly references one pronoun object: You.
 *   The parser specifically references this pronoun because it binds to
 *   the addressee of a command, which has a special role in the parsing
 *   process.  Apart from You, though, the library's use of pronouns is
 *   directed by the grammar: if a given Pronoun doesn't appear in the
 *   grammar anywhere, the library will never use it.  (Other than in
 *   iterations over Pronoun instances, anyway; but these will be harmless
 *   because the parser is just trying to be inclusive.)  This means that
 *   language modules are free to ignore pronouns (other than You) from the
 *   standard set when they're not a good match for the language's needs.
 *   For example, if you need distinct Animate and Inanimate forms of Him
 *   and Her, you could simply define four new Pronoun objects for these
 *   forms, and use them in place of Him and Her throughout your grammar.  
 *   
 *   Note that these objects are NOT grammar rules or dictionary words.
 *   These are abstract objects representing the "binding" of the pronouns
 *   - basically the set of grammatical attributes (gender, number) that
 *   determine whether a given noun phrase is a valid antecedent for a
 *   given pronoun.  That's why we don't define separate Pronoun objects
 *   the different grammatical cases (nominative, accusative, dative, etc):
 *   case is a feature of the grammar, and we're one step removed from that
 *   here.  
 */
class Pronoun: object
    /*
     *   Resolve the pronoun during parsing.  The usual way of doing this
     *   is to return the list of antecedents we store as part of the
     *   pronoun object.  This lets each type of pronoun store an
     *   appropriate list of antecedents.
     *   
     *   For a reflexive pronoun, return the Pronoun object for the
     *   ordinary form of the pronoun.  This tells the parser that it needs
     *   to find a match for the pronoun within the command itself, rather
     *   than looking for an external antecedent.  Second person is
     *   inherently reflexive, in that it refers to the addressee(s), so
     *   this should return 'self' for a second-person pronoun.  
     */
    resolve() { return ante; }

    /* 
     *   The grammatical person of the pronoun.  Pronouns come in three
     *   persons: first (me, us), second (you), and third (her, them).  We
     *   represent these as 1, 2, and 3.  
     */
    person = 3

    /* 
     *   Set the antecedent(s) for future pronoun usage based on the
     *   objects mentioned in the current command input or narrative
     *   output.  'obj' can be a single antecedent object, or it can be a
     *   list.  Even a singular pronoun can have a list of antecedents:
     *   some commands have more than one noun phrase, and there's no way
     *   of knowing which one the user might want to refer to with a
     *   pronoun in a future command.  We can't know until we see the
     *   context of the future pronoun use.  For example, UNLOCK DOOR WITH
     *   KEY could be followed by OPEN IT, in which case IT is probably the
     *   door; or by DROP IT, in which case IT is probably the key.  The
     *   best thing to do is to save both the door and the key as possible
     *   antecedents, so that we can choose the most suitable object when
     *   we actually see a pronoun in a subsequent command.  
     */
    setAntecedents(obj){ ante = obj; }

    /*
     *   Does this pronoun match the given object or list of objects?  By
     *   default, we won't match lists, and we'll ask the object if it
     *   thinks we're a match.  
     */
    matchObj(obj)
    {
        return !obj.ofKind(Collection) && obj.matchPronoun(self);
    }

    /* my antecedent or list of antecedents */
    ante = []

    /* 
     *   the corresponding reflexive pronoun, if any - this is set up
     *   automatically during preinit 
     */
    reflexive = nil

    /* 
     *   Class property - list of all regular Pronoun objects.  (Note that
     *   this excludes the reflexive pronouns, because the ReflexivePronoun
     *   class has its own separate 'all' list for its instances.)  
     */
    all = []

    /* on initialization, add me to the master list of pronoun objects */
    construct()
    {
        /* get the nearest class that has a master list */
        local cl = propDefined(&all, PropDefGetClass);

        /* add me to my class's master list */
        cl.all += self;
    }
;

/* It - third-person neuter singular */
It: Pronoun
;

/* Her - third-person feminine singular */
Her: Pronoun
;

/* Him - third-person masculine singular */
Him: Pronoun
;

/* Them - third-person mixed-gender plural */
Them: Pronoun
    /* 
     *   Them is a plural, so it can match a list, as well as an individual
     *   object that matches Them 
     */
    matchObj(obj)
    {
        return obj.ofKind(Collection) || obj.matchPronoun(self);
    }
;

/* 
 *   You - the second-person singular.  YOU always binds to the addressee
 *   of the command: either the player character, or the actor being given
 *   orders via a construct like ACTOR, DO THIS.
 *   
 *   Binding to the PC is grammatically correct in a first-person
 *   narration, because the PC is the narrator's ME and therefore the
 *   player's YOU.  It's less so in a second-person game: the PC is the
 *   narrator's YOU, so the player's YOU ought to be the narrator.
 *   However, some players are literal-minded about second-person
 *   narration, so rather than reflecting the narrator's YOU into the
 *   player's ME, they simply say YOU too.  Fortunately, there's not any
 *   serious ambiguity here.  The narrator is typically not a game-world
 *   object, but is an entity that exists outside the game world, so it's
 *   off-limits for discussion in commands.  So YOU can't mean the
 *   narrator.  That means that if the player uses YOU at all, they must
 *   mean the PC.  
 */
You: Pronoun
    /* 
     *   The second-person pronoun binds to information contained within
     *   the command itself, namely the addressee of the command, so we
     *   need to resolve it using the parser's "late binding" scheme.  That
     *   is, we return 'self' to tell the parser that it needs to go back
     *   and resolve this pronoun after resolving other phrases.  
     */
    resolve() 
    { 
        /* 
         *   But if no other actor has been specified, 'YOU' must mean 'ME',
         *   i.e. the player character
         */
             
        if(gCommand && gCommand.actorNPs == [] && gCommand.actorPerson == 2)
            return [gPlayerChar];
        
        return [self]; 
    }

    /* this is a second-person pronoun */
    person = 2
;

/*
 *   Y'all - the second-person plural.  ("Y'all" isn't exactly standard
 *   English, but it's as close as English comes to having a distinct
 *   plural You, and we had to call this *something*.)
 *   
 *   By default, we treat Y'all as a synonym for You, since there's rarely
 *   any reason in an IF context to distinguish them.  The main value in
 *   natural language is in group conversation, where it can be useful to
 *   clarify whether the speaker is addressing the whole group or just an
 *   individual.  In IF, though, this is never ambiguous: the addressee is
 *   either explicitly stated in the command, or it's the player character.
 *   The only thing we could do with a plural is check that the verb agrees
 *   in number, and chastise the player's sloppy grammar if not.  But that
 *   would be contrary to our general philosophy that we should be as lax
 *   as we can about the input grammar, to minimize the player's typing
 *   workload.  So our advice here is to implement a grammar rule for the
 *   various YOUs that treats all of the second-person pronoun forms as
 *   synonyms for the basic singular YOU.  
 */
Yall: Pronoun
    resolve() { return You.resolve(); }
    person = 2
;

/*
 *   Me - the first-person singular.  ME always binds to the player
 *   character.
 *   
 *   The discussion about the validity of binding YOU to the PC applies in
 *   mirror image here.  In a second-person game, the PC is the narrator's
 *   YOU and the player's ME; in a first-person game, she's the narrator's
 *   ME and the player's YOU.  But there is no game-world object for ME to
 *   bind to in commands in a first-person game - if anything, ME would be
 *   the player (not the player character, but the actual player), who is
 *   clearly not a game-world entity.  Since that's not meaningful, we can
 *   assume that a player talking about ME in a first-person game is being
 *   literal-minded and just using the same pronouns the narrator does, or
 *   that they're so accustomed to the second-person convention of most IF
 *   that they're saying ME out of habit.  In either case, the PC is the
 *   one they're talking about.  
 */
Me: Pronoun
    /* 
     *   the first person always resolves to the player character,
     *   regardless of context 
     */
    resolve() { return [libGlobal.playerChar]; }

    /* this is a first-person pronoun */
    person = 1
;

/* 
 *   Us - the first-person plural.  We throw this one in for relative
 *   completeness, but we simply treat it as a synonym for Me.  This could
 *   be useful in a game with a PC that represents a group of people (an
 *   adventuring party in a hack-n-slash game, say), or a royal personage.
 *   
 *   A more sophisticated use would be to allow the player to refer
 *   collectively to the PC and a group of accompanying NPCs.  The base
 *   library doesn't implement this because it doesn't define a way to
 *   identify such a group, but a game could add that capability.  Once
 *   you've defined what US means, you could make the pronoun US bind to
 *   that group simply by modifying the resolve() method here.  
 */
Us: Pronoun
    resolve() { return Me.resolve(); }
;


/*
 *   Base class for reflexive pronouns.  These are pronouns like "himself"
 *   that specifically refer to an antecedent in the same sentence, rather
 *   than to an earlier sentence: ASK BOB ABOUT HIMSELF is an inquiry about
 *   Bob, while ASK BOB ABOUT HIM refers to some male antecedent from an
 *   earlier statement.  
 *   
 *   Note that the first- and second-person reflexives are generally not
 *   needed in the parser.  (We define them here anyway, because they're
 *   useful for message generation.)  The third-person reflexive pronouns
 *   have distinct meanings in input from the corresponding ordinary
 *   pronouns, in that they refer to noun phrases within the same command
 *   rather than in earlier exchanges.  In contrast, the second-person
 *   pronouns have the same meaning in ordinary and reflexive forms, at
 *   least within the confines of the IF parser: EXAMINE ME and EXAMINE
 *   MYSELF mean the same thing in all typical IF command syntax.
 */
class ReflexivePronoun: Pronoun
    /* during construction, set the regular pronoun to point back at me */
    construct()
    {
        inherited();
        pronoun.reflexive = self;
    }

    /*
     *   A reflexive pronoun binds to another noun phrase contained in the
     *   same command, so we resolve using the parser's "late binding"
     *   scheme.  We invoke this by returning the ordinary (non-reflexive)
     *   pronoun object representing the attributes that we match; upon
     *   seeing this, the parser will know to come back to this pronoun
     *   after it's finished resolving earlier phrases, and look for the
     *   appropriate pronoun binding within those other phrases.  
     */
    resolve() { return pronoun; }

    /*
     *   Get the corresponding ordinary (non-reflexive) form of the
     *   pronoun.  For example, for HIMSELF we'd return HIM.  
     */
    pronoun = nil

    /* my grammatical person is the same as my underlying pronoun's */
    person = (pronoun.person)

    /* 
     *   Class property - list of all reflexive pronoun objects.  This
     *   keeps the reflexive pronouns in a separate list from the base
     *   Pronoun list.  
     */
    all = []
;

/* first-person singular reflexive */
Myself: ReflexivePronoun
    pronoun = Me
;

/* second-person singular reflexive */
Yourself: ReflexivePronoun
    pronoun = You
;

/* third-person singular neuter reflexive */
Itself: ReflexivePronoun
    pronoun = It
;

/* third-person singular feminine reflexive */
Herself: ReflexivePronoun
    pronoun = Her
;

/* third-person singular masculine reflexive */
Himself: ReflexivePronoun
    pronoun = Him
;

/* first-person plural reflexive */
Ourselves: ReflexivePronoun
    pronoun = Us
;

/* second-person plural reflexive */
Yourselves: ReflexivePronoun
    pronoun = Yall
;

/* third-person mixed-gender plural reflexive */
Themselves: ReflexivePronoun
    pronoun = Them
;


/* ------------------------------------------------------------------------ */
/*
 *   Determiners.  A determiner qualifies a noun phrase with information on
 *   how it relates to the objects it describes.  For example, "a book"
 *   refers to any book that's in scope for the discussion (in IF terms,
 *   this is usually any book that's physically present), while "the book"
 *   refers to some specific single book.
 *   
 *   Language modules can add determiners as needed.  For example, a
 *   language with grammatical gender would probably find gendered versions
 *   of Definite and Indefinite useful, to represent the use of gendered
 *   articles in input.
 */
class Determiner: object;

/* Unqualified mode ("book") */
Unqualified: Determiner;

/* Definite mode ("the book", "book", "both books", "the three books") */
Definite: Determiner;

/* Indefinite mode ("a book", "any book", "one of the books", three books") */
Indefinite: Determiner;

/* All ("the books", "all", "all of the books") */
All: Determiner;


/* ------------------------------------------------------------------------ */
/*
 *   ParseError is an Exception subclass for parsing errors. 
 */
class ParseError: Exception
    /*
     *   Display the error message
     */
    display() { "Unknown parsing error."; }

    /*
     *   Rank a spelling correction candidate for input that triggered this
     *   error on parsing.
     *   
     *   'toks' is the new token list, with the spelling correction
     *   applied; 'idx' is the index in the list of the corrected word.
     *   'dict' is the Dictionary used for parsing.
     *   
     *   Returns an integer value giving the ranking.  The ranking is used
     *   for sorting, so the scale is arbitrary - we simply take the
     *   highest ranking item.  The value 0 is special, though: it means
     *   that we should filter out the candidate and not consider it at
     *   all.  
     */
    rankCorrection(toks, idx, dict) { return 1; }

    /*
     *   Is this error allowed on a spelling correction candidate?  By
     *   default, this is nil, meaning that this error invalidates a
     *   correction candidate.  We mostly reject spelling "corrections"
     *   that result in errors because these are probably false positives:
     *   they probably replace a misspelled word with one that's in the
     *   dictionary but that's still wrong.  However, there are a few
     *   curable errors where it can make sense to keep a correction, such
     *   as an ambiguous noun phrase: that's so close to being a working
     *   command that we probably have a good correction.  
     */
    allowOnRespell = nil

    /*
     *   Is this a "curable" error?  A curable error is one that the user
     *   can fix by answering a question, such as "which one do you mean?"
     *   or "what do you want to unlock it with?"
     *   
     *   When we find more than one grammar match to an input string, the
     *   parser tries resolving each one, in order of the predicate match
     *   quality.  If one resolves without an error, the parser stops and
     *   uses that match.  But if *none* of the possible matches resolve
     *   without an error, the parser picks a match with a curable error
     *   over one with an incurable error.  
     */
    curable = nil

    /*
     *   Try curing this error with the user's answer to the error message.
     *   The parser calls this when (a) the PREVIOUS command resulted in
     *   this error, (b) this error is curable, and (c) the user typed
     *   something on the CURRENT command that didn't parse as a valid new
     *   command.  Since the new input doesn't look like a valid command,
     *   the parser calls this to determine if the input was instead meant
     *   as an answer to the question posed by the last error.
     *   
     *   If this new command is indeed a valid response to the error
     *   message, we return a CommandList with the "cured" version of the
     *   command.  This new command list should supplement the command with
     *   the new information provided by the reply.  If not, we simply
     *   return nil.  
     */
    tryCuring(toks, dict) { return nil; }

    /*
     *   The parsing "stage" of this error.  We can distinguish three
     *   levels of intelligibility as we work through the parsing process:
     *   (1) completely unintelligible, (2) valid verb structure, and (3)
     *   resolved noun phrases.  This property tells us which stage we
     *   finish in when we encounter an error of this type.
     */
    errStage = 1
;

/*
 *   The basic command-level parsing error.  This occurs when we can't find
 *   a grammar match to the overall command phrasing. 
 */
class NotUnderstoodError: ParseError
    display()
    {
        /*
         *   We couldn't parse a command.  This means that we were unable
         *   to find any grammar match for the input, so we basically have
         *   no idea what the player was trying to say.  
         */
        DMsg(not understood, 'I don\'t understand that command.');        
    }

    /* 
     *   This is a general verb syntax error, so our best candidates will
     *   words that are used in verb phrases.  The next best is a corrected
     *   word that's used in any GrammarProd, since the problem might
     *   actually be in some other structural part of the command phrase
     *   above the verb phrase (a conjunction, for example).  
     */
    rankCorrection(toks, idx, dict)
    {
        /* get the text of the token */
        local txt = getTokVal(toks[idx]);

        /* look up the word in the action vocabulary table */
        local w = actionDictionary.wordToAction[txt];
        if (w != nil)
        {
            /* get the highest spelling priority of the matching actions */
            local maxAct = w.maxVal({ a: a.spellingPriority });

            /* 
             *   Look for other words in the token list that are also in
             *   this words associated word list.  (For example, if the
             *   candidate is 'up', look for words like 'pick', 'go', or
             *   'look' that occur in the same verb rules with 'up'.) 
             */
            local xlst = actionDictionary.xwords[txt] - txt;
            local xbonus = (toks.indexWhich(
                { t: xlst.indexOf(getTokVal(t)) != nil }) != nil);

            /* 
             *   Give this a high rating, with the action priority and
             *   associated word bonuses to break ties.
             */
            return 200 + maxAct*2 + (xbonus ? 1 : 0);
        }

        /* okay, no predicate; how about any other GrammarProd word? */
        w = dict.findWord(txt);
        if (w.indexWhich(
            { x: dataType(x) == TypeObject && x.ofKind(GrammarProd) }) != nil)
            return 100;

        /* 
         *   No luck on any of our preferences; give it our lowest rank.
         *   We still want to allow it to be considered, so give it a
         *   non-zero rank. 
         */
        return 1;
    }
;

/*
 *   An UnknownWordError points out a word that's not in the game's
 *   dictionary. 
 */
class UnknownWordError: ParseError
    construct(txt)
    {
        self.badWord = txt;
    }

    display()
    {
        /*
         *   The command contains a word that's not in the dictionary.
         *   This error is only used when the parser is set to admit to
         *   unknown words, and only when we fail to parse the command.
         *   (It's possible for a command to succeed even when it contains
         *   words that aren't in the dictionary, since objects and topics
         *   can sometimes match arbitrary input.)  
         */
        DMsg(unknown word, 'I don\'t know the word "{1}".', badWord);

    }

    /* the text of the unknown word */
    badWord = nil
;

/* 
 *   OopsError is the base class for errors in an OOPS command.
 */
class OopsError: ParseError
;

/*
 *   A CantOopsError means that the player typed OOPS when we don't have a
 *   previous command typo we can correct.  
 */
class CantOopsError: OopsError
    display()
    {
        /*
         *   The player typed an OOPS command, but we don't have anything
         *   from a past command that we can correct.  This means that
         *   either the last command succeeded, or that it simply didn't
         *   contain any non-dictionary words. 
         */
        DMsg(no oops now, 'Sorry, I\'m not sure what you\'re correcting.');
    }
;


/*
 *   A CommandError is an error in parsing that occurs within the build
 *   process for a Command object.  
 */
class CommandError: ParseError
    construct(cmd)
    {
        /* remember the command */
        self.cmd = cmd;
    }

    /* the Command object where the error occurred */
    cmd = nil

    /* 
     *   these errors occur once we have a valid predicate structure, so
     *   we're in stage 2 of the parsing when we encounter an error of this
     *   type 
     */
    errStage = 2
;

/*
 *   Rejected parsing structure.  There are certain structures that are
 *   hard to eliminate in the grammar, but which we don't want to accept
 *   semantically.  This error can be thrown when such a structure is
 *   encountered.  This effectively rules out a parse tree.  It's not a
 *   displayable error; the parser simply rules out these structures.  
 */
class RejectParseTreeError: CommandError
    display()
    {
        /* 
         *   users should never see this error - it should be handled
         *   internally to the library 
         */
        "\n(Internal: Parse tree rejected.)\n";
    }
;

/*
 *   Empty noun slot error.  This occurs when there are no noun phrases in
 *   a functional slot in the predicate (e.g., when the player types "TAKE"
 *   without a direct object).  
 */
class EmptyNounError: CommandError
    construct(cmd, role)
    {
        inherited(cmd);
        self.role = role;
    }

    /* our message is a missing noun query (e.g., "What do want to open?") */
    display()
    {
        askMissingNoun(cmd, role);
    }

    /*
     *   Try curing the error.  After a missing noun query, the player can
     *   respond with a simple noun phrase answering the question. 
     */
    tryCuring(toks, dict)
    {
        /* try parsing against the appropriate reply grammar */
        local lst = new CommandList(
            cmd.verbProd.missingRoleProd(role), toks, dict,
            new function(prod)
        {
            /* create a copy of the original command */
            local newCmd = cmd.clone();
            
            /* plug the new noun phrase tree into the empty role */
            newCmd.addNounProd(role, prod);
                        
            /* the new command is the mapped list entry */
            return newCmd;
        });

        /* accept curable resolutions as replies */
        lst.acceptCurable();

        /* return the list */
        return lst;
    }

    /* we can cure by asking the player for the missing noun phrase */
    curable = true
;



/*
 *   Noun phrase resolution error.  This is a special type of parsing error
 *   that indicates that the problem is with resolving a noun phrase to
 *   game-world objects.  
 */
class ResolutionError: ParseError
    construct(np)
    {
        /* do the normal work */
        inherited();
        
        /* save the noun phrase */
        self.np = np;

        /* note the error-display text of the noun phrase */
        self.txt = np.errName;
    }

    /* the NounPhrase object for the errant phrase, if available */
    np = nil

    /* the text of the errant phrase, if available */
    txt = nil

    /*
     *   For a noun resolution error, our best bet for a spelling
     *   correction would be a word associated with a game-world object.
     *   Only consider in-scope objects when making the correction, to
     *   avoid spurious corrections that give away information on objects
     *   the player has yet to encounter.  We'll also allow words that are
     *   used in non-predicate grammar productions, since we might have a
     *   structural noun phrase word (an article, pronoun, etc).  
     */
    rankCorrection(toks, idx, dict)
    {
        /* make a list consisting of the single changed word */
        local disList = [getTokVal(toks[idx])];

        /* look for an in-scope object association for the word */
        local m = 0;
        foreach (local obj in World.scope())
            m |= obj.matchNameDisambig(disList);

        /* 
         *   if we found any matches, give this the highest score; adjust
         *   slightly to prefer nouns, then adjectives, then plurals 
         */
        if (m != 0)
        {
            return ((m & MatchNoun) != 0 ? 102 :
                    (m & MatchAdj) != 0 ? 101 : 100);
        }

        /* check for non-predicate grammar words */
        local w = dict.findWord(getTokVal(toks[idx]))
            .subset({ x: dataType(x) == TypeObject });

        if (w.indexWhich({ x: x.ofKind(GrammarProd) && !x.ofKind(predicate) })
            != nil)
            return 90;

        /* 
         *   it's not an in-scope object word or a structural word, so
         *   don't allow it, in case it refers to a yet-unseen object 
         */
        return 0;
    }
;

/*
 *   ActorResolutionError - this is for resolution errors that are in the
 *   context of what the target actor of the command (the addressee) can
 *   see.  These require the Command in addition to the noun phrase, since
 *   that provides the target actor information.  
 */
class ActorResolutionError: ResolutionError
    construct(cmd, np)
    {
        inherited(np);
        self.cmd = cmd;
    }

    /* the command that we were attempting to resolve */
    cmd = nil
;

/*
 *   No objects match the addressee of a command (BOB, GO NORTH or TELL BOB
 *   TO GO NORTH).  
 */
class UnmatchedActorError: ResolutionError
    display()
    {
        /*
         *   An actor phrase in a command (BOB, GO NORTH) didn't match any
         *   in-scope objects.  This doesn't necessarily mean that the
         *   phrase doesn't refer to any object anywhere in the game, just
         *   that it doesn't refer to anything in scope.  Since we didn't
         *   match anything, all we have is the text of the actor phrase
         *   from the player's input.  
         */
        DMsg(unmatched actor, '{I} {see} no {1} {here}.', txt);
    }
;

/*
 *   No objects match a noun phrase. 
 */
class UnmatchedNounError: ActorResolutionError
    display()
    {
        /*
         *   A noun phrase didn't match any in-scope objects.  This doesn't
         *   necessarily mean that the phrase doesn't refer to any object
         *   anywhere in the game, just that it doesn't refer to anything
         *   in scope.  Since we didn't find an object, all we have is the
         *   text of the noun phrase from the player's input.  
         */
        DMsg(unmatched noun, '{I} {see} no {2} {here}.', cmd, stripArticle(txt));
    }
;

/*
 *   Base class for resolution errors involving pronouns 
 */
class PronounError: ResolutionError
    construct(np, pro)
    {
        inherited(np);
        pronoun = pro;
    }

    /* the pronoun that caused the error, as a Pronoun object */
    pronoun = nil
;
        

/*
 *   No pronouns match a noun phrase. 
 */
class NoAntecedentError: PronounError
    display()
    {
        /*
         *   The player used a pronoun for which there's currently no
         *   antecedent.  
         */
        DMsg(no antecedent,
             'I\'m not sure what you mean by "{1}".', np.prod.getText());
    }
;


/*
 *   The antecedent of the pronoun is no longer in scope
 */
class AntecedentScopeError: PronounError
    construct(cmd, np, pro)
    {
        inherited(np, pro);
        self.cmd = cmd;
    }

    cmd = nil

    display()
    {
        /*
         *   The player used a pronoun that refers to an object that's no
         *   longer in scope. 
         */
        DMsg(antecedent out of scope,
             '{I} no longer {see} that {here}.', cmd);
    }
;


/*
 *   There aren't enough objects matching a noun phrase to satisfy a
 *   quantifier (e.g., TAKE FIVE COINS, but only three coins are present). 
 */
class InsufficientNounsError: ActorResolutionError
    display()
    { 
        
        if(cmd.matchedAll)
            
            /* 
             *   The player used ALL when there's nothing suitable for all to
             *   refer to.
             */
            DMsg(nothing suitable for all, 'There{\'s} nothing suitable for        
                ALL to refer to. ');
        
        else
            
            /*
             *   The player used a noun phrase that specifically calls for some
             *   number of objects (such as FIVE COINS or BOTH BOOKS), but there
             *   aren't enough of those objects present.
             */
            DMsg(not enough nouns,
                 '{I} {don\'t see} that many {2} {here}.', cmd, txt);       

    }
;

/*
 *   The owner in a possessive phrase doesn't have any of the objects
 *   named. 
 */
class NoneInOwnerError: ActorResolutionError
    construct(cmd, np, poss)
    {
        inherited(cmd, np);
        possQual = poss;
    }

    /* the possessive qualifier */
    possQual = nil

    display()
    {
        /* 
         *   if we have other than one possible owner, show the original
         *   text of the owner phrase, since we can't be sure which matched
         *   object was intended; otherwise show the name of the actual
         *   owner we matched 
         */
        if (possQual.matches.length() != 1)
        {
            /*
             *   The player used a possessive to qualify a noun phrase, and
             *   we matched multiple objects for the possessive phrase, but
             *   none of those owners actually owns anything in scope that
             *   matches the main noun phrase.  For example, the player
             *   entered THE GUARD'S SWORD, and there are two guards
             *   present, but neither of them has a sword (not that's in
             *   scope, anyway).  
             */
            DMsg(none in owners, 'No {2} {dummy}appear{s/ed} to have any {3}.',
                 cmd, possQual.prod.getText(), txt);
        }
        else
        {
            /*
             *   The player used a possessive to qualify a noun phrase, and
             *   we matched a single object for the possessive phrase, but
             *   that owner doesn't actually possess anything in scope that
             *   matches the main noun phrase.  For example, the player
             *   entered BOB'S WALLET, and Bob is indeed in scope, but Bob
             *   doesn't own a wallet (not one that's in scope, anyway).  
             */
            local obj = possQual.matches[1].obj;
            gMessageParams(obj);
            
            DMsg(none in owner, '{The subj obj} {doesn\'t appear[ed]} to have
                any {2}.',  cmd,  txt);
                      
        }
    }    
    
;

/*
 *   The location in a locational qualifier doesn't contain any of the
 *   objects named. 
 */
class NoneInLocationError: ActorResolutionError
    construct(cmd, np, loc)
    {
        inherited(cmd, np);
        locQual = loc;
    }

    /* the locational qualifier */
    locQual = nil
    
    display()
    {
        /* 
         *   if we have other than one possible location, show the original
         *   text of the locational phrase, since we can't be sure which
         *   matched object was intended; otherwise show the name of the
         *   actual location we matched 
         */
        if (locQual.matches.length() !=  1)
        {
            /*
             *   We have a locational qualifier, and we have multiple
             *   objects that match the location, but there's nothing in
             *   scope that matches the main noun phrase that's in any of
             *   those locations.  For example, the player typed THE BOX ON
             *   THE TABLE, and we have two tables in scope, but there's no
             *   box in scope that's on either of them.  
             */
            DMsg(none in locations,
                 '{I} {see} no {2} {3} any {4}.',
                 cmd, txt, locQual.locType.prep, locQual.prod.getText());
        }
        else
        {
            /*
             *   We have a locational qualifier, and we have exactly one
             *   object that matches the location, but there's nothing in
             *   scope that matches the main noun phrase that's in that
             *   location.  For example, the player typed THE BOX ON THE
             *   TABLE, and we have a table in scope, but there's no box on
             *   it. 
             */
            DMsg(none in location,
                 '{I} {see} no {2} {3} {the 4}.',
                 cmd, txt, locQual.locType.prep, locQual.matches[1].obj);
        }
    }
;

/*
 *   There are no objects matching this noun phrase name that have the
 *   contents mentioned in the contents qualifier. 
 */
class NoneWithContentsError: ActorResolutionError
    construct(cmd, np, cont)
    {
        inherited(cmd, np);
        contQual = cont;
    }

    /* the contents qualifier */
    contQual = nil

    display()
    {
        /* 
         *   if we have other than one possible contents object, show the
         *   original text of the contents phrase, since we can't be sure
         *   which matched object was intended; otherwise show the name of
         *   the object we matched 
         */
        if (contQual.matches.length() != 1)
        {
            /*
             *   We have a contents qualifier, and we have multiple objects
             *   in scope that match the contents phrase, but there's
             *   nothing in scope that matches the main noun phrase that
             *   actually contains any of the contents objects.  For
             *   example, the player typed THE BUCKET OF WATER, and we do
             *   have multiple "water" objects present, but none of them
             *   are inside buckets that are in scope.  
             */
            DMsg(none with contents in list,
                 '{I} {see} no {2} of {3}.',
                 cmd, txt, contQual.prod.getText());
        }
        else
        {
            /*
             *   We have a contents qualifier, and we have exactly one
             *   object in scope that matches the contents phrase, but
             *   there's nothing in scope that matches the main noun phrase
             *   that actually contains that object.  For example, the
             *   player typed THE BUCKET OF WATER, and we do have a "water"
             *   object present, but it's not inside anything called a
             *   bucket (not one that's in scope, anyway).  
             */
            DMsg(none with contents,
                 '{I} {see} no {2} of {3}.',
                 cmd, txt, contQual.matches[1].obj);
        }
    }
;

/*
 *   A noun phrase is ambiguous, so we'll have to ask for clarification.
 */
class AmbiguousError: ResolutionError
    construct(cmd, np, names)
    {
        inherited(np);
        self.cmd = cmd;
        self.nameList = names;
    }

    display()
    {
        /* ask the language-specific ambiguous noun question */
        askAmbiguous(cmd, np.role, nameList.mapAll({ n: n[1] }));     
        
    }

    /* 
     *   Accept spelling corrections that trigger an ambiguous noun error.
     *   If we find an ambiguous noun it means that we have valid overall
     *   verb syntax *and* we have noun phrases that match in-scope objects
     *   - in fact, they match too many objects.  This is pretty good
     *   evidence that the respelling is valid.  
     */
    allowOnRespell = true


    /* 
     *   this is a curable error, since the player can fix the problem by
     *   answering the disambiguation question 
     */
    curable = true

    /* 
     *   Try curing the error.  After an ambiguous noun error, the player
     *   can type a partial noun phrase that clarifies which object was
     *   intended: a distinguishing adjective, a locational phrase, a
     *   possessive, etc. 
     */
    tryCuring(toks, dict)
    {
        /* try parsing against the main disambiguation grammar */
        local lst = new CommandList(
            mainDisambigPhrase, toks, dict,
            new function(prod)
            {
                /* create a new copy of the Command to apply the change to */
                local newCmd = cmd.clone();

                /* add the reply to the new command */
                local dnp = newCmd.startDisambigReply(np, prod);

                /* build the reply tree */
                prod.build(newCmd, dnp);

                /* the mapping is the new command */
                return newCmd;
            });

        /* accept curable errors in the reply */
        lst.acceptCurable();

        /* return the list */
        return lst;
    }

    /* the original Command that we were trying to resolve */
    cmd = nil

    /* 
     *   The list of object names, with distinguisher information.  This is
     *   the same information returned from Distinguisher.getNames(). 
     */
    nameList = []
;

class AmbiguousMultiDefiniteError: UnmatchedNounError
    display()
    {
        DMsg(be more specific, 'I don\'t know which ones you mean.  
            Can you be more specific?');
    }

    /* 
     *   this is not really curable, but we need to say it is curable so that
     *   our custom message is displayed.  Would like to find a way to do this
     *   where curable=nil
     */
    curable = true
;


/*
 *   Ordinal out of range.  This occurs when the player replies to a
 *   disambiguation query with an ordinal that's out of the bounds of the
 *   offered list. 
 */
class OrdinalRangeError: ResolutionError
    construct(np, ordinal)
    {
        inherited(np);
        self.ordinal = ordinal;
    }

    display()
    {
        /*
         *   An ordinal reply to a disambiguation question is out of range.
         *   For example, we asked "Which do you mean, the red book or the
         *   blue book?", and the player answered THE THIRD ONE.  There are
         *   only two options, so THIRD is out of range.  
         */
        DMsg(ordinal out of range,
             'Sorry, I don\'t see what you\'re referring to.');
    }

    /* the ordinal, as an integer value (1=first, 2=second, etc) */
    ordinal = nil
;

class BadMultiError: ParseError
    display() 
    { 
        DMsg(multi not allowed, 'Sorry; multiple objects aren\'t allowed with
            that command.'); 
    }
;

/* ------------------------------------------------------------------------ */
/*
 *   A placeholder object for dictionary entries.  The Dictionary class
 *   stores a three-way association: word string, part-of-speech property,
 *   and object.  The object association is designed to allow the parser to
 *   come up with a list of objects that could match a given word, but the
 *   adv3L library doesn't use this feature.  We instead figure out the
 *   word-to-object association by directly asking the objects in scope if
 *   they're associated with a word.  We still need *something* to store as
 *   the object association for each word entry in the dictionary, though.
 *   That's where this object comes in: it's a dummy object that serves as
 *   the required object to associate with each word.
 *   
 *   A language module can ignore this and use the word-object-property
 *   association feature of the dictionary, if desired.
 */
dictionaryPlaceholder: object
;



/* Exception thrown by exit macro */
class ExitSignal: Exception
;

/* Exception thrown by abortImplicit macro */
class AbortImplicitSignal: Exception
;

/* Exception thrown by abort macro */
class AbortActionSignal: Exception
;

/* Exception thrown by exitAction macro */
class ExitActionSignal: Exception  
;

/* Exception thrown to terminate a command. */
class TerminateCommandException: Exception
;


/* 
 *   A SpecialVerb allows us to define grammar for verbs applying to only a handful of objects in
 *   our game, where we want the rarely used command to be translated into a common one, e.g. RIMG
 *   BELL -> PUSH BELL, which we could implement as:
 *
 *.  SpecialVerb 'ring' 'push' @doorbell
 */
class SpecialVerb: object
    /* 
     *   The word or words this SpecialVerb should match, e.g. 'ring'. Different forms can be listed
     *   separated by a vertical bar, e.g. 'cross|go across|walk across'
     */
    specVerb = ''
    
    /* 
     *   The verb words needed to trigger the action we want this SpecialVerb to perform, e.g.
     *   'push' or 'go through'
     */
    stdVerb = ''
    
    /* 
     *   The object ot objects we want this SpecialVerb to apply to. This can be a single object or
     *   class or list of objecta and/or classes, but it should result only in a small number of
     *   matches.
     */
    matchObjs = nil
    
    /* 
     *   A Room, Region or list of Rooms and/or Regions where this SpecialVerb is applicable. Note
     *   we'd very rarely need to specficy this since SpecialVerb already makes scope checks.
     */
    where = nil
    
    /*  A Scene or list of Scenes that must be happening for this SpecialVerb to apply. */
    during = nil
    
    /*  
     *   A condition that must be true for this SpecialVerb to apply. This cannot refer to the
     *   objects involved in the command (e.g. dobj or iobj) since these won't be known the first
     *   time the when condition is tested. For conditions involving the dobj, iobj, or aobj of a
     *   the current command, use objCheck() instead.
     */     
    when = true

    /* 
     *   A check that can be applied once the objects involved in the command are known, (e.g. to
     *   check whether dobj.isOpen). The method can return nil to reject the command at this stage,
     *   or else display a message to explain why it can't go ahead and then use abort to stop it.
     *   It's unlikely that this will be needed often.
     */
    objChecks(dobj, iobj, aobj)
    {
        return true;
    }
    
    /* 
     *   The priority we want to assign this SpecialVerb if the library needs to break a tie between
     *   two or more SpecialVerbs that might otherwise be matched after all other conditions have
     *   been applied. The default is 100. Other things being equal, the SpecialVerb with the
     *   higher/highest priority will be matched.
     */
    priority = 100
    
    /* 
     *   Our best guess at the direct object of the command this SpecialVerb will match before the
     *   objects of the command have been fully resolved.
     */
    tentativeDobj = nil
    
    /* 
     *   A list of the possible tentative direct objects of the command this SpecialVerb eill match
     *   before the objecs of the command have been fully resolved. This will be all the objects in
     *   scope that match our matchObjs property and the noun phrase the player apparently entered.
     */
    tentativeDobjList = []
    
    
    /* The verb phrase that's been matched to invoke this SpecialVerb. */
    verbPhrase = (specialVerbMgr.vWords.join())
    
    /* The remaining methods and properties of SpecialVerb are for internal library use. */

    /* 
     *   This method is called once the command has been resolved and we kmow the objects involved
     *   in it. lst is a list containing the action followed by the objects involved in the action,
     *   e.g. [action, dobj]. svPhrase is the special verb phrase that's been translated into
     *   another action, e.g. 'ring' or 'walk across'
     */     
    checkSV(lst, svPhrase)
    {
        /* The direct object of the command, which is the second element of lst. */
        obj = lst.element(2);
        
        /* 
         *   If the direct object of the command does not not match any of the objects/classes
         *   listed in out matchObjs property, we fail the action at this stage.
         */
        if(!matchObjs.indexWhich({x:obj.ofKind(x)}))
           failCheck(svPhrase);
        
        /* Carry out the custom checks based on our where, when and during properties. */
        customChecks(lst);
    }
    
    /* 
     *   Display a messsage saying the command can't proceed and then stop it in its tracks.
     *   svPhrase is the command we matched, e.g. 'ring' or 'walk over'.
     */
    failCheck(svPhrase)
    {
        /* Display the failure message. */
        showFailureMsg(svPhrase);
        
        /* Abort the command. */
        abort;
    }
    
    /* Display our failure message. By default this is "You can't <<svPhrase>> that. */
    showFailureMsg(svPhrase)
    {
        DMsg(cant do that special, '{I} {can\'t} <<svPhrase>> {that dobj}. ');
    }
    
    /* Initialise (in fact preinitialize) this SpecialVerb */
    initSpec()
    {
        /* Make sure our matchObjs property contains a list. */
        matchObjs = valToList(matchObjs);
        
        /* 
         *   Ensure that specVerb is in lower case so that we can compare it with player input
         *   converted to lower case.
         */
        specVerb = specVerb.toLower();
        
        /* 
         *   Split our special verbs into a list, in case we have different options separated by a
         *   vertical bar.
         */
        local specVerbs = specVerb.split('|');
        
        /* 
         *   Then interate through every element in our special verb variants to add them to the
         *   special verbs Lookup table.
         */
        foreach(local s in specVerbs)
        {    
            /* 
             *   Split the grammar in variant s into a list of separate words, e.g. 'walk over' ->
             *   ['walk', 'over']
             */
            local sWords = s.split(' ');
            
            /* Add this SpeciaVerb to the special verb table with sWords as the key/ */
            specialVerbMgr.addToTable(sWords, self); 
            
            /* 
             *   If the number of words in this variant is greater than the number of words
             *   encountered in any previous SpecialVerb that's been initialized, update the maximum
             *   length stored on specialVerbManager. We do this so SpecialVerbManager knows the
             *   maximum number of words it needs to look for when trying to match keys in its
             *   table.
             */             
            if(sWords.length > specialVerbMgr.maxKeyLen)
                specialVerbMgr.maxKeyLen = sWords.length;
        }       
    }
    
    /* Check whether this SpecialVerb matches its conditions. */
    matches(scope_, toks)
    {       
        /* 
         *   Start calculating our score for this match by making it our priority plus the result of
         *   looking for mactching objects in scope.
         */        
        score = priority + vocabCheck(scope_, toks);
        
        /* Then carry out the custom checks, which may further adjuest our score */
        return customChecks();
    }
 
    
    /* 
     *   Check how well any objects in soope that match our matchObjs also match the noun phrase we
     *   think the player entered.
     */
    vocabCheck(scope_, toks)
    {
        /* Cache a list of all the objects in scope for the current command. */
        scope_ = scope_.toList();
        
        /* Our verb phrase length is the number of words in specialVerbMgr's vWords list. */
        local vPhraseLen = specialVerbMgr.vWords.length();
        
        /* 
         *   The tokens that come after the verb phrase tokens potentially constitute our noun
         *   phrase tokens, which is what we'll want to match.
         */
        local nPhraseToks = toks.sublist(vPhraseLen + 1);
        
        /* 
         *   Check for a preposition in the noun phrase; this may mark the end of the noun phrase
         *   proper.
         */
        local prepTokPos = nPhraseToks.indexWhich({x: prepositions.find(x)});
        
        /*  
         *   If we found a preposition, truncate our nounPhrase tokan list to end just before the
         *   preposition.
         */
        if(prepTokPos)
            nPhraseToks = nPhraseToks.sublist(1, prepTokPos - 1);
        
        /* Remove any articles from the noun phrase before attempting to match. */        
        nPhraseToks = nPhraseToks.subset({x: articles.find(x) == nil});
        
        /* Make a note of whether we found a potentially suitable object in scope. */
        local scopeMatch = nil;
        
        /* 
         *   We'll keep a note of the best match score from the ojects in scope that match our
         *   matchObjs.
         */
        local bestScore = 0;
        
        /* reset our tentativeDobj and tentativeDobjList */
        tentativeDobj = nil;
        tentativeDobjList = [];
        
        /* loop through every object in scope to find a match */
        foreach(local obj in matchObjs)
        {            
            /* We're looking for objects in scope that match one of our matchObjs */
            if(scope_.indexWhich({x: x.ofKind(obj)}))
            {
                /* Note that we have at least found a potential match in scope */
                scopeMatch = true;
                
                /* Obtain the vocab match score for the current obj. */
                local mScore = obj.matchName(nPhraseToks);
                
                if(mScore)
                {
                    /* If we have match, add obj to our list of tentative direct objects */
                    tentativeDobjList += obj;
                    
                    /* 
                     *   If our match score is grreater than any we've found up to now, update the
                     *   bestScore to that match score and make obj our tentativeDobj.
                     */
                    if(mScore > bestScore)
                    {
                        bestScore = mScore;
                        
                        tentativeDobj = obj;
                    }
                }
            }
        }
            
        /* 
         *   If we haven't even found a suitable object in scope, return -500000 to note that we're
         *   a poor candidate for the best matching SpecialVerb; otherwise return 100 times out best
         *   vocab match score so that specialVerbMgr can prioritize the VerbRule that bests matches
         *   a suitable object in scope.
         */
        return scopeMatch ? (bestScore * 100) : -500000;           
    }
    
    /* 
     *   This method is called twice, first when the specialVerbMgr is seeking the best SpecialVerb
     *   option to match, and the second once the objects of the revised command have been
     *   identified and the command is about to be executed. The first time round the lst parameter
     *   won't be supplied (so will be effectively nil); the second time it will be a list
     *   containing the action and its objects. The customChecks() method uses this simply to
     *   determine at which stage its being invoked. The first time its being asked to adjust the
     *   match score; the second time its being asked to check whether or not the proposeed action
     *   should be allowed to proceed.
     */
    customChecks(lst?)
    {
        /* 
         *   If the Scenes module is present and our during property has been designed check whether
         *   one of the scenes in our suring list is currently happening.
         */
        if(defined(sceneManager) && during && !(valToList(during).indexWhich({x: x.isHappening})))
        {
            /* 
             *   If not, return nil on the second invocation and deduct 100000 from our score on the
             *   first.
             */
            if(lst)
                return nil;
            else
                score -= 100000;
        }
        
        /* 
         *   Check whether the actor is located in one of the rooms or regions in our where list, if
         *   the where list has been defined.
         */
        if(where && !valToList(where).indexOf(gActorRoom))
            /* 
             *   If not, return nil on the second invocation and deduct 100000 from our score on the
             *   first.
             */
        {
            if(lst)
                return nil;
            else
                score -= 100000;
        }
        
        /* Check whether our when condition has been met. */
        if(!when)
        {
            /* 
             *   If not, return nil on the second invocation and deduct 100000 from our score on the
             *   first.
             */
            if(lst)
                return nil;
            else
                score -= 100000;       
        }
        
        /* 
         *   On the second invocation, return the value of objAfterChecks(lst); on the first simply
         *   return true.
         */
        return lst ? objAfterChecks(lst) : true;
    }
        
    /* Checks involving the objects of the commmand, once we know them */
    objAfterChecks(lst)
    {
        /* 
         *   Call objChecks with the second, third, and fourth elements of lst as arguments; these
         *   correspond to the dobj, iobj and aobj of the command. Note that the element(i) method
         *   simply returns nil if i is greater than the length of lst.
         */
        return objChecks(lst.element(2), lst.element(3), lst.element(4));
    }
        
    /* Our adjusted priority after attempting a match. */
    score = 0
 
    /* Verb phrases we want to exclude from matching, e.g. 'pick up' if our specVerb is 'pick' */
    exclusions = []
    
    /* Check whether the command entered by the player matches an item in our exclusions list. */
    matchExclusions(tokWords)
    {
        
        /* Iterate over our list of exclusions to see if any match */
        foreach(local ex in exclusions)
        {
            /* Split the current exclusion into a list of words */
            local excWords = ex.split(' ');
            
            /* Start by assuming we'll find a match */
            local matched = true;
           
            /* Iterate through the words in our excWords list. */
            for(local i in 1..excWords.length)
            {
                /* 
                 *   If we've run out of words in our tokwords list or we find a word in one list
                 *   that differs from the corresponding word in the other list, then we don't
                 *   match, so we can note that the match has failed and stop comparing the two
                 *   lists.
                 */               
                if(i > tokWords.length() || tokWords[i] != excWords[i])
                {
                    matched = nil; 
                    break;                }
                
            }
            
            /* 
             *   If we didn't find a mismatch, then what the player entered matched our exclusions
             *   list, so we've found a match and can return true.
             */
            if(matched)
                return true;
        }
        
        /* 
         *   If we reach here then none of our exclusions match the player's input, so we return
         *   nil.
         */
        return nil;
    }
;


/* 
 *   The specialVerbMgr carries out various tasks needed for working with SpecialVerbs. In
 *   particular it preinitializes SpecialVerbs to add them to its LookupTable, and is responsible
 *   for matching SpecialVerbs to player input and translating and checking actions appropriately.
 *   Its methods and properties are entirely intended for internal use by the library and shouldn't
 *   normally be invoked or changed in user game code.
 */
specialVerbMgr: PreinitObject
    findMatchingSV(toks)
    {
        /* Get a list of words in toks list */         
        local tokWords = toks.mapAll({t: t[1].toLower}); 
        
        /* Set up a variable to hold the list of SpecialVerbs we'll match. */
        local specs = nil; // new
        
        /* 
         *   Work down from the maximum key length (in terms of the number of words it contains) to
         *   1, so that a shorter key doesn't mask a longer one.
         */
        for(local klen in maxKeyLen .. 1 step -1)
        {
            /* Then key we're looking to match is the first klen elements of our tokWords list. */
            local ky = tokWords.sublist(1, klen);
            
            /* Obtain the SpecialVerbs that match that key. */
            specs = specTable[ky];
            
            /* Exclude any SpecialVerbs whose exclusion property matches our tokWords. */
            specs = valToList(specs).subset({x: !x.matchExclusions(tokWords)});
            
            /* 
             *   If we find any, store the key in our vWords property (the words in the player input
             *   we're just matching) and break out of the loop, since we won't need to keep
             *   looking.
             */
            if(valToList(specs).length > 0)
            {
                vWords = ky;
                break;
            }
        }
        
        /* If we don't find any we're done. */
        if(specs == nil || specs.length == 0)
            return nil;        
        
        /* Cache the player character's scope list. */
        local scope_ = Q.scopeList(gPlayerChar);
        
        /* Calculate each SpecialVerb's match score, based partly on what's in scope. */
        foreach(local s in specs)
            s.matches(scope_, tokWords);
        
        /* Sort the list in descending order of score. */
        local svmatches = specs.sort(true, {a, b: a.score  - b.score });
        
        /* Return the first one in the list, which should tbe the best match. */
        return svmatches[1];                                           
    }
    
    /* The verb words we're currently matching. */
    vWords = nil
    
    matchSV(toks)
    {
        /* Find the best SpecialVerb matching the tokens passed to us by the parser */
        local sv = findMatchingSV(toks);
        
        /* 
         *   If we've found one, update the parser's tokens acordingly, so they now contain the
         *   standard verb (stdVerb) we want to use in place of the special verb word.
         */
        if(sv)
        {              
            /* 
             *   We now need to add the rest of the toks back to the revised first one. We need to
             *   start from the next tok we just matched our special verb to.
             */
            local nToks = toks.sublist(vWords.length + 1);
            
            /* 
             *   Split the stdVerb into a list of words. Check its length. Update the first len toks
             *   with the list of words from stdVerb. Append nToks to the first stdVerb.length toks.
             */            
            local stdVerbWords = sv.stdVerb.split(' ');
            local stdVerbLen = stdVerbWords.length();
            local vToks = [];
            
            /* Create the toks for the new standard verb */
            for(local i in 1 .. stdVerbLen)
            {
                vToks = vToks.append([stdVerbWords[i], tokWord, stdVerbWords[i]]);                 
            }           
            
            /* Change toks to our new verb phrase plus the old noun phrase */
            toks = vToks + nToks;
            
            
            /* Make a note of the current SpecialVerb we're working with. */
            currentSV = sv;
            
        }
        
        /* Return the (possibly adjusted) list of tokens. */
        return toks;
    }
    
    /* 
     *   Call the checkSV() method on our currently active SpecialVerb, if we have one. Tbis is
     *   called by the current Command object just before invoking the relevant Doer for the
     *   command.
     */
    checkSV(lst)
    {
        try
        {
            /* 
             *   If have have a current SpecialVerb, invoke its checkSV method. lst is a list of
             *   objects involved in the action in the form [action, dobj, ...] whiile vWords
             *   contains the words making up the current SpecialVerb verb, e.g. ['ring'] or
             *   ['walk', 'across']
             */
            if(currentSV)
            {
                currentSV.checkSV(lst, vWords.join(' '));
                
                if(lst[1] == SpecialAction)
                    lst[1].specialPhrase = vWords.join(' ');
            }
        }
        finally
        {
            /* 
             *   Make sure we reset currentSV to nil, whataver happens in checkSV (which might well
             *   through an Exception such as abort to stop the action.
             */
            currentSV = nil;
        }
    }
    
    /* The SpecialVerb currently in use. */
    currentSV = nil
    
    /* Carry out our preinitialization. */
    execute()
    {
        /* Iterate over every object of the SpecialVerb class calling its initSpec() method. */
        for(local spec = firstObj(SpecialVerb); spec != nil; spec = nextObj(spec, SpecialVerb))
            spec.initSpec();               
    }

    /* 
     *   Add a SpecialVerb to our specTable; s is the key value containing the words that can invoke
     *   the SpecialVerb (e.g. ['ring'] or ['walk', 'across'] and spec is the matching SpecialVerb.
     */
    addToTable(s, spec)
    {
        /* Obtain the current value corresponding to the s key in specTable. */
        local specs = valToList(specTable[s]);
        
        /* Add spec to the list of SpecialVerbs it matches. */
        specTable[s] = specs + spec;
    }
    
    /* 
     *   Our LookupTable containing lists of grammar tokens (e.g., ['ring'] or ['go', 'across']) as
     *   keys and a list of matching SpecialVerbs as values.
     */
    specTable = static new LookupTable
    
       
    /* 
     *   The maxium number of words that can occur in a key to our specTable. This is calculated and
     *   stored here at preinit.
     */
    maxKeyLen = 0
;
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1