TADS Revision History - versions 2.2.3 through 2.4.0
====================================================
This file contains a list of changes that have been made to TADS from
version 2.1.0 through version 2.4.0. Most of the changes are fixes
to bugs, so they don't change the documented behavior, but a few, as
explained below, add new functionality to TADS. Releases are listed
with the most recent release first; each release incorporates all new
features and bug fixes of each prior release unless otherwise stated.
Starting with version 2.2.6, these release notes are divided into
two separate files: one for "generic" changes that apply to TADS on
all types of computers and operating systems, and one for changes
that apply only to specific platforms. This file contains the
generic release notes. Platform-specific release notes are in a
separate file:
tadsv240.txt - MS-DOS and Windows changes, 2.2.3 through 2.4.0
These release notes sometimes refer to the "character-mode" run-time.
This is meant to distinguish the traditional TADS run-time from the
HTML TADS run-time. The traditional TADS run-time has a graphical
user interface on some systems, such as the Macintosh, so it's not
really a character-mode application on those systems; nonetheless,
we still refer to it here as the character-mode run-time simply to
make it clear that we're not talking about the HTML TADS version.
Note that changes from version 2.0 up to 2.2.2 are not included in
this file in order to keep its size under control. Older release
notes are in separate files for each platform. For DOS, revisions
from 2.0 through 2.1.0 are available in the file TADSV200.DOS, and
those from 2.1.1 through 2.2.2 are in TADSV222.DOS. Similar files
are available for other platforms; please refer to your platform-
specific release notes for details. Because these files are quite
large and are static (they are, after all, historical), they're not
included in the normal TADS distributions, but you can download them
from the Interactive Fiction Archive via the internet at
ftp://ftp.gmd.de/if-archive/programming/tads.
------------------------------------------------------------------------------
2.4.0 - May 16, 1999
- A new built-in function, exitobj, provides a new method for skipping
the remaining processing for a command at any point. This new
function is very similar to the "exit" statement, but differs in one
respect: whereas the "exit" statement terminates all further
processing for a command and skips directly to the fuses and daemons,
"exitobj" skips the remaining processing only for the current object
in a command and proceeds to the next object.
This difference is significant when the player types in a command
involving multiple objects. For example, suppose that you define a
roomAction method in the current room as follows:
roomAction(actor, verb, dobj, prep, iobj) =
{
/*
* when the player touches anything not already in their inventory,
* make it vanish
*/
if (dobj != nil && !dobj.isIn(actor))
{
"\^<<dobj.thedesc>> disappears in a flash of light!\n";
dobj.moveInto(nil);
exit;
}
}
Consider the following transcript:
>take ball
The ball disappears in a flash of light!
>take hammer and chisel
The hammer disappears in a flash of light!
The first response makes sense, but the second isn't exactly what you
wanted. The problem is that the "exit" statement tells the parser to
skip processing of any objects other than the current one.
To change this, you can simply change the "exit" statement in the code
listing above to "exitobj". The result will be more sensible:
>take hammer and chisel
The hammer disappears in a flash of light!
The chisel disappears in a flash of light!
"exitobj" is useful when you want to skip the remaining processing for
a command for the current object, but you still want the command to be
considered successful. "exit" is more suited for situations where the
outcome of the command is something less than total success, and you
want to skip further processing of other objects involved in the
command. "exitobj" is particularly useful with the new execCommand()
built-in function (see below), because it allows you to completely
redirect the processing of a command, skipping all or part of the
normal processing for the original command without telling the parser
that the original command was unsuccessful.
You an use exitobj anywhere you can use exit.
- A new built-in function, execCommand, gives a game program direct
access to the parser's command execution system. This new function
doesn't provide direct access to the string-parsing portion of the
parser, but to the command execution portion, which takes the objects
involved in the command and executes the command, performing object
validation (validDo, validIo), room notification (roomAction),
actor notification (actorAction), direct and indirect object checks
(dobjCheck and iobjCheck), general object handling (dobjGen and
iobjGen), object validation (verIoVerb and verDoVerb), and object
action processing (ioVerb, doVerb, or verb.action, as appropriate).
execCommand is called like this:
errorCode := execCommand(actor, verb, dobj, prep, iobj, flags);
The "actor" parameter is the object (usually of class Actor) of the
character to perform the action; if the player character is to carry
out the command, use Me as the "actor" parameter. The "verb"
parameter is the deepverb object of the command to execute. "dobj"
is the direct object of the command, and must be a single object
(not a list); if you want to execute the same command on a series
of direct objects, simply call execCommand in a loop. Use nil for
the "dobj" parameter if the command takes no direct object. "prep"
is the object (usually of class Prep) for the preposition that
introduces the indirect object, or nil if there is no preposition.
"iobj" is the indirect object of the command, or nil if the command
takes no indirect object.
The "flags" parameter lets you control how the parser processes the
command. The flags are "bit field" values, which means that you can
combine any number of the constants below using the bitwise "or"
operator, "|". The constants below are defined in adv.t.
EC_HIDE_SUCCESS - if this flag is included, the parser will hide
any messages that the command generates when the command completes
successfully. The parser considers the command successful if the
return code from execCommand is zero (see below). If this flag is
not included, and the command is successful, all messages will be
displayed. Note that this flag is independent of EC_HIDE_ERROR;
whether or not this flag is included has no effect on messages if
the command is not successful.
EC_HIDE_ERROR - if this flag is included, the parser will hide
any messages that the command generates when the command fails.
The parser considers the command to have failed when the return
code from execCommand is non-zero (see below). If this flag is
not included, and the command fails, all messages will be displayed.
Note that this flag is independent of EC_HIDE_SUCCESS; whether or
not this flag is included has no effect on messages if the command
is successful.
EC_SKIP_VALIDDO - if this flag is included, the parser skips
validation of the direct object. If this flag is not used, the
parser uses the normal validation procedure for the direct object.
You can use this flag when you want to bypass the normal validation
process and execute the command even when the actor would not
normally have access to the direct object.
EC_SKIP_VALIDIO - if this flag is included, the parser skips
indirect object validation. If this flag isn't used, the parser
uses the normal procedure for validating the indirect object.
If you want to execute a command silently, so that the player doesn't
see the messages the command would normally generate, you should
specify both EC_HIDE_SUCCESS and EC_HIDE_ERROR:
err := execCommand(actor, takeVerb, ball, nil, nil,
EC_HIDE_SUCCESS | EC_HIDE_ERROR);
In some cases, you may want to show messages only in case of error.
This is particularly useful when you're executing an implied command,
such as opening a door in the course of moving to a new room, because
you'll usually show a message of some kind noting the implied action.
In this situation, you can use EC_HIDE_SUCCESS to suppress the normal
confirmation message you'd receive on success, but still show any
errors that occur:
"(Opening the door)\n";
err := execCommand(actor, openVerb, steelDoor, nil, nil,
EC_HIDE_SUCCESS);
if (err = EC_SUCCESS)
{
// successfully opened the door, so proceed with the movement...
}
All of the parameters to execCommand after "verb" can be omitted,
in which case dobj, iobj, and prep will default to nil, and flags
will default to 0 (zero). In addition, you can supply a 'flags'
value but omit one or more of 'dobj', 'prep', and 'iobj', in
which case the interpreter will use nil as the default value for
the omitted object arguments.
execCommand returns an error code indicating the parser's
results. A return value of zero indicates that the command was
processed without any parser errors. A return value of zero does
not always mean that the command did what you wanted -- it merely
indicates that all of the checks succeeded, including xobjCheck,
xobjGen, roomAction, actorAction, verDoVerb, and verIoVerb, and
that no "exit" or "abort" statement was executed. In some cases,
though, the action, doVerb, or ioVerb method will make further
checks and generate an error; in these cases, execCommand will
return zero even though the command failed. You may therefore
want to make an extra check to ensure that whatever state change
you were attempting to accomplish actually occurred.
This function can return the following values (the constants are
defined in adv.t):
EC_SUCCESS
success - the doVerb or ioVerb method was successfully invoked,
and no "exit" or "abort" statement was executed.
EC_EXIT
an "exit" statement was executed. This usually means that a
roomAction, actorAction, xobjCheck, or xobjGen method disallowed
the command.
EC_ABORT
an "abort" statement was executed.
EC_INVAL_SYNTAX
sentence structure is invalid. This indicates that the
combination of verb, objects, and preposition does not form a
valid command in the game. The parser does not display any
error message in this case; this indicates an error in your
source code, since you're attempting to execute a verb
pattern that your game doesn't define.
EC_VERDO_FAILED
verDoVerb failed. The direct object's verification method
displayed some text, indicating that verification failed.
EC_VERIO_FAILED
verIoVerb failed. The indirect object's verification method
displayed text.
EC_NO_VERDO
no verDoVerb method is defined for the direct object. This
is almost equivalent to EC_VERDO_FAILED, but indicates that a
default parser message was displayed, because the object had
no defined or inherited handler of its own.
EC_NO_VERIO
no verIoVerb method is defined for the indirect object. The
parser displays a default message.
EC_INVAL_DOBJ
direct object validation failed. This indicates that that
direct object isn't accessible for the verb; the parser will
display the usual message (via the cantReach mechanism)
before returning.
EC_INVAL_IOBJ
indirect object validation failed. The indirect object is
not accessible for the verb; the parser will display the
usual message before returning.
Note that the parser does not check to see if the actor is present
in the same room with the current actor, or perform any other
addressibility validation for the actor. This allows you to use
execCommand to script non-player character actions to be carried
out autonomously, without regard to whether the player could have
issued the same commands directly.
The current actor, for the purposes of format string ("%xxx%")
evaluation, is set to the "actor" parameter specified in the call.
execCommand does not invoke any daemons or fuses. The recursive
command is considered part of the current turn.
You should not use execCommand in verification methods (verIoVerb or
verDoVerb), because execCommand invokes complete commands, which may
make changes in game state. Note that changes in game state will
occur regardless of EC_HIDE_SUCCESS or EC_HIDE_ERROR - these flags
merely hide the messages the command produces, but don't prevent
the command from carrying out its other actions.
One good use for execCommand is to treat different phrasings of a
command the same way. For example, suppose you had a can of spray
paint in your game. You might want to allow players to paint things
with the spray paint using commands like "spray <paint> on <target>"
and "spray <target> with <paint>." To make these commands equivalent,
you traditionally would have coded the verIoSprayOn, verDoSprayOn,
ioSprayOn, and doSprayOn routines appropriately, then essentially
duplicated the code in the SprayWith equivalents. Alternatively, you
could have set up the SprayWith routines to call the SprayOn routines
(or vice versa); this kind of indirection is tricky, though, because
of the TADS parser's assymetrical handling of direct and indirect
object verification -- note that the direct and indirect objects
reverse roles between the two phrasings.
This kind of redirection is easy using execCommand, though. First,
choose a "canonical" phrasing -- this is the phrasing where you'll
always implement your handlers for the command. Let's choose
"spray <paint> on <target>" as the canonical phrasing. We now set
up command handlers for our canonical phrasing just as we would for
any other command: we'd write verIoSprayOn and ioSprayOn methods for
objects that we want to allow as targets for spraying, and we'd write
verDoSprayOn methods for objects that can do the spraying. For
example, in the sprayPaintCan object, we'd write a verDoSprayOn
handler to allow spraying the paint on things:
sprayPaintCan: item
// put your sdesc, ldesc, vocabulary, etc. here
verDoSprayOn(actor, iobj) = { }
;
We'd proceed with the normal implementation of "spray <paint> on
<target>" until that worked correctly. Once the canonical phrasing
worked, we'd set up the redirection. Rather than setting up a
complicated series of method-by-method redirections, we can simply
allow any "spray <target> with <paint>" command to proceed all the
way to the ioSprayWith handler, then redirect the entire command at
that point. Since we want to redirect the command for every pair of
objects, we can put all of the handlers in the basic "thing" object:
modify thing
/* allow ANY "spray <target> with <object>" command */
verIoSprayWith(actor) = { }
verDoSprayWith(actor, iobj) = { }
/* re-cast "spray <self> with <iobj>" as "spray <iobj> on <self> */
ioSprayWith(actor, dobj) =
{
execCommand(actor, sprayVerb, self, onPrep, dobj);
}
;
That's all that we need to do -- because execCommand will run through
the entire parsing sequence for the new phrasing, we don't need to
worry about doing any verification for the non-canonical phrasing.
Note that we must put the execCommand call in ioSprayWith, and not
in one of the verXoSprayWith methods -- if we put the call in one of
the verification methods, we could execute the recursive command
multiple times in silent calls during disambiguation. Note also that
we can override the equivalence of "spray <x> on <y>" and "spray <y>
with <x>" on an object-by-object basis, if we wanted, by overriding
the SprayWith methods in the objects where we wanted different
behavior; while the "spray" commands may never need such special
handling, other equivalencies might benefit: "put <x> in <y>" and
"fill <y> with <x>," for example, might only be equivalent for
liquids and containers of liquids.
Another use for execCommand is to perform "implied" commands -- these
are commands that the game carries out automatically, without the
player specifically typing them in, because they're obviously needed
in the course of what the player actually did type.
As an example, suppose you want the player to be wearing sunglasses
every time they enter a particular room. You could simply check to
see if the player is wearing the sunglasses, and forbid travel into
the room if not:
>north
You can't enter the fusion chamber without your sunglasses on.
>wear sunglasses
You're now wearing the sunglasses.
>north
This would work, but it's tedious for the player, in that the game
tells the player exactly what to type, but still makes the player
type it. Some people would still prefer to believe (despite evidence
to the contrary) that computers are our servants and not our masters,
and tend to balk at this type of laziness on the part of the game.
Even more tedious, though, was writing the code traditionally necessary
to make this operation automatic. The problem is that you'd have had
to write code to make all of the same checks that the parser would
normally make to find out if wearing the sunglasses is possible, and
also make sure that any side effects are invoked.
execCommand makes this kind of operation easy, by allowing you to
use exactly the same code that the parser would invoke in order to
carry out an explicit command from the player. In effect, this
lets you automatically run obviously implied commands, rather than
telling the player to run them manually. Here's how we might use
this for the sunglasses:
outsideChamber: room
// normal sdesc/ldesc stuff
north =
{
/* if the sunglasses aren't being worn, try putting them on */
if (sunglasses.isIn(parserGetObj(PO_ACTOR)) && !sunglasses.isworn)
{
/*
* The sunglasses are here but not worn - put them on.
* Tell the player what we're doing, then execute a "wear"
* command recursively. Note that we use EC_HIDE_SUCCESS
* in the execCommand call to suppress the normal success
* confirmation message - we only want a message if the
* player can't wear the sunglasses for some reason.
*/
"(First wearing the sunglasses)\n";
if (execCommand(parserGetObj(PO_ACTOR), wearVerb, sunglasses,
nil, nil, EC_HIDE_SUCCESS) != 0)
{
/*
* that failed - since execCommand showed error messages,
* we have already explained what went wrong, so simply
* return nil to disallow the travel
*/
return nil;
}
}
/* if they're not wearing eye protection, don't allow entry */
if (!(sunglasses.isIn(parserGetObj(PO_ACTOR))
&& sunglasses.isworn))
{
/* explain the problem */
"%You% venture%s% a few steps, but the light in the chamber
is so intense that %you're% forced to retreat. %You%'ll
need some sort of eye protection to enter. ";
/* don't allow travel */
return nil;
}
/* the sunglasses are deployed - we're good to go */
return fusionChamber;
}
;
Note that this example uses the new parserGetObj() built-in function
(described below), rather than parserGetMe(), to determine which actor
is performing the travel. In addition, the messages all use format
strings (such as "%You%"). These two elements ensure that the travel
method can be used for travel by the player, but also can be used for
travel by a non-player character; this is especially important if you
plan to use execCommand() to script NPC actions, since the current
actor during recursive commands sent to an NPC would reflect the
NPC object, rather than the player character ("Me") object.
- A new action method gives the game program a chance to perform special
handling on a command after the objects involved have been resolved,
but before any of the usual parser processing begins. This new hook
is similar to preparse() and preparseCmd(), but takes as parameters
the objects involved in the command rather than the original text.
The new hook is a method named verbAction. This new method is similar
to the roomAction and actorAction methods, but gives the deepverb
object a chance to act. The parser calls this method like this:
verb.verbAction(actor, dobj, prep, iobj)
'actor' is the object representing the actor to whom the command is
addressed; 'dobj' is the direct object, or nil if there is no direct
object; 'prep' is the object for the preposition introducing the
indirect object; and 'iobj' is the indirect object, or nil if there
is no indirect object.
This method returns no value. If this method wants to cancel
execution of the command, it should use the "exit" statement (to
continue execution of any remaining commands on the command line,
but terminate processing of the current command) or the "abort"
statement (to terminate processing of the current command line
entirely). If can also use "exitobj" to terminate processing of
the current object only, and continue execution with the next
object of a multi-object command.
The parser calls verbAction just before calling actorAction.
The verbAction method can be used in conjunction with the new
execCommand built-in function to substitute one sentence pattern
for another, without the usual complexity of mapping several
individual verification and action methods. For example, if you
wanted to process all "fill x with y" commands as "put y in x"
commands, you could put this in your fillVerb definition:
fillVerb: deepverb
verb = 'fill'
sdesc = "fill"
ioAction(withPrep) = 'FillWith'
verbAction(actor, dobj, prep, iobj) =
{
/* check for fill-with syntax */
if (prep = withPrep)
{
/* handle "fill x with y" exactly like "put y in x" */
execCommand(actor, putVerb, iobj, inPrep, dobj);
/* we're done with the command - do not process it further */
exitobj;
}
}
;
- A new parser hook lets the game take control whenever the parser
encounters an unknown verb, or unknown sentence syntax. The new
hook is a function called parseUnknownVerb, which is defined like
this:
parseUnknownVerb: function(actor, wordlist, typelist, errnum)
'actor' is the current actor object. The 'wordlist' parameter is a
list with the strings of the words in the command, in the same format
as the list that is passed to preparseCmd. The 'errnum' parameter is
the parser error number for the condition that caused the call to
parseUnknownVerb; this is the same error number that is passed to
parseError (and related functions).
The 'typelist' argument is a list of the types of the words in the
'wordlist' parameter. Each element of 'typelist' gives the word type
of the corresponding element of 'wordlist' (so typelist[3] gives the
type of the word in wordlist[3], for example). Each type is a number,
which can contain any number of the values below combined with the
bitwise OR operator ("|"). To test for a particular type, use an
expression like this: ((typelist[3] & PRSTYP_NOUN) != 0). The
type values, defined in adv.t, are:
PRSTYP_ARTICLE - the word is defined as an article
PRSTYP_ADJ - adjective
PRSTYP_NOUN - noun
PRSTYP_PLURAL - plural
PRSTYP_PREP - preposition
PRSTYP_VERB - verb
PRSTYP_SPEC - special word (".", "of", "and", etc.)
PRSTYP_UNKNOWN - the word is not in the dictionary
This function can return, true, nil, or a number, or it can use
"abort" to abort the command.
Returning true indicates that the function has successfully handled
the entire command itself; the parser does not display any error
messages, it executes fuses and daemons as normal, and it proceeds to
continue parsing any remaining text on the command line (after a
period or "then").
Returning a number (greater than zero) indicates success, just as true
does, but also indicates that the function parsed only the words
before the returned index, and that the remaining words (starting with
the word at the index value returned) are to be considered an
additional command. The parser will run fuses and daemons as normal,
and then will resume its normal parsing, starting with the word at the
index given by the return value. You can use this if you find "and"
following a noun phrase that you parse, or if for any other reason you
find that another sentence follows and should be parsed separately.
For example, if you succesfully parse the first three words of the
list, you should return the value 4 to indicate that you want the
parser to apply the default parsing starting with the fourth word in
the list.
Returning nil indicates that the function has failed to handle the
command, and wants the parser to display the default error message.
The parser will display the message in the normal fashion, using
parseErrorParam or parseError as appropriate, and will abort the
command. No fuses or daemons will be executed, and any remaining
text on the command line will be discarded.
If this function uses "abort" to end the command, the parser will
not execute any fuses or daemons, and it will ignore any remaining
text on the command line. The difference between returning nil and
executing an "abort" statement is that the parser will display the
default message when this function returns nil, but will not display
anything if the function uses "abort".
The parseUnknownVerb function is currently called with the following
error codes:
17 There's no verb in that sentence!
18 I don't understand that sentence.
19 There are words after your command I couldn't use.
20 I don't know how to use the word "%s" like that.
21 There appear to be extra words after your command.
23 internal error: verb has no action, doAction, or ioAction
24 I don't recognize that sentence.
Error code 17 indicates that the first word in the sentence is not
defined as a verb (which may mean that the word is entirely uknown, or
that it's defined as another part of speech but not as a verb). 18
means that a noun phrase was not formed properly, or that the
combination of the verb and verb preposition ("pick up," for example)
is not defined. 19 means that a preposition occurred at the end of a
sentence, but it could not be combined with the verb. 20 indicates
that the word separating the indirect object is not defined as a
preposition. 21 means that another word follows what the parser
thinks should be the last word in the sentence (for example, the
sentence ends with two prepositions). 23 means that the deepverb
object has no defined templates (action, doAction, or ioAction). 24
indicates that too many objects were used with the sentence: a direct
object is present, but the deepverb doesn't have a doAction, or an
indirect object is present, but the deepverb doesn't have an ioAction.
The purpose of this function is to let you defined your own parsing
for commands outside of the bounds of the built-in parser. Although
this function is similar to preparseCmd, it differs in that
parseUnknownVerb runs only when the parser can't handle a command
directly, which means that parseUnknownVerb doesn't have to decide
whether or not to pass the command to the parser. In addition,
parseUnknownVerb integrates into the turn-handling mechanism, in
that it can control fuse and daemon execution as well as the handling
of remaining text on the command line.
- A new parser hook lets the game program parse noun phrases and perform
the initial resolution to possible matching objects. The parser now
calls a game-defined function named parseNounPhrase, which is defined
like this:
parseNounPhrase: function(wordlist, typelist, current_index,
complain_on_no_match, is_actor_check)
The parameter 'wordlist' is a list of strings, where each string is a
token in the player's command. This is the same type of list that
preparseCmd receives.
'typelist' is a list of word types for the tokens in the list. The
types are bit flag values, so each element may have multiple types
combined with OR. To test to see if a particular type flag is set,
use the bitwise AND operator, "&"; for example, to test the second
element to determine if it's a noun, use this expresion:
((typelist[2] & PRSTYP_NOUN) != 0)
The type flag values, defined in adv.t, are:
PRSTYP_ARTICLE - the word is defined as an article
PRSTYP_ADJ - adjective
PRSTYP_NOUN - noun
PRSTYP_PLURAL - plural
PRSTYP_PREP - preposition
PRSTYP_VERB - verb
PRSTYP_SPEC - special word (".", "of", "and", etc.)
PRSTYP_UNKNOWN - the word is not in the dictionary
'current_index' is the index in the word list of the start of the
noun phrase. This function can look at the previous words in the
list if desired, but the parser has already determined that words
before 'current_index' are part of the verb or of another part of
the command. This function should start parsing at 'current_index'.
'complain_on_no_match' is a boolean value (true or nil) indicating
whether the function should display an error message if the noun
phrase has no matching objects. If this parameter is true, you should
display an appropriate message on this type of error; otherwise, you
should not display any message in this case. You should always
display an error if the noun phrase is not syntactically correct; the
'complain_on_no_match' parameter applies only to error messages for
syntactically correct noun phrases that don't refer to any objects.
'is_actor_check' is a boolean value indicating whether the function
is being called to check for an actor noun phrase. When this is
true, the function should not allow syntax that is obviously
inappropriate for an actor, such as the word "all," or a string or
number; in such cases, the function should simply return an empty
object list to indicate that no valid actor is present.
This function can do one of four things: it can parse the noun phrase,
and return a list of matching objects; it can determine that no noun
phrase is present; it can indicate that a noun phrase is present but
contains a syntax error; or it can let the parser perform the default
noun phrase parsing.
If this function parses a noun phrase successfully, it should return a
list. The first element of the list must be a number, which is the
index of the next token in the word list after the noun phrase. For
example, if 'current_index' is 3 when the function is called, and the
noun phrase consists of one word, the first element of the returned
list should be 4. This tells the parser where it should resume
parsing. The remaining elements of the list are pairs of elements;
the first of each pair is a game object matching the noun phrase, and
the second is a number giving flags for the object. At this point,
it's not necessary to determine whether or not the objects are
accessible, reachable, visible, or anything else; the parser will
disambiguate the list later, when it knows more about the sentence
structure. For now, the routine can simply return a list of all of
the objects that match the vocabulary words.
Multiple flag values can be combined with the bitwise OR operator,
"|". The flags, defined in adv.t, are:
PRSFLG_ALL - the entry is for the word "all" or equivalent
PRSFLG_EXCEPT - the entry is excluded from the "all" list
PRSFLG_IT - the entry matched the pronoun "it"
PRSFLG_THEM - the entry matched the pronoun "them"
PRSFLG_HIM - the entry matched the pronoun "him"
PRSFLG_HER - the entry matched the pronoun "her"
PRSFLG_NUM - the entry is a number
PRSFLG_STR - the entry is a string
PRSFLG_PLURAL - the entry matched a plural usage
PRSFLG_COUNT - the entry has a numeric count as the first word
PRSFLG_ANY - the entry was qualified with "any"
PRSFLG_UNKNOWN - the entry contains an unknown word
PRSFLG_ENDADJ - the entry ends with an adjective
PRSFLG_TRUNC - the entry uses a truncated word
Some examples might be helpful, since the return value is complicated.
For "all" and for the pronouns (it, him, her, them), you should return
a list containing nil as the object, and the appropriate flag value.
For example, if the noun phrase is simply the word "everything", you
would return this (assuming that the index of the word "everything"
was 2):
[3 nil PRSFLG_ALL]
Similarly, if the noun phrase was simply "her", you would return:
[3 nil PRSFLG_HER]
The construction "all except" is also special. You would return a
list whose first entries are nil and PRSFLG_ALL, just as though you
were parsing a simple "all" phrase, but then you'd add entries for all
of the items in the "except" list, with each additional entry's flag
including PRSFLG_EXCEPT. For example, if the player typed "take all
except book and candle", you might return something like this:
[3 nil PRSFLG_ALL book PRSFLG_EXCEPT candle PRSFLG_EXCEPT]
Strings and numbers work the same way: return nil for the object,
and set the appropriate flag. For example, if the player typed
"type 'hello' on keypad", you'd return this:
[3 nil PRSFLG_STR]
If you encounter an unknown word in the noun phrase, and you want to
let the parser resolve the unknown word using its normal mechanisms,
you should return a nil object with the PRSFLG_UNKNOWN flag set:
[4 nil PRSFLG_UNKNOWN]
If you simply want to return a list of objects that match the noun
phrase, it's easy:
[4 book 0 candle 0]
The parser also lets you omit the flags entirely, if you don't need to
include any flags with an object. If an element that follows an
object is another object (or nil), the parser will assume that the
flag value for the preceding object is zero. So, for the example
above, this list is equivalent:
[4 book candle]
If the function parses a noun phrase successfully, but can find no
objects matching the words in the noun phrase (in other words, the
words form a valid noun phrase syntactically, but don't actually refer
to any object in the game), it should return a list that contains only
the index of the next word after the noun phrase.
If the function determines that a noun phrase appears to be present,
but is not syntactically correct, you should display an error message
and return PNP_ERROR. You should not display an error if it doesn't
look like a noun phrase is present at all; instead, you should simply
return a list consisting of the original 'current_index' value to
indicate that you didn't parse any words at all. You should only
display an error and return PNP_ERROR if you determine that a noun
phrase is actually present, but is syntactically incorrect.
If your function determines that it doesn't want to parse the noun
phrase after all, it should simply return PNP_USE_DEFAULT. This tells
the parser to proceed with the default parsing for the noun phrase.
The default noun phrase parser (built in to the TADS interpreter) does
not attempt to resolve or disambiguate objects at this stage.
Instead, it simply creates a list of all of the objects that match
every word in the noun phrase. The reason that the parser doesn't try
to resolve the objects at this stage is that the parser doesn't have
enough information when this routine is called. So, the parser merely
determines the syntactic structure of the noun phrase, and ensures
that at least one object in the game can match all of the words;
later, after the parser has fully analyzed the sentence structure and
knows the verb, prepositions, and number of objects, the parser
resolves and disambiguates the noun phrase. If you write an
implementation of this function, keep this design in mind.
In most cases, you will not want to write a function that completely
replaces the built-in noun phrase parser. Instead, you'll probably
want to check for special cases. When you see a special case, you
should perform your parsing and return an appropriate object list;
when you don't see a special case, you should simply return
PNP_USE_DEFAULT to use the default parsing routine.
- A new parser hook allows the game program to control the disambiguation
process, wherein noun phrases are resolved to specific game objects.
The parser calls two new methods: dismabigDobj, to disambiguate direct
objects; and disambigIobj, for indirect objects. These new methods
are defined on a deepverb object.
The parser calls disambigDobj as follows:
verb.disambigDobj(actor, prep, iobj, verprop,
wordlist, objlist, flaglist,
numWanted, isAmbig, silent);
The parser calls this routine, if defined, most times it resolves a
noun phrase to a concrete list of objects, whether or not the phrase
is ambiguous. This allows you to perform special resolution on
specific noun phrases or for specific verbs, even when the parser
wouldn't normally think the phrases require disambiguation.
The 'actor' parameter is the actor involved in the command.
'prep' is the preposition object associated with the word that
introduces the indirect object, if present; if there is no
preposition, 'prep' will be nil.
'iobj' is the indirect object, if available; if there is no indirect
object, or the indirect object has not yet been resolved when
disambigDobj is invoked, 'iobj' will be nil.
'verprop' is the property address of the verification method
(verDoVerb) defined for the direct object for the verb; this parameter
is included because a single verb object could define several
verification methods that very by preposition ("put x in y" usually
has a different verification method than "put x on y").
'wordlist' is a list of strings giving the tokens of the player's
command that make up the noun phrase. This is the same type of list
that preparseCmd receives.
'objlist' is a list of the objects that the parser found with
dictionary entries matching all of the words in the word list.
'flaglist' is a list of numbers giving flags for the corresponding
'objlist' entries; for example, flaglist[3] gives the flags associated
with objlist[3].
The flag values for 'flaglist' are defined in adv.t. Thee flags are
bit-field values, so multiple flags can be combined with OR into a
single value. To test if a particular flag is set, use the bitwise
AND operator, "&"; for example, to test the second element to see if
the PLURAL flag is set, use this expression:
((flaglist[2] & DISAMBIG_PLURAL) != 0)
The flags are:
PRSFLG_COUNT - the object matched with a numbered count. For
example, if the noun phrase is "3 gold coins," objlist will contain
one or more objects matching the plural phrase "gold coins," and the
VOCS_COUNT flag will be set for each object to indicate that a count
is present. In these cases, the first element of wordlist should
always be the string with the user's number (the string '3' in the
example).
PRSFLG_PLURAL - the object matched a plural usage.
PRSFLG_ANY - the noun phrase started with "any"
PRSFLG_ENDADJ - the object matched with an adjective at the end of
the noun phrase. For example, suppose the noun phrase is "letter",
and the game defines parchmentLetter with noun = 'letter', and
defines letterOpener with adjective = 'letter'. In this case,
objlist would contain both parchmentLetter and letterOpener, and the
flaglist entry corresponding to letterOpener would have the
PRSFLG_ENDADJ flag set. This flag allows the disambiguator to select
in favor of the noun interpretation in case of ambiguity, to avoid
having to ask a stupid disambiguation question ("which letter do you
mean, the parchment letter, or the letter opener?" should clearly
not be asked).
PRSFLG_TRUNC - the object matched with one or more truncated words.
This will be set when a word in the player's noun phrase matches the
leading substring of a dictionary word, and is at least six characters
long, but doesn't match the entire dictionary word. For example,
"flashlig" matches "flashlight" because "flashlig" is at least six
characters long and matches "flashlight" in its first eight characters
(i.e., the length of "flashlig"), but the parser will flag the word
with PRSFLG_TRUNC to indicate that it wasn't an exact match.
'numWanted' is the number of objects that the parser wants in the
resolved list. For a definite singular noun phrase ("take the box"),
this will be 1. For a plural noun phrase, this will be the number
of objects in the proposed resolution list (in the 'objlist' parameter).
When the player specifies a count ("take 3 boxes"), this will be the
give number. For an "any" phrase ("take any box"), this will be 1.
You don't have to obey the 'numWanted' parameter, but this information
may be helpful in some cases.
'isAmbig' is true if the parser thinks the noun phrase is ambiguous,
nil if not. For example, if the player specified a singular definite
noun phrase, but the parser found two matching objects, 'isAmbig' will
be true. You can always deduce whether or not the list is ambiguous
by examining the 'numWanted' value and all of the object flags, but
doing so is complicated, so the parser provides 'isAmbig' for your
convenience. If your disambigDobj or disambigIobj is interested only
in resolving ambiguity, as opposed to performing special noun phrase
resolution, you can simply return DISAMBIG_CONTINUE immediately if
'isAmbig' is nil.
'silent' specifies whether the disambiguation is in interactive or
non-interactive mode. If 'silent' is true, it means that you should
not display any messages or ask the player to help resolve any
ambiguity. If 'silent' is nil, you can display messages or prompt
for more information if you wish.
The disambigIobj method is essentially the same:
verb.disambigIobj(actor, prep, dobj, verprop,
wordlist, objlist, flaglist,
numWanted, isAmbig, silent);
The only difference from disambigDobj is that disambigIobj receives
the direct object in the 'dobj' parameter. Note that the direct
object is not normally available during indirect object
disambiguation, so 'dobj' will usually be nil. The only time the
direct object will be available will be for verb templates with the
[disambigDobjFirst] flag. For verbs without this flag, the indirect
object is resolved before the direct object, hence the direct object
is not yet known.
The parser's built-in disambiguation routine calls these methods after
it has done everything it can, short of asking the player, to resolve
and disambiguate a noun list. The parser will apply the normal
validation checks (validDo, validDoList, etc.) to the objects,
eliminating any that don't pass; and it will apply the silent
verification checks (verDoVerb, verIoVerb) as well. The parser will
next call disambigDobj or disambigIobj, if the verb object defines it.
These methods can return a status code from the list below, or they
can return a list (see below). The status codes (defined in adv.t)
are:
DISAMBIG_CONTINUE - continue through the remainder of the
disambiguation process as normal.
DISAMBIG_ERROR - abort the current command entirely. This can be
used when the method encounters an error and displays an error
message to the player. The parser will simply terminate the current
command. Note that the parser does not display an error of its own,
so the method must display an appropriate message before returning
this status code.
DISAMBIG_PROMPTED - continue through the process as normal, but do
not show a prompt for an interactive response ("which foo do you
mean..."), because the disambigXobj function already displayed an
appropriate prompt of its own. This allows the disambigXobj
function to display a customized prompt (using more information
than the traditional parseDisambig function has available), but
still use the parser's default response reader and parser.
DISAMBIG_PARSE_RESP - this indicates that your code has asked the
player a question and read a response (via the input() built-in
function, for example), but that you want to use the default
response parser in the normal disambiguation mechanism to parse
the response. Do not return this as a raw status code; instead,
return a list containing this value as the first element, and the
string to be parsed as the second element.
DISAMBIG_DONE - consider the object list fully resolved. This skips
any additional checking the disambiguator would normally perform and
uses the current list as-is. This should generally never be
returned directly, but is used when returning a list of objects.
The method can also return a list, which contains objects that replace
the original input list (in the 'objlist' parameter). The first
element of the returned list is a status code, from the list above.
Subsequent elements are the objects to use as the result of
disambiguation.
You can optionally specify a flag value for each object value. To
specify a flag for an object, simply place the flag value after the
object in the list.
return [DISAMBIG_DONE redBox PRSFLG_PLURAL blueBox PRSFLG_PLURAL];
Each flag value pertains to the object immediately preceding it.
If you omit a flag value, zero is assumed. So, the following two
return lists are equivalent:
[DISAMBIG_CONTINUE redBox 0 blueBox 0]
[DISAMBIG_CONTINUE redBox blueBox]
You can optionally omit the status code from the list. If the first
element of the list is an object, the parser uses DISAMBIG_CONTINUE as
the default status code, so it takes the list you returned and
proceeds to the next step in the disambiguation process.
Note that the parser doesn't call disambigDobj or disambigIobj for
certain cases: resolving pronouns (it, her, him, them); strings;
numbers that don't map to vocabulary words, but simply resolve to
numObj; and "all" phrases. However, the parser does call these
routines to resolve the items in an "except" phrase.
- A new built-in function, parserGetObj, lets the game program learn
the objects involved in the current command. This function takes a
single argument, which indicates which object you want, using the
following constants (defined in adv.t):
PO_ACTOR - the current command's actor
PO_VERB - the deepverb object for the command's verb
PO_DOBJ - the direct object
PO_PREP - the preposition object introducing the indirect object
PO_IOBJ - the indirect object
The return value is an object, or nil if there is no such object
for the current command. For example, a command with no indirect
object will return nil for PO_PREP and PO_IOBJ.
Here's an example of using parserGetObj to get the direct object
of the current command:
local obj := parserGetObj(PO_DOBJ);
parserGetObj returns valid information at any time from (and including)
actorAction to (and including) the doVerb or ioVerb routines. You
can't use parserGetObj() outside of these bounds; if you do, it will
simply return nil. The reason for the limited lifetime of this
function is that the parser simply doesn't know the final values
for the command objects before actorAction, since it is still busy
resolving the words in the command to objects until it's about to
call actorAction. Note some particular times when you can't call
parserGetObj: in the "init" and "preinit" functions; during object
disambiguation (thus during some verDoVerb and verIoVerb calls);
during roomCheck; during preparse() and preparseCmd(); and during
fuse and daemon execution.
Note one important exception to the limited lifetime: the actor
can be retrieved at any time after the preparse() function returns.
The parser determines the actor very early in the process of
interpreting the command, so the actor is available throughout
the parsing sequence.
parserGetObj returns information on the current command. When a
recursive command is in progress (using execCommand), parserGetObj
returns information on the recursive command; once execCommand
finishes and returns to the code that called it, parserGetObj will
once again return information on the enclosing command.
- A new built-in function lets the game invoke the parer's tokenizer to
obtain a token list for a string of text. You can call this new
function like so:
tokenList := parserTokenize(commandString);
The 'commandString' parameter is any string of text. The tokenizer
will scan the string and break it up into tokens, and return a list of
token strings. The token strings follow the same rules as the token
list passed to the preparseCmd() function.
If the command string contains any invalid characters (such as
punctuation marks that the tokenizer doesn't accept), it will return
nil, but it won't display any error messages.
- A new built-in function lets you obtain a list of token types given a
list of tokens. You can use this function to get the types of the
tokens in a list returned by parserTokenize(), for example. Call the
new function like this:
typeList := parserGetTokTypes(tokenList);
The result is a list of numbers. Each element of the result list
gives the type of the corresponding element of the token list
(typeList[3] contains the type of the token in tokenList[3], for
example).
The types in the result list are combinations of the following
values, defined in adv.t:
PRSTYP_ARTICLE - article (a, an, the)
PRSTYP_ADJ - adjective
PRSTYP_NOUN - noun
PRSTYP_PREP - preposition
PRSTYP_VERB - verb
PRSTYP_SPEC - special word
PRSTYP_PLURAL - plural
PRSTYP_UNKNOWN - unknown word
These type codes are bit-field values, so they can be combined with
the bitwise OR operator ("|"). For example, a token that appears in
the dictionary as both a noun and an adjective will have a token type
value of (PRSTYP_ADJ | PRSTYP_NOUN).
Because more than one PRSTYP_xxx value can be combined into a type
code, you must use the bitwise AND operator ("&") to check a type code
for a specific PRSTYP_xxx value. For example, if you want to check a
token to see if has "noun" among its types, you'd write this:
((typeList[3] & PRSTYP_NOUN) != 0)
- A new built-in function lets you perform a parser dictionary look-up
to obtain the list of objects that define a set of vocabulary words.
This function can be used to perform your own noun-phrase parsing.
Call the new function like this:
objList := parserDictLookup(tokenList, typeList);
The 'tokenList' parameter is a list of the token strings you want to
look up in the dictionary; this list uses the same format as the list
returned by parserTokenize(), so you can use the result of
parserTokenize() as input to parserDictLookup().
The 'typeList' parameter is a list of token types. Each entry in
'typeList' gives the token type of the corresponding entry in
'tokenList'. This list uses the same PRSTYP_xxx codes returned by
parserGetTokTypes(), but each entry in the type list should have only
a single PRSTYP_xxx code (a type code in this list should not be a
combination of more than one PRSTYP_xxx code).
Because the 'typeList' entries must contain individual PRSTYP_xxx type
codes, rather than combinations of type codes, you should generally
not pass the result of parserGetTokTypes() directly to to
parserDictLookup(). Instead, you need to determine how you want to
interpret the words in the token list by choosing a single token type
for each entry. How you determine each single type is up to you. If
you're parsing a noun phrase, for example, you might decide that all
words in the noun phrase except the last must be adjectives, and the
last must be a noun. The assignment of token types will depend on the
type of parsing you're doing, and the syntax rules that you decide to
implement for the type of input you're parsing.
The return value of parserDictLookup() is a list of all of the game
objects that match all of the vocabulary words, with the given types.
If there are no objects that match all of the words, the result is an
empty list.
Verbs that use combining prepositions (such as "pick up" or "go
north") use a special form of the token string. To look up a
combining, two-word verb, use a token string that contains both words,
separated by a space. parserTokenize() will never return such a
string, because it will always break up the tokens according to word
separators, so you must re-combine such tokens yourself. For example,
to look up the deepverb object matching "pick up" as a verb, you could
write this:
objList := parserDictLookup(['pick up'], [PRSTYP_VERB]);
Note that parserDictLookup() simply looks up words in the dictionary.
This function doesn't perform any disambiguation, access checking,
visibility checking, or any other validation on the objects.
- A new built-in function allows the game to invoke the parser's
internal function to parse a noun list. This can be used in
conjunction with the parseUnknownVerb to allow the function to
interpret part of the word list as a noun list.
Your game program can call the function like this:
ret := parseNounList(wordlist, typelist, startingIndex,
complainOnNoMatch, multi, checkActor);
'wordlist' is a list of the strings making up the command. 'typelist'
is a list of word types; each entry in 'typelist' is a number giving
the type of the corresponding word in 'wordlist'. The values in
'wordlist' have the same meaning as the 'typelist' parameter to the
parseUnknownVerb function:
PRSTYP_ARTICLE - the word is defined as an article
PRSTYP_ADJ - adjective
PRSTYP_NOUN - noun
PRSTYP_PLURAL - plural
PRSTYP_PREP - preposition
PRSTYP_VERB - verb
PRSTYP_SPEC - special word (".", "of", "and", etc.)
PRSTYP_UNKNOWN - the word is not in the dictionary
'startingIndex' is the index in 'wordlist' and 'typelist' of the first
word to parse; the function will ignore all of the words before this
index. This allows you to parse a portion of a word list in your own
code, and start parsing a noun phrase that follows the portion you
parsed.
Set 'complainOnNoMatch' to true to make the function display an error
message if it parses a syntactically valid noun phrase, but there are
no objects in the game that match the noun phrase; set this to nil if
you want to suppress this message. Note that the function will
display any syntax error messages regardless of this setting. If you
want to suppress all messages, you can use outhide() or outcapture()
to hide any error messages displayed.
'multi' specifies whether you want the function to parse multiple noun
phrases (separated by "and", for example) or just a single noun
phrase. If 'multi' is true, the function will parse any number of
noun phrases; if 'multi' is nil, the function will only parse a single
phrase, stopping if it reaches "and" or equivalent.
'checkActor' specifies if you want to perform an actor check. If this
is true, the function will reject "all", quoted strings, and phrases
involving "both" or "any"; it will only parse a single noun phrase
(regardless of the setting of 'multi'); and it will not display an
error if the noun phrase cannot be matched. The parser uses this mode
internally to check the beginning of a command to determine if the
command is directed to an actor, and this is probably the only context
in which 'checkActor' should ever need to be true. In most cases, you
should set 'checkActor' to nil. Note that you should not use true
just because noun phrase may happen to contain an actor or is expected
to contain an actor; you should only use true when you want the
special error-handling behavior. Note also that using true for
'checkActor' does not cause the parser to reject noun phrases that
refer to non-actor objects; this flag simply controls the
error-handling behavior and does not affect what objects can be
matched.
If the parser encounters a syntax error, the function returns nil.
This indicates that the function displayed an error message
(regardless of the value of 'complainOnNoMatch'), and that the words
do not form a syntactically-correct noun phrase.
If the parser finds a syntactically valid noun phrase, but finds no
objects that match the noun phrase, it returns a list containing a
single number. The number is the index of the next word in 'wordlist'
following the noun phrase. For example, suppose we have this word
list:
['take' 'red' 'ball' 'with' 'hook']
Suppose that we start parsing at index 2 ('red'), and that 'red' and
'ball' are in the dictionary as adjective and noun, respectively.
The parser will parse the noun phrase "red ball", consuming two
words from the word list. Now, suppose that there are no objects
in the game matching both vocabulary words (i.e., there's no red
ball in the game). The parser will indicate that a syntactically
valid noun phrase is present, but that no objects match the noun
phrase, by returning this:
[4]
The number is the index of the next word after the noun phrase (in
this example, 'with').
If the parser finds a syntactically valid noun phrase, and finds one
or more matching objects, it returns a list giving the matching
objects. The first element of the list, as above, is the index in the
word array of the next word after the noun phrase. Each additional
element is a sublist.
Each sublist gives information on one noun phrase. If 'multi' is nil,
there can be at most one sublist. If 'multi' is true, there will be
one sublist per noun phrase (each noun phrase is separated from the
previous one by "and" or equivalent). The first element of the
sublist is the index in the word array of the first word of the noun
phrase, and the second element is the index of the last word of the
noun phrase; the noun phrase is formed by the words in the array from
the first index to the last index, inclusive, so the last index will
always be greater than or equal to the first index. After these two
elements, the sublist contains pairs of entries: a matching object,
and flags associated with the matching object. Each matching object
is a game object that matches all of the vocabulary words in the noun
phrase.
The flags value associated with each matching object is a combination
of any number of the PRSFLG_xxx values described with the
parseNounPhrase function. These flag values can be combined with the
bitwise OR operator ("|"), so to test for a particular flag value, use
the bitwise AND operator: ((flag & PRSFLG_EXCEPT) != 0).
Since the return list is rather complicated, some examples might be
helpful.
Suppose that we start with this word list:
['take' 'knife' ',' 'cardboard' 'box']
Suppose also that we use 2 as the starting index (because we want to
start at the word 'knife'), and that 'knife', 'cardboard' and 'box'
are defined words in the game.
Now, suppose we have the following game objects defined:
rustyKnife: item noun='knife' adjective='rusty';
sharpKnife: item noun='knife' adjective='sharp';
dagger: item noun='dagger' 'knife';
box: item noun='box' adjective='cardboard';
Given all of this, the return list would look like this:
[6 [2 2 rustyKnife 0 sharpKnife 0] [4 5 box 0]]
The first element indicates that the next word after the noun list is
element 6; since the list has only five elements, this simply means
that the noun list runs all the way to the end of the word list.
The next two elements are the sublists, one per noun phrase:
[2 2 rustyKnife 0 sharpKnife 0]
[4 5 box 0]
The first sublist specifies a noun phrase that runs from word 2 to
word 2, inclusive, hence 'knife'. The remaining pairs of elements in
the list tell us that the matching objects are rustyKnife (with flags
of 0) and sharpKnife (also with flags of 0).
The second sublist specifies a noun phrase that runs from word 4 to
word 5, inclusive, hence 'cardboard box'. The matching object for
this phrase is box (with flags 0).
To interpret this return value, consider this code:
if (ret = nil)
{
/* the noun phrase had a syntax error; give up */
return; // or whatever we want to do in case of error
}
"Next word index = <<ret[1]>>\b";
if (length(ret) = 1)
{
/* valid noun phrase, but no matching objects */
"I don't see that here.";
return;
}
/* handle each sublist individually */
for (i := 2 ; i <= length(ret) ; ++i)
{
local sub;
local firstWord, lastWord;
local j;
/* get the current sublist */
sub := ret[i];
/* get the first and last word indices for this noun phrase */
firstWord := sub[1];
lastWord := sub[2];
/* display the word list (or whatever - this is just an example) */
"\bNoun phrase #<<i>> is: '";
for (j := firstWord ; j <= lastWord ; ++j)
{
say(wordlist[j]);
if (j != lastWord)
say(' ');
}
"'\n";
/* scan the objects in the list - each object takes two elements */
for (j := 3 ; j <= length(sub) ; j += 2)
{
/* display this object and its flags */
"matching object = <<sub[j].sdesc>>, flags = <<sub[j+1]>>\n";
}
}
Note that in many cases you won't care about interpreting this list
directly; instead, you'll simply want to pass the list to the
parserDisambig() built-in function for resolution and disambiguation.
The return list is in the same format required for input to that
function.
This function directly invokes the parser's noun list parser, which is
exactly the same code the parser uses while parsing a player's command
line. The noun list parser will in turn invoke your parseNounPhrase()
function, if you've defined such a function in your game. So, you
should be careful not to set up an infinite recursion by calling this
function from your parseNounPhrase() function.
- A new built-in function lets you access the parser's object resolution
and disambiguation subsystem programmatically. You can use the object
resolver in conjunction with parseNounList() or with your own
noun-list parser to implement your own command parsing system.
In the standard TADS parser, object resolution occurs after the parser
has finished parsing the syntax structure of a sentence, and thus
knows the verb, all of the noun phrases, and connecting prepositions.
Once all of this information is known, the parser can intelligently
determine the objects to which each noun phrase refers. As a result
of this design, the object resolver requires parameters that specify
the other aspects of the sentence structure.
The object resolution function is called like this:
resultList := parserResolveObjects(actor, verb, prep, otherobj,
usageType, verprop,
tokenList, objList, silent);
The 'actor' parameter is the actor object for the command for which
the objects are to be resolved. The 'verb' parameter is the deepverb
object involved in the command. The 'prep' parameter is the
preposition object that introduces the indirect object; if there's no
indirect object or no preposition, 'prep' should be nil.
'usageType' specifies the type of object that you're resolving. You
should use one of these constants, defined in adv.t, for this
parameter:
PRO_RESOLVE_DOBJ - direct object
PRO_RESOLVE_IOBJ - indirect object
PRO_RESOLVE_ACTOR - actor: use this if you're resolving an object
for use as an actor to whom the player is directing the command.
'verprop' is the verification method address; this is the address of
the verDoVerb for your verb. This must be specified in addition to
the deepverb object, because a single deepverb can be associated with
multiple verification/action methods (for example, "put x on y" uses a
different set of methods from "put x in y", but both are associated
with putVerb). For example, for the direct object of "put x in y",
you'd specify &verDoPutIn.
If you're validating an actor (not a direct or indirect object that
happens to be an actor, but rather an actor that the player is
addressing and who is to carry out a command), the parser normally
uses &verDoTake for 'verprop', rather than the actual verb being
executed, because the point is to verify that the player can access
the actor, not that the player can perform the command on the actor.
"Taking" the actor has reasonably suitable accessibility rules for
talking to an actor. You could conceivably define your own verb
simply for the purposes of talking to an actor, and then use that verb
and its appropriate verification method instead of takeVerb and
verDoTake.
'tokenList' is the list of tokens which was parsed to build the input
object list. If you obtained the object list from parseNounList(),
you should simply use the same token list that you used with
parseNounList(). The importance of 'tokenList' is that the token list
indices in the object list refer to words in the token list.
'objList' is the input object list. The resolver starts with this
list to produce the resolved list. 'objList' is in exactly the same
format as the list returned by parseNounList(), so you can use the
result of parseNounList() as the 'objList' parameter. If you use your
own noun list parser instead, you must prepare a list that uses the
same format as the parseNounList() result.
'silent' specifies whether the resolver is interactive or not. If
'silent' is true, the resolver will not display any messages to the
player, and will not ask the player to resolve the list in case of
ambiguity; instead, the resolver will simply return an error code. If
'silent' is nil, the resolver will display a message if an error
occurs, and will ask the user to resolve ambiguity using the
traditional interactive process ("Which foo do you mean...").
The return value of this function is always a list. The first element
of this list is always a number giving a status code. The status
codes are the same values and have the same meanings as the codes
passed to parseError() and parseErrorParam().
The status code PRS_SUCCESS (this constant and the PRSERR_xxx
constants mentioned below are defined in adv.t) indicates that the
resolution was successful. In this case, the remainder of the list
simply contains the resolved objects:
[PRS_SUCCESS goldCoin shoeBox]
PRSERR_AMBIGUOUS indicates that the result list is ambiguous. This
code will only be returned if 'silent' is true, because in other cases
the resolver will not return until the player resolves any ambiguity
interactively, or an error occurs. When this status code is returned,
the remainder of the list contains the partially-resolved objects; the
resolver will have narrowed down the list as much as possible by
including only objects that are accessible to the actor for the
purposes of the verb, but the list will still require further
disambiguation to obtain the final set of objects.
[PRSERR_AMBIGUOUS goldCoin silverCoin shoeBox cardboardBox]
PRSERR_DISAMBIG_RETRY indicates that the player entered a new command
in response to a disambiguation query. This can only happen when
'silent' is nil, because the parser won't ask the player any questions
at all when 'silent' is true. When this status code is returned, the
list contains only one additional element, which is a string with the
player's new command. If you want to execute the new command, you can
use parserReplaceCommand() to abandon the current command and execute
the new command instead.
[PRSERR_DISAMBIG_RETRY 'go north']
Any other status code indicates an error which caused the resolver to
fail. The list will contain no other elements in these cases.
Note that this function calls the identical internal parser code that
the player command parser normally uses to process a command. The
object resolver in some cases calls the disambigDobj and disambigIobj
methods defined in the deepverb object. As a result, you should be
careful not to call this function from disambigDobj or disambigIobj
methods, since doing so could result in infinite recursion.
Here's an example that uses several of the new parser functions,
including parserResolveObjects(). This function reads a string from
the keyboard, tokenizes it, gets the token types, parses the token
list as a noun list, and then resolves the noun list to an object.
askForObject: function
{
local str;
local toklist, typelist;
local objlist;
/* get an object */
"Type an object name: ";
str := input();
/* tokenize it */
toklist := parserTokenize(str);
if (toklist = nil)
{
"The object name is invalid!";
return nil;
}
/* get the token types */
typelist := parserGetTokTypes(toklist);
/* parse a single noun phrase */
objlist := parseNounList(toklist, typelist, 1, true, nil, nil);
if (objlist = nil)
return nil;
if (length(objlist) = 1)
{
"You see no such thing. ";
return nil;
}
if (objlist[1] <= length(toklist))
{
"There seem to be words after the object name that I can't use. ";
return nil;
}
/* resolve and disambiguate */
objlist := parserResolveObjects(Me, takeVerb, nil, nil,
PRO_RESOLVE_DOBJ, &verDoTake,
toklist, objlist, nil);
if (objlist[1] = PRS_SUCCESS)
{
/* success! return the objects, which follow the status code */
return cdr(objlist);
}
else if (objlist[1] = PRSERR_DISAMBIG_RETRY)
{
/* run the new command, which is in the second element */
parserReplaceCommand(objlist[2]);
}
else
{
/* we were in non-silent mode, so the resolver displayed an error */
return nil;
}
}
- A new built-in function, parserReplaceCommand(), allows you to abort
the current command and start executing a new command using a given
text string. Call the function like this:
parserReplaceCommand(commandString);
This function doesn't return -- it effectively executes an "abort"
statement to terminate the current command. The given command string
is entered into the parser's internal buffer, and the system parses
and executes the command as though the player had typed the command
directly.
- A new built-in function, setOutputFilter, allows the game program to
intercept and optionally change all display output just before it's
formatted for display. setOutputFilter() takes one argument, which
is the address of a user-defined function; this tells the output
formatter to call this function each time a string is displayed.
You can also call the function with a value of nil, which cancels
the current output filter function, restoring unfiltered output.
The filter function is defined like this:
myFilter: function(str)
You can use any name in place of "myFilter". The parameter "str"
is the string that the output formatter is about to display. Your
filter function can return nil, in which case the original string
is displayed unchanged; or it can return a (single-quoted) string
value, which the formatter will display instead of the original
string.
Here's an example that converts all displayed output to upper-case
when the player types the verb "uppercase on," and restored output to
normal when the player types "uppercase off."
ucFilter: function(str)
{
return upper(str);
}
uppercaseonVerb: deepverb
verb = 'uppercase on'
action(actor) =
{
"Upper-case mode is now ON. ";
setOutputFilter(ucFilter);
}
;
uppercaseoffVerb: deepverb
verb = 'uppercase off'
action(actor) =
{
"Upper-case mode is now OFF. ";
setOutputFilter(nil);
}
;
The output filter function is called before the output formatter does
any processing on the text to display. After the filter returns, the
formatter translates "%fmt%" sequences, applies caps() and nocaps()
changes, translates "\t" and similar sequences, performs word-wrapping
on the line, adjusts spacing for punctuation, and, when in HTML mode,
interprets HTML mark-ups. As a result, you can perform your own
translations on any of these sequences; in addition, you can use such
sequences in the returned string, and the formatter will interpret them
normally.
Note that the output formatter will make a separate call to your filter
function to display the result of translating each "%fmt%" sequences in
the text to be displayed. These separate calls will occur after your
filter function returns. This allows you to perform the same operations
on the translated "%fmt%" sequences that you perform on any other text.
- Fixed a bug in the objwords() built-in function. In the past, this
function at times incorrectly returned a non-empty list of words for
a command that didn't have the corresponding object at all. This has
been corrected; objwords() will now return an empty list when the
command does not have the requested object.
- The parser now re-validates objects in a multi-object command after
executing the command on the first object. This re-validation occurs
before the verbAction method is called for each object, and is done
only for the second and subsequent object in a multi-object command.
Re-validation is necessary because an action performed on the first
object in a multi-object command could change conditions in the game
such that the second object becomes invalid for the command, even
though it was valid during the object resolution phase. Consider
this example:
>x large box
The large box is open. It contains a small box.
>x small box.
The small box is open.
>close large box, small box
large box: Closed.
small box: You don't see that here.
In the past, the parser only validated the objects in a multi-object
command during the resolution phase, which happens before executing
the command on any of the objects. Since the small box is accessible
before the large box is closed, the parser would have allowed the
player to close the small box after closing the large box in the last
command above. By re-validating the second and subsequent objects,
the parser now correctly detects when objects become inaccessible in
the course of executing a command on multiple objects.
If an object fails re-validation, the parser checks to see if the
object is visible. If it is, the parser uses the cantReach method
as usual to display the error. If the object is not visible, the
parser displays error 38, "You don't see that here any more."
- The parser error codes (the numbers passed to parseError and
parseErrorParam, and returned by parserResolveObjects) now have
constants defined in adv.t. These constants have names like
PRSERR_xxx. Refer to adv.t for the complete list.
You may prefer to use the constants in your code whenever possible for
clarity. The numeric values will be stable in future releases, so
it's perfectly safe to use the numbers directly, but using the
symbolic constants will make the purpose of your code clearer to
others (and to you, if you come back to some code after not having
looked at it for a few weeks).
- Several new parser error codes have been added.
The new codes 40, 41, 42, 43, and 44 never result in a
parser-generated error message, thus these codes have no default
message. These codes can, however, be returned by
parserResolveObjects(), so that callers can distinguish among
different types of error conditions if necessary.
Code 38, default message "You don't see that here any more." This
message is used during object re-validation when an object that was
valid at the start of the command is no longer valid.
Code 39, default message "You don't see that here." This new message
is used when object validation (via validDo or validIo) fails in the
course of executing a command recursively with execCommand().
Code 40, no default message. This error code is used when the parser
is unable to create a new generic numeric object when calling a
newNumbered method.
Code 41, no default message. This error occurs when disambigDobj or
disambigIobj returns an invalid status code.
Code 42, no default message. This error occurs when the parser gets
an empty line of text in response to a disambiguation question.
Code 43, no default message. This error occurs when the parser gets
what looks like an entirely new command in response to a
disambiguation question.
Code 44, no default message. This occurs when the parser finds that
it still has an ambiguous list of objects after applying validation
and verification tests, but the disambiguator is being called in
"silent" mode and hence is not allowed to ask the player for help.
When this occurs, the parser simply returns this error to indicate
that the list cannot be disambiguated.
- Parser message 16 has been changed slightly. The parser generates
message 16 when the player's answer to a noun disambiguation question
("which <object> do you mean...") doesn't refer to any of the possible
ambiguous objects. For example, suppose the parser asks "which book
do you mean, the red book, or the blue book?," and the player answers
"silver." In the past, message 16 simply said "I don't see that
here." Some players found this slightly confusing, especially since
the answer may have referred to some other object that wasn't a book
but was indeed present. The new message is intended to be somewhat
more explicit: "You don't see any %s %s here," where the first "%s"
is replaced with the new noun phrase the player typed, and the second
"%s" is replaced with the original noun phrase.
>take book
Which book do you mean, the red book, or the blue book?
>the silver one
You don't see any silver book here.
Note that you can use the parseErrorParam() user-defined function if
you want to intercept this message have have access to the string
parameters.
- In adv.t, the "doorway" class has a new method, setIslocked.
This new method behaves analogously to setIsopen: when you call
setIslocked on a door, the method automatically updates both sides
of a two-sided door to the same locking status. All of the code
in "doorway" that updates the islocked property now uses setIslocked
rather than changing the property directly.
- The default "sav" extension on newly-created saved game files is now
in lower-case letters on operating systems that support mixed-case
filenames.
- A long-standing parser bug involving vocabulary words defined as both
adjectives and nouns (for different objects) has been fixed. In the
past, if a particular word was defined as a noun for one object, and
as an adjective for another object, the word could not ever be used
alone to refer to the second object. For example, suppose the game
defined one object, "letter," and another, "letter opener"; the word
"letter" is defined as a noun for the first object, and as an adjective
for the second object. If the player typed "get letter," the parser
formerly assumed that the word was to be used as a noun; even if the
"letter" object wasn't present and the "letter opener" object was,
the parser would never understand the sentence to mean "get letter
opener." This was inconsistent with other objects, since in other
cases an adjective alone could be used to refer to an object when the
meaning was not ambiguous. This has now been fixed; the parser now
will interpret the adjective alone to refer to an associated object
when no other object defining the word as a noun is accessible.
Note that this does not create a new ambiguity: if the player types
"get letter," and both the letter and the letter opener objects
are present, the parser will still assume that "letter" is being
used as a noun. The parser will only attempt the adjective-only
interpretation as a last resort, when no matching objects using the
word as a noun are accessible.
- Fixed a parser bug involving disambiguation of objects with redundant
nouns or adjectives. When the player types a word, and the word is at
least six characters long, the parser will match the word to any entry
in the game's dictionary of which the player's word is a leading
substring. For example, "flashlig" matches "flashlight", because
"flashlig" is eight characters long (which is greater than or equal
to the required six), and matches the first eight characters of
"flashlight". Similarly, "headlight" will match "headlights",
because "headlight" is nine characters long, and matches "headlights"
in the first nine characters. This short-hand feature is meant as a
convenience for the player, but occasionally caused ambiguity problems
in past versions of TADS. In particular, when a single object had
a vocabulary word that was a leading substring of another vocabulary
word of the same object (for example, if an object defined as nouns
both 'headlight' and 'headlights'), the object could in some cases
show up twice in a list of ambiguous objects ("which headlight do
you mean..."). The parser now always eliminates redundant entries
in lists of ambiguous objects during object disambiguation.
- Another parser bug involving truncated words has been fixed. Suppose
the game defines one object with a noun 'prince', and another object
with a noun 'princess'. In the past, these two objects both matched
the word 'prince' in a player's command, so if both objects were
present, the parser considered them ambiguous. The parser now
considers an exact match to be better than a truncated match, so the
word 'prince' in a player's command will now match the 'prince' object
more strongly than it will the 'princess' object. If only the
'princess' object is accessible, 'prince' will still match the
'princess' object, but if both are present, the parser will simply
assume the player is referring to the 'prince' object.
- Fixed a parser bug that caused two error messages (without any spacing
between them) when the first word in a command was unknown: the
parser responded with both "There's no verb in that sentence!" and
"I don't know the word 'xxx'". The parser now displays only the
first ("no verb") message.
- Fixed a parser bug that caused fuses and deamons to be skipped when
an "exit" statement was executed in the midst of executing a command
involving multiple direct objects. If the "exit" was executed during
the processing of any but the last object in the direct object list,
the parser incorrectly skipped fuses and daemons. This has been
corrected; fuses and daemons will now run after an "exit" is executed,
regardless of how many direct objects are involved in the command.
- In adv.t, in the definition of movableActor, the definitions of the
format strings fmtYou, fmtYour, fmtYoure, fmtYoum, and fmtYouve now
vary according to the gender defined for the object with the isHim
and isHer properties. If isHim evaluates to true, the format strings
now use masculine pronouns; otherwise, if isHer evaluates to true,
the format strings use feminine pronouns; otherwise, they use neuter
pronouns.
Note that, in the past, these format strings always used masculine
pronouns; if you defined any Actor or movableActor objects with
neither isHim nor isHer set to true, messages that in the past used
masculine pronouns will now use neuter pronouns. You should be
careful to ensure that you're defining the appropriate gender property
(isHim = true or isHer = true) for your Actor and movableActor
objects, so that messages using the format strings display the correct
pronouns.
- The text of system error messages 1021 ("index value too low") and
1022 ("index value too high") have been modified slightly so that
they don't use ">" or "<" characters. (When running in HTML mode,
the "<" and ">" characters in these messages caused problems, because
the renderer tried to parse them as HTML tags.)
- In the past, when the player's command contained a quoted string,
the value that the parser passed to preparseCmd() did not contain
a usable form of the string. This has been corrected; the value
that the parser passes to preparseCmd() will now always be the
original string text, enclosed in double quote marks. You can
therefore detect a string token in the preparseCmd() list by
checking to see if the first character (obtained with substr())
is a double quote mark, '"':
if (substr(lst[i], 1, 1) = '"')
/* this token is a quoted string */ ;
- In adv.t, the implementation of clothingItem has been modified
slightly, to correct some bugs and make some improvements.
- An actor can now attempt to wear objects that aren't being
carried; the game will now automatically attempt to take the item
(via a recursive "take" command using execCommand) before wearing
it if the actor isn't carrying it.
- The game will also automatically take an object if it's within
another object in the actor's inventory; this ensures that an
object is always at the top level of an actor's inventory while
being worn.
- When doffing a clothingItem, the game first checks to ensure that
the actor's maximum "bulk" carrying capacity is not exceeded; this
check must be made because an item being worn doesn't encumber an
actor with bulk since it's not being carried by hand. If the
maximum bulk would be exceeded, the game will not let the actor
doff the item.
- The verDoWear method now checks to make sure that another actor
isn't wearing the item; in the past, if the player attempted to
wear an item already being worn by another actor, the game replied
with the nonsensical message "you're already wearing that."
------------------------------------------------------------------------------
2.3.0 02/01/1999
- A new systemInfo() feature code lets the game determine whether
the interpreter is in HTML mode or plain text mode. The new code
is __SYSINFO_HTML_MODE; systemInfo(__SYSINFO_HTML_MODE) returns
true if the interpreter is currently interpreting HTML markups,
nil if not. Note that this new code has nothing to do with whether
the interpreter is a full multimedia system (such as HTML TADS) or
a text-only system (such as the DOS "TR" interpreters); this new
code instead indicates only whether or not a "\H+" sequence is
currently in effect.
- Several new systemInfo() feature codes have been added to provide
information on MPEG audio support:
__SYSINFO_MPEG_AUDIO - this returns 1 if MPEG 2.0 audio support
of any kind is present, 0 if not. It is possible that some
systems may support some types of MPEG audio, but not all
three layers. This feature code indicates whether MPEG
audio of any kind is supported; the specific layer codes
below can be used to check for each individual layer.
__SYSINFO_MPEG_AUDIO_1 - 1 if MPEG 2.0 layer I is supported
__SYSINFO_MPEG_AUDIO_2 - 1 if MPEG 2.0 layer II is supported
__SYSINFO_MPEG_AUDIO_3 - 1 if MPEG 2.0 layer III is supported
- To accomodate the new TADS-Input font feature in HTML TADS, std.t
now has a definition of the commandPrompt and commandAfterRead
functions that automatically switch to the TADS-Input font. This
is important only for HTML-enabled games. If your game uses HTML
features, you should #define USE_HTML_PROMPT before including std.t
in order to use these new functions. (If you're providing your own
definitions of these functions, you should consider adding the font
settings made in the new std.t versions.)
- New feature: TADS now has a built-in regular expression matching
facility. Regular expressions provide a powerful and simple way
to search for a complex pattern within a text string. This feature
is particularly useful for writing preparse() and preparseCmd()
functions, since it allows you to perform complex pattern matching
and replacing with very little code.
The new function reSearch() searches for the first occurrence of a
regular expression pattern within a string. It returns nil if the
pattern is not found. If the pattern is found, the function returns
a list, the first element of which is a number giving the character
position within the string of the start of the match (the first
character is at position 1), the second element giving the number
of characters in the match, and the third element a string giving
the actual text of the match.
ret := reSearch(pattern, string_to_search);
The pattern is specified using regular expression syntax similar to
that used by "grep" and other similar utilities. Here are the basic
building blocks of the regular expression syntax:
| Alternation: matches the expression on the left or the
expression on the right. This operator affects as many
characters as it can, out to the nearest parentheses.
( ) Groups an expression.
+ Indicates that the immediately preceding character or
parenthesized expression repeats one or more times.
* Indicates that the immediately preceding character or
parenthesized expression repeats zero or more times.
? Indicates that the immediately preceding character or
parenthesized expression can occur zero or one time.
. (a period) Wildcard: matches any single character.
^ Matches the beginning of the string.
$ Matches the end of the string.
% Quotes the following character, removing the special
meaning of these characters: | . ( ) * ? + ^ $ % [
Also introduces the special sequences listed later.
[ ] Indicates a character list or range expression. Matches
any one of the listed characters. A range can be specified
by following a character with '-' and another character;
this matches all of the characters between and including
these two characters. For example, [a-z] matches any
one lower-case letter, and [0-9] matches any one digit.
Ranges and single characters can be combined; for example,
[a-zA-Z] matches any letter, upper- or lower-case. To
include the character ']' in a list, make it the first
character after the opening bracket; to include '-', make
it the next character after that. For example, []] matches
just ']', [-] matches just '-', and []-] matches '-' and ']'.
[^ ] Exclusionary character list or range. This matches any
character *except* the ones listed. For example, [^0-9]
matches anything single character except a digit.
%1 This matches the same text that matched the first parenthesized
expression. For example, consider the pattern '(a*).*%1'.
The string 'aaabbbaaa' will match, because the first three
characters match the parenthesized 'a*' expression, which
causes '%1' to match the last three characters; the middle
three characters are matched by the '.*' expression.
%2 Matches the text matching the second parenthesized expression.
And so on through...
%9 Matches the text matching the ninth parenthesized expression.
%< Matches at the beginning of a word. Words are considered to
be contiguous groups of letters and numbers.
%> Matches at the end of a word. For example, '%<and%>' matches
the "and" in 'ball and box' and 'and then', but not in
'rubber band' or 'the android'. Note that %< and %> do not
actually contribute any characters to the match - they simply
ensure that they fall on a word boundary. So, searching for
'%<and%>' in 'ball and box' matches the string 'and' -- the
spaces are not included in the match.
%w Matches any word character (a letter or a digit).
%W Matches any non-word character (anything but a letter or digit).
%b Matches at any word boundary (beginning or end of a word).
%B Matches except at a word boundary.
Any character other than those listed above simply matches the exact
same character. For example, 'a' matches 'a'.
Here are some examples of simple regular expressions, to help clarify
the meanings of the basic building blocks:
abc|def either 'abc' or 'def'
(abc) 'abc'
abc+ 'abc', 'abcc', 'abccc', etc.
abc* 'ab', 'abc', 'abcc', 'abccc', etc.
abc? 'ab' or 'abc'
. any single character
^abc 'abc', but only at the start of the string
abc$ 'abc', but only at the end of the string
%^abc literally '^abc'
[abcx-z] 'a', 'b', 'c', 'x', 'y', or 'z'
[]-] ']' or '-'
[^abcx-z] any character except 'a', 'b', 'c', 'x', 'y', or 'z'
[^]-q] any character except ']', '-', or 'q'
Here are some more complicated examples:
(%([0-9][0-9][0-9]%) *)?[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]
This matches a North American-style telephone number, either with
or without an area code in parentheses. If an area code is
present, it can optionally be separated by spaces from the rest
of the number: '(415)555-1212', '555-1212', '(415) 555-1212'.
[-+]?([0-9]+%.?|([0-9]*)%.[0-9]+)([eE][-+]?[0-9]+)?
This matches a floating-point number in the notation used by C and
some other programming languages: either a string of digits optionally
ending with a decimal point, or zero or more digits followed by a
decimal point followed by one or more digits; optionally followed
by an exponent specified with the letter "E" (upper- or lower-case),
an optional sign ('+' or '-'), and one or more digits; all of this
can be preceded by an optional sign. This matches: '3e9', '.5e+10',
'+100', '-100.', '100.0', '-5e-9', '-23.e+50'.
^ *tell%>(.*)%<to%>(.*)
This matches the word "tell" at the beginning of the string,
preceded only by zero or more spaces, followed by any text, followed
by the word "to", followed by any more text. This matches
'tell bob to go north' and 'tell teeterwaller to give me the mask'.
Here's a code example:
ret := reSearch('d.*h', 'abcdefghi');
if (ret = nil)
"No match.";
else
"Start = <<ret[1]>>, length = <<ret[2]>>, text = \"<<ret[3]>>\". ";
When run, this code will display the following:
Start = 4, length = 5, text = "defgh".
- Another new built-in function, reGetGroup(), lets you retrieve the
matching text for parenthesized groups within regular expressions.
reGetGroup() returns information about the last call to reSearch().
The function takes one argument, which is a number giving the group
number to retrieve: the first parenthesized expression is group 1,
the second is group 2, and so on. (When groups are nested, the
position of the open parenthesis is used to determine the group
numbering. The leftmost open parenthesis is numbered as group 1.)
reGetGroup() returns nil if there is no such group in the most
recent regular expression or if the last call to reSearch() did not
match the expression. Otherwise, reGetGroup() returns a list with
three elements identifying the group. The first element is a
number giving the character position within the original search
string of the start of the text that matched the group. The second
element is the length of the text that matched the group. The third
element is the actual text that matched the group.
Here's a code example:
ret := reSearch('d(.*)h', 'abcdefghi');
if (ret != nil)
{
grp := reGetGroup(1);
if (grp != nil)
"Start = <<grp[1]>>, len = <<grp[2]>>, text = \"<<grp[3]>>\". ";
}
This will display the following:
Start = 5, len = 3, text = "efg".
You can use regular expression grouping to carry out complicated
transformations on strings with relatively little code. Since you
determine exactly where in a string a particular group in a regular
expression occurs, you can take the group text out of the string
and put it back into the string in a different order or with other
changes.
For example, suppose you want to write a preparse() function that
finds sentences of the form "tell <actor> to <command>" and converts
them to the normal TADS actor command format, "<actor>, <command>".
You can use regular expression grouping to find this pattern of text
and build the new command from pieces of the original:
ret := reSearch('^ *tell%> *(.*)%<to%> *(.*)', cmd);
if (ret != nil)
cmd := reGetGroup(1)[3] + ', ' + reGetGroup(2)[3];
Or, suppose you have a telephone in your game, and you want to let
the player dial numbers on the phone using normal North American
telephone number notation, including an area code. The TADS parser
won't normally let you do this, since it would try to parse the
number as several words. You could solve this problem using preparse:
after the player enters a command, find anything that looks like a
telephone number, and enclose it in quotation marks; this will make
the parser treat the phone number as a quoted string, so you can
write your "dial" verb so that it uses strObj as the direct object.
Here's how you could write the preparse routine:
ret := reSearch('(%([0-9][0-9][0-9]%) *)?'
+ '[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]', cmd);
if (ret != nil)
cmd := substr(cmd, 1, ret[1] - 1) + ' "' + ret[3] + '" '
+ substr(cmd, ret[1] + ret[2], length(cmd));
- New feature: a new function extends the capabilities of the existing
input functions, input() and inputkey(), to give you more control
over event processing. The new function, inputevent(), can read
multiple types of events, and can also apply a timeout to limit the
how long it waits for an event to occur.
The inputevent() function takes zero or one argument. With no
arguments, inputevent() simply waits until an event occurs. With
one argument, which must be a number, inputevent() waits until an
event occurs, or until the number of milliseconds specified by the
argument has elapsed without an event occurring, in which case the
function "times out" and returns without any event having occurred.
Note that the timeout value, if given, may not always be obeyed
to the exact millisecond. Different types of computers have
different system clock resolutions; in addition, multi-user and
multi-tasking systems often have unpredictable latencies for event
processing. As a result, if you specify a timeout value, the actual
time that elapses before the function times out and returns may be
slightly longer than the specified timeout value. Any additional
latency should be no more than a few hundred milliseconds in most
cases, so this shouldn't be noticeable for most purposes.
The function returns a list value describing the event that occurred.
The first element of the list is a number that specifies the type of
the event. The rest of the list varies according to the event type.
Constants for the event codes are defined in adv.t. The possible
event codes are:
INPUT_EVENT_KEY - the user pressed a key. The second element of
the list returned by inputevent() in this case is a string containing
the key that the user pressed. The string is the same that would be
returned by inputkey() for the same keystroke.
INPUT_EVENT_HREF - the user clicked on an <A HREF=xxx> link. This
event is only returned by an HTML TADS interpreter, never by a
character-mode TADS interpreter. The second element of the return
list is a string containing the text of the HREF that the user
clicked.
INPUT_EVENT_TIMEOUT - no event occurred before the specified timeout
elapsed. The return list contains no additional elements.
INPUT_EVENT_EOF - this indicates that the TADS interpreter is
terminating or an error occurred reading an event.
INPUT_EVENT_NOTIMEOUT - this is not actually an event, but an error
indicating that the current system does not support the timeout
feature of inputevent(). If this occurs, you can still use
inputevent(), but you cannot specify a timeout. The DOS TADS
interpreters (TR, TRX, TR32) all support timeouts, as does HTML TADS
for Windows; interpreters on most systems should be able to support
this feature, but a few systems may not be able to.
- New feature: the inputkey() function now returns a portable
representation for certain extended keys. In the past, keys outside
of the normal ASCII character set were almost impossible to use with
inputkey(), because TADS returned a useless "raw" format for extended
keys, such as cursor navigation keys and function keys. TADS now
uses a portable string format to represent many common keys.
Each extended key is represented as a string containing a key name
enclosed in square brackets. The key name are:
[bksp] - backspace (destructive backspace/delete left)
[up] - up arrow (cursor navigation key)
[down] - down arrow
[right] - right arrow
[left] - left arrow
[end] - "end" key
[home] - "home" key
[del] - delete character (the "del" key)
[page up] - "page up" key
[page down] - "page down" key
[f1] - function key F1
[f2] - F2
[f3] - F3
[f4] - F4
[f5] - F5
[f6] - F6
[f7] - F7
[f8] - F8
[f9] - F9
[f10] - F10
In addition, "control" keys (i.e., keys entered by holding down the
"control" or "ctrl" key on the keyboard and pressing an alphabetic
key) are returned as "[ctrl-X]", where "X" is the lower-case letter
key; and "alt" keys are returned as "[alt-X]". Finally, the "Return"
and "Enter" keys are returned as "\n", and the tab key is returned
as "\t".
Even though these key names are portable, be aware that not every
computer has all of these keys, so you can't count on the player
actually being able to enter them. The only keys that you can always
count on being present are the regular ASCII keys, Enter/Return, Tab,
and Backspace. So, if you're using these extended keys, you should
always be sure to provide an alternative for each extended key using
an ordinary key. For example, if you want to implement a menu system
that uses the up and down arrow keys to navigate through a set of
choices, you could use "N" (for "next") and "P" (for "previous") as
synonyms for [down] and [up], respectively.
The arrow keys ([up], [down], [left], and [right]) are probably the
most portable of the extended keys, since most computers and terminals
have some sort of arrow keys. The function keys ([f1] through [f10])
are also available on many systems, although some systems use some or
all of the function keys for special purposes; for example, Windows
uses the F10 key to begin navigating the menu bar, so your game will
never receive the [f10] extended key when running on Windows. The
ALT and CONTROL keys are also very non-portable.
Here's an example of using the arrow keys.
numberVerb: deepverb
verb = 'number'
action(actor) =
{
local num;
local done;
local changed;
"Press the Up or Down arrow keys, or the + or - keys,
to change the number. Press Enter when finished.\b";
num := 5;
changed := true;
for (done = nil ; !done ; )
{
if (changed)
{
"\nCurrent value = <<num>>";
changed := nil;
}
switch(inputkey())
{
case '\n':
done := true;
break;
case '+':
case '[up]':
++num;
changed := true;
break;
case '-':
case '[down]':
--num;
changed := true;
break;
}
}
"\bThe final value was <<num>>. ";
}
;
- New feature: the gettime() built-in function can now return additional
system real-time clock information. The function now optionally takes
an argument specifying what type of information to return. Constants
for the argument values are defined in adv.t:
GETTIME_DATE_AND_TIME - this returns the traditional date and time
information that gettime() returned in the past. This is the same
information that the function returns if called with no arguments
(thus ensuring that existing code that calls gettime() will continue
to work unchanged).
GETTIME_TICKS - this returns the number of milliseconds since an
arbitrary zero point, which is usually some system event, such as
starting the current session of the TADS interpreter, or turning
on the computer. The actual zero point is arbitrary, but it will
remain fixed for a particular session, so you can use this form
of gettime() to compute relative times between events over a short
period of time. For example, if you're reading events with the
new inputevent() function, you can use this time value to set a
limit on how long you read events. For example:
local max_time, cur_time, evt;
/* process events for no more than 5 seconds (5000 milliseconds) */
max_time := gettime(GETTIME_TICKS) + 5000;
for (;;)
{
/* check to see if we've reached our time limit */
cur_time := gettime(GETTIME_TICKS);
if (cur_time >= max_time)
break;
/* get events, but time out if we exceed our time limit */
evt := inputevent(max_time - cur_time);
/* process the event */
switch(evt[1])
// and so on
}
- New feature: a new built-in function lets you pause the game for a
specified interval. You can use this function in the middle of
displaying a long text passage, for example, to create a dramatic
pause effect, without making the user press a key to continue.
The new function is called timeDelay(), and takes a single argument
giving the number of milliseconds to pause the game. For example,
to pause the game for five seconds, use this:
timeDelay(5000);
- The player command parser now allows a preposition to be used when
answering a request for an indirect object. For example:
>unlock door
What do you want to unlock it with?
>with key
- The "oops" command (which lets the player correct a misspelled word in
the previous command) now accepts multiple words to substitute for a
misspelled word. This lets you correct an omitted space in a command:
>take redbox
I don't know the word "redbox".
>oops red box
Taken.
- New feature: the askfile() built-in function now takes two additional,
optional parameters that let you specify what type of prompt to show
and what type of file to request. These new arguments are hints to
the system-specific code that displays the "open file" dialog; by
specifying this new information, you help the system code show the
correct type of dialog.
The new askfile() syntax looks like this:
filename := askfile(prompt_text, prompt_type_code, file_type_code);
The prompt_type_code tells the open-file dialog whether you're
opening an existing file or saving a file. On some systems (Windows
and Macintosh included), the user interface uses one type of dialog
for opening an existing file, and a different type of dialog for
saving a file; you can use this parameter to select the appropriate
dialog type on systems that make this distinction. This parameter
can have one of the following values, defined in adv.t:
ASKFILE_PROMPT_OPEN - open an existing file for reading
ASKFILE_PROMPT_SAVE - open a file for saving information
On some systems, the open-file dialog will filter the files it
displays so that the player only sees files of the particular type
being requested. The file_type_code parameter lets you specify
the type of file you're interested in, so that the dialog can use
the appropriate filtering on systems that support this. The
file_type_code can be one of the following values, defined in adv.t:
FILE_TYPE_GAME - a game data file (.gam)
FILE_TYPE_SAVE - a saved game (.sav)
FILE_TYPE_LOG - a transcript (log) file
FILE_TYPE_DATA - general data file (used for fopen())
FILE_TYPE_CMD - command input file
FILE_TYPE_TEXT - text file
FILE_TYPE_BIN - binary data file
FILE_TYPE_UNKNOWN - unknown file type
adv.t and std.t have been updated to specify these new arguments in
all of their calls to askfile().
If you leave out the new arguments in a call to askfile(), the
function will behave as it did in the past. This means that your
prompt string must contain the word "save" or "write" in order to
show a "save file" dialog rather than an "open file" dialog on
those systems that differentiate between these dialog types.
- Fixed a bug with the parserSetMe() function: on restart(), the system
did not correctly restore the current "Me" object to the original
object. This works properly now.
- Fixed a player command parser bug that caused problems with numbers
in commands under certain circumstances. In particular, if an object
happened to use a number as an adjective, and the player typed a
command involving the same number, but to refer to the number itself
rather than to the object with the numeric adjective, the parser
incorrectly assumed that the player meant to use the object rather
than the number. Consider this example:
>i
You are carrying Chapter 3.
>turn dial to 3
Previously, the parser interpreted the "3" as referring to the
"chapter 3" object. This problem has been corrected; now, the
parser will still try the "chapter 3" object first, but when the
parser finds that the command fails the verification pass (because
verIoTurn or verDoTurn display an error), and the original word
used in the command was simply a number, the parser will assume
that the player means to refer to the number itself rather than
any object that has an adjective matching the number.
- Fixed a bug in adv.t that caused misleading messages when the player
was in a chairItem and tried to refer to something in the enclosing
room when the chairItem's reachable list did not include items in
the enclosing room. In this case, adv.t displayed the message "I
don't see that here," which was clearly incorrect in that the objects
in a chairItem's enclosing room are generally visible even when not
reachable.
To correct this, the room object in adv.t now calls a new method
from its cantReach() method when the actor is not directly in the
room. The new method is cantReachRoom(); room.cantReach() calls
actor.location.cantReachRoom(self) to display an appropriate
message. In other words, if the actor is in a chair within a room,
adv.t will call the chair's cantReachRoom() method with the chair's
enclosing room as the argument.
adv.t defines a default cantReachRoom() method for the classes thing,
room, and chairItem. These default methods all generate similar
messages saying roughly "You can't reach that from here."
Note that the new cantReachRoom() method provides new versatility for
situations where objects are visible from a location but not reachable.
For example, if you want to create two rooms separated by a glass
partition, so that objects in one room are visible from the other
room but not reachable, you can override isVisible and getVisibleList
for the rooms to make the contents of the other room visible from
each room, and then add a cantReachRoom(otherRoom) method like this:
cantReachRoom(otherRoom) =
{
if (otherRoom = eastSideOfGlassRoom)
"That's on the other side of the glass partition. ";
else
inherited.cantReachRoom(otherRoom);
}
- Fixed a bug in the player command parser that caused the parser to
repeatedly say "I don't know the word <word>" when an invalid word
was entered in a command *and* the preparseCmd function modified
the command by returning a list value. In this situation, the
parser would not stop saying "I don't know the word <word>" until
the player entered an "oops" command to fix the misspelled word.
This no longer occurs.
- The parser responded incorrectly to unknown words entered in
response to a disambiguation question ("which box do you mean, the
red box, or the blue box?"). If the player entered an unknown word
in response to such a question, the parser responded by asking the
same question over again, without mentioning that the word was
unknown. The parser now correctly mentions the unknown word.
Note that this change removes a certain amount of control from the
game's processing of unknown words through parseUnknownXobj, since
these methods are not called when unknown words are entered in
response to disambiguation questions.
- Fixed a parser bug that caused errors for certain valid commands
involving an indirect object that used an adjective that also was
used as a preposition. For example, if the game had a "south wall"
object, the parser did not correct interpret the command "put box
on south wall" (the response was "I don't understand that sentence").
This has been corrected.
- Numerous changes in adv.t make the default messages more consistent
at using parameterized pronouns and verbs to refer to objects. These
changes generally make the default messages agree more automatically
in number and gender with the objects described. The parameterized
messages are mostly keyed on the isThem property; if you have an
object that's described by a plural noun phrase ("the pants" or
"some marbles," for example), setting isThem = true in the object
will help adv.t generate the correct default messgaes when referring
to the object ("you can't put the marbles in themselves," for example).
Many thanks to Stephen Granade for his extensive work to fix these.
- adv.t is now somewhat more consistent in its handling of reachability
from nested rooms. In the past, chairItem objects and other nestedroom
objects had a strange difference: from a normal nestedroom, you could
reach anything in the nestedroom's "reachable" list, plus anything in
any open containers in the list, to any nesting level; in contrast,
from a chairItem, you could only reach the objects directly in the
chairItem's "reachable" list.
For compatibility with past versions, this behavior hasn't changed.
However, it's now simple to make the behavior the same for both types
of rooms. If you want chairItem objects to behave the same way that
all other nestedroom objects behave, simply modify chairItem and set
its "canReachContents" property to true:
modify chairItem
canReachContents = true
;
This is probably the most logical behavior, so authors of new games
may want to consider including this change. However, the default
remains for chairItem objects to limit reachability to the items
directly in the "reachable" list with no access to their contents,
so that any existing games that depended (intentionally or otherwise)
on the old behavior are not broken by an incompatible change.
- Fixed a bug that caused a spurious error (TADS-601, "error writing
to game file") when attempting to create a pre-compiled header file,
when a #define symbol was defined to an empty string. This problem
has been corrected.
- The compiler went into an infinite loop reporting an error
(TADS-353, "'local' is only allowed at the beginning of a block")
when a misplaced "local" statement appeared. This is fixed; the
compiler now simply ignores the entire "local" statement and
continues attempting to parse the code.
- The random number generator caused a divide-by-zero error (which
could abruptly terminate the interpreter on some platforms) when
the game called rand(0) at any time after calling randomize().
This has been corrected; rand(0) now simply returns 0 without
causing a crash.
- In adv.t, the clothingItem code was written so that, if the player
attempted to wear something that another actor was wearing, the
game responded with "You're already wearing that," which clearly
isn't correct. The game now says "You don't have that," which makes
more sense.
------------------------------------------------------------------------------
2.2.6 09/30/98 bug fixes
- The release notes are now separated into a generic section and a
platform-specific section. This file (TADSVER.TXT) describes the
changes that apply to all types of computers and operating systems.
A separate file describes the changes specific to each platform;
for example, DOSVER.TXT describes the changes that apply only to
the MS-DOS and Windows versions of TADS.
- adv.t now has a "knock" verb (knock, knock on, and knock at are
all synonyms). The "doorway" class provides has a default doKnock
method that simply displays "there is no answer" in response to
the player knocking on the door.
- adv.t now has a "go through" verb. The "doorway" class provides a
default handler for "go through" that has the same effect as the
player traveling in the direction of the door.
- In adv.t, the "doorway" class now has a default handler for the
"enter" verb that has the same effect as the player traveling in
the direction of the door.
- In adv.t, the default message for eating a "thing" now correctly
incorporates the ordinality of the object, and the verb agrees
with the subject (so "the tire doesn't appear appetizing" whereas
"the keys don't appear appetizing").
- In the adv.t "doorway" class, the setIsopen method now calls
setIsopen on the object representing the other side of the door,
rather than updating its isopen property directly. In addition, the
code that automatically opens the door when the player attempts to
travel through the door calls setIsopen rather than updating the
isopen properties of the door object and the other side object
directly. These change make it easier to code a door that has
special behavior when opened or closed, since all changes to the
isopen property made by code in the doorway class now occur through
the setIsopen method, so all of the special behavior code can be
placed in an overridden setIsopen method.
- The player command parser did not correctly respond to disambiguation
questions (such as "which box do you mean, the green box, or the
red box?"). In some cases, when the player ignored the question and
simply typed a new command, the parser completely ignored the command
and show a new command prompt with no other comment; alternatively,
the parser responded to the new command as though it were an object,
showing a message such as "I don't see any open door here." In yet
other situations, when the player attempted to answer the question
with a more detailed object description, the parser would complain
that it didn't see such an object even though the additional player
input was correct. All of these problems have been corrected; the
parser should handle player responses to disambiguation questions
correctly now.
- A bug in the player command parser caused an infinite loop under
certain conditions. The loop occurred if one of an object's nouns
was a word that was also used as a verb, and the player typed a
command of the form "<verb> <noun> of <noun>", where <verb> is
the word used as both a noun and a verb. For example, the command
"discard ace of spades" caused the loop if "discard" was defined as
a verb and was also used as a noun for some object. This has been
corrected.
- The compiler did not correctly handle the error "else without if".
Rather than ignoring the offending "else" keyword, the compiler got
stuck in an infinite loop generating the same error message over and
over. This has been corrected; the compiler now only generates the
error message once per occurrence of the error in the source code.
- The compiler did not correctly handle the -case- option. Although
games compiled correctly, they did correctly handle verbs with objects
at run-time. This has been corrected.
- The compiler did not correctly interpret expressions of this form:
(x ? "first string" : "second string << 5 >> end of second string")
In particular, when a string containing an embedded expression was
used as the third operand of a ternary conditional operator (the "?:"
operator), the compiler interpreted the embedded expression and
everything that followed as an entirely separate expression, which
was incorrect. The compiler now handles these expressions correctly.
- The compiler did not correctly interpret a statement that consisted
entirely of a parenthesized expression when used as the "true" branch
of an "if" statement when an "else" clause was present:
if (expr)
(1+2+3);
else
...
The compiler incorrectly generated an "else without if" error in this
situation, because it incorrectly ignored the semicolon that followed
the statement. This has been corrected; the semicolon is now parsed
properly. Note that if your game inadvertantly exploited this error
by omitting semicolons in such cases, you will have to change your
source code; because of the obscurity of this case we consider it
extremely unlikely that anyone will be affected, but if you are, you
will receive "expected semicolon" messages at the erroneous lines of
code.
- A compiler bug caused crashes when using __LINE__ under certain
circumstances; this bug has been fixed.
- The interpreter now displays a separate error message (TADS-617) when
it attempts to load an external resource file (.RS0, etc) with an
invalid header. The interpreter previously displayed the message for
an invalid game file, which was not helpful in tracking down the
problem. The error message for TADS-617 displays the name of the
resource file causing the error, to help pinpoint the problem.
------------------------------------------------------------------------------
2.2.5 08/24/98 enhancements and bug fixes
- New parser feature: The parser now calls a new pair of methods on
the direct and indirect object in the course of processing each
command. The new methods are called dobjCheck and iobjCheck, and
are called with these parameters:
iobjCheck(actor, verb, dobj, prep)
dobjCheck(actor, verb, iobj, prep)
iobjCheck is called on the indirect object, if any, just before
iobjGen is or would be called. (iobjCheck is always called, whether
or not iobjGen is called.) Similarly, dobjCheck is called on the
direct object just before dobjGen is or would be called. Note that
the dobj, iobj, and prep parameters may be nil if the corresponding
objects are not present in the command.
These new methods are very similar to dobjGen and iobjGen, but have
one difference: the new methods are ALWAYS called. This differs from
dobjGen and iobjGen, which are called only when the object does not
define the corresponding verb handler method (do<Verb> or io<Verb>)
for the verb being used.
The new dobjCheck and iobjCheck methods are called immediately
BEFORE the corresponding dobjGen and iobjGen methods are called.
So, the new calling sequence looks like this:
actor.actorAction
actor.location.roomAction
*NEW* iobj.iobjCheck (if there's an indirect object)
iobj.iobjGen (if the indirect object doesn't define io<Verb>)
*NEW* dobj.dobjCheck (if there's a direct object)
dobj.dobjGen (if the direct object doesn't define do<Verb>)
and then on to the normal verIo<Verb> and/or verDo<Verb>, as
appropriate.
So, why have both xobjGen and xobjCheck? The reason is that each
is useful in its own way.
The xobjGen routines are meant as *default* handlers. They're
catch-all methods that handle any command that isn't specially
handled by the object. Because any normal verb handler (one of the
io<Verb> or do<Verb> routines) "overrides" the xobjGen routines, it's
not necessary to include a special check in the xobjGen method for
the verbs that the object handles in a non-default fashion.
The xobjCheck routines, on the other hand, are mandatory checks that
are applied to all verbs used with an object, whether or not the verb
is otherwise handled by the object. This makes these routines useful
in cases when the object's behavior can change, because you can perform
tests based on the object's state in a single place for all verbs,
rather than having to apply the same test in every verb handler.
- A bug in past versions handled doSynonym and ioSynonym incorrectly
when they were used in classes. Consider the following example:
class draggableItem: fixeditem
verDoPull(actor) = { }
doPull(actor) =
{
"You manage to drag <<self.thedesc>> along the ground
for a few feet. ";
// etc...
}
doSynonym('Pull') = 'Push' 'Move'
;
desk: draggableItem
// noun, location, sdesc, etc...
doPull(actor) =
{
"The desk scrapes along the floor for a few feet,
but everything falls off! ";
// etc...
}
;
Now, if the player typed PULL DESK, the result would be as you'd
expect: "the desk scrapes along the floor for a few feet, but
everything falls off!"
However, if the player typed PUSH DESK, you'd get the unexpected
reply "You manage to drag the desk along the ground for a few feet."
The problem was that the system was invoking the synonym routine for
the definition in the class defining the synonym, rather than properly
using the overriding method in the actual object used in the command.
This problem has been corrected.
- A player command parser bug allowed using AGAIN to repeat a command
directed to an actor after the actor had left the room (or otherwise
become inaccessible). The parser did not check to ensure that the
actor was still present for the repeated command. This has been
corrected; the parser now checks that the actor is still accessible
when using AGAIN.
- A bug in the player command parser, introduced with the parseUnknownXobj
feature added in version 2.2.4, caused the parser to fail to display
proper default prompts when the player entered a command with an
unknown word that also required an additional object (for example,
if the player typed "put foo", where "foo" was not a valid vocabulary
word). This has been corrected; the parser now simply reports the
unknown word in these cases.
- A bug in the compiler caused a subtle problem when 'modify' was
used to modify a class where the original class had vocabulary
defined, but the 'modify' did not add any new vocabulary. In such
cases, the vocabulary defined in the original class was lost, so
any objects derived from the class did not have the original class
vocabulary words. This problem has been corrected.
Several people encountered this problem when using 'modify' with
the 'basicMe' class defined in adv.t. In these cases, the bug
prevented the 'Me' object from inheriting the nouns from the
original 'basicMe' in adv.t, so at run-time a command such as
"look at me" would not work properly. (The work-around, which
was to add a noun='' definition to the modified object, is no
longer necessary now that the bug has been fixed, but should not
cause any problems.)
- When restoring a saved game directly from the command line when
starting the run-time, the run-time incorrectly invoked the init()
function before invoking the initRestore() function. Since the
initRestore() function is meant to be called *instead of* the init()
function in this situation, this was incorrect behavior. This
problem has been fixed; the run-time now invoked *either* init()
or initRestore(), but not both, depending on whether the game is
started normally or with a restored game on the command line. This
problem affected both the character-mode and HTML run-time versions.
- A bug in the character-mode run-time caused HTML markups to be
misinterpreted if closely preceded by a percent sign that wasn't
being used with a format string (such as "%You%"). In such cases,
the character-mode run-time sometimes displayed any tags that
closely followed the percent sign (within about forty characters
of output), rather than interpreting them and removing them from
the display as it normally would. This has been corrected. Note
that this problem affected only the character-mode run-time when
operating with an HTML-enabled game.
- The run-time did not correctly handle backslashes in text captured
by the outcapture() built-in function; extra backslashes were
unnecessarily (and incorrectly) added to escape backslashes. This
has been corrected.
- The run-time was incompatible with games compiled with version of
the compiler before 2.2.0. This incompatibility normally resulted
in the game ignoring most commands. The problem has been corrected,
so old games should work correctly again.
- The character-mode run-time now recognizes the <TITLE> tag when the
game is operating in HTML ("\H+") mode. The character-mode version
simply suppresses all text displayed between the <TITLE> and
corresponding </TITLE> tags.
- The character-mode run-time now allows hexadecimal values to be
specified in numeric entity markups ("&#nnn"). This change is for
compatibility with the HTML TADS run-time, which allows such markups.
- The character-mode run-time did not always break lines correctly in
text that switched in and out of HTML mode (using \H+ and \H-). This
has been corrected. Note that this problem only affected the
character-mode run-time (TR32), not the HTML run-time.
- The dialItem object in adv.t now has a minsetting property, which
specifies the minimum setting for the dial; the default value is 1.
------------------------------------------------------------------------------
2.2.4 07/20/98 enhancements and fixes
- The TADS language has a new construct that allows you to write a method
so that it inherits explicitly from a particular superclass. This new
syntax adds to the existing 'inherited' syntax, which allowed you to
inherit from the superclass according to rules that TADS uses to resolve
the superclass that a particular method overrides.
The new syntax lets you specify the name of the superclass after the
'inherited' keyword, but is otherwise similar to the normal 'inherited'
syntax:
inherited fixeditem.doTake(actor);
This specifies that you want the method to inherit the doTake
implementation from the fixeditem superclass, regardless of whether
TADS might normally have chosen another superclass as the overridden
method. This is useful for situations involving multiple inheritance
where you want more control over which of the base classes of an object
should provide a particular behavior for the subclass.
- adv.t now includes all of the bugs fixes that Stephen Granade assembled
in his BUGS.T file. If you've been using BUGS.T, you should be able to
get the same effect by compiling with adv.t alone now. I'd like to thank
Stephen for creating this excellent improvement to the original adv.t.
- The parser now provides a way to change the player character dynamically
during game play.
In the past, the parser used the object named "Me" to represent the
player character; there was no way to change this. This made it
difficult to write a game with different personas for the player
character.
The parser still uses "Me" as the initial player character, but you
can now switch the player character to a different object at any
time using the new built-in function parserSetMe(newMe). The argument
to parserSetMe() is the new object to use to represent the player
character.
Another new built-in function, parserGetMe(), allows you to get the
parser's current player character object. This function takes no
arguments.
Note that adv.t and std.t no longer refer to the "Me" object directly
in code related to the player character's status (such as inventory
or room location descriptions). Instead, adv.t and std.t use the new
parserGetMe() function to get the player character object.
If you use parserSetMe() in your game, you should be careful not to
refer to the "Me" object directly in contexts where you really want
the current player character object; use parserGetMe() instead to get
the correct object from the parser. Note that existing games should
not be affected by this change; if you don't call parserSetMe(), then
parserGetMe() will always return the "Me" object, so you can safely
use a fixed "Me" object.
IMPORTANT NOTE: this feature may be somewhat incomplete, in that
additional adv.t support may be needed for some games to take full
advantage of this feature. If you try using this feature in your game
and you experience any problems or have suggestions on adv.t changes
that you would help you use this feature, please contact TADS's author
by email at
[email protected].
- The compiler is now more tolerant of non-standard newline conventions
used in source files. MS-DOS, Macintosh, and Unix each have different,
incompatible conventions for how text files represent line endings
(and it's likely that there are a few other operating systems with
even more different conventions). When copying a file from one
type of computer to another, the newline conventions aren't always
correctly translated. In the past, the TADS compiler was sometimes
unable to process a file that did not use the correct newline
conventions for the machine running the compiler. The compiler can
now accept most combinations of line ending conventions on any
platform that runs the compiler.
- The player can now specify a saved game to restore directly from the
run-time command line. Use the new -r argument to specify the name
of the saved game to be restored:
tr -r mygame.sav
Note that the full filename must be provided; no default suffix is
applied to the -r argument.
TADS now stores the name of the .GAM file in each saved game file,
and uses this information with the -r option. If you start the
run-time, and you specify a saved game file to restore using -r but
do not specify a game file, the TADS run-time attempts to load the
game file named in the saved game file. You can always specify the
name of the game file explicitly, in which case TADS will ignore the
game file name stored in the save file:
tr -r mygame.sav deep.gam
When you use the -r option, TADS loads the game, and then immediately
restores the save file.
Game authors: note that you can customize the way this new feature
works by using the initRestore function; see below for details.
- The system now calls a new optional game-defined function named
initRestore() if the player specifies a game to restore on the
run-time command line. initRestore() is called with a single-quoted
string argument giving the name of the saved game file to be restored.
If initRestore() is defined, and the player restores a game using
the -r option, the system will *not* call your game's init() function.
The reason that TADS skips init() in this case is that the player
will normally want to skip your game's introductory text when jumping
directly to a saved position at the start of the game. (Note, however,
that your preinit() function will always run as usual.)
If initRestore() is not defined in your game, TADS will simply call
the init() function as usual, and will then restore the game itself.
This provides compatibility with older games, although it may result
in a confusing initial display for the player, because TADS will not
be able to show the current location immediately after restoring the
game. If possible, you should define initRestore() in your game.
std.t provides a default implementation of initRestore() that simply
restores the game, and then shows a full description of the current
location. If you use std.t in your game, you should be aware that
your init() function will not be called when the player restores a
game explicitly from the run-time command line, and make any
necessary adjustments to your init() routine and the initRestore()
routine defined in std.t.
If you have code in your init() function that sets up any variables,
or if you enter HTML mode in your init() function, you should be sure
to call the same code from your initRestore() function, because init()
will not be called in this case. The best way to structure your
initialization code is to break out the common initialization code
into a separate function, and call this function from your normal
init() routine as well as your initRestore() routine:
initCommon: function
{
/* set up HTML mode */
"\H+";
"<body bgcolor=purple text=fuchsia>";
}
init: function
{
/* perform common initialization */
initCommon();
/* display opening text messages */
"A long time ago in a cavern far, far away...";
}
initRestore: function(fname)
{
/* perform common initialization */
initCommon();
/* restore the game */
mainRestore(fname);
}
- To facilitate the new initRestore function, adv.t now isolates the
game restoration code that was previously in the restoreVerb object
in the new mainRestore() function. You can call this function from
your own initRestore() routine, if you want the normal game restore
functionality. mainRestore() takes a single argument, which is a
single-quoted string giving the name of the save file to restore.
mainRestore() restores the saved game, then updates the status line
and displays a full description of the current location.
- Just as the system calls the game hook function commandPrompt prior
to reading a command, the system now calls a new game hook function,
commandAfterRead, immediately after reading a player command. The
new function looks like this:
commandAfterRead: function(code)
where 'code' is the same prompt type code that is passed to the
corresponding call to commandPrompt. This new function is intended
to make it easier to customize certain aspects of your game's user
interface. In particular, if you're using special formatting for
the player command in the HTML run-time (for example, you want to
use a special typeface or font color for player commands), you can
use commandAfterRead to turn off the special formatting you turned
on in commandPrompt. Each call to commandPrompt is matched by an
equivalent call to commandAfterRead, so you can always count on
being able to undo any formatting changes you make in commandPrompt
by placing the matching formatting commands in commandAfterRead.
- A new parser hook provides you with more flexibility in determining
how unknown words in a player command are handled. The new parser
hook operates during noun phrase resolution (this is the process
by which the parser attempts to determine what object should be used
for each noun phrase entered by the player in a command).
In the past, the parser detected unknown words (i.e., words not
defined as vocabulary properties, such as 'noun' or 'adjective',
somewhere in the game program) during the initial dictionary lookup
step of command processing. Upon encountering an unknown word at
this step, the parser simply reported the error ("I don't know the
word <word>") and aborted the command.
The parser now marks unknown words found during the dictionary lookup
step, but doesn't generate an error at this step. Instead, it
tentatively considers such words to be parts of noun phrases, and
continues parsing. The parser then attempts to determine the verb
and general structure of the command as normal; if this fails, the
parser goes back and reports the unknown word as it used to. However,
if the parser is able to find a valid sentence structure, it continues
to the next step, which is noun phrase resolution.
Note that the parser considers unknown words to have a completely
neutral part of speech, which means that unknown words are merged with
any known nouns and adjectives to which they are adjacent to form the
noun phrase. Since the parser doesn't know the word, it can't decide
whether to treat it as a noun or adjective, so it simply considers it
to be neutral and allows it to combine with whatever other words are
present.
Noun phrase resolution proceeds as normal until the parser once again
encounters one of the unknown words. So, the parser will call the
verification (verDoXxx) and validation (validDo, validDoList)
routines as usual for any noun phrases containing recognized words,
in the usual order (for most verbs, this means that the indirect
object is resolved first, then the direct objects).
Upon encountering an unknown word, however, the parser checks to see
if your game program defines the new parser hook. The new hook is a
method on the deepverb object called parseUnknownDobj (to resolve
direct objects) or parseUnknownIobj (to resolve indirect objects).
The methods are called with these parameters:
parseUnknownDobj(actor, prep, iobj, wordlist)
parseUnknownIobj(actor, prep, dobj, wordlist)
In both cases, 'actor' is the actor object to which the command is
directed; 'prep' is the preposition that introduces the indirect
object, or nil if there is no preposition; and 'wordlist' is a list
of single-quoted strings containing all of the words -- both known
and unknown -- making up the noun phrase that contains the unknown
word or words.
The 'iobj' argument in parseUnknownDobj is the indirect object, if
present and known; similarly, the 'dobj' argument in parseUnknownIobj
is the direct object, if present and known. The 'iobj' or 'dobj'
argument will be nil if there was no other object in the command, or
if that object has not been resolved by the time this method is called.
When the indirect object is resolved first, as it is with most verbs,
parseUnknownIobj will always receive nil for the direct object, since
the direct object cannot be resolved until the indirect object is
resolved, which is what parseUnknownIobj is doing.
These routines can return the following values:
nil - This indicates that the routine did not successfully resolve
the object, and the system should use the default handling. The
parser reports the unknown word error as usual.
true - This indicates that the routine has fully processed the
command for this noun phrase, and no further parser action is
necessary. The parser simply continues processing any other
objects in the command, but treats this noun phrase as having
no corresponding objects in the command and does no further
processing on it.
object - The routines can return an object value, which the parser
treats as the resolution of the noun phrase. The parser proceeds
to apply all normal processing to the object as though the parser
had found an appropriate set of dictionary words for the noun
phrase matching the returned object. So, all of the normal
verification, validation, and action routines are called for the
object.
list - The routines can return a list containing object values.
The parser uses *all* of the objects in the list as the resolution
of the noun phrase, and applies all of the normal processing to
each object in the list. The effect is the same as if the user
had entered the objects in the command separated by commas.
If your game doesn't define this new method for the verb, the parser
simply uses the default handling, and reports the unknown word error.
Here's a simple example that changes the ASK ABOUT command so that
we always let the actor respond, even if a word that the player is
asking about isn't defined anywhere in the game. To accomplish this,
we add a parseUnknownIobj to askVerb. This routine will return a
special object, unknownAskIobj, that we define solely as a placeholder
for ASK ABOUT with an unknown word. We'll set unknownAskIobj's
wordlist property to the list of unknown words, and the object uses
the list of words to construct its sdesc.
#pragma C-
/*
* Special object that we use as the indirect object of any
* ASK ABOUT command that refers to unknown words
*/
unknownAskIobj: thing
wordlist = []
sdesc =
{
local i;
for (i := 1 ; i <= length(wordlist) ; ++i)
{
if (i != 1)
" ";
say(self.wordlist[i]);
}
}
;
/*
* For "ask about," use special handling for unknown words so
* that the actor can respond directly to the unknown words.
*/
modify askVerb
parseUnknownIobj(actor, prep, dobj, words) =
{
/* if we're asking about something, have the actor respond */
if (prep = aboutPrep)
{
/* use our special ASK ABOUT object for the unknown words */
unknownAskIobj.wordlist := words;
return unknownAskIobj;
}
else
{
/*
* it's not ASK ABOUT, return nil to use the
* default system handling
*/
return nil;
}
}
;
- The compiler can now capture all of the strings used in a game to a
text file. This can be useful for spell-checking your game, since
it gives you a listing of all of the text in the game, separated
from your source code. To capture strings during compilation, use
the -Fs option to specify the output file:
tc -Fs mygame.lis mygame.t
This compiles mygame.t, producing mygame.gam as usual, and writes
all of the strings in the source code to the file mygame.lis.
- The character-mode run-time now provides improved, but still limited,
HTML support. As in version 2.2.3, after you display an "\H+"
sequence, the character-mode run-time becomes sensitive to HTML tag
and character markup sequences. However, whereas version 2.2.3
ignored all tags and ampersand sequences, the new version now obeys
certain markups. In particular, the character-mode run-time will now
process the following tags:
<br> ends the current line; additional <br>'s display blank lines.
The HEIGHT attribute is accepted, and produces results that
are consistent with the graphical handling.
<p> displays a blank line.
</p> displays a blank line
<b> and <em> start boldface mode.
</b> and </em> end boldface mode.
<tab> indents to the next tab stop (exactly like "\t")
<img> and <sound> accept the ALT attribute, and display the text
of the ALT attribute value in place of the image or sound.
No other decoration is added; the ALT value is simply included
in the text display as though it had appeared as ordinary text.
<hr> starts a new line, displays a line of dashes, and starts
another new line
The character-mode run-time ignores all other tags. As before,
unrecognized tags are simply removed from the text entirely.
In addition, the ampersand character-code markups are now supported.
Since the DOS character set does not contain all of the characters in
the HTML character set (ISO Latin 1), some ampersand markups are
displayed as blanks, and others are displayed as approximations.
The following characters are displayed correctly:
&endash;
&emdash;
¡
¢
£
¥
¦
ª
«
¬
°
±
²
µ
·
º
»
¼
½
¿
Ä
Å
Æ
Ç
É
Ñ
Ö
Ü
à
á
â
ä
å
æ
ç
è
é
ê
ë
ì
í
î
ï
ñ
ò
ó
ô
ö
÷
ù
ú
û
ü
ÿ
The following are displayed using approximations:
„ is displayed as a normal double-quote
‹ is displayed as '<'
‘ is displayed as a normal single-quote
’ is displayed as a normal single-quote
“ is displayed as a normal double-quote
” is displayed as a normal double-quote
› is displayed as '>'
­ is displayed as '-'
´ is displayed as a normal single-quote
¸ is displayed as a comma
× is displayed as an 'x'
‚ is displayed as a normal single-quote
ß is displayed as a "beta"
™ is displayed as "(tm)"
© is displayed as "(c)"
The following are displayed using the corresponding unaccented
character, since the accented version of the character is not in
the DOS character set:
Ÿ
À
Á
Â
Ã
È
Ê
Ë
Ì
Í
Î
Ï
Ò
Ó
Ô
Õ
Ø
Ù
Ú
Û
Ý
ã
õ
ø
ý
The following are not supported at all, since the DOS character set
has no equivalents for these characters. Each of these markups is
rendered as a single space.
†
‰
Œ
œ
¤
§
¨
®
¯
³
¶
¹
¾
Ð
Þ
ð
þ
- The new built-in function morePrompt() allows your game to explicitly
display the system MORE prompt. You can use this for such special
effects as a dramatic pause prior to a chapter change. The new
function takes no arguments and returns no value.
- The built-in file manipulation functions now allow you to read and
write text-mode files. Files written in text mode can be used by
other applications as ordinary text files.
To use text-mode files, you must specify the new 't' file mode
suffix in fopen (for symmetry, a new 'b' mode suffix, for binary
mode files, is also allowed, but for compatibility with past
versions, binary mode is the default if no mode suffix is
specified). You can use the 't' suffix with 'r' (read) and 'w'
(write) modes; 't' is not currently allowed with 'r+' or 'w+'
modes.
When a file is opened in text mode, fwrite() can only be used with
string values. Strings passed to fwrite() can contain the escape
characters '\t', '\n', and '\\'; other escapes are not allowed.
'\t' is translated to a tab, '\n' is translated to a newline (using
the appropriate local conventions for the current system), and '\\'
is translated to a single backslash. fwrite() does NOT add any
newlines to the text you provide, so you must explicitly include
any newlines you want to write to the file.
Because TADS obeys local newline conventions, fwrite() always
produces the correct sequence of characters for the current machine
when you include '\n' in a string, so you don't have to worry about
how newlines are handled on each platform.
fread() always reads a line of text from the file. If the end of
the file is not reached, the line returned will end with a '\n'
sequence (as with fwrite(), fread() translates newlines according
to local conventions, and always returns the TADS '\n' sequence to
represent a newline in the file). If fread() encounters the end
of the file in the middle of a line, it will return the text up
to the end of the file, with no trailing newline. The subsequent
call will return nil to indicate that the end of the file has been
reached.
- Because TADS now has several ways of reading and writing files,
some game players may be uncomfortable about the possibility that
a malicious game author could harm their systems by writing a game
that, for example, modifies their AUTOEXEC.BAT files. To address
any concerns that players may have, TADS now provides a "file safety
level" setting. This is an optional setting that allows the player
to control the amount of access that a game has to files on the
system at run-time.
The file safety level is set through a command line option (note
that HTML TADS also provides this setting through the "Preferences"
dialog). Use the new -s option to specify one of the possible
safety levels:
-s0 (default) minimum safety - read and write in any directory
-s1 read in any directory, write in current directory
-s2 read-only access in any directory
-s3 read-only access in current directory only
-s4 maximum safety - no file I/O allowed
If the game attempts a file operation that is not allowed by the
current safety level, the fopen() function returns nil to indicate
that the file open failed.
These options affect only explicit file I/O operations performed by
the game. Operations handled by the system, such as saving and
restoring games and logging a transcript to a file, are not affected
by the file safety level setting.
- The TADS resource manager (TADSRSC) now supports building external
resource files for HTML TADS games. Refer to the resource file
documentation (RES.HTM) that accompanies the HTML TADS distribution
for details.
- The new TADS built-in function systemInfo() allows your game to
determine programmatically whether the TADS run-time that is
currently executing the game has certain capabilities. This
function is called like this:
result = systemInfo(__SYSINFO_xxx);
where __SYSINFO_xxx is one of the pre-defined constants (defined
automatically by the compiler) listed below. The result tells
you about the particular run-time that is executing your game,
so you can customize the game, if you wish, for certain system
capabilities. For example, you might want to change some text
in your game slightly depending on whether sound effects can be
played.
Before calling systemInfo() with any of the other codes, you
*must* check to see if systemInfo() itself is supported. Versions
of the run-time prior to 2.2.4 do not support this function, so
the return codes are meaningless. Fortunately, you can determine
if systemInfo() is itself supported using the following code
fragment:
if (systemInfo(__SYSINFO_SYSINFO) = true)
{
/*
* systemInfo IS supported by this run-time - other
* systemInfo codes will return meaningful results
*/
}
else
{
/*
* systemInfo is NOT supported by this run-time
*/
}
Only one version of HTML TADS (version 2.2.3) was ever released
without systemInfo support, and this was the first public beta
release. So, it should be fairly safe to assume that any system
that doesn't support systemInfo() doesn't support any of the HTML
TADS features.
The __SYSINFO_xxx codes are:
__SYSINFO_VERSION - returns a string with the run-time version
number. This will be a string such as '2.2.4'.
__SYSINFO_HTML - returns 1 if HTML markup is supported, 0 if not.
0 indicates that this is a standard text-mode run-time system.
If this returns 0, then JPEG, PNG, WAV, MIDI, WAV/MIDI overlap,
WAV overlap, and the images, sounds, music, and links preference
items can all be assumed to be unsupported, since these features
are only provided by HTML TADS.
Note that __SYSINFO_HTML returns 0 (HTML not supported) for the
character-mode run-time, even for the newer versions of the
run-time that do provide some limited HTML support, because this
information code is intended to indicate whether the full HTML
feature set is supported. The character-mode version only supports
a limited subset of HTML features, so it indicates that HTML is
not supported.
__SYSINFO_OS_NAME - returns a string with the name of the operating
system on which the run-time is currently executing. (The name
is the same as the string that the compiler uses to pre-define
the __TADS_SYSTEM_NAME preprocessor symbol, but this lets you
determine what system is executing at run-time, rather than the
system that was used to compile the game.)
__SYSINFO_JPEG - returns 1 if JPEG images are supported, 0 if not.
__SYSINFO_PNG - returns 1 if PNG images are supported, 0 if not.
__SYSINFO_WAV - returns 1 if WAV sounds are supported, 0 if not.
__SYSINFO_MIDI - returns 1 if MIDI music is supported, 0 if not.
__SYSINFO_MIDI_WAV_OVL - returns 1 if MIDI and WAV sounds can be
played back simultaneously (overlapped), 0 if not. If this
returns 0, it means that WAV playback will suspend MIDI playback.
__SYSINFO_WAV_OVL - returns 1 if multiple WAV sounds can be played
back simultaneously (overlapped), 0 if not. If this returns 0,
it means that any WAV played back in a foreground layer will
suspend a WAV being played in any background layer.
__SYSINFO_PREF_IMAGES - returns 1 if the user preferences are set
to allow images to be displayed, 0 if not. Note that, even if
this preference is set so that images are not displayed, the
preferences for JPEG and PNG images will still return 1 for each
of those image types that are supported by the platform. The
image format codes (__SYSINFO_PNF and __SYSINFO_JPEG) indicate
whether the image formats are supported at all, whereas this
preference code indicates whether images are currently allowed
for display.
__SYSINFO_PREF_SOUNDS - returns 1 if the user preferences are set
to allow digitized sound effects (WAV files) to play back, 0 if
not.
__SYSINFO_PREF_MUSIC - returns 1 if the user preferences are set
to allow music (MIDI files) to play back, 0 if not.
__SYSINFO_PREF_LINKS - returns 0 if the user preferences are set
so that links are not highlighted at all (in which case they'll
be displayed as ordinary text; they won't be highlighted as links
and won't be active for the mouse); returns 1 if links are
highlighted and active; and returns 2 if links are set to a "hot
key" mode, in which case links aren't highlighted except when
the user is holding down a special key to explicitly illuminate
the links.
- TADS has a new mechanism for better supporting non-US character
sets in a more portable fashion. The TADS Compiler, Run-Time, and
Debugger (including the HTML TADS versions) now provide an option
that lets you specify a character set translation to use for your
game. The character set translation allows your game to use a
standardized character set, such as ISO Latin 1, but still run on
any system by providing a mapping from your game's internal character
set to the native system character set for each player's system.
Refer to CHARMAP.HTM for information on how to use this new
feature.
- The parser now accepts a comma immediately after "oops" or "o".
- The parser now allows strings in player commands to be entered with
single quotes as well as double quotes. So, the following commands
are now interchangeable:
type "hello" on keyboard
type 'hello' on keyboard
Note that this change means you can't create a vocabulary word that
starts with a single quote, such as "'til", although you can still use
a single quote within a vocabulary word, as in "bob's". The parser
treats a single quote within a word as part of the word, but it now
treats a single quote at the start of a word as a quotation mark
intended to mark a string.
- In adv.t, 'q' is now a synonym for 'quit' for player commands.
- The #define preprocessor directive did not work correctly in past
versions when the "-case-" option (for case-insensitive compilation)
was used. In particular, preprocessor symbols defined with capital
letters were not matched. This has been corrected so that #define
works properly when "-case-" is used.
- The #define preprocessor directive sometimes did not work correctly
with long source lines (over about 128 characters). This has been
corrected; #define now works correctly with long input lines, which
are often necessary for lengthy macro expansions.
- The TADS error code 1026, "wrong number of arguments to user function,"
now includes the name of the function or object.method that was the
target of the invalid function call. This can be helpful in situations
where the TADS parser calls the function directly, since there was
previously no simple way of determining which function was being called
in these cases. Note that you must compile your code with debugging
enabled, and run under the TADS Debugger, to see the extra information
in the error message, since only the Debugger can load a game's symbol
table, and can only do so if the game was compiled for debugging.
- A bug that caused debugger crashes under certain obscure circumstances
has been fixed. In the past, if a game used the "replace" keyword to
replace a method in an object, tracing into other methods in the same
original object (the base object, before applying the modifications
using the "modify" construct) could sometimes crash the debugger.
This has been corrected.
- The run-time is now more consistent about converting special escape
sequences in strings obtained externally, such as from the input()
function or from quoted strings in a player command. In particular,
when these strings contain characters such as backslashes or newlines,
the run-time now consistently converts these characters into a
backslash sequence (a backslash turns into '\\', a newline turns
into '\n', and a tab turns into '\t').
- adv.t and std.t are now considerably more consistent in terms of
indenting and punctuation style.
------------------------------------------------------------------------------
2.2.3 03/25/98 corrections and enhancements, HTML
- This new version of the TADS compiler and run-time supports
HTML TADS, the new HTML-enabled version of the TADS run-time.
You should use this new version of the compiler to generate
games for the HTML TADS run-time.
- Fixed a parser bug that caused various problems when the player
issued a command to an actor, but used a pronoun to refer to
the actor: "him, go east." (This always failed, but the type
of problem depended on the platform; in most cases, a TADS
error such as "error loading object on demand" resulted.)
- Fixed up the indenting in adv.t. At some point adv.t was
detabified with an incorrect tab size setting, which randomized
the indenting. The file has now been fully re-indented with
spaces; since it has no tabs in it any more, it should look
the same on any editor, regardless of the local tab size
setting.
- Added 'n', 's', and the other one-letter direction words to
dirPrep's preposition vocabulary list in adv.t. This corrects
the problem that a command such as "push box n" was not accepted,
even though "push box north" was.
- A new parser error handling function has been added. The new
function, parseErrorParam(), is similar to parseError(), but
provides additional arguments that provide the actual values of
the "%" parameters in the error message:
parseErrorParam: function(errNum, errMsg, ...)
The errNum and errMsg parameters are the error code number and
default error message text string, just as with parseError().
The additional arguments give the values of the "%" parameters;
for example, for message 2, "I don't know the word '%s'", the
first extra parameter will be a string with the word that the
parser didn't recognize.
If your game defines a parseErrorParam() function, the parser
will call this function rather than parseError(); parseError()
is essentially obsoleted by the new function. However, if your
game does not contain a parseErrorParam() function, the parser
will call parseError() as before.
- Fixed a parser bug: if an object had the same name as a verb
(for example, the defined an object with a noun of 'lock'), and
the player attempted to issue a command to an actor starting
with the re-used verb, the parser incorrectly issued the error
"There's no verb in that sentence!"
------------------------------------------------------------------------------
Please consult TADSV222.DOS for information on releases from 2.1.1
through 2.2.2, and refer to TADSV200.DOS for information on releases
prior to 2.1.1. See the note at the top of this file for details on
where to find these older release notes files.