command.t

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

/* ------------------------------------------------------------------------ */
/*
 *   A Command describes the results of parsing one player predicate - that
 *   is, a single verb phrase, with all its parts.  This includes the
 *   action to be performed and the objects to perform it on.  It also
 *   includes information on the text of the player's input, and how it
 *   maps onto the grammar structures defined by the language module.
 *   
 *   The Command object is built in several steps, so its contents aren't
 *   complete until all of the steps are completed.  
 */
class Command: object
    /* 
     *   Create the command object.  There are several ways to create a
     *   command:
     *   
     *   new Command(parseTree) - create from a parsed command syntax tree.
     *   
     *   new Command(action, dobjProd...) - create from a given Action and
     *   a set of parsed syntax trees for the noun phrases.  The first noun
     *   phrase is the direct object, the second is the indirect object,
     *   and the third is the accessory.
     *   
     *   new Command(action, dobjs...) - create from a given Action and a
     *   set of objects or object lists for the noun slots.  The first
     *   argument after the Action, dobjs, can be a single Mentionable
     *   object to use as the resolved direct object, or a list or vector
     *   of Mentionables to use as the multiple direct objects.  The next
     *   argument is in the same format and is used for the indirect
     *   object.  The third is the accessory.
     *   
     *   new Command(actor, action, dobjs...) - create from a given actor
     *   (as a Mentionable object), an Action object, and the object list.
     *   
     *   new Command() - create a blank Command, for setting up externally
     *   or in a subclass.  
     */
    construct([args])
    {
        /* presume the command will be implicitly addressed to the PC */
        actor = gPlayerChar; //World.playerChar;

        /* check the various argument list formats */
        if (args.matchProto([Production]))
        {
            /* build the command from the parse tree */
            args[1].build(self, nil);

            /* save the parse tree */
            parseTree = args[1];
        }
        else if (args.matchProto([Action, '...'])
                 || args.matchProto([Mentionable, Action, '...']))
        {
            /* retrieve and skip the actor, if present */
            local i = 1;
            if (!args[i].ofKind(Action))
                actor = args[i++];

            /* retrieve the action */
            action = args[i++];

            /* the additional arguments are for the noun phrase slots */
            local roles = NounRole.all, rlen = roles.length();
            local alen = args.length();
            for (local r = 1 ; r <= rlen && i <= alen ; ++i, ++r)
            {
                /* get this noun phrase match tree or object list */
                local np = args[i];

                /*
                 *   Check the type of this argument: if it's a Production,
                 *   it's a parse tree to build into a NounPhrase list for
                 *   the slot.  If it's a list/vector, it's a list of
                 *   resolved objects for the slot. 
                 */
                if (np.ofKind(Production))
                {
                    /* parse tree - assign the role for this noun phrase */
                    np.nounPhraseRole = roles[r];

                    /* build it */
                    np.build(self, addNounListItem(roles[r], np));
                }
                else if (np.ofKind(List))
                {
                    /* it's an object list - save a copy */
                    self.(roles[r].objListProp) = np;
                }
                else if (np.ofKind(Vector))
                {
                    /* it's an object vector - save a list copy */
                    self.(roles[r].objListProp) = np.toList();
                }
                else if (np.ofKind(Mentionable))
                {
                    /* single object - make it into a single-element list */
                    self.(roles[r].objListProp) = [np];

                    /* also set it as the current object */
                    self.(roles[r].objProp) = np;

                    /* synthesize an NPMatch object for it */
                    local m = new NPMatch(nil, np, 0);
                    m.flags = SelProg;
                    self.(roles[r].objMatchProp) = m;
                   
                }
                else
                    throw new ArgumentMismatchError();
            }
        }
        else if (args.matchProto([]))
        {
            /* no arguments - they just want a basic empty command */
        }
        else
            throw new ArgumentMismatchError();

        /* set up the reflexive antecedent table (if we didn't already) */
        if (reflexiveAnte == nil)
            reflexiveAnte = new LookupTable(16, 32);
    }

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

        /* 
         *   make a copy of the antecedent table, so that changes made in
         *   the clone don't affect the original, and vice versa 
         */
        cl.reflexiveAnte = new LookupTable(16, 32);
        reflexiveAnte.forEachAssoc({ key, val: cl.reflexiveAnte[key] = val });

        /* likewise the object list vectors, if any */
        foreach (local role in npList)
        {
            local v = cl.(role.objListProp);
            if (v.ofKind(Vector))
                cl.(role.objListProp) = new Vector(v.length(), v);
        }

        /* return the clone */
        return cl;
    }

    /* clone a noun phrase that's part of this command */
    cloneNP(np)
    {
        /* create a clone of the noun phrase */
        local cl = np.clone();

        /* find and replace the original copy with the clone */
        foreach (local role in npList)
        {
            /* look for 'np' in this role's list of noun phrases */
            local idx;
            if ((idx = self.(role.npListProp).indexOf(np)) != nil)
            {
                /* found it - replace it with the clone, and we're done */
                self.(role.npListProp)[idx] = cl;
                break;
            }
        }

        /* return the clone */
        return cl;
    }

    /*
     *   Execute the action.  This carries out the entire command
     *   processing sequence for the action.  If the action involves a list
     *   of objects (as in TAKE ALL or DROP BOOK AND CANDLE), we iterate
     *   over the listed objects, executing the action on each object in
     *   turn.  
     */
    exec()
    {
        try
        {
            action.reset();
            gAction = action;
            originalAction = action;
            lastAction = nil;
            gCommand = self;
            actions = [];
            
            if(verbProd != nil)
                local lastCommandStr = buildCommandString();
                           
            if(gameMain.autoSwitchAgain && action != Again)           
            {                
                gameMain.againRepeatsParse = action.againRepeatsParse;
            }
            
            if(action.isRepeatable)
            {
                libGlobal.lastCommand = self.createClone();
                libGlobal.lastCommandForAgain = lastCommandStr;                    
            }
            
            if(action.includeInUndo && verbProd != nil)
            {               
                libGlobal.lastCommandForUndo = lastCommandStr;
                savepoint();
            }
            
            
            
            /* 
             *   First, carry out the group action.  This gives the verb a
             *   chance to perform the action collectively on all of the objects
             *   at once.
             */          
            action.execGroup(self);
            
            
            /* 
             *   Get the list of predicate noun roles.  We only iterate over the
             *   noun roles that are verb arguments.
             */
            local predRoles = npList.subset({ r: r.isPredicate });
            
            /* 
             *   If we have any noun phrases, iterate over each combination of
             *   objects.  Otherwise, this is an intransitive verb, so just
             *   perform a single execution of the Action, with no objects.
             */
            if (predRoles.length() == 0)
            {
                /* it's intransitive - just execute once */
                execIter([action]);
            }
            else
            {
                /* 
                 *   It's transitive, so execute iteratively over the objects.
                 *   First, generate the distinguished object names for each
                 *   list.
                 */
                foreach (local role in predRoles)
                {
                    /* get the NPMatch list for this role */
                    local matches = self.(role.objListProp);
                    
                    /* get the list of names for this role's list of objects */
                    local names = Distinguisher.getNames(
                        matches.mapAll({ m: m.obj }), nil);
                    
                    /* 
                     *   Assign each NPMatch object its name from the name list.
                     *   The name list is of the form [name, [objects]], so for
                     *   each object, we need to find the element n such that
                     *   n[2] (the object list) contains the object in question,
                     *   then retrieve the name string from n[1].
                     */
                    matches.forEach(
                        { m: m.name = names.valWhich(
                            { n: n[2].indexOf(m.obj) != nil })[1] });
                }
                
                /* 
                 *   Sort the preRoles into canonical order (typically
                 *   DirectObject, IndirectObject, AccessoryObject)
                 */
                predRoles = predRoles.sort(SortAsc, {a, b: a.order - b.order});
                                /* 
                 *   execute for each combination of objects, starting with the
                 *   objects in the first role list
                 */
                execCombos(predRoles, 1, [action]);
            }
            
            /* 
             *   Let every action that has been involved in this Command
             *   summarize or report on what it has just done.
             */

            
            foreach(local a in actions)
            {
                action = a;
                action.reportAction();
            }
            
            /* 
             *   Reset the action to the original one, if it has been executed,
             *   or else to the first one actually executed.
             */
            
            action = (actions.indexOf(originalAction) || actions.length == 0) 
                ? originalAction : actions[1];
            
            /* List our own sequence of afterReports, if we have any. */
            afterReport();
            
            /* Carry out the after action handling for the current action */
            action.afterAction();
                        
            
            /* 
             *   Carry out the turn sequence handling (daemons and turn count
             *   increment) for the current action.
             */
            action.turnSequence();
            
            
            /*   Advance the game clock time for the current action. */
            action.advanceTime();
           
        }
        catch(AbortActionSignal aas)
        {
            /* 
             *   An AbortActionSignal skips the rest of the command including
             *   the post-action processing such as daemons and advancing the
             *   turn counter; the idea is that an abort command (macro)
             *   effectively cancels the entire command, or at least the rest of
             *   the command from the point it's issued.
             */
        }
    }
    
    /* 
     *   Rebuild the original command string from the tokens. We call this out
     *   as a separate method so language-specific code can override it.
     */
    buildCommandString()
    {
        return valToList(verbProd.tokenList).mapAll({x:        
            getTokVal(x)}).join(' ');
    }
    
    /*  A list of actions executed directly by this command or via a Doer */
    actions = []

    /*  
     *   The originalAction this Command started out with, which may be changed
     *   by a Doer (or some other mechanism)
     */
    originalAction = nil
    
     /*   
     *   A list of strings containing reports to be displayed at the end of the
     *   command execution cycle for this command.
     */
    
    afterReports = []
    
    /* 
     *   A list of strings containing reports to be immediately displayed after any implicit action
     *   reports
     */
    postImplicitReports = []
    
    /* 
     *   Run through our list of afterReports displaying each in turn. We do
     *   this on the Command rather than on any of the Actions since actions may
     *   invoke other actions (implicit, remapped, nested or replaced), while
     *   the afterReports pertain to the command as a whole.
     */
    
    afterReport()
    {
        foreach(local cur in afterReports)
        {
            "<.p>";
            say(cur);
        }
    }
    
    
    
    /* 
     *   A list of reports of previous implicit actions performed in the course
     *   of executing this command which can be used if we need to collate a
     *   report of a stack of implicit actions.
     */
  
    
    implicitActionReports = []

    /*
     *   Execute the command for each combination of objects for noun role index
     *   'n' and above.  'lst' is a list containing a partial object combination
     *   for roles at lower indices.  We iterate over each combination of the
     *   remaining objects. predRoles is a list containing predicate roles (such
     *   DirectObject, IndirectObject, AccessoryObject) relating to this action.
     *   Callers are responsible for sorting predRoles into the correct order
     *   before calling this method.
     */
    execCombos(predRoles, n, lst)
    {
        /* get this slot's role */
        local role = predRoles[n];

        /* iterate over the objects in the slot at this index */
        foreach (local obj in self.(role.objListProp))
        {
            /* set the current object and selection flags for this role */
            self.(role.objProp) = obj.obj;
            self.(role.objMatchProp) = obj;
             
            /* 
             *   create a new list that includes the new object at the
             *   appropriate place.
             */            
            local nlst = lst;

            /* add the object to the list */
            nlst += obj.obj;
            
            /* 
             *   if there are more noun roles, recursively iterate over
             *   combinations of the remaining roles 
             */
            if (n < predRoles.length())
            {
                /* we have more roles - iterate over them recursively */
                execCombos(predRoles, n+1, nlst);
            }
            else
            {
                /* 
                 *   this is the last role - we have a complete combination
                 *   of current objects now, so execute the action with the
                 *   current set 
                 */
                execIter(nlst);
            }
        }
    }

    /*
     *   Execute one iteration of the command for a particular combination of objects.  'lst' is the
     *   object combination about to be executed: the object combination to execute: this is an
     *   [action, dobj, iobj, ...] list.
     */
    execIter(lst)
    {
       
        try
        {      
            /* 
             *   Allow the special verb manager to veto this action if we've been executing a
             *   SpecialVerb.
             */
            specialVerbMgr.checkSV(lst);
            
            /* 
             *   Give our actor's preAction method the chance to veto this action (or maybe do
             *   something else) before it's passed to a Doer.
             */
            gActor.preAction(lst);
            
            /* carry out the default action processing */            
            execDoer(lst);
        }
        catch (ExitSignal ex)
        {
        }
        
        finally
        {
            /* 
             *   If the action isn't one this Command has executed before while
             *   iterating over its list of objects, note that we've now
             *   executed it
             */
            if(actions.indexOf(action) == nil)
                actions += action;
            
            /*  Restore the original action */
            action = originalAction;
        }
    }

    /*
     *   Execute the command via the Doers that match the command's action
     *   and objects.  'lst' is the object combination to execute: [action,
     *   dobj, iobj, ...].  
     */
    execDoer(lst)
    {
        /* 
         *   In case the author tries to use gDobj and the like to match a
         *   command, assign values to them
         */
        
        if(dobj)
            gDobj = dobj;
        if(iobj)
            gIobj = iobj;
        if(acc)
            gAobj = aobj;
        
        /* find the list of matching Doers */
        local dlst = DoerCmd.findDoers(lst);
      
        IfDebug(doers, oSay('''[Executing Doer; cmd = '<<dlst[1].cmd>>']\n'''));       
        dlst[1].exec(self);
        
    }
    
    /* Change the action to a new action with a new set of objects */
    changeAction(newAct, newDo, newIo, newAo)
    {
        action = newAct;
        
        /* 
         *   If we haven't used this action before while executing this command,
         *   reset it.
         */
        if(actions.indexOf(action) == nil)
            action.reset();
        
        dobj = newDo;
        iobj = newIo;
        acc = newAo;
        action.curDobj = newDo;
        action.curIobj = newIo;
        action.curAobj = newAo;
        gAction = action;
        gAction.redirectParent = originalAction;
    }
  
    /*
     *   Invoke a callback for each object in the current command
     *   iteration.  This invokes the callback on the direct object,
     *   indirect object, accessory, and any other custom roles added by
     *   the game.  
     */
    forEachObj(func)
    {
        
        try
        {
            /* loop over the occupied roles */
            foreach (local role in npListSorted)
            {
                /* if this role is occupied, invoke the callback */
                local obj = self.(role.objProp);
                if (obj != nil)
                    func(role, obj);
            }
        }
        catch (BreakLoopSignal sig)
        {
            /* we've broken out of the loop, so 'sig' is now handled */
        }
    }
    

    /*
     *   Are terse messages OK for this command?  A terse message is a
     *   simple acknowledgment of a standard command, such as "Taken",
     *   "Dropped", "Done", etc.  The action is so ordinary that the result
     *   of a successful attempt should be obvious to the player; so the
     *   only reply needed is an acknowledgment, not an explanation.
     *   
     *   Terse replies only apply to simple actions, and only when the
     *   actor is the player character, AND there's no disambiguation
     *   involved.  If the actor isn't the PC, an acknowledgment isn't
     *   sufficient; we should instead describe the NPC carrying out the
     *   action, since it's something we observe, not something we do.  If
     *   any objects were disambiguated, we also want to describe the
     *   action fully, because the ambiguity calls for a description of
     *   precisely which objects were chosen.  Disambiguation guesses are
     *   sometimes wrong, so when they're involved, it's not safe to assume
     *   that the player and parser must both be thinking the same thing.
     *   Showing a full description of the action will make it obvious to
     *   the player when we guessed wrong, because the description won't
     *   accord with what they had in mind.  A terse acknowledgment would
     *   hide this difference, allowing the player to wrongly assume that
     *   the parser did what they thought it was going to do and
     *   potentially leading to confusion down the road.  
     */
    terseOK()
    {
        /* use full messages for NPC-directed commands */
        if (actor != gPlayerChar)
            return nil;

        /* 
         *   use full message for commands where ALL was used, since
         *   the player might not otherwise know what ALL referred to.
         */
        
        if(matchedAll || matchedMulti)
            return nil;
        
        /* check all noun roles for Disambig flags */
        foreach (local role in npList)
        {
            /* 
             *   if this is a predicate role, and there's an object in this
             *   slot with the Disambig flag, don't allow terse messages 
             */
            if (role.isPredicate
                && self.(role.objProp) != nil
                && (self.(role.objMatchProp).flags & SelDisambig) != 0)
                return nil;
        }

        /* 
         *   If we're reporting on fewer objects than the player requested then
         *   we'd better be specific about which ones we mean.
         */
        
        
        if(dobjs.length > action.reportList.length)
            return nil;
        
        /* we have no objection to terse messages */
        return true;
    }

    /* 
     *   Add a noun production, building it out as though it had been part
     *   of the original parse tree.  This can be used to add a noun phrase
     *   after the initial parsing, such as when the player supplies a
     *   missing object. 
     */
    addNounProd(role, prod)
    {
        /* create a noun list item for the production */
        local np = addNounListItem(role, prod);
        
        if(npListSorted.length != npList.length)
            npListSorted = npList;        
        
        
        /* build the tree */
        prod.nounPhraseRole = role;
        prod.build(self, np);

        /* let the verb production know about the change */
        verbProd.answerMissing(self, np);
    }

    /* add a noun phrase to the given role (a NounRole) */
    addNounListItem(role, prod)
    {
        /* create the new noun phrase object of the appropriate type */
        local np = prod.npClass.createInstance(nil, prod);

        /* remember the role in the noun phrase */
        np.role = role;

        /* add it to the given list */
        self.(role.npListProp) += np;

        /* 
         *   If this role isn't already in our list or roles, and it has a
         *   match property, add it.  Roles without match properties aren't
         *   predicate noun roles, so they don't go in our predicate object
         *   list.  
         */
        if (npList.indexOf(role) == nil)
        {
          
            npList += role;
            
            /* 
             *   if the action is a TIAction then make sure the Direct and
             *   Indirect Objects are dealt with in the right order as specified
             *   by the action's resolveIobjFirst property. We do this on a copy
             *   of the list (npSorted) so we don't break anything that needs
             *   the original order (such as matching a Doer).
             */
            
            npListSorted = npList;
            
            if(action != nil && action.ofKind(TIAction))
            {
                local doIdx = npListSorted.indexOf(DirectObject);
                local ioIdx = npListSorted.indexOf(IndirectObject);
                if(doIdx != nil && ioIdx != nil)
                {
                    if((action.resolveIobjFirst && ioIdx > doIdx)
                        ||  (!action.resolveIobjFirst && doIdx > ioIdx))
                    {
                        npListSorted[ioIdx] = DirectObject;
                        npListSorted[doIdx] = IndirectObject;
                    }
                }
            }
        }

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

    /* 
     *   Start processing a new disambiguation reply.  This adds a reply to
     *   a disambiguation question.  
     */
    startDisambigReply(parent, prod)
    {
        /* create the first NounPhrase for this reply */
        local np = new NounPhrase(parent, prod);

        /* add a new NounPhrase list to the reply list */
        disambig = disambig.append([np]);

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

    /* 
     *   Add a disambiguation list item.  This adds a NounPhrase item to
     *   the current reply list.
     */
    addDisambigNP(prod)
    {
        /* get the current reply list */
        local idx = disambig.length(), lst = disambig[idx];

        /* create the new noun phrase */
        local np = new NounPhrase(lst[1].parent, prod);

        /* add it to the current disambiguation reply list */
        disambig[idx] = lst + np;

        /* return it */
        return np;
    }

    /*
     *   Fetch a disambiguation reply.  If we have more replies available,
     *   this returns the next reply's noun phrase list, otherwise nil.  
     */
    fetchDisambigReply()
    {
        return (disambigIdx <= disambig.length()
                ? disambig[disambigIdx++]
                : nil);
    }

    /* mark a noun phrase role as empty */
    emptyNounRole(role)
    {
        /* if this role isn't in our list yet, add it */
        if (npList.indexOf(role) == nil)
            npList += role;

        /* count the missing phrase */
        ++missingNouns;

        /* clear out the role list */
        self.(role.npListProp) = [];
    }

    /* resolve the noun phrases */
    resolveNouns()
    {
        /* 
         *   Note that we're the current command, in case anything wants to know
         */
        gCommand = self;
        
        /* we don't have an error for this resolution pass yet */
        cmdErr = nil;

        /* we haven't started pulling disambiguation replies yet */
        disambigIdx = 1;
        
        /* Note the orginal lists of nps */
        local npListOld = npList;
        local npListSortedOld = npListSorted;
            
             
        /* 
         *   If the player has just supplied a missing object, we only want to
         *   resolve the object for that role.
         */
        if(npToResolve != nil)
        {
            npList = [npToResolve];
            npListSorted = [npToResolve];
        }

        /* 
         *   Start by getting the basic vocabulary matches for each noun
         *   phrase.  Run through each noun phrase list.  
         */
        forEachNP({ np: np.matchVocab(self) });

        /* 
         *   Before we do the object selection, build the tentative lists
         *   of resolved objects.  This can be handy during disambiguation
         *   to help decide the resolution of one slot based on the
         *   possible values for other slots.  For example, for PUT COIN IN
         *   JAR, it might help us choose a coin to know that the iobj is
         *   JAR.  
         */
        buildObjLists();

        /* determine the actor */
        if (actorNPs != [])
        {
            /* 
             *   We have an explicit addressee.  If we have more than one
             *   object for the actor phrase, disambiguate to a single
             *   object.  Disambiguate in the context of TALK TO.  
             */
            local anp = actorNPs[1];
            if (anp.matches.length() > 1)
                anp.disambiguate(self, 1, TalkTo);
            if (anp.matches.length() == 0)
                throw new UnmatchedActorError(anp);

            /* pull out the match as the actor object */
            actor = anp.matches[1].obj;
        }

        /* 
         *   select the objects from the available matches according to the
         *   grammatical mode (definite, indefinite, plural) 
         */
        forEachNP({ np: np.selectObjects(self) });

        /* 
         *   Go back and re-resolve ALL lists.  For two-object commands,
         *   resolving ALL in one slot sometimes depends on resolving the
         *   object in the other slot first. 
         */
        forEachNP({ np: np.resolveAll(self) });

        /*
         *   Set up the second-person reflexive pronoun antecedent.  For a
         *   command addressed in the imperative form to an NPC (e.g., BOB,
         *   EXAMINE YOURSELF), YOU binds to the addressee.  For anything
         *   else (e.g., EXAMINE YOURSELF, or TELL BOB TO EXAMINE
         *   YOURSELF), YOU binds to the player character.  
         */
        if (reflexiveAnte != nil)
        {
            if (actorNPs != [] && actorPerson == 2)
            {
                /* imperative addressed to an actor: YOU is the actor */
                reflexiveAnte[You] = [actor];
            }
            else
            {
                /* for anything else, YOU is the PC */
                reflexiveAnte[You] = [gPlayerChar]; //[World.playerChar];
            }
        }

        /*
         *   Resolve reflexive pronouns (e.g., ASK BOB ABOUT HIMSELF).  We
         *   have to do this as a separate step because reflexives refer
         *   back to other noun phrases in the same command.  We can't do
         *   this until after we resolve everything else.  
         */
        forEachNP({ np: np.resolveReflexives(self) });

        /* check for empty roles */
        foreach (local role in npList)
        {
            if (self.(role.npListProp).length() == 0)
                throw new EmptyNounError(self, role);
        }

        /* 
         *   Clear out the old object lists, then build them anew.  The old
         *   object lists were tentative, before disambiguation; we want to
         *   replace them now with the final lists. 
         */
        buildObjLists();
        
        /* Restore the original lists of nps */
        npList = npListOld;
        npListSorted = npListSortedOld;
    }

    /* carry out a callback for each noun phrase in each list */
    forEachNP(func)
    {       
        /* run through each noun phrase list in the command */
        foreach (local role in npListSorted)
        {
            /* run through each NounPhrase in this slot's list */
            foreach (local np in self.(role.npListProp))
            {
                /* invoke the callback on this item */
                func(np);
            }
        }
    }

    /* 
     *   If the parser has just asked the player to supply a missing object via
     *   the askMissingObject() function, we don't want to resolve the nouns for
     *   every object role, but only for the role with which askMissingObject()
     *   is currently concerned; askMissingObject() stores that role here so
     *   that our resolvedNouns() method knows to resolve only the noun for this
     *   role rather than for all the roles in the command. If npToResolve is
     *   nil (as it normally will be) then it will be ignored, and all noun
     *   roles will be resolved.
     */
        
    npToResolve = nil
    
    /* 
     *   Build the object lists.  This runs through each NounPhrase in the
     *   command to build its 'objs' list, then builds the corresponding
     *   master list in the Command object.  
     */
    buildObjLists()
    {
        /* 
         *   Note that we're the current command object, so that other object
         *   methods can refer to our object lists.
         */
        gCommand = self;
        
        /* run through each active noun phrase list */
        foreach (local role in npList)
        {
            /* set up a vector to hold this list's nouns */
            self.(role.objListProp) = new Vector(10);

            /* build the object list for each NounPhrase */
            foreach (local np in self.(role.npListProp))
            {
                /* build the list */
                np.buildObjList();

                /* append it to the master match list for this slot */
                self.(role.objListProp).appendAll(np.matches);
            }
        }
    }

    /*
     *   Save a potential antecedent for a reflexive pronoun coming up
     *   later in the command.  Each time we visit a noun phrase during the
     *   reflexive pronoun phase, we'll note its resolved objects here.
     *   Since we visit the noun phrases in their order of appearance in
     *   the command, we'll naturally always have the latest one mentioned
     *   when we come to a reflexive pronoun.  This gives us the correct
     *   resolution, which is the nearest preceding noun.  Note that the
     *   noun phrase shouldn't call this routine to note reflexive
     *   pronouns, since they don't bind to earlier reflexive pronouns -
     *   they only bind to regular noun phrases.  
     */
    saveReflexiveAnte(obj)
    {
        /* if we don't have a reflexive antecedent table, skip this */
        if (reflexiveAnte == nil)
            return;

        /* if the object isn't already a list, wrap it in a list */
        local lst = obj;
        if (!lst.ofKind(Collection))
            lst = [obj];

        /* 
         *   Run through the regular pronoun list, and save this object
         *   with the pronouns that apply to this object.  Note that a
         *   given object might match multiple pronouns, so we might save
         *   the object for several different pronouns.  
         */
        foreach (local p in Pronoun.all)
        {
            if (p.matchObj(obj))
                reflexiveAnte[p] = lst;
        }
    }

    /*
     *   Resolve a reflexive pronoun on behalf of one of the NounPhrases
     *   within this command.  
     */
    resolveReflexive(pronoun)
    {
        /* if there's no table, there's no antecedent */
        if (reflexiveAnte == nil)
            return [];

        /* get the meaning from the reflexive antecedent table */
        local ante = reflexiveAnte[pronoun];

        /* if there's no antecedent defined, return an empty list */
        if (ante == nil)
            ante = [];

        /* return the result */
        return ante;
    }

    /* table of reflexive pronoun antecedents */
    reflexiveAnte = nil

    /*
     *   Class method: Sort a list of Command matches, in priority order.
     *   The priority order is the order for processing predicate grammar
     *   matches: start at the highest priority, and work through the list
     *   until you find one where the noun phrases resolve to valid
     *   game-world objects; that's the one to execute.  
     */
    sortList(cmdLst)
    {
        
        /* First reduce the list to those with active predicates */
        cmdLst = cmdLst.subset({c: c.predActive});
        
        /* pre-calculate the priorities, to save work during the sort */
        foreach (local cmd in cmdLst)
            cmd.fixPriority();
        
        

        /* sort in descending order of priority */
        return cmdLst.sort(SortDesc, {a, b: a.priority - b.priority});
    }

    /*
     *   Calculate the parsing priority.
     *   
     *   When the parser looks for grammar rule matches to the input, it
     *   considers *all* of the possible matches.  Natural language is full
     *   of syntactic ambiguity, so a given input string can often be
     *   parsed into several different, but equally valid, syntax trees.
     *   It's often impossible to tell which parsing is correct based on
     *   syntax alone - you often have to look at the overall meaning of
     *   the sentence.  For example, GIVE BOOK TO BOB could be interpreted
     *   as having a direct object (BOOK) and an indirect object (BOB), or
     *   it could be seen as having only a direct object (BOOK TO BOB,
     *   treating the TO as a prepositional phrase modifying BOOK rather
     *   than as a part of the verb phrase structure).  The initial parsing
     *   phase only looks at the syntax, so it has to consider all of the
     *   valid phrase structures, even though a human speaker would
     *   immediately dismiss many of them as nonsensical.  Once we find all
     *   of the syntax matches, the parser puts them into priority order,
     *   and then goes down the list looking for the first one that makes
     *   sense semantically (which is defined roughly as having noun
     *   phrases that refer to actual objects).
     *   
     *   The priority, then, represents our guess at the likelihood that
     *   the grammar structure matches the user's intentions, based on the
     *   syntax.  Our fundamental assumption is that the command is valid:
     *   that is, it's well-formed grammatically, AND it expresses
     *   something that's possible, or at least logical to try, within the
     *   game-world context.  Given this, our strategy is to find a grammar
     *   structure that gives us a command that we can actually carry out.
     *   
     *   The priority is a composite value, made up of weighted component
     *   values.  We combine the components into a single scalar value
     *   simply by adding up the parts multiplied by their weights.  (Or,
     *   looked at another way, we combine the values using a high-radix
     *   numbering system.)  The components are, from most significant to
     *   least significant:
     *   
     *   - Grammatically correct commands sort ahead of commands with
     *   structural errors.
     *   
     *   - The predicate priority, from the VerbProduction.  (This tells us
     *   how "complete" the predicate structure is: a predicate with
     *   missing information has a lower priority.  This is in keeping with
     *   our assumption that the user's input is well-formed - we'll try
     *   the most complete structures first before falling back on the
     *   possibility that the user left out some information.)
     *   
     *   - Filled noun slots ahead of missing noun slots.  A missing noun
     *   slot occurs when the player leaves one of the noun roles empty
     *   (PUT BOX, TAKE).  We can fill in this information with automatic
     *   defaults, so it's not necessarily a reason to reject the parsing,
     *   but if there's another interpretation that has fully occupied noun
     *   slots, try the occupied one first.
     *   
     *   - More noun phrase slots first.  For example, sort a command with
     *   a direct and indirect object (two slots) ahead of one with only a
     *   direct object.  More slots means that we found more "structure" in
     *   the command; we can sometimes interpret the same command with less
     *   structure by subsuming more words into a long noun phrase.
     *   
     *   - Longest noun phrases, in aggregate, first.  This is in terms of
     *   tokens matched from the user input.  (We want to consider longer
     *   noun phrases first because it's more likely that they'll match
     *   exact objects, so there's less chance of ambiguity, *and* it's
     *   more likely that if we're wrong about the structure, we'll simply
     *   fail to find a matching object and move on to other parse trees.
     *   Longer noun phrases are less likely to yield spurious matches
     *   simply because they have more words that have to match.)
     *   
     *   - Grammatical noun phrases take priority over misc word phrases (a
     *   misc word phrase is text in a noun phrase slot that doesn't match
     *   any of the defined patterns in the grammar rules).
     *   
     *   - Longest command first, in terms of tokens matched from the user
     *   input.  (The more user input we use the better, since that gives
     *   us more confidence that we're correctly interpreting what the user
     *   said.  When we leave extra tokens for later, we can't be sure that
     *   we'll be able to make any sense of what's left over, whereas
     *   tokens in the current match are known to fit a grammar rule.)  
     */
    calcPriority()
    {
        return (badMulti == nil ? 250000000 : 0)
            + predPriority*2500000
            + 500000*(4 - min(missingNouns, 4))
            + 100000*min(numNounSlots(), 4)
            + 10000*(9 - min(miscWordLists.length(), 9))
            + 100*min(npTokenLen(), 99)
            + min(tokenLen, 99);
    }

    /* 
     *   Set a fixed priority.  This makes the priority a fixed value
     *   rather than a calculated value.  We call this before sorting a
     *   list of commands, so that we don't have to recalculate the
     *   priority value repeatedly during the sort.  
     */
    fixPriority() { priority = self.calcPriority(); }

    /* note a noun phrase with a miscellaneous word list */
    noteMiscWords(np)
    {
        /* if we haven't already noted this one, add it to our list */
        if (miscWordLists.indexOf(np) == nil)
            miscWordLists += np;
    }

    /* the calculated priority */
    priority = 0

    /*
     *   List of noun phrases containing misc word phrases.  The misc word
     *   phrase grammar rules will notify us when they're visited in the
     *   build process, and we'll note them here.  
     */
    miscWordLists = []

    /*
     *   Do we have any missing or empty noun phrases in the match?  The
     *   verb and noun phrases will fill this in.  
     */
    missingNouns = 0

    /* 
     *   The number of tokens from the command line that we matched for the
     *   command.  The CommandProduction object sets this for us as it
     *   builds the command from the parse tree.  We use this to determine
     *   the priority order of the syntax matches, when there are multiple
     *   matches: other things being equal, we'll take the longest match.
     *   Longer matches are better because they come closer to using
     *   everything the user typed, which is our eventual goal.
     *   
     *   This reflects the number of tokens used in the first predicate
     *   phrase; it omits any additional predicates or conjunctions.  We
     *   only count the first predicate because we always go back and
     *   re-parse any additional text on the line from scratch after
     *   executing the first predicate, in case the execution changes the
     *   game state in such a way that the parsing changes.  
     */
    tokenLen = 0

    /* Calculate the sum of the token lengths of our noun phrases */
    npTokenLen()
    {
        /* sum the token counts of all of the noun phrases */
        local tot = 0;
        forEachNP({ np: tot += np.tokens.length() });

        /* return the sum */
        return tot;
    }

    /* Calculate the number of noun slots we have filled in */
    numNounSlots() { return npList.length(); }

    /* the predicate priority (see VerbProduction.priority) */
    predPriority = 0
    
    /* is our predicate currently active (see VerbProduction.isActive) */
    predActive = true

    /*
     *   The parse tree (the root of the grammar match), if applicable.
     *   Commands built from user input have a parse tree; those built
     *   internally don't.  Note that the parse tree doesn't necessarily
     *   include *all* of the user input, since we could have asked
     *   questions (disambiguation, missing noun phrases) before the
     *   command was completed.  The question replies will be represented
     *   in noun phrases or other data added to the command after the
     *   initial parse.  
     */
    parseTree = nil

    /* the Action object giving the action to be performed */
    action = nil

    /* the Previous action performed by this command */
    lastAction = nil
    
    /* the VerbProduction object for the command */
    verbProd = nil

    /* the resolved actor; we determine this before disambiguation */
    actor = nil

    /* the actor(s) to whom the command is addressed, as a NounPhrase list */
    actorNPs = []

    /* the actor(s), as NPMatch objects */
    actors = []

    /*
     *   The grammatical person in which we're addressing the actor.  This
     *   is 2 for a second-person address, 3 for third-person orders.
     *   (It's hard to think of a case for first-person orders, but 
     *   
     *   The conventional IF syntax for giving orders is ACTOR, DO
     *   SOMETHING, which addresses ACTOR in the second person (as YOU).
     *   This means that second-person pronouns 
     */
    actorPerson = 2

    /* the direct object phrases, as a list of NounPhrase objects */
    dobjNPs = []

    /* the list of resolved direct objects, as NPMatch objects */
    dobjs = []

    /* the current direct object for the current action iteration */
    dobj = nil

    /* the NPMatch object for the current iteration's direct object */
    dobjInfo = nil

    /* the indirect object phrases, as a list of NounPhrase objects */
    iobjNPs = []

    /* the list of resolved indirect objects, as NPMatch objects */
    iobjs = []

    /* the indirect object for the current iteration */
    iobj = nil

    /* the NPMatch object for the current indirect object */
    iobjInfo = nil

    /* the accessory phrases, as a list of NounPhrase objects */
    accNPs = []

    /* the list of resolved accessory objects, as NPMatch objects */
    accs = []

    /* the accessory object for the current iteration */
    acc = nil

    /* synonym for the accessory object */
    aobj = (acc)
    
    /* the NPMatch object for the current accessory */
    accInfo = nil

    /*
     *   Disambiguation replies.  Each time the player answers a
     *   disambiguation question, we add the reply to this list.  We then
     *   go back and re-resolve the noun phrases, fetching replies from the
     *   list as we encounter the ambiguous objects again.
     *   
     *   Note that this is a list of list.  Each reply is a list of
     *   NounPhrase objects, and we might have a series of replies, so one
     *   list represents one reply.  
     */
    disambig = []

    /* the next available disambiguation reply */
    disambigIdx = 1

    /* 
     *   Is this command at the end of a sentence?  The grammar match sets
     *   this to true if the input syntax puts this predicate at the end of
     *   a sentence.  For example, in the English grammar, this is set if
     *   there's a period after this predicate.  This tells the parser that
     *   the next predicate in the same line is the start of a new
     *   sentence, so sentence-opening syntax is allowed.  
     */
    endOfSentence = nil

    /*
     *   The noun phrase roles (as NounRole objects), in the order they
     *   actually appear in the user input.  We build this list as the
     *   VerbProduction adds our noun phrases.  The phrase order is
     *   important when there are reflexive pronouns, because a reflexive
     *   pronoun generally refers back to the nearest preceding phrase of
     *   the same number and gender.  
     */
    npList = []
    
    /*  
     *   A copy of the npList sorted to ensure that the direct and indirect
     *   objects of a TIAction are verified in the order specified on that
     *   action.
     */
    
    npListSorted = []

    /* 
     *   Error flag: we have a noun list (grammatically) where a single
     *   noun is required.  When this occurs, this will be set to the role
     *   where the error was noted.  
     */
    badMulti = nil

    /*
     *   The token list for the next predicate.  The first predicate
     *   production fills this in during the build process with the token
     *   list for the next predicate on the same command line, based on the
     *   location of the conjunction or punctuation that ends the first
     *   predicate.  This is just what's left of the token list after the
     *   tokens used for our own predicate and after any conjunctions or
     *   punctuation marks that separate our predicate from the next one.  
     */
    nextTokens = []

    /* 
     *   The error we encountered building the command, if any.  This is
     *   usually a noun resolution error. 
     */
    cmdErr = nil
    
    /*   Does this command apply to objects matched to ALL? */
    
    
    matchedAll = nil
    
    /*   Does this command apply to objects matched to multiple objects? */
    matchedMulti = nil
    
    madeTopic = nil
    
    /* 
     *   Get the command phrase entered by the player, with the words used to
     *   match the direct, indirect and accessory objects replaced by (dobj),
     *   (iobj) and (acc) respectively; e.g. PUT RED BALL ON TABLE becomes 'put
     *   (dobj) on (iobj)'
     */
    getCommandPhrase()
    {
        /* 
         *   If for any reason we don't have a verbProd property, return an
         *   empty string (to avoid any run-time errors below).
         */
        if(verbProd == nil)
            return '';
        
        /*  Get the tokens making up the current command. */
        local toks = verbProd.tokenList.mapAll({t: getTokVal(t)});
        
        /*  Define a couple of local variables for use below */
        local idx1, idx2;
        
        /*  
         *   For each of the possible dobjMatch, iobjMatch and accMatch
         *   properties, replace the tokens used to match the dobj, iobj or
         *   accessory object with the placeholder '(dobj)', '(iobj)' or
         *   '(acc)'. To do that, replace the first token used to match the
         *   object with the placeholder, and then the other tokens that matched
         *   the object with '?-?'.
         */
        if(verbProd.dobjMatch)
        {
            idx1 = verbProd.dobjMatch.np_.firstTokenIndex;
            idx2 = verbProd.dobjMatch.np_.lastTokenIndex;
            toks[idx1] = '(dobj)';
            for(local i = idx1 + 1; i <= idx2; i++)
                toks[i] = '?-?';
        }
        
        if(verbProd.iobjMatch)
        {
            idx1 = verbProd.iobjMatch.np_.firstTokenIndex;
            idx2 = verbProd.iobjMatch.np_.lastTokenIndex;
            toks[idx1] = '(iobj)';
            for(local i = idx1 + 1; i <= idx2; i++)
                toks[i] = '?-?';
        }
        
        if(verbProd.accMatch)
        {
            idx1 = verbProd.accMatch.np_.firstTokenIndex;
            idx2 = verbProd.accMatch.np_.lastTokenIndex;
            toks[idx1] = '(acc)';
            for(local i = idx1 + 1; i <= idx2; i++)
                toks[i] = '?-?';
        }
        
        /* Now remove all the '?-?' tokens */
        toks = toks.subset({t: t != '?-?'});
        
        /* Join the tokens together into a string and return it. */
        return toks.join(' ');
    }
;




/* ------------------------------------------------------------------------ */
/*
 *   A FuncCommand is a special version of Command that carries out its
 *   action via a custom callback function, rather than by executing a
 *   regular Action.  This can be used to create a simple one-off custom
 *   command without having to create a separate Action for it.  
 */
class FuncCommand: Command
    /*
     *   Create: provide the grammar match object, if any, and the callback
     *   function to execute to carry out the command. 
     */
    construct(prod, func)
    {
        /* call the appropriate inherited constructor */
        if (prod != nil)
            inherited(prod);
        else
            inherited();

        /* save the callback function */
        self.func = func;
    }

    /* the callback function for carrying out our command action */
    func = nil
;
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1