doer.t

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


/*
 *   **************************************************************************
 *   doer.t
 *
 *   This module forms part of the adv3Lite library (c) 2012-13 Eric Eve, but is
 *   heavily based on parts of the Mercury library (c) 2012 Michael J. Roberts.
 */

/*  
 *   A Redirector is an object that can redirect one action to another via a
 *   doInstead wrapper method that provides a common interface. Subclasses are
 *   responsible for implementing the redirect method.
 *
 *   We begin this module by defing the Redirector class since in adv3Lite
 *   (though not in Mercury) Redirector is the base class for Doer.
 */
class Redirector: object
    
    /* 
     *   doInstead() turns the current action into altAction with the objects
     *   specified in args, and executes altAction as a replacement for the
     *   current action.
     */
    doInstead(altAction, [args])
    {
        doOtherAction(true, altAction, args...);
    }
    
    /* 
     *   doNested() executes altAction with the objects specified in args,
     *   executins altAction as part of the current action.
     */
    doNested(altAction, [args])
    {
        doOtherAction(nil, altAction, args...);
    }
    
    /* 
     *   Execute altAction on the objects specified in the args parameter. If
     *   isReplacement is true make altAction a replacement for the current
     *   action.
     */
    doOtherAction(isReplacement, altAction, [args])
    {
        
        /* Extract our dobj and our iobj from the args parameter. */
        local dobj = args.element(1);
        local iobj = args.element(2);    
        local aobj = args.element(3);
        
        /* 
         *   If the action involves a Literal argument and one of the arguments
         *   is supplied as a single-quoted string, wrap it in a LiteralObject
         *   before passing it.
         */
        if(altAction.ofKind(LiteralAction) || altAction.ofKind(LiteralTAction)
           || aobj != nil)
        {
            if(dataType(dobj) == TypeSString)
                dobj = new LiteralObject(dobj);
            if(dataType(iobj) == TypeSString)
                iobj = new LiteralObject(iobj);
            if(dataType(aobj) == TypeSString)
                aobj = new LiteralObject(aobj);
        }
        
        /*  
         *   If the action involves a Topic object and one of the argumnets is
         *   supplied as a single-quoted string, wrap it in a Topic before using
         *   it.
         */                
        if(altAction.ofKind(TopicAction) || altAction.ofKind(TopicTAction)
           || aobj != nil)
        {
            if(dataType(dobj) == TypeSString)
                dobj = new Topic(dobj);
            if(dataType(iobj) == TypeSString)
                iobj = new Topic(iobj);
            if(dataType(aobj) == TypeSString)
                aobj = new Topic(aobj);
        }
        
        /*   
         *   If the action is a TopicTAction check that the appropriate object
         *   (usually but not necessarily the indirect object) has been passed
         *   as a ResolvedTopic; if not, wrap it in a new Resolved Topic object.
         */
        if(altAction.ofKind(TopicTAction))
        {
            if(altAction.topicIsGrammaticalIobj && !iobj.ofKind(ResolvedTopic))
                iobj = new ResolvedTopic([iobj], iobj.name.split(' '));
            
            if(!altAction.topicIsGrammaticalIobj && !dobj.ofKind(ResolvedTopic))
                dobj = new ResolvedTopic([dobj], dobj.name.split(' '));
            
//            if(!altAction.topicIsGrammaticalAobj && !aobj.ofKind(ResolvedTopic))
//                dobj = new ResolvedTopic([aobj], aobj.name.split(' '));
        }
        
        
               
        /*  
         *   If the new action is of a kind that requires two objects, call the
         *   redirect method with both objects
         */
        if(altAction.ofKind(TIAction) || altAction.ofKind(LiteralTAction) ||
           altAction.ofKind(TopicTAction))
        {
            redirect(gCommand, altAction, dobj: dobj, iobj: iobj, aobj: aobj,
                     isReplacement: isReplacement);
            return;
        }
        
        /*   
         *   If the action is a TopicAction check that its object has been
         *   passed as a ResolvedTopic; if not, wrap it in a new Resolved Topic
         *   object.
         */
        if(altAction.ofKind(TopicAction) && !dobj.ofKind(ResolvedTopic))
        {
            dobj = new ResolvedTopic([dobj], dobj.name.split(' '));
        }
        
        /*  
         *   If the new action requires a single object, call redirect with the
         *   direct object.
         */
        if(altAction.ofKind(TAction) || altAction.ofKind(LiteralAction) ||
           altAction == Go || altAction.ofKind(TopicAction))
        {
            redirect(gCommand, altAction, dobj: dobj, isReplacement:
                     isReplacement);
            return;
        }      
        
        /*  
         *   Otherwise call redirect with the new action alone (it's some form
         *   of intransitve action).
         */
        redirect(gCommand, altAction, isReplacement: isReplacement);
    }
    
    /* Ask for a missing literal and retry action with the literal in role. */
    askMissingLiteral(action, role = IndirectObject)
    {
        local obj;
        
        if(objOfKind(gDobj,Thing))
            obj = gDobj;
        else if(objOfKind(gIobj, Thing))
            obj = gIobj;  
        
        /* Ask for the missing literal */
        askMissingLiteralQ(action, role);
        "<.p>";
        
        /* Read a new command from the keyboard. */        
        local txt = readCommandLine();       
        
        if(role == IndirectObject && action.ofKind(LiteralTAction))
        {           
            doInstead(action, obj, txt);           
        }
        
        if(role == DirectObject)
        {
            if(action.ofKind(LiteralAction))
                doInstead(action, txt);
            
            if(action.ofKind(LiteralTAction))
                doInstead(action, txt, obj);
        }
    }
    
