# 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)
                    {

                       /*
                        *   If we're dealing with the direct object and our action wants to combine
                        *   duplicate objects remove any duplicate direct objects from our list for
                        *   direcc objects that allow duplicates to be removed.
                        */
                        if(role == DirectObject && action.combineDuplicateObjects)
                        {
                            local lst1 = self.(role.objListProp).toList();
                            local lst2 = lst1.subset({x:x.obj.combineDuplicateObjects});
                            local lst3 = lst1.subset({x:x.obj.combineDuplicateObjects == nil});
                            local lst4 = lst2.mapAll({x:x.obj}).getUnique();

                            lst1  = [];

                            foreach(local o in lst4)
                            {
                                lst1 += lst2.valWhich({x:x.obj==o});
                            }

                            lst1 += valToList(lst3);

                            self.(role.objListProp) = new Vector(lst1);
                        }

                        /* 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);

                /*
                 *   Next give any active Scenes the opportunity to veto this action.
                 */

                if(defined(Scene) && forEachInstance(Scene, {s: s.tryPreAction(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 13/06/2025 from adv3Lite version 2.2.1