#charset "us-ascii"
#include "advlite.h"
/*
* ***************************************************************************
* messages.t
*
* This module forms part of the adv3Lite library (c) 2012-13 Eric Eve, but is
* based substantially on the Mercury Library (c) 2012 Michael J. Roberts
*/
/* ------------------------------------------------------------------------ */
/*
* Global narration parameters. This object's properties control the way
* parameter-based messages are generated. The message generator
* consults the narration parameters each time it generates a message, so
* you can change the settings on the fly, and subsequent output will
* automatically adapt to the latest settings.
*/
Narrator: object
/*
* The verb tense of the narration. This is one of the VerbTense
* objects (Present, Past, Perfect, Past Perfect, Future, Future
* Perfect). This controls the way {verb} substitution parameters
* are generated, which in turn affects most library messages. The
* default is Present, which is the conventional tense for most IF.
*
* Examples of the English tenses:
*
*. Present: Bob opens the box.
*. Past: Bob opened the box.
*. Perfect: Bob has opened the box.
*. Past Perfect: Bob had opened the box.
*. Future: Bob will open the box.
*. Future Perfect: Bob will have opened the box.
*
* (Language modules are free to add their own tenses if the target
* language has others that would be of interest to IF authors.
* They're also free to ignore any of the "standard" tenses we
* define. At a minimum, though, some form of present tense should
* always be provided, since most IF is narrated in the present. If
* you need to differentiate among different present tenses, you
* might prefer to define your own IDs instead of using the generic
* Present, but you should still support *some* present tense that's
* suitable for narration. Some type of past tense usable in
* narration is also nice to have. The others are probably of
* marginal value; in English, at least, other tenses are rare in any
* kind of narrative fiction, and are mostly limited to experimental
* or novelty use.)
*/
tense = (gameMain.usePastTense ? Past : Present)
;
/*
* Root class for verb tenses
*/
class VerbTense: object
/*
* Tense name. This is just an identifier for internal and degugging
* purposes, so we don't bother farming it out to the language module
* for translation.
*/
name = nil
;
Present: VerbTense
name = 'present'
;
Past: VerbTense
name = 'past'
;
Perfect: VerbTense
name = 'perfect'
;
PastPerfect: VerbTense
name = 'past perfect'
;
Future: VerbTense
name = 'future'
;
FuturePerfect: VerbTense
name = 'future perfect'
;
/* ------------------------------------------------------------------------ */
/*
* Show a message.
*
* This looks for a customized version of the message text, as defined in
* CustomMessages objects. If we don't find one, we use the provided default
* message text.
*
* Substitution parameters take the form {x y z} - curly braces with one or
* more space-delimited tokens. The first token is the parameter name, and
* any additional tokens are arguments. The parameter names and their
* arguments are up to the language module to define.
*
* In addition to the parameters, the string itself can have two sections,
* separated by a vertical bar, '|'. The first section (before the bar) is
* the "terse" string, which is for a straightforward acknowledgment of a
* simple, ordinary action: "Taken", "Dropped", etc. The terse string is used
* only if the Command argument's actor is the player character, AND the
* command doesn't have any disambiguated objects. If these conditions aren't
* met, the second half of the string, the "verbose" version, is used.
*
* Once we have the message text, we perform parameter substitutions.
* Parameters can be provided as strings, which are substituted in literally;
* or as objects, whose names are inserted according to the grammar in the
* template text.
*/
message(id, txt, [args])
{
txt = buildMessage(id, txt, args...);
/*
* use the oSay function to output the text to avoid an infinite
* recursive call through say.
*/
oSay(txt);
}
/*
* Build a message to be shown by message()
*
* We put this in a separate function to make it easy to obtain the text of a
* message for subsequent use without first displaying it.
*/
buildMessage(id, txt, [args])
{
/* look for a customized version of the message */
local cm = nil;
foreach (local c in CustomMessages.all)
{
/*
* if this customizer is active and defines the message, and it
* has a higher priority than any previous customizer we've
* already found, remember it as the best candidate so far
*/
if (c.active && c.msgTab[id] != nil
&& (cm == nil || c.priority > cm.priority))
cm = c;
}
/* show debugging information, if desired */
IfDebug(messages, debugMessage(id, txt, cm, args));
/* if we found an override, use it instead of the provided text */
if (cm != nil)
txt = cm.msgTab[id];
/* if txt is a function pointer, retrieve the value it returns */
if(dataType(txt) == TypeFuncPtr)
txt = txt();
/* set up a sentence context for the expansion */
local ctx = new MessageCtx(args);
/*
* Carry out any language-specific adjustments to the text prior to any
* further processing
*/
txt = langAdjust(txt);
/*
* First look for a tense-swithing message substitution of the form
* {present-string|past-string} and choose whichever is appropriate to the
* tense of the game.
*/
local bar, openBrace = 0, closeBrace = 0, newTxt = txt;
for(;;)
{
/* Find the next opening brace */
openBrace = txt.find('{', closeBrace + 1);
/* If there isn't one, we're done, so leave the loop. */
if(openBrace == nil)
break;
/* Find the next vertical bar that follows the opening brace */
bar = txt.find('|', openBrace);
/* If there isn't one, we're done, so leave the loop. */
if(bar == nil)
break;
/* Find the next closing brace that follows the opening brace */
closeBrace = txt.find('}', openBrace);
/* If there isn't one, we're done, so leave the loop. */
if(closeBrace == nil)
break;
/*
* If the bar doesn't come before the closing brace, then it's not
* between the two braces, so we don't want to process it in this
* circuit of the loop. Instead we need to see if there's another
* opening brace on the next iteration.
*/
if(bar > closeBrace)
continue;
/*
* Extract the string that starts with the opening brace we found and
* ends with the closing brace we found.
*/
local pString = txt.substr(openBrace, closeBrace - openBrace + 1);
/*
* If the game is in the past tense, extract the second part of this
* above string (that following the bar up to but not including the
* closing brace). Otherwise extract the first part (that following
* but not including the opening brace up to but not including the
* bar)
*/
local subString = (gameMain.usePastTense || Narrator.tense == Past) ?
txt.substr(bar + 1, closeBrace - bar - 1) : txt.substr(openBrace +
1, bar - openBrace - 1);
/*
* In the copy of our original text string, replace the string in
* braces with the substring we just extracted from it.
*/
newTxt = newTxt.findReplace(pString, subString, ReplaceOnce);
}
/* Copy our adjusted string back to our original string */
txt = newTxt;
/* check for separate PC and NPC messages */
bar = txt.find('|');
if (bar != nil)
{
/* there's a bar - check to see if the terse format can be used */
if (ctx.cmd != nil && ctx.cmd.terseOK())
txt = txt.left(bar - 1);
else
txt = txt.substr(bar + 1);
}
/* get the message object */
local mo = MessageParams.langObj;
/* apply substitutions */
for (local i = 1 ; i <= txt.length() ; )
{
/* find the end of the current sentence */
local eos = txt.find(R'<.|!|?><space>', i) ?? txt.length() + 1;
/*
* Build a list of the parameters in the sentence, and preprocess
* each one. We need to preprocess the entire sentence before we
* can expand any of it, because some parameters can depend upon
* other parameters later in the sentence. For example, some
* languages generally place the subject after the verb; to
* generate the verb with proper agreement, we need to know the
* subject when we expand the verb, which means we need to have
* scanned the entire sentence before we expand the verb.
*/
ctx.startSentence();
local plst = new Vector(10);
for (local j = i ; ; )
{
/* find the next parameter */
local lb = txt.find('{', j);
if (lb == nil || lb >= eos)
break;
/* find the end of the parameter */
local rb = txt.find('}', lb + 1);
if (rb == nil)
break;
/* pull out the parameter string */
local param = txt.substr(lb + 1, rb - lb - 1);
/* turn it into a space-delimited token list */
param = param.trim().split(' ');
/*
* do the preliminary expansion, but discard the result -
* this gives the expander a chance to update any effect on
* the sentence context
*/
mo.expand(ctx, param);
/* add it to the list */
plst.append([lb, rb, param]);
/* move past this item */
j = rb + 1;
}
/* restart the sentence */
ctx.endPreScan();
/* do the actual expansion */
local delta = 0;
for (local j = 1 ; j <= plst.length() ; ++j)
{
/* pull out this item */
local cur = plst[j];
local lb = cur[1], rb = cur[2], param = cur[3];
local paramLen = rb - lb + 1;
/* get the expansion */
local sub = mo.expand(ctx, param);
/*
* if it starts with a "backspace" character ('\010', which
* is a ^H character, the standard ASCII backspace), delete
* any spaces immediate preceding the substitution parameter
*/
if (sub.startsWith('\010'))
{
/* count spaces immediately preceding the parameter */
local m = lb + delta - 1, spCnt = 0;
while (m >= 1 && txt.toUnicode(m) == 32)
--m, ++spCnt;
/* if we found any spaces, splice them out */
if (spCnt != 0)
{
/* splice out the spaces */
txt = txt.splice(m + 1, spCnt, '');
/* adjust our delta for the deletion */
delta -= spCnt;
}
/* remove the backspace from the replacement text */
sub = sub.substr(2);
}
/* splice the replacement text into the string */
// txt = txt.splice(lb + delta, paramLen, sub);
txt = txt.substr(1, lb + delta -1) + sub + txt.substr(lb + delta
+ paramLen );
/* adjust our delta to the next item for the splice */
delta += sub.length() - paramLen;
}
/* move to the end of the sentence */
i = eos + delta + 1;
}
return txt;
}
/*
* Message debugging. This shows the message before processing: the ID,
* the default source text with the {...} parameters, the overriding
* custom source text, and the arguments.
*/
#ifdef __DEBUG
debugMessage(id, txt, cm, args)
{
if(id is in (nil,'','command results prefix', 'command prompt', 'command
results suffix')
|| outputManager.curOutputStream != mainOutputStream)
return;
local idchk = [id, libGlobal.totalTurns];
if(DebugCtl.messageIDs[idchk] != nil)
return;
else
DebugCtl.messageIDs[idchk] = true;
oSay('\nmessage(id=<<id>>, default text=\'<<txt>>\' ');
if (cm != nil)
oSay('custom text=\'<<cm.msgTab[id]>>\'');
if (args.length() != 0)
{
oSay(', args={ ');
for (local i = 1 ; i <= args.length() ; ++i)
{
local a = args[i];
if (i > 1)
oSay(', ');
if (dataType(a) == TypeSString)
oSay(''''<<args[i]>>'''');
else
oSay('object(name=<<a.name>>)');
}
oSay(' }');
}
oSay(')\n');
}
#endif
/* ------------------------------------------------------------------------ */
/*
* Noun parameter roles for MessageCtx.noteObj()
*/
/* the noun is the subject of the sentence */
enum vSubject;
/* the noun is an object of the verb */
enum vObject;
/*
* the role is ambiguous (it's not marked clearly in the case or
* position, for example)
*/
enum vAmbig;
/* the noun is a possessive, not the subject or the object */
enum vPossessive;
/* ------------------------------------------------------------------------ */
/*
* Message expansion sentence context. This keeps track of the parts of
* the sentence we've seen so far in the substitution parameters.
*
* The sentence context is important for expanding certain items. For
* verbs, it tells us which object is the subject, so that we can
* generate the agreeing conjugation of the verb (in number and
* grammatical person). For direct and indirect objects, it lets us
* generate a reflexive when the same object appears in a second role
* ("You can't put the box in itself").
*/
class MessageCtx: object
construct(args)
{
/* remember the message arguments */
self.args = args;
/* if there's a Command among the arguments, note it */
cmd = gCommand;
/* if there's no command, use the placeholder */
if (cmd == nil)
cmd = messageDummyCommand;
}
/* start a new sentence */
startSentence()
{
/* forget any subject/object information */
subj = nil;
vobj = nil;
gotVerb = nil;
reflexiveAnte.clear();
/* note that we're on the initial scan */
prescan = true;
/* we're starting a new scan, so reset the previous parameter */
lastParam = nil;
}
/*
* End the pre-expansion scan. The expander makes two passes over
* each sentence. The first scan doesn't actually do any
* substitutions, but merely invokes each parameter to give it a
* chance to exert its side effects on the sentence context. The
* second scan actually applies the substitutions. At the end of the
* first pass, the expander calls this to let us finalize the initial
* scan and prepare for the second scan.
*/
endPreScan()
{
/*
* Forget the reflexive antecedent. Reflexives are generally
* anaphoric (i.e., they refer back to earlier clauses in the
* same sentence), so we generally don't care about anything we
* found later in the same sentence.
*/
reflexiveAnte.clear();
/* we're no longer on the initial scan */
prescan = nil;
/* we're starting a new scan, so reset the previous parameter */
lastParam = nil;
}
/*
* Note a parameter value. Some parameters refer back to the
* immediately preceding parameter, so it's useful to have the most
* recent value stashed away. Returns the parameter value as given.
*/
noteParam(val)
{
/* remember the value */
return lastParam = val;
}
/*
* Convert a parameter value to a string representation suitable for
* message substitution.
*/
paramToString(val)
{
switch (dataType(val))
{
case TypeSString:
return val;
case TypeInt:
return toString(val);
case TypeObject:
if (val.ofKind(Mentionable) || val.ofKind(Pronoun))
return val.name;
else if (val.ofKind(List) || val.ofKind(Vector))
return val.mapAll({ x: paramToString(x) }).join(', ');
else if (val.ofKind(BigNumber))
return toString(val);
else
return '(object)';
case true:
return 'true';
case nil:
return '';
default:
return '(?)';
}
}
/*
* Convert a parameter value to a numeric representation. If the
* value is an integer or BigNumber, we return it as is; if a list or
* vector, we return the number of elements; if nil, 0; if a string,
* the parsed numeric value of the string; otherwise we simply return
* 1.
*/
paramToNum(val)
{
switch (dataType(val))
{
case TypeSString:
return toInteger(val);
case TypeInt:
return val;
case TypeObject:
if (val.ofKind(BigNumber))
return val;
if (val.ofKind(List) || val.ofKind(Vector))
return val.length();
return 1;
case TypeList:
return val.length();
case nil:
return 0;
default:
return 1;
}
}
/*
* Note an object being used as a parameter in the given sentence
* role. The role is one of the noun role enums defined above:
* vSubject, vObject, or vAmbig. If the object is a subject, we'll
* save it as the sentence subject, so that we can generate an
* agreeing verb. Regardless of role, we'll also save it as a
* reflexive antecedent, so that we can generate a reflexive pronoun
* if we see the same object again in another role in the same
* sentence.
*/
noteObj(obj, role)
{
/* remember the object as the last parameter value */
noteParam(obj);
/*
* If the role is ambiguous, guess at the role based on the
* general sentence order.
*/
if (role == vAmbig && MessageParams.langObj.sentenceOrder != nil)
{
/* get the sentence order flags */
local so = MessageParams.langObj.sentenceOrder;
/* figure whether the subject/object is before/after the verb */
local ssv = (so.find(R'S.*V') != nil ? -1 : 1);
local osv = (so.find(R'O.*V') != nil ? -1 : 1);
/* figure whether the subject or object comes first */
local fo = (so.find(R'S.*O') != nil ? vSubject : vObject);
/* figure which side of the verb we're on (-1 before, 1 after) */
local sv = (gotVerb ? 1 : -1);
/*
* If we're on the right side of the verb for both subject
* and object: if we have an object, this is the subject; if
* we have a subject, this is the object; if we have neither
* this is the first role in sentence order.
*
* Otherwise, if we're on the subject side of the verb, and
* we don't have a subject, this is the subject.
*
* Otherwise, if we're on the object side of the verb, and we
* don't have an object, this is an object.
*/
if (ssv == sv && osv == sv)
role = (subj != nil ? vObject :
vobj != nil ? vSubject : fo);
else if (ssv == sv && subj == nil)
role = vSubject;
else if (osv == sv && vobj == nil)
role = vObject;
}
/* if it's the subject, remember it as such */
if (role == vSubject)
subj = obj;
else if (role == vObject)
vobj = obj;
/*
* Only record the reflexive antecent for the subject, so that the
* reflexive pronoun is used when an actor or object tries to act on
* itself.
*/
if(role != vSubject)
return;
/*
* If there's nothing in the reflexive antecedent list that uses
* the same pronoun, add this to the list. Otherwise, replace
* the object that used the same antecedent.
*/
// local p = obj.pronoun();
// local idx = reflexiveAnte.indexWhich({ o: o.pronoun() == p });
/*
* The foregoing rule seems to generate false positives (that is, it
* can result in reflexive pronouns where they're not appropriate), so
* we'll try the different strategy of looking for anything in the
* reflexive ante list. The reason for this is that if a new
* subject is introduced into the sentence, it doesn't have to be the
* same gender as the previous subject to become the most likely
* antecedent for a reflexive.
*/
local idx = reflexiveAnte.indexWhich({ o: o.ofKind(Thing) });
if (idx == nil)
{
/*
* There's no object with this pronoun yet - add it. We want
* to keep earlier antecedents with different pronouns
* because we can still refer back to earlier objects
* reflexively as long as it's clear which one we're talking
* about. The distinct pronouns provide that clarity.
*/
reflexiveAnte.append(obj);
}
else
{
/*
* We already have an object with this pronoun, so replace it
* with the new one. Reflexives generally bind to the prior
* noun phrase that matches the pronoun *and* is closest in
* terms of word order. Once another noun phrase intervenes
* in word order, we can only refer back to an earlier one by
* reflexive pronoun if the earlier noun is distinguishable
* from the later one by its use of a distinct pronoun from
* the later one. For example, we can say "Bob asked Sue
* about himself", because "himself" could only refer to Bob,
* but most people would take "Bob asked Sam about himself"
* to mean that Bob is asking about Sam.
*/
reflexiveAnte[idx] = obj;
}
}
/*
* Note a verb parameter.
*/
noteVerb()
{
/* note that we got a verb */
gotVerb = true;
}
/*
* Was the last parameter value plural? If the value is numeric, 1
* is singular and anything else is plural. If it's a list, a
* one-element list is singular and anything else is plural. If it's
* a Mentionable, the 'plural' property determines it.
*/
lastParamPlural()
{
switch (dataType(lastParam))
{
case TypeInt:
return lastParam != 1;
case TypeObject:
if (lastParam.ofKind(Mentionable) || lastParam.ofKind(Pronoun))
return lastParam.plural;
else if (lastParam.ofKind(List) || lastParam.ofKind(Vector))
return lastParam.length() != 1;
else if (lastParam.ofKind(BigNumber))
return lastParam != 1;
else
return nil;
case TypeList:
return lastParam.length() != 1;
default:
return nil;
}
}
/*
* Is the actor involved in the Command the PC? If there's a Command
* with an actor, we check to see if it's the PC. If there's no
* Command or no actor, we assume that the PC is the relevant actor
* (since there's nothing else specified anywhere) and return true.
*/
actorIsPC()
{
return cmd == nil || cmd.actor == nil
|| cmd.actor == gPlayerChar;
}
/* the last parameter value */
lastParam = nil
/* are we on the initial pre-expansion scan? */
prescan = nil
/* the subject of the sentence (as a Mentionable object) */
subj = nil
/* the last object of the verb we saw */
vobj = nil
/* have we seen a verb parameter in this sentence yet? */
gotVerb = nil
/* the message argument list */
args = nil
/* the Command object among the arguments, if any */
cmd = nil
/*
* The reflexive antecedents. Each time we see an object in a
* non-subject role, and the object has different pronoun usage from
* any previous entry, we'll add it to this list. If we see the same
* object subsequently in another non-subject role, we'll know that
* we should generate a reflexive pronoun for the object rather than
* the name or a regular pronoun:
*
* You can't put the tongs in the box with the tongs -> with
* themselves
*/
reflexiveAnte = perInstance(new Vector(5))
;
/*
* Dummy command placeholder for messages generated without a command.
*/
messageDummyCommand: object
/* use the player character as the actor */
actor = (gPlayerChar)
;
/*
* Use the message builder to format a message without supplying a key
* to look up at alternative message. We can use this with library
* messages that employ object properties (e.g. cannotTakeMsg) or user
* code.
*
* dmsg() displays the resultant message.
*/
dmsg(txt, [args])
{
message('', txt, args...);
}
/* bmsg returns the text of a message formatted by the message formatter. */
bmsg(txt, [args])
{
return buildMessage('', txt, args...);
}
/* ------------------------------------------------------------------------ */
/*
* Message customizer object. Language extensions and games can use this
* class to define their own custom messages that override the default
* English messages used throughout the library.
*
* Each CustomMessages object can define a list of messages to be
* customized. This lets you centrally locate all of your custom
* messages by putting them all in a single object, if you wish.
* Alternatively, you can create separate objects, if you prefer to keep
* them with some other body of code they apply to. In either case, the
* library gathers them all up during preinit.
*/
class CustomMessages: object
/*
* The priority determines the precedence of a message defined in
* this object, if the same message is defined in more than one
* CustomMessages object. The message with the highest priority is
* the one that's actually displayed.
*
* The library defines one standard priority level: 100 is the
* priority for language module overrides. Each language module
* provides a translated set of the standard library messages, via a
* CustomMessages object with priority 100. (The default English
* messages defined in-line throughout the library via DMsg() macros
* effectively have a priority of negative infinity, since any custom
* message of any priority overrides a default.)
*
* Games will generally want to override all library messages,
* including translations, so we set the default here to 200.
*/
priority = 200
/*
* Is this customizer active? If you want to change the messages at
* different points in the course of the game, you can use this to
* turn sets of messages on and off. For example, if your game
* includes narrator changes at certain points, you can create
* separate sets of messages per narrator. By default, we make all
* customizations active, but you can override this to turn selected
* messages on and off as needed. Note that the library consults
* this every time it looks up a message, so you can change the value
* dynamically, or use a method whose return value changes
* dynamically.
*/
active = true
/*
* The message list. This can contain any number of messages; the
* order isn't important. Each message is defined with a Msg()
* macro:
*
*. Msg(id key, 'Message text'), ...
*
* The "id key" is the message ID that the library uses in the DMsg()
* message that you're customizing. (DON'T use quotes around the ID
* key.) The message text is a single-quoted string giving the
* message text. This can contain curly-brace replacement
* parameters.
*/
messages = []
/*
* Construction. Build the lookup table of our messages for fast
* access at run-time.
*/
construct()
{
/* add me to the master list of message customizers */
CustomMessages.all += self;
/* create the lookup table */
msgTab = new LookupTable(64, 128);
/* populate it with our key->string mappings */
for (local i = 1, local len = messages.length() ; i <= len ; i += 2)
msgTab[messages[i]] = messages[i+1];
}
/* message lookup table - this maps ID keys to message text strings */
msgTab = nil
/*
* class property: the master list of CustomMessages objects defined
* throughout the game
*/
all = []
;
/* ------------------------------------------------------------------------ */
/*
* Message Parameter Handler. This object defines and handles parameter
* expansion for '{...}' strings in displayed messages.
*
* The language module must provide one instance of this class. The name
* of the instance doesn't matter - we'll find it at preinit time. The
* object must provide the 'params' property giving the language-specific
* list of substitution parameter names and handler functions.
*/
class MessageParams: object
/*
* Expand a parameter string. 'ctx' is a MessageCtx object with the
* current sentence context. This contains the message expansion
* arguments (ctx.args), the Command object from the arguments
* (ctx.cmd), and information on the grammar elements of the
* sentence. 'params' is the list of space-delimited tokens within
* the curly-brace parameter string. Returns the string to
* substitute for the parameter in the message output.
*/
expand(ctx, params)
{
/* look up the parameter name */
local pname = params[1].trim();
local t = paramTab[pname.toLower()];
/*
* If we found an entry, let the entry handle it. If there's no
* matching entry, it's basically an error: return the original
* parameter source text, with the braces restored, so that it'll
* be obvious in the output.
*/
local txt = nil;
if (t != nil)
{
/*
* If we have not previously identified a subject for this
* context, use the dummy_ object, which provides a dummy third
* person singular noun as a default subject for the verb.
*/
if(ctx.subj == nil)
ctx.subj = dummy_;
/* we found the parameter - get the translation */
txt = t[2](ctx, params);
/*
* if this isn't a pre-scan, and the parameter name starts
* with a capital, mimic that pattern in the result
*/
if (txt != nil && !ctx.prescan && rexMatch(R'<upper>', pname) != nil)
txt = txt.firstChar().toUpper() + txt.delFirst();
}
/*
* if we failed to find an expansion, and this isn't just a
* pre-scan, return the source text as an error indication
*/
if (txt == nil && !ctx.prescan)
txt = '{' + params.join(' ') + '}';
/* return the result */
return txt;
}
/*
* Parameter mapping list. This is a list of lists: [name, func],
* where 'name' is the parameter name (as a string), and 'func' is
* the expansion handler function.
*
* The parameter name must be all lower case. During expansion, we
* convert the first space-delimited token within the {curly brace}
* parameter string to lower case, then look for an entry in the list
* with the matching parameter name. If we find an entry, we call
* its handler function.
*
* The handler function is a pointer to a function that takes two
* arguments: func(params, ctx), where 'params' is the list of tokens
* within the {curly braces} of the substitution string, as a list of
* strings, where each string is a space-delimited token in the
* original {curly brace} string; and 'ctx' is the MessageCtx object
* for the expansion. The function returns a string giving the
* expansion of the parameter.
*
* The parameter list must be provided by the language module, since
* each language's list of parameters and expansions will vary.
*/
params = [ ]
/*
* Some parameters expand to properties of objects involved in the
* command. cmdInfo() makes it easier to define the expansion
* functions for such parameters. We search the parameters for a
* Command object, and if we find it, we retrieve a particular source
* object and evaluate a particular property on the source object to
* get the result string.
*
* For example, {the dobj} could be handled via cmdInfo('ctx, dobj',
* &theName, vSubject): we find the current 'dobj' object in the
* Command, then evaluate the &theName property on that object.
*
* 'ctx' is the MessageCtx object with the current sentence context.
*
* 'src' is the source object in the Command. This can be given as a
* property pointer (&actor, say), in which case we simply evaluate
* that property of the Command object (cmd.(src)) to get the source
* object. Or, it can be a string giving a NounRole name (dobj,
* iobj, acc), in which case we'll retrieve the current object for
* the noun role from the Command. Or, it can be a string with a
* number, in which case we'll use the number as an index into the
* argument list.
*
* 'objProp' is the property of the source object to evaluate to get
* the expansion string.
*
* 'role' is vSubject if this is a noun phrase with subject usage (in
* most languages, this is a noun phrase in the nominative case; in
* English this is called subjective case). It's vObject for any
* other noun phrase role (direct object, prepositional object, etc).
* If the role isn't clear from the context (the case marking of the
* parameter, or the position), use vAmbig to mark it as ambiguous.
*/
cmdInfo(ctx, src, objProp, role)
{
try
{
/* we don't have a source object yet */
local srcObj = nil;
/* if the source is a role name, get the corresponding property */
if (dataType(src) == TypeSString)
{
/* check for a number */
if (rexMatch(R'<digit>+', src) != nil)
{
/* it's an argument index */
src = ctx.args[toInteger(src)];
}
else
{
/* Find the source object corresponding to the string */
src = findStrParam(src, role);
/*
* If we didn't find an object, return nil to the caller to
* indicate an error
*/
if(src == nil)
return nil;
}
}
/* retrieve the source object from the command */
if (dataType(src) == TypeProp)
srcObj = ctx.cmd.(src);
else if (dataType(src) == TypeObject
&& (src.ofKind(Mentionable) || src.ofKind(Pronoun)
|| src.ofKind(LiteralObject) ||
src.ofKind(ResolvedTopic)))
srcObj = src;
/* check for reflexivity */
if (srcObj != nil && role == vObject && !ctx.prescan)
{
local r = cmdInfoReflexive(ctx, srcObj, objProp);
if (r != nil)
return r;
}
/* note the object's role in the sentence context */
if (srcObj != nil)
ctx.noteObj(srcObj, role);
/*
* if we're in pre-scan mode, skip the actual expansion;
* otherwise evaluate the target property on the source
* object to get the expansion text
*/
return ctx.prescan ? nil : srcObj.(objProp);
}
catch (Exception e)
{
/*
* if anything went wrong, return nil to indicate we failed
* to find an expansion
*/
return nil;
}
}
findStrParam(src, role)
{
local targetObj;
if (gAction != nil)
{
/* get the target object by name through the action */
targetObj = gAction.getMessageParam(src);
}
else
{
/* there's no action, so we don't have a value yet */
targetObj = nil;
}
if (targetObj == nil)
{
/* look up the name */
targetObj = libGlobal.nameTable_[src];
/*
* if we found it, and the result is a function pointer or an
* anonymous function, invoke the function to get the result
*/
if (dataTypeXlat(targetObj) == TypeFuncPtr)
{
/* evaluate the function */
targetObj = (targetObj)();
}
}
/*
* If we still haven't found a targetObj, try getting it from the
* role's object property.
*/
if (targetObj == nil && role != nil)
{
/* it's a role name - look up the role */
local role = NounRole.all.valWhich({ r: r.name == src });
/* get the role's object property */
if(role != nil)
targetObj = role.objProp;
}
/*
* If we still haven't found a targetObj, there's probably an error in
* the way the object parameter was specified, but rather than
* allowing this to cause a run-time error down the track, substitute
* a dummy object and note the offending src parameter on an
* appropiate property so that the text presented should make it clear
* where the problem lies.
*/
if(targetObj == nil)
{
targetObj = dummy_;
dummy_.noteName('[' + src + ']');
}
return targetObj;
}
/*
* Parameter lookup table. This maps a parameter name to its handler
* function.
*/
paramTab = nil
/* the language module's instance of the class */
langObj = nil
/* construction - build the lookup table */
construct()
{
/* remember the instance */
MessageParams.langObj = self;
/* create the parameter table */
paramTab = new LookupTable(64, 128);
foreach (local p in params)
paramTab[p[1]] = p;
}
;
/* Dummy object to use as a fallback when a parameter can't be identified */
dummy_: Thing
noteName(src) { }
;
pluralDummy_: Thing
noteName(src) { }
;
/*----------------------------------------------------------------------------*/
Adv3Lite Library Reference Manual
Generated on 03/07/2024 from adv3Lite version 2.1