#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