;


/* ------------------------------------------------------------------------ */
/*
 *   A Doer is a command handler for a specific action acting on particular
 *   objects under a given set of conditions.  We use these for all of the
 *   levels of customization in command handling.
 *   
 *   Doer objects are inherently static.  All Doer objects should be
 *   defined at compile time; they're not designed to be created
 *   dynamically during execution.  Rather than creating and removing Doer
 *   objects as conditions in the game change, use the Doer conditions to
 *   define when a given Doer is active and when it's dormant.  
 */
class Doer: Redirector
    /*
     *   The command that the object handles.  This is a string describing
     *   the action and object combination that this handler recognizes.
     *   
     *   The command string specifies a verb and its objects, generally
     *   using the same verb phrase syntax that a player would use to enter
     *   a command to the game.  The exact verb syntax is up to the
     *   language library to define; for English, we replicate the same
     *   verb phrases used to parse command input.
     *   
     *   The verb phrase syntax is generally the same as for regular player
     *   commands, but the noun syntax is different.  Each noun is written
     *   as the SOURCE CODE name of a game object or class.  That is, not a
     *   noun-and-adjective phrase as the player would type it, but the
     *   program symbol name as it appears in the source code.  If you use
     *   a class name, the command matches any object of the class.  For
     *   example, to handle putting any treasure in any container:
     *   
     *.    cmd = 'put Treasure in Container'
     *   
     *   You can match multiple objects or classes in a single noun slot
     *   (and you can freely mix objects and classes).  For example, to
     *   handle putting any treasure or magic item in a container:
     *   
     *.    cmd = 'put Treasure|Magical in Container'
     *   
     *   You can't use the '|' syntax with verbs, because the verb syntax
     *   covers the entire phrase.  You can match multiple verbs by writing
     *   out the entire phrasing for each verb, separating each phrase with
     *   a semicolon:
     *   
     *.    cmd = 'take skull; put skull in Thing'
     *   
     *   You can also write a command that matches ANY verb, by using "*"
     *   as the verb.  You can follow the "*" with any number of objects;
     *   the first is the direct object, the second is the indirect, and
     *   the third is the accessory.  This phrasing will match any verb
     *   that matches the given objects AND the given number of objects.
     *   For example, '* Thing' will match any verb with a direct object
     *   that's of class Thing, but it won't match verbs without any
     *   objects or verbs with an indirect object.  Using "*" as a noun
     *   will match any object as well as no object at all.  So to write a
     *   handler for every possible command, you'd write:
     *   
     *.    cmd = '* * * *'
     *   
     *   That is, match any verb, with or without any direct object,
     *   indirect object, and accessory object.  
     */
    cmd = ''

    /*
     *   The priority of this handler.  You can use this when it's
     *   necessary to override the default precedence order, which is
     *   figured according to the specialization rules described below.
     *   
     *   Most of the time, you shouldn't need to set a priority manually.
     *   If you don't, the library determines the precedence automatically
     *   according to the degree of specialization.  However, the way the
     *   library figures specialization is a heuristic, so it's not always
     *   right.  In cases where the heuristic produces the wrong results,
     *   you can bypass the rules by setting a priority manually.  A manual
     *   priority takes precedence over all of the standard rules.
     *   
     *   Our basic approach is to process Doers in order from most specific
     *   to most general.  This creates a natural hierarchy of handlers
     *   where more specific rules override the generic, default handlers.
     *   Here are the degrees of specialization, in order of importance:
     *   
     *   1. A Doer with a higher 'priority' value takes precedence over one
     *   with a lower value. 
     *   
     *   2. A Doer with a 'when' condition is more specific than a Doer
     *   without one.  A 'when' condition means that the Doer is designed
     *   to operate only at specific times, so it's inherently more
     *   specialized than one that always operates.
     *   
     *   3. A Doer with a 'where' condition is more specific than a Doer
     *   without one.  A 'where' condition means that the Doer only applies
     *   to a limited geographical area.
     *   
     *   4. A Doer that matches a particular Action is more specific than
     *   one that matches any Action.
     *   
     *   5. If two Doer commands are for the same Action, the Doer that
     *   matches a more specialized subclass (or just a single object
     *   instance) for a noun phrase is more specific than one that matches
     *   a base class for the same noun phrase.  For example, 'take
     *   Container' is more specific than 'take Thing', because Container
     *   is a subclass of Thing, and 'take backpack' (where the 'backpack'
     *   is a Container) is more specific than either.  This type of
     *   specialization applies in the canonical object role order: direct
     *   object, indirect object, accessory.  For example, we consider 'put
     *   Container in Thing' to be more specific than 'put Thing in
     *   Container', because we look at the direct object by itself before
     *   we even consider the indirect object.  This rule only applies when
     *   the Action is the same: 'put Thing in Container' and 'open Door'
     *   are equal for the purposes of this rule.
     *   
     *   It's important to understand that each degree of specialization is
     *   considered independently of the others, in the order above.  For
     *   example, if you have a Doer with just a 'when' condition, and
     *   another with only a 'where' condition, the one with the 'when'
     *   condition has higher priority.  This is because we look at the
     *   presence of a 'when' condition first, before even considering
     *   whether there's a 'where' condition.
     *   
     *   The library has no way to gauge the specificity of a 'when' or
     *   'where' condition, so there's no finer-grained priority to the
     *   conditions than simply their presence or absence.
     *   
     *   If two Doers have the same priority based on the rules above, the
     *   one that's defined LATER in the source code has priority.  This
     *   means that Doers defined in the game take priority over library
     *   definitions.  
     */
    priority = 100

    /*
     *   Execute the command. 
     */
    
    /* 
     *   ECSE ADDED a curCmd parameter (the command being added) to give the Doer
     *   access to what it's meant to be acting on, together with a default
     *   handling (execute the action associated with the current command).
     */
    exec(curCmd)
    {        
        /*
         *   If the command specifies a direction, check that the direction is
         *   valid for the actor's location. This will rule out meaningless
         *   commands like THROW BALL PORT, GO AFT or PUSH TROLLEY STARBOARD
         *   when we're not aboard a vessel.
         */
        if(curCmd.verbProd && curCmd.verbProd.dirMatch != nil)
            checkDirection(curCmd);
	
        /*   
         *   Temporarily set gDobj and gIobj to the dobj and iobj of the curCmd
         *   so that they're available to be passed as parameters
         */        
        gAction.curDobj = curCmd.dobj;
        gAction.curIobj = curCmd.iobj;
        gAction.curAobj = curCmd.acc;
        
        /* 
         *   If the command is an action to be carried out by the player
         *   character, execute the action in the normal manner.
         */
        if(curCmd.actor == gPlayerChar)
        {
            /* 
             *   If our execAction() method is going to handle the action in
             *   some non-standard way, instead of simply stopping it or
             *   replacing it with another one, we need to perform some
             *   housekeeping to ensure everything works properly.
             */
            if(handleAction)
            {
                /* 
                 *   If we specified an object on the handleAction property,
                 *   it's the action we're going to simulate. We don't need to
                 *   specify a different action if we're notionally handling the
                 *   one we've just matched.
                 */
                if(propType(&handleAction) == TypeObject)
                {
                    /* 
                     *   Change the current action to the one we're notionally
                     *   handling.
                     */
                    gAction = handleAction;
                    
                    /*  
                     *   Set the objects of the new gAction in case we need them
                     */
                    gAction.curDobj = curCmd.dobj;
                    gAction.curIobj = curCmd.iobj;
                    gAction.curAobj = curCmd.acc;
                }
                   
                /* Send the beforeAction notifications. */                
                gAction.beforeAction();                
                
            }
            
            execAction(curCmd); 
        }
        
        /* 
         *   If the command is directed to another actor (or object) let the
         *   actor or object in question handle it.
         */
        else
            curCmd.actor.handleCommand(curCmd.action);
    }
    
    /* 
     *   If this Doer is handling a complete action (instead of stopping one or
     *   replacing it with another) this should either be true (if it's the same
     *   action that we've matched) or some other action (if that's the one
     *   we're simulating and it's not the one we matched).
     */
    handleAction = nil
    
    /* 
     *   We separate out execAction() as a separate method from exec() so that
     *   custom Doers can readily override this for the player character while
     *   leaving commands directed to other actors (or objects) to be handle by
     *   their handleCommand() method.     */
    
    execAction(curCmd)
    {
        /* 
         *   Our default behaviour is to let the current action handle the
         *   command.
         */
        curCmd.action.exec(curCmd);
    }
    
    /* 
     *   Check whether the direction associatated with this command is valid for
     *   the actor's current location.
     */
    checkDirection(curCmd)
    {
        local dirn = curCmd.verbProd.dirMatch.dir;
        local loc = curCmd.actor.getOutermostRoom();
        
        /* 
         *   Rule out a command involving a shipboard direction where shipboard
         *   directions aren't allowed.
         */
        if(dirn.ofKind(ShipboardDirection) && !loc.allowShipboardDirections())
        {
            DMsg(no shipboard directions, 'Shipboard directions {plural} {have}
                no meaning {here}. ');
            abort;
        }
        
        
        /*
         *   Rule out a command involving a compass direction where compass
         *   directions aren't allowed.
         */
        if(dirn.ofKind(CompassDirection) && !loc.allowCompassDirections)
        {
            DMsg(no compass directions, 'Compass directions {plural} {have}
                no meaning {here}, ');
            abort;
        }
        
        /* 
         *   Set the direction property of the current Command's association
         *   Action object to the direction determined by its
         *   verbProd.dirMatch.dir property in case the game author tries to use
         *   action.direction to get at the direction entered.
         */
        curCmd.action.direction = dirn;
        
    }
    
    /* 
     *   Utility method that can be called from execAction() to redirect the
     *   command to a new action with the same (or new) objects. This will
     *   normally be called via the doInstead()/doNested() interface defined on
     *   our Redirector superclass.
     */    
    redirect(curCmd, altAction, dobj: = 0, iobj: = 0, aobj: = 0,
             isReplacement: = true)
    {
        
        /* 
         *   We use a default value of 0 for the dobj and iobj parameters to
         *   mean 'keep the current value' so that we can explicitly pass nil
         *   values if we want to.
         */             
        dobj = dobj == 0 ? curCmd.dobj : dobj;
        iobj = iobj == 0 ? curCmd.iobj : iobj;
        aobj = iobj == 0 ? curCmd.acc : aobj;
        
        /* 
         *   Get the current command to change its current action to altAction
         *   performed on dobj and iobj. Note that this will change gAction to
         *   altAction.
         */
        curCmd.changeAction(altAction, dobj, iobj, aobj);
        
        /* Execute the command on our new action. */
        gAction.exec(curCmd);
    }
    
    /* 
     *   Set this property to true for this Doer to match only if the wording
     *   corresponds (and not just the action). At the moment the check is
     *   only on the first word of the command, but this may usually be enough
     */
    
    strict = nil
    
    /*  
     *   Flag, do we want to ignore (i.e. not report) an error in the
     *   construction of this Doer. We may want to do this when the error is
     *   simply due to the exclusion of a module like extras.t
     */
    ignoreError = nil
;

/* Define isHappening as a property in case the scenes module is not included */
property isHappening;

/* ------------------------------------------------------------------------ */
/*
 *   A DoerCmd is a helper object that stores a single command match
 *   template for a Doer object.  A given Doer can match multiple commands;
 *   each match is represented by one of these objects.  
 */
class DoerCmd: object
    /* construction */
    construct(d, c)
    {
        doer = d;
        cmd = c;
    }

    /* the Doer I'm associated with */
    doer = nil

    /* 
     *   The parsed command template.  This is a list consisting of the
     *   Action we match plus the objects or classes we match for the noun
     *   phrases, in the canonical order (direct object, indirect object,
     *   accessory).  The action can also be the Action class itself, to
     *   indicate that we match all actions.  We only match a command with
     *   the same number of noun phrases as in the template.  
     */
    cmd = []

    /*
     *   My global sequence number.  During initialization, we set this to
     *   reflect our position in the global list of DoerCmd objects after
     *   the list is sorted into priority order.  This makes it easy to
     *   sort a new list of DoerCmd objects into the original priority
     *   order.  
     */
    seqno = 0

    /*
     *   Class member: the master table of DoerCmd objects.  The library
     *   builds this automatically during preinitialization.  This is a
     *   lookup table indexed by Action.  Each Action entry has a list of
     *   DoerCmd objects associated with that Action.  Note that the
     *   generic all-verb handlers are listed under Action.  
     */
    doerTab = nil

    /*
     *   Class method: Get a list of Doer objects matching the given
     *   command.  'cmdLst' is the command's action and object list in
     *   canonical format: [action, dobj, iobj, accessory].  
     */
    findDoers(cmdLst)
    {
        /* 
         *   Start with a list of the DoerCmd objects that could *possibly*
         *   match this command.  This includes all of the DoerCmds listed
         *   in the master table under the command's action, plus all of
         *   the wildcard "any action" DoerCmds, which are listed in the
         *   table under Action.  
         */
        local lst = nilToList(doerTab[cmdLst[1]])
            + nilToList(doerTab[Action]);

        /* keep only the elements that match the command's objects */
        lst = lst.subset({ d: d.matchCmd(cmdLst) });
        
        /* 
         *   keep only the elements whose where and when conditions don't
         *   exclude them.
         */
        
        lst = lst.subset({ d: d.matchConditions() } );

        /* sort the combined list into the original priority order */
        lst = lst.sort(SortAsc, { a, b: a.seqno - b.seqno });

        /* pull out the list of Doers that the DoerCmds map to */
        lst = lst.mapAll({ d: d.doer });

        /* 
         *   It's conceivable that a given Doer is listed more than once,
         *   since the same Doer could have more than one matching command
         *   template.  In such cases we'd still only want to process each
         *   Doer once, so eliminate any duplicates. 
         */
        lst = lst.getUnique();

        /* return the result */
        return lst;
    }

    /*
     *   Check for a match to a command list.  'cmdLst' is the command
     *   object list in canonical format: [action, dobj, iobj, ...].  This
     *   routine determines if our Doer is a handler for the given command.
     */
    matchCmd(cmdLst)
    {
        /*
         *   The first element of the template is the action.  For the
         *   template to match, the Command's action must either exactly
         *   match the template action, or it must be an instance of the
         *   template action class.  
         */
        if (cmdLst[1] != cmd[1] && !cmdLst[1].ofKind(cmd[1]))
            return nil;

        /*   
         *   If the strict property is set we want the doer to match not only
         *   the command but the wording of the command, or rather, at least the
         *   first word, so that, for example, 'go through junk' would not be
         *   treated as matching 'walk through junk' (in English idiom the first
         *   but not the second might be treated as meaning 'search junk').
         */
           
        
        if (gCommand != nil && doer.strict && gCommand.verbProd.tokenList !=
            nil)
        {
            local cmdToks = gCommand.verbProd.tokenList.mapAll( {x:
                getTokVal(x) });
            local doerToks = doer.cmd.split(' ');
            
            if(cmdToks[1] != doerToks[1])
                return nil;        
        }
        
        /* 
         *   The rest of the template is the list of noun roles, in
         *   canonical order (direct object, indirect object, accessory).
         *   The Command must have the same number of objects, and each
         *   object in the Command must match the corresponding template
         *   object or be an instance of the template object class.
         *   (There's one special case: if the template object is nil, we
         *   match anything.)
         *   
         *   First, check that we have the same number of objects.  
         */
        if (cmdLst.length() != cmd.length())
            return nil;

        /* now check each object */
        for (local i = 2, local len = cmd.length() ; i <= len ; ++i)
        {
            /* get the object from the Command and template */
            local cobj = cmdLst[i];
            local tobj = cmd[i];

            /* 
             *   if the template object is non-nil, we have to match the
             *   object or class 
             */
            if (tobj != nil && cobj != tobj && !cobj.ofKind(tobj) 
                && cobj.lexicalParent != tobj)
                return nil;
        }

        /* everything matches, so this Command matches this template */
        return true;
    }

    /*
     *   Get the processing priority sorting order relative to another
     *   DoerCmd.  (See Doer.priority for a discussion of the priority
     *   rules.)
     */
    compareTo(other)
    {
        local p, a, b;
        
        /* the explicitly priority takes precedence over all other rules */
        if (doer.priority != other.doer.priority)
            return doer.priority - other.doer.priority;

        /* a 'when' has priority over no 'when' */
        p = doer.propDefined(&when);
        if (p != other.doer.propDefined(&when))
            return p ? 1 : -1;

        /* a 'where' has priority over no 'where' */
        p = doer.propDefined(&where);
        if (p != other.doer.propDefined(&where))
            return p ? 1 : -1;

         /* a 'who' has priority over no 'where' */
        p = doer.propDefined(&who);
        if (p != other.doer.propDefined(&who))
            return p ? 1 : -1;
        
        /* a 'during' has priority over no 'during' */
        p = doer.propDefined(&during);
            if (p != other.doer.propDefined(&during))
            return p ? 1 : -1;
        
        /* get each command's Action */
        a = cmd[1];
        b = other.cmd[1];
        
        /* 
         *   a 'direction' has priority over no 'direction' for a Travel
         *   command.
         */        
        if(a == Travel)
        {
            p = doer.propDefined(&direction);
            if (p != other.doer.propDefined(&direction))
                return p ? 1 : -1;
        }

        /* 
         *   if one is a specific Action and the other is the generic
         *   'Action' class (a wildcard that matches all actions), the
         *   specific action takes precedence 
         */
        if (a != Action && b == Action)
            return 1;
        if (a == Action && b != Action)
            return -1;

        /* if the actions are the same, compare the objects */
        if (a == b && cmd.length() == other.cmd.length())
        {
            /* 
             *   Check each object role in turn, in canonical order.  The
             *   first one where the precedence differs determines the
             *   overall precedence. 
             */
            for (local i = 2 ; i < cmd.length() ; ++i)
            {
                /* get the two objects */
                a = cmd[i];
                b = other.cmd[i];

                /* 
                 *   if one is a subclass of the other, the subclass takes
                 *   precedence because it's more specialized
                 */
                if (a == nil || b == nil)
                {
                    if (a != nil)
                        return 1;
                    if (b != nil)
                        return -1;
                }
                else
                {
                    if (a.ofKind(b))
                        return 1;
                    if (b.ofKind(a))
                        return -1;
                }
            }
        }

        /* 
         *   Failing all else, go by the relative location of the source
         *   code definitions: the definition that appears later in the
         *   source code takes precedence.  If the two are defined in
         *   different modules, the one in the later module takes
         *   precedence.  
         */
        if (doer.sourceTextGroup != other.doer.sourceTextGroup)
            return doer.sourceTextGroup.sourceTextGroupOrder
            - other.doer.sourceTextGroup.sourceTextGroupOrder;

        /* they're in the same module, so the later one takes precedence */
        return doer.sourceTextOrder - other.doer.sourceTextOrder;
    }
    
    /* Check whether a Doer matches its where, when, who and during conditions. */    
    matchConditions()    
    {
        /* first check the where condition, if there is one. */
        if(doer.propDefined(&where))
        {
            local whereLst = valToList(doer.where);
                                    
            /* 
             *   if we can't match any item in the where list to the player
             *   char's current location, we don't meet the where condition, so
             *   return nil
             */
            if(whereLst.indexWhich( {loc: gPlayerChar.isIn(loc)}) == nil)
                return nil;
        }
        
        /* 
         *   Interpret 'when' as simply a routine that returns true or nil
         *   aocording to some condition defined by the author; so we simply
         *   test whether doer.when returns nil if the property is defined.
         */        
        if(doer.propDefined(&when) && doer.when() == nil)
            return nil;       
        
         /* check the who condition, if there is one. */
        if(doer.propDefined(&who))
        {
            local whoLst = valToList(doer.who);
                        
            
            /* 
             *   If we can't match any item in the who list to the current
             *   actor, we don't meet the who condition, so return nil
             */
            if(whoLst.indexOf(gCommand.actor) == nil)
                return nil;
        }
        
        
        /* 
         *   if we're using the scene manager and a during condition is
         *   specified, test whether the scene is currently happening.
         */        
        if(defined(sceneManager) && doer.propDefined(&during))
        {
            local duringList = valToList(doer.during);
            
            if(duringList.indexWhich({s: s.isHappening}) == nil)
                return nil;
        }
        
        /* 
         *   If the command is a travel action and a direction has been
         *   specified, check that we match the direction.
         */
        if(doer.propDefined(&direction) && cmd[1] is in (Travel,
            PushTravelDir, ThrowDir))
            return valToList(doer.direction).indexOf(
                gCommand.verbProd.dirMatch.dir) != nil;
        
        /* 
         *   If we haven't failed any of the conditions, we're okay to match, so
         *   return true.
         */
        return true;
    }
;



/* ------------------------------------------------------------------------ */
/*
 *   A DoerParser is a helper object we use during initialization for
 *   parsing Doer 'cmd' strings and turning them into action description
 *   lists.  The language-specific library creates these for us based on
 *   the language grammar.
 *   
 *   These objects are only used during initialization, since they're only
 *   needed to set up the internal representation of a Doer command
 *   template string.  During normal play we only need that internal
 *   representation.  
 */
class DoerParser: object
    /*
     *   Construction.  The language library should create one of these
     *   objects for each verb phrasing it wants to define for use in
     *   writing Doer 'cmd' strings.  
     */
    construct(action, v, pat, roles)
    {
        action_ = action;
        verb_ = v;
        pat_ = new RexPattern('<space>*' + pat + '<space>*$');
        roles_ = roles;
    }

    /* The Action object for the verb. */
    action_ = nil

    /* 
     *   The main verb word.  This is simply the first word of the verb's
     *   token list.  This is essentially a hash, to reduce the number of
     *   regular expressions we have to test individually.  This saves us a
     *   lot of compute time, since it's very quick to pull out the first
     *   word and get a list of the small set of rules with the same first
     *   word.  We then test each of those potential matches by doing the
     *   full regular expression comparison.  
     */
    verb_ = nil

    /* 
     *   The regular expression for the verb rule.  The verb initializer
     *   sets this up to contain the literal text of the verb rule's
     *   literal tokens, and to substitute a parenthesized group wildcard
     *   pattern for each noun slot.  For example, for English, a Give To
     *   rule might look like 'give (.+) to (.+)'.  
     */
    pat_ = nil

    /* 
     *   The list of object roles.  This is a list of NounRole objects.
     *   The list entries correspond positionally to the parenthesized
     *   groups in the regular expression string, so roles_[1] is the noun
     *   role for the first parenthesized group, roles_[2] is the noun role
     *   for the second group, and so on.  
     */
    roles_ = []
;


/* ------------------------------------------------------------------------ */
/*
 *   DoerParser Table.  This stores a lookup table of DoerParser objects,
 *   indexed by the first word (the verb) of the command template.  
 */
class DoerParserTable: object
    /* add a parser to the table */
    addParser(p)
    {
        /* get the verb from the parser */
        local v = p.verb_;

        /* make sure there's a list at the verb entry */
        if (ptab[v] == nil)
            ptab[v] = [];

        /* add this parser to the list for its verb */
        ptab[v] += p;
    }

    /* get the list of parsers for a given verb word */
    getParsers(v)
    {
        /* look up the list */
        local lst = ptab[v];

        /* return the list, or an empty list if the verb is unknown */
        return (lst != nil ? lst : []);
    }

    /* the lookup table */
    ptab = perInstance(new LookupTable(64, 128))
;

/*
 *   Initialize the Doer objects.  This parses each Doer's command string
 *   to generate a list of command templates.  
 */
doerPreinit: PreinitObject
    execute()
    {              
               
        /* initialize the DoerParser objects */
        local ptab = new DoerParserTable();
        initDoerParsers(ptab);

        /* get the global symbols */
        local gtab = t3GetGlobalSymbols();

        /* get the predicate noun phrase list */
        local roles = NounRole.allPredicate;

        /* 
         *   Add the special "any verb" wildcard verbs.  Add one for each
         *   subset of the roles list.  (The role subsets are always
         *   cumulative, because we don't ever have a later role without
         *   also including all of the earlier roles.  E.g., if a verb
         *   takes an indirect object, it must take a direct object as
         *   well.)  
         */
        local pat = '<star>';
        local npat = ' (<alphanum|_|vbar|star>+)';
        for (local i = 0 ; i <= roles.length() ; ++i, pat += npat)
        {
            ptab.addParser(new DoerParser(
                Action, '*', pat, roles.sublist(1, i)));
        }
        
        /* set up an empty list of command template (DoerCmd) objects */
        local tlst = new Vector(100);

        /* call the language-specific parser for each Doer's command string */
        forEachInstance(Doer, new function(d)
        {
            /* get the string to parse */
            local c = d.cmd;

            /* 
             *   split it into commands - a given Doer might match multiple
             *   commands 
             */
            local clst = c.split(';');
  
                        
            /* process each command */
            foreach (c in clst)
            {
                /* check for direction names in the command */
                local tokList = c.split(R'<space|vbar>');
                
                foreach(local tok in tokList)
                {
                    local dir = Direction.nameTab[tok];
                    if(dir != nil)
                    {                        
                        d.direction = valToList(d.direction) + dir;
                    }
                }
                
                /* pull out the first word of the command */
                rexMatch(R'<space>*(<alphanum|star>+)', c);

                /* match it against each template with the same first word */
                local found = nil;
                foreach (local p in ptab.getParsers(rexGroup(1)[3]))
                {
                    /* if we match this item's template, this is the one */
                    if (rexMatch(p.pat_, c))
                    {
                        /* 
                         *   It matches.  Set up the initial action template
                         *   with just the action. 
                         */
                        local tpl = [p.action_];
                        
                        /* 
                         *   Add each noun slot.  Note that we need to add
                         *   the nouns in the canonical order: dobj, iobj,
                         *   accessory.  This might differ from the order
                         *   of the noun phrases in the verb; for example,
                         *   Give To in English can be phrased as "give
                         *   dobj to iobj" or as "give iobj dobj" - the
                         *   second form uses the reverse of the canonical
                         *   order.  We must always use the canonical
                         *   ordering for the template, regardless of how
                         *   the verb is phrased.  
                         */
                        foreach (local r in roles)
                        {
                            /* 
                             *   find this role in the template list order
                             *   - this tells us which parenthesized group
                             *   it matches in the regular expression for
                             *   the verb template 
                             */
                            local idx = p.roles_.indexOf(r);
                            
                            /* 
                             *   If the role isn't in the verb, we're done.
                             *   A verb with an indirect object always has
                             *   a direct object, and a verb with an
                             *   accessory always has direct and indirect
                             *   objects, so if this role isn't in the
                             *   verb's list, we know there are no more
                             *   roles to find.  
                             */
                            if (idx == nil)
                                break;
                            
                            /* get the regular expression match */
                            local n = rexGroup(idx)[3];

                            /* add it to the template list */
                            tpl += n;
                        }

                        /* expand "noun|noun" constructions */
                        expandNounLists(gtab, d, tlst, tpl, 2);

                        /* we found a match, so we can stop looking */
                        found = true;
                        break;
                    }
                }

                /* if we didn't a match, note the error */
                if (!found && d.ignoreError == nil)
                {
                    "Error in Doer command phrase \"<<d.cmd>>\": this
                    command syntax doesn't match any known verb grammar.";
                }
            }
        });

        /* sort the DoerCmd list by descending precedence */
        tlst.sort(SortDesc, { a, b: a.compareTo(b) });

        /* build the master table of DoerCmd objects */
        local dtab = DoerCmd.doerTab = new LookupTable(64, 128);
        local seqno = 1;
        foreach (local d in tlst)
        {
            /* 
             *   Set this item's global sequence number.  This lets us
             *   quickly sort into the original priority order when we
             *   combine two lists. 
             */
            d.seqno = seqno++;

            /* get this item's Action - it's the first template item */
            local action = d.cmd[1];

            /* make sure this entry has a list */
            if (dtab[action] == nil)
                dtab[action] = [];

            /* add this item to this action entry's list */
            dtab[action] += d;
        }
    }

    /*
     *   Expand an initial template list.  This takes a list of the form
     *   [action, 'a|b|c', 'd|e|f'], and converts it into multiple lists
     *   with an individual noun in each slot.  
     */
    expandNounLists(gtab, d, tlst, tpl, idx)
    {
        /* 
         *   if we've run out of slots, we have a fully expanded template,
         *   so simply add the current template to the results and return 
         */
        if (idx > tpl.length())
        {
            tlst.append(new DoerCmd(d, tpl));
            return;
        }

        /* process the list of elements for the current item */
        foreach (local item in tpl[idx].split('|'))
        {
            /* look up the object name in the symbol table */
            local obj = gtab[item];

            /* if we didn't find it, note the error */
            if (obj == nil && item != '*')
            {
                /* explain the problem */
                if(!d.ignoreError)
                    "Error in Doer command phrase \"<<d.cmd>>\": the word \"<<
                      item>>\" is not a known object or class name.
                    Each noun must be the source code name of an object
                    or class.\n";

                /* abort processing this template */
                return;
            }

            /* build the simplified list with the current single item */
            local stpl = tpl;
            stpl[idx] = obj;

            /* recursively process the rest of the list */
            expandNounLists(gtab, d, tlst, stpl, idx + 1);
        }
    }
    
    
;

/* 
 *   Define four DefaultDoers that between them will match any command unless a
 *   more specialized Doer intervenes. This allows most commands to be executed
 *   by the appropriate action.
 */

default4Doer: Doer
    cmd = '* * * *'
;

default3Doer: Doer
    cmd = '* * *'
;

default2Doer: Doer
    cmd = '* *'
;

default1Doer: Doer
    cmd = '*'
;
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1