This file is part of the TADS 2 Author’s Manual.
Copyright © 1987 - 2002 by Michael J. Roberts. All rights reserved.
Edited by NK Guy, tela design.


Chapter Five


Advanced Parsing Techniques

This chapter describes some of the more advanced features of the TADS parser. The techniques described in this chapter allow you to change most of the default behavior of the parser, so you can use these features to implement special types of parsing that TADS would not normally provide.

If you’re still learning the basics of TADS, be assured that you probably won’t need to use any of these advanced features in the course of building a moderately complicated game. Even the most complex games usually need only a handful of these techniques. Even so, you may enjoy browsing this material to get a flavor for some of the capabilities of the system.


Compound Words

TADS can only handle prepositions that consist of a single word. Unfortunately, English and other human languages have constructions in which multiple words are used for the function of a single preposition in a sentence; for example, “take the book out of the bag” has two prepositions in a row, “out of,” serving the function of a preposition in the sentence.

Although TADS doesn’t have any way to specify a sentence template that uses multiple prepositions like this, it does provide a mechanism that lets you specify that certain pairs of words are “glued together” into a single word when they occur together. In the example above, you could define “out of” as such a pair of words: whenever the parser sees “out” followed directly by “of,” it can be made to put these two words together and treat the pair as a single word.

To define a pair of words that should be treated as a single word, you use the compoundWord statement. For example:

  compoundWord 'out' 'of' 'outof';

This specifies that when the parser sees the word “out,” followed directly by the word “of,” it should take the pair of words and replace it with the single word “outof.” (Note this replacement word follows the convention in adv.t, which is to simply concatenate the two words to form the replacement word, but the replacement can be anything at all - you could have made the replacement word, for example, “out_of.”)

Note that you must still define “outof” as a preposition. Even though “out” and “of” are separately defined as prepositions, the result of a compound word replacement must also be defined as a vocabulary word. Once the pair of words is replaced with the compound word, the parser forgets ever knowing about the pair as separate words.

You can’t directly define three-word (or longer) compound words, but you can indirectly do so by constructing the longer words from two-word directives. For example, to convert “out from under” into a single preposition, you would use two compound word definitions:

  compoundWord 'out' 'from' 'outfrom';

  compoundWord 'outfrom' 'under' 'outfromunder';

When the parser finds the sequence “out from under,” it first converts “out from” into “outfrom.” Then, it checks the sentence again, and finds the pair “outfrom under,” which it converts to “outfromunder.”


Special Words

The parser treats a number of words as special reserved words. These words aren’t ordinary vocabulary words, because they don’t fit in to the parts of speech used for normal vocabulary words - they aren’t nouns, plurals, adjectives, articles, verbs, or prepositions. Since they aren’t ordinary vocabulary words, you can’t define them using the normal vocabulary definitions; instead, you use a separate command to specify these words: the specialWords statement.

This command can appear anywhere that one of the other special commands (such as formatstring or compoundWord) is allowed. Following the specialWords keyword, you list all of the special words in a fixed order. You must specify the words in the order shown below, and you must provide at least one word for every position. You can provide more than one word for each position, by listing each synonym separated by an equals sign (=). The default list, which matches the built-in list if no specialWords command is used in your game, looks like this:

  specialWords
    'of',
    'and',
    'then',
    'all' = 'everything',
    'both',
    'but' = 'except',
    'one',
    'ones',
    'it',
    'them',
    'him',
    'her',
    'any' = 'either'
  ;

Except for the words in the “of,” “one,” and “ones” positions, the words specified in the list can’t be used as ordinary vocabulary words at all - they’re always considered to have their special meaning as listed here. The slots for “of,” “one,” and “ones” are not reserved in ordinary use; their special meanings are in effect only when the parser expects them according to context.

Note that the slot occupied by “any” is a recent addition. You can omit this position in a specialWords, in which case the system will use the default (“any” and “either” will be used as synonyms for this position).

You can use the special replace and modify commands with specialWords. If your statement starts off with “replace specialWords”, any previous special words definitions are discarded, and the new set is used. (Note that the same thing happens if you specify a second specialWords directive in your game program with neither a replace nor modify prefix, but the compiler will warn you that you should use replace to make your intentions explicit.) If your statement starts off with “modify specialWords”, the compiler adds the special words in the new list to the words that have already been defined in previous specialWords statements. When using modify, you can use nil in any slot to indicate that you don’t wish to add any words to that position. You can therefore use modify as a means of adding a new special word or words to one or more slots, without having to specify the entire list again.


Format Strings

For the most part, TADS doesn’t treat the player actor’s object, the current value of which is given by parserGetMe(), any differently from any other actor in the game. When the player types a command that isn’t explicitly directed to another actor, TADS assumes that the actor is the object given by parserGetMe(), and from that point on acts the same as it would if the command were being directed to any other actor.

All of the command handling methods that the parser calls have the current actor object as a parameter, which allows them to be written independently of actor - or, if desired, to take special actions for certain actors. The verb handlers in adv.t generally treat all actors the same. For example, the definition of doTake in the class thing doesn’t move the object being taken into the player’s inventory, but rather into the current actor’s inventory. This allows commands such as “take the box” and “joe, take the box” to be processed using the same code.

If a command handler is written independently of the actor, it needs to be able to display its messages in terms of the actor carrying out the command, rather than simply in terms of “you.” It would be very inconvenient if you had to call actor.thedesc every time you wrote a message in a verb handler, so TADS provides a much more convenient mechanism called “format strings.”

A format string is a special sequence of characters that signals the output formatter to substitute a property of the current command’s actor. Instead of using “you” and related words, the messages in adv.t use format strings; your game program should do the same thing any place where an actor other than the player may be taking some action that results in a message.

A format string associates a special character sequence with a property name. When a message includes a format string, enclosed in percent signs (%), the output formatter removes the format string and replaces it by calling the property associated with the format string. For example, one of the format strings defined in adv.t is:

  formatstring 'you' fmtYou;

This tells the system that whenever it sees “%you%” in a message being displayed, it removes the “%you%” sequence, and replaces it with the text that results from evaluating the property fmtYou in the current actor.

You can define new format strings of your own, although adv.t defines most of the format strings you’ll probably need.

In adv.t, most messages are phrased in terms of format strings. For example, when the player tries to travel a particular direction but can’t, adv.t doesn’t display “You can’t go that way,” but instead displays “%You% can’t go that way.” When the player tries to go east but can’t, the system displays “You can’t go that way,” just as it would have if the format string hadn’t been used. But, when the player types “joe, go east,” the system displays “Joe can’t go that way,” because joe.fmtYou displays “joe” rather than “you.”

Note that the capitalization of the replacement text follows the capitalization of the format string itself. So, “%you%” becomes “you,” while “%You%” becomes “You.” (“Joe” is displayed capitalized in either case if the message displayed by joe.fmtYou is itself capitalized.)

Note that many format strings are defined for other parts of the sentence. When writing a message, you will usually have to use several format strings to keep the sentence grammatical. For example:

  "%You% %are%n't wearing %your% spacesuit."

This message will become “You aren’t wearing your spacesuit” when displayed for the player character, but will become “Joe isn’t wearing his spacesuit” when displayed for Joe.


Indistinguishable Objects

Most objects in an adventure game are one-of-a-kind. Sometimes, though, it’s desirable to create groups of interchangeable objects, each of which has its own separate location and other properties, but are otherwise the same. TADS has a mechanism for this: indistinguishable objects.

Indistinguishable objects are particularly useful for creating collections of objects dynamically (with the operator new), because each new object you create is a copy of the same superclass object. Although you could differentiate the objects (by adding vocabulary words with the addword built-in function, for example), it’s often desirable to treat the new objects as equivalent for most purposes.

For example, you might want to create coins that serve as currency in your game. You could give each coin a separate identify (one gold coin, one silver coin, one copper coin, one iron coin), but this would quickly become impractical: the periodic table of elements is only so large, and paying for things would eventually become tedious. This is a good use for indistinguishable objects.

To support indistinguishable objects, TADS defines a property that you can set to indicate to the parser that an object does not need to be distinguished from others of the same class. The property is isEquivalent. When isEquivalent returns true for an object, all other objects with the same immediate superclass are considered interchangeable by the parser. When a player uses one of these objects in a command, the parser will simply pick one arbitrarily and use it, without asking the player which one.

If a player uses a noun that is ambiguous with multiple equivalent items and one or more other items, the parser will need to disambiguate the objects as usual. In such cases, the parser’s question will list the distinguishable items only once. For example, assume we have five gold coins that are all equivalent (in other words, they all have isEquivalent set to true, and they all are immediate subclasses of the same class). Assume further that a silver coin and a bronze coin are also present in the room.

  Treasure Room
    You see a bronze coin, five gold coins, and a silver
  coin here.

  >get coin
  Which coin do you mean, the bronze coin, a gold coin, or
  the silver coin?

Note that the objects which appear only once are listed with “the” (using the thedesc property), while the indistinguishable objects are listed only once, with “a” (using the adesc property).

The parser also allows the player to refer to a number of indistinguishable items all at once, by specifying a count of the objects. For example, the player could type “take 5 gold coins”; the parser would interpret this by arbitrarily choosing five gold coins from the available gold coin objects.

When you define an indistinguishable object, it’s important that you define a pluraldesc property for the object. The parser (and utility functions defined in adv.t) use pluraldesc to refer to a group of indistinguishable items collectively. pluraldesc should be similar to sdesc, but should provide a plural description of the object. For example, for the gold coin objects, you might define pluraldesc = "gold coins".

The standard library (adv.t) has support for indistinguishable objects in some of the basic utility functions:

The listcont(obj) function condenses each set of indistinguishable objects in the contents list to a single mention, with a count. For example, if the contents of an object include two gold coins and three silver coins, the function would display “two gold coins and three silver coins” rather than listing each object individually.

The listcontcont(obj) function condenses sets of indistinguishable objects in the same manner as listcont().

The itemcnt(list) function returns the number of distinguishable items in the list. Each set of indistinguishable items in the list is counted as only a single item.

The isIndistinguishable(obj1, obj2) function tests two objects to determine if they’re indistinguishable from one another; it returns true if the objects should be treated as equivalent, nil if the objects are distinct.

The sayPrefixCount(count) function displays a number in a nice format. If the number is small (no more than twenty), it spells out the number (for example, sayPrefixCount(5) will display “five”). If the number is larger (more than twenty), it displays the number with digits (sayPrefixCount(35) displays “35”).

In addition, the built-in function firstsc(obj) is helpful in handling indistinguishable objects. This function returns the object that is the first superclass of the given object. Since two objects are indistinguishable if each object’s isEquivalent property is true and both objects have the same superclass, firstsc() is necessary to test two objects for equivalence.


Obtaining Information on the Current Command

Most functions and methods that the parser calls are supplied with arguments that tell them everything they should need to know about the command. For example, the actorAction method takes arguments that tell it about the verb, preposition, and direct and indirect objects. At times, though, it’s necessary to know what objects are involved in the current command in cases where the information is not included in the function’s or method’s parameters.

Perhaps the most glaring example of this is the travel methods (a room’s north and south methods, for example): these methods do not receive an “actor” parameter to tell them who’s attempting the travel. In most cases the information isn’t necessary, since the room that lies to the north of a given room doesn’t usually depend on who’s traveling. Sometimes, though, travel has a side effect on the actor. Traveling over a shaky rope bridge, for example, an actor might drop something out of their inventory. In other cases, an actor might not be able to travel in a certain direction unless they’re carrying a certain object. In these situations, the travel method must know what actor is traveling.

The parser provides a built-in function that lets you determine the actor, verb, direct object, preposition, and indirect object of the current command, regardless of whether any of those objects are passed as parameters to the current method. The function is called parserGetObj(); it 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

PO_IT - the antecedent for "it"

PO_HIM - the antecedent for "him"

PO_HER - the antecedent for "her"

PO_THEM - the antecedent for "them", returned as a list

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. In the case of PO_THEM, the return value is a list of objects, since "them" can naturally refer to more than one object. (For consistency, the return value for PO_THEM is always a list, even if "them" refers to only one object - in that case it's simply a list with one element. If there's no "them" match at all, the result is an empty list.)

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 for the various selectors relevant to an active command at any time from (and including) actorAction to (and including) the doVerb or ioVerb methods. The pronoun selectors are valid at any time. If you use parserGetObj() to query command elements outside of these bounds, the function simply returns 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() to query command elements: 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 of the command elements: 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.


Obtaining Verb Information

TADS has a mechanism that lets the game program retrieve certain information about a verb from the parser. The built-in function verbinfo lets you obtain this information. This function takes two arguments: the first is the deepverb object whose information you want to retrieve; the optional second argument is a preposition object. If you call verbinfo with only the verb argument, it returns the verification and action properties that are defined with the doAction definition for the verb. If you also include the preposition object argument, verbinfo returns the properties that are defined with the ioAction definition for the preposition in the verb.

verbinfo returns a list. The contents of the list depend on whether you call the function with one or two arguments.

If you call verbinfo with only one argument (a verb), the list has two elements:

[1] = the verification property pointer (verDoXxxx)
[2] = the action property pointer (doXxxx).

If you call verbinfo with both the verb and preposition arguments, the return value is a list with four elements:

[1] = direct object verification property pointer (verDoXxxx)
[2] = indirect object verification property pointer (verIoXxxx)
[3] = indirect object action property pointer (ioXxxx)
[4] = true if ioAction has the [disambigDobjFirst] flag, nil otherwise

In any case, if no matching doAction or ioAction definition exists for the verb, verbinfo returns nil.

Note that it is possible that additional flags (similar to disambigDobjFirst) may be added in the future; the returned list may be expanded to include information on any such added flags. So, for compatibility with future versions, we recommend that you don’t write conditional code based on the length of the list. The lists will never shrink, but they may expand.

Examples

For the removeVerb object defined in adv.t, you would get these results:

verbinfo(removeVerb) would return [&verDoUnwear &doUnwear]

verbinfo(removeVerb, fromPrep) would return [&verDoRemoveFrom &verIoRemoveFrom &ioRemoveFrom nil]

verbinfo(removeVerb, aboutPrep) would return nil, since removeVerb doesn’t define an ioAction for aboutPrep.


Dynamic Vocabulary

Normally, you define vocabulary words statically, by using the normal vocabulary properties (noun, adjective). These words are defined when you compile your game, and they never change.

At times, though, you might want to change the vocabulary words associated with an object while the game is running. For example, you might want to let the player name a dog that they encounter; you’d want the name the player gives the dog to become a vocabulary word that the player can use to refer to the dog.

TADS has built-in functions that let you dynamically manipulate the vocabulary words associated with an object while the game runs.

addword()

The addword function adds a vocabulary word to an object. This function takes three arguments: an object, a vocabulary property pointer, and a (single-quoted) string. The string is added as a vocabulary word, using the given property, and associated with the object. For example:

  local dogName;

  "What would you like to call the dog? ";
  dogName := input();

  addword(doggy, &noun, dogName);

delword()

The delword function has the opposite effect of addword: this function deletes a word associated with an object. This function takes the same arguments as addword. You can delete words dynamically added with addword, and you can also delete words that were originally defined statically in the game’s source code.

  delword(doggy, &noun, dogName);

getwords()

The getwords() function lets you retrieve a list of the words associated with a particular object for a given vocabulary property. This function is especially useful when you’re dynamically changing an object’s vocabulary words, but you can use it just as well with statically-defined vocabulary words. This function takes two arguments: an object, and a property pointer for a vocabulary property. The function returns a list containing (single-quoted) strings giving the vocabulary words for the given property of the given object. For example, if we define an object called redBook with adjectives 'red', 'small', and 'tiny', then this:

getwords(redBook, &adjective);

would return this:

['small' 'red' 'tiny']

Note that the order of the words is arbitrary, so you shouldn’t expect the words to be in any particular order.


Changing the Player Character Object

When the game starts, the parser assumes that the player character is represented by the game-defined object named “Me”. (The file std.t, included with the TADS distribution, has a basic definition of this object that is a good starting point for most games.)

Most games have only one player character throughout the entire game, so this single “Me” object is adequate in most cases. However, some games use more than one player character, shifting the first-person viewpoint of the player among different characters in the game.

At any given time, the parser has a single object identified as the current player character. Commands that are given without an explicit actor (“lloyd, go east”) are directed to the current player character object.

You can change the current player character using the built-in function parserSetMe(). This function takes as its argument the new player character object that you want to establish as the current player character. For example, to make the object named “Lloyd” the current player character, you’d write code like this:

  parserSetMe(Lloyd);

You can ask the parser for the current player character object at any time by using the parserGetMe() function:

  local curPlayer := parserGetMe();

If you change the player character in the course of your game, you should be careful never to refer to the object “Me” directly in your code to refer to the current player. Instead, you should always use parserGetMe() to get the current player character. Of course, if you really want to do something with the “Me” object specifically, regardless of the current player character, you would still refer to “Me” directly. The library functions in adv.t always use parserGetMe to get the current player.

switchPlayer()

The standard library, adv.t, defines a function called switchPlayer() that provides a high-level mechanism to switch to a new player character. If you’re using adv.t, you should call switchPlayer() rather than calling parserSetMe() directly. switchPlayer() uses parserSetMe() to change the parser’s record of the current player, but also performs some necessary bookkeeping with the outgoing and incoming player character objects. Call the function with the new player character object as its argument:

  switchPlayer(Lloyd);


Verb Synonyms and Verb Redirection

TADS has a couple of convenience features that make it easy to create certain common definitions.

First, TADS lets you define object-level verb synonyms. It’s often desirable to make a number of verbs work the same way with a particular object, because different users will try to do the same thing with slightly different commands. For example, if you have a touch-pad on a control panel in your game, the player may try a number of different verbs with it: touch, push, tap, press. “Push” and “press” are already synonymous in adv.t (they both refer to pushVerb), but “touch” is a separate verb, and “tap” isn’t even defined, so you would have to add this verb in your game program. Since “touch” and “tap” are separate verbs from “push,” you will need to make the touch pad object respond to all three verification methods and all three action methods the same way. You could do this with code like this:

  verDoPush(actor) = { self.verDoTouch(actor); }
  doPush(actor) = { self.doTouch(actor); }

This is tedious, though - especially if you have more than two or three verbs that you want to make synonymous. Instead of these lengthy definitions, you can use the TADS object-level verb synonym feature. To do this, you use the special pseudo-property doSynonym:

  doSynonym('Touch') = 'Push'

This simple definition has the same effect as the much lengthier definitions above. The way to read this definition is: the direct-object verb synonym for “touch” is “push.”

You can specify that more than one verb is a synonym for another verb. To do this, simply list all of the different verb synonyms on the right-hand side of the equals sign:

  doSynonym('Touch') = 'Push' 'Tap'

Read this definition as: the direct-object verb synonyms for “touch” are “push” and “tap.” Note that the fact that you can use multiple verbs on the right-hand side may make it easier to remember which way this direction works. All of the verbs in the command are redirected to a single verb, so the “target” - the verb handler that’s actually called when any of these verbs are used - must be in the position where only one verb can go, which is inside the parentheses. So, if the player types “push pad,” the parser will call verDoTouch and doTouch.

ioSynonym works the same way, but maps the verIoVerb and ioVerb handlers of one or more verbs to those of another verb. Whereas doSynonym makes one or more verbs synonymous when the object is used as a direct object, ioSynonym makes verbs synonymous when the object is used as an indirect object.

Note that verb synonyms created with doSynonym and ioSynonym apply only to the object in which they are defined. A verb synonym created for one object doesn’t affect any other objects, so “touch” and “push” are still separate verbs for other objects. Of course, verb synonyms are inherited just like any other verb handler.

The second convenience feature lets you specify that when a verb is applied to a particular object, it should instead be applied to a different object. This is often convenient when you are composing a complex object in your game from several internal objects. For example, you might want to include a desk with a drawer. The desk and drawer are separate objects, but the player may type “open desk” to open the drawer. You could code this by redirecting the verb verification and action methods for “open” from the desk to the drawer:

 desk: fixeditem
   noun = 'desk'
   sdesc = "desk"
   location = office
   verDoOpen(actor) =
   {
      deskDrawer.verDoOpen(actor);
   }
   doOpen(actor) =
   {
      deskDrawer.doOpen(actor);
   }
 ;

This works, but redirecting more verbs would be tedious - and you’d probably want to redirect at least “close,” and probably “look in” and “put in” as well. To avoid the large amount of typing this would involve, you can use the verb handler redirection syntax:

  doOpen -> deskDrawer

(Note that the symbol “->” is made up of a hyphen followed by a greater-than sign.) This single line replaces the verDoOpen and doOpen definitions above. It indicates that both the doOpen and verDoOpen methods, when called on the desk, should be redirected to the drawer.


Error Messages

When an error occurs during parsing, the parser tells the user what went wrong with as specific an error message as possible. One of the design goals of the parser is to be friendly to players; although error messages are inherently somewhat unfriendly, they certainly can’t be eliminated entirely, so we at least try to make them informative. The player should always be able to understand why the game rejects a command, so that he or she can figure out what to say instead.

The parser lets your game program provide a custom message for every error. Most errors are handled through a game-defined function called either parseErrorParam or parseError. For most errors, if your game defines parseErrorParam, the parser will call it whenever it wants to display an error; otherwise, if your game defines parseError, the parser calls it; otherwise, the parser displays a default message.

A few parser messages are more specialized, and used different functions that you can optionally provide. The error messages are listed in Appendix E.

parseError

If no parseErrorParam function is defined in the game, the parser calls parseError instead, if it’s defined. Note that the game will never call parseError if a parseErrorParam function is defined.

The parser calls the parseError function with two parameters: a message number, and the text of the default message. The message number identifies the reason the parser is displaying a message - each situation that requires a message is given its own message number. The default message is the text that the parser would have displayed if you did not define a parseError function in your game at all.

You may have noticed references to message numbers elsewhere in this manual - these were referring to parseError message numbers. The message numbers are listed in detail below.

parseErrorParam

The parser calls parseErrorParam, if defined, with two or more arguments. The first is a message number, and the second is the text of the default message; these first two arguments have the same meanings as they do for parseError. Any additional arguments provide the values for replacement slots in the default message. Because this function takes a variable number of parameters, you must define it with an ellipsis after the last fixed argument:

  parseErrorParam: function(errnum, str, ...)
  {
    // put your code here
  }

The default message may contain one or more “%” sequences. Each “%” is followed by another character that specifies a replacement value type. If you’ve programmed in C, you’ll be familiar with these format characters: “%s” indicates a string value, and “%d” indicates a decimal integer value. (C has many other format specifiers, and a set of modifiers as well, but “%s” and “%d” are the only ones that TADS uses for default messages.)

For each ”%” sequence in the default message, parseErrorParam will be called with an additional argument giving the value for that “%” sequence. For example, for message 2, “I don’t know the word '%s'”, the function is called with a third parameter, a string containing the unknown word.

Return Value

The return values of parseErrorParam and parseError work the same way.

Your function should return either nil or a (single-quoted) string value. If your function returns nil, it means that you want to use the default message - this is the same as not defining a parseError or parseErrorParam function at all. If your function returns a string value, the string is displayed in place of the parser’s default message.

Note that a few of the default messages contain the sequence “%s”. This special sequence is replaced in the actual message displayed by a string value of some sort; for example, in message 2, the “%s” is replaced with the unknown word. Similarly, the special sequence “%c” in message is replaced with a single character, and “%d” is replaced by a number. You can use a “%” sequence in any message you provide to replace a message where the default text uses the same special sequence.

Message Numbers

Messages with numbers below 100 are complete messages - they’re not part of more complex messages built out of pieces. Messages 100 and above are different: they’re fragments of complete messages; several such fragments are combined (sometimes with other text as well) to form a complete message. So, if you want to do some special formatting, such as enclosing any parser error messages in parentheses, you can easily do so for any of the regular messages, and avoid doing so for the complex messages, you can simply check the message number to make sure it’s less than 100, and apply your special formatting if so.

Most of the messages in the first group are self-explanatory, although a few require some explanation.

Messages 3, 10, and 11 are generated if a word the player uses refers to more than a fixed number of objects in the game. Note that the limit applies regardless of whether the objects are all present or not - the limit is for the total number of objects in the entire game with the same vocabulary word. (For this reason, it’s probably not a good idea to define a noun or adjective in a very general class such as “thing” or “item”.) The limit in version 2.2 is 200 objects; prior to 2.2 it was 100 objects.

Message 9 is generated if the words the player uses to refer to an object are not all defined for at least one object. For example, if your game has a blue book, and a red box, the words “red” and “blue” will both be recognized as adjectives, and “book” and “box” as nouns; if the player types “look at red book,” the phrase “red book” will be recognized as a valid noun phrase in form, but it doesn’t refer to any object defined in the game. The parser will respond with message 9.

Messages 13 and 14 are used for pronouns. When the player refers to one of the singular pronouns (it, her, and him), and the object that the pronoun represents is no longer accessible, message 13 is used. Message 14 is used with the pronoun “them.”

Message 15 is used when the player uses “all” as the object of a command, but there are no suitable objects present.

Message 28 is displayed when the player enters a command with multiple direct objects for a verb that requires the direct object to be disambiguated first. Verbs that process the direct object first accept only one direct object.

Message 30 is displayed when the player enters a command like “take 3 balls,” but fewer such items are present.

Message 38 is displayed when an object that was originally valid fails re-validation in the course of a multi-object command.

Message 39 is displayed when an object is neither visible nor reachable in a call to execCommand.

When the player addresses a command to an actor, and the actor is visible to the player (the actor’s isVisible(parserGetMe()) returns true), but the actor is not a valid actor (the actor’s validActor method returns nil), the parser displays message 31.

1 - I don't understand the punctuation "%c".

2 - I don't know the word "%s".

3 - The word "%s" refers to too many objects.

4 - I think you left something out after "all of".

5 - There's something missing after "both of".

6 - I expected a noun after "of".

7 - An article must be followed by a noun.

8 - You used "of" too many times.

Note: message 8 is no longer used.

9 - I don't see any %s here.

10 - You're referring to too many objects with "%s".

11 - You're referring to too many objects.

12 - You can only speak to one person at a time.

13 - I don't know what you're referring to with '%s'.

14 - I don't know what you're referring to.

15 - I don't see what you're referring to.

16 - I don't see that here.

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.

22 - There seem to be extra words in your command.

23 - internal error: verb has no action, doAction, or ioAction

24 - I don't recognize that sentence.

25 - You can't use multiple indirect objects.

26 - There's no command to repeat.

27 - You can't repeat that command.

28 - You can't use multiple objects with this command.

29 - I think you left something out after "any of".

30 - I only see %d of those.

31 - You can't talk to that.

32 - Internal game error: preparseCmd returned an invalid list

33 - Internal game error: preparseCmd command too long

34 - Internal game error: preparseCmd loop

38 - You don't see that here any more.

39 - You don't see that here.

The next several messages are status codes only. These are used internally to relay information from the object disambiguation subsystem to game code through parseNounList, so they should never be used in a call to parseError or parseErrorParam; they’re listed here for completeness.

40 - Status code only; no message: can't create a new numbered object

41 - Status code only; no message: invalid status from disambigXobj

42 - Status code only; no message: empty response to disambiguation query

43 - Status code only; no message: retry object disambiguation as command

44 - Status code only; no message: objects are still ambiguous

The next set of messages is used to ask questions when the player refers to an object with words that could refer to more than one object. The parser must in these cases ask the player which of the possible objects was meant. Note that these messages will only be used if your game does not define a parseDisambig function, which will be used instead if defined.

100 - Let's try it again:

101 - Which %s do you mean,

102 - ,

103 - or

104 - ?

The next set is used when an object is not suitable for a verb. These messages are needed when a player uses a verb with an object, but the object does not define (or inherit from a superclass) an appropriate “verification” method (verDoVerb or verIoVerb). Messages 111 and 114 consist of a single space.

Note that these messages are not used if your game defines a parseError2 function, since that function will be called instead to display this error.

110 - I don't know how to

111 -

112 - anything

113 - to

114 -

115 - .

The next message is used after each object when multiple objects are listed. For example, when the player enters the command “take all,” the parser will apply the verb “take” to each accessible object in sequence; before applying the verb to each object, the parser displays the name of the object, followed by this message, so that the player can see the results of the command for each object.

120 - :

The next messages are used to note objects being used by default. When the player types a command that omits an object, and the parser can determine that the verb implies that a particular object is being used, the parser will display the name of the object with these messages. For example, if the player types “dig”, the parser might determine that the direct object is implicitly the ground, the preposition is “with,” and the indirect object is implicitly the shovel; in this case, it will display message 130, then the name of the ground, then message 131; then it will display message 130 again, then the name of the assumed preposition, then message 132, then the name of the shovel, then message 131. Message 132 consists of a single space.

Note that these messages won’t be used if your game defines a parseDefault function.

130 - (

131 - )

132 -

When the player leaves out the object in a command, but the parser is not able to find a suitable default object, the parser will ask the player to supply an object using these messages.

Note that these messages aren’t used if your game defines a parseAskobj, parseAskobjActor, or parseAskobjIndirect function.

140 - What do you want to

141 - it

142 - to

143 - ?

144 - them

145 - him

146 - her

147 - them

148 - What do you want

149 - to

The next message is displayed when the player refers to an object defined in the game with a generic numeric adjective (adjective = '#'), but the player doesn’t supply a number in the command.

160 - You'll have to be more specific about which %s you mean.

Note: message 200 is no longer used, starting with version 2.5.1. (In the past, when the player used words that could refer to more than one object, and the objects in question were visible but not accessible, the parser called the cantReach method in each of the objects after displaying the name of the object via its sdesc message followed by parser message 200. The parser now uses the multisdesc method and message 120 instead, so that the same display mechanism is used for inaccessible objects as for any other multiple-object prefix message.)

200 - : (no longer used)

Examples

Here’s a sample of a parseError function that encloses any of the regular messages in square brackets. It ignores any messages that aren’t in the simple message range (below 100), since messages outside this range are fragments of more complicated messages, so can’t be formatted as though they were entire messages.

  parseError: function(num, str)
  {
    if (num < 100)
       return '[' + str + ']';
    else
       return nil;
  }


Parsing a String from the Game Program

At times, you might want to perform specific parsing operations on a string that you obtain directly from the player or other sources. The built-in parser can help: TADS provides several built-in functions that let you directly access parts of the parser from your program code.

Tokenizing a String

If you want to tokenize a string programmatically within your game, the parser provides a built-in function for this purpose:

   local tokenList;
   tokenList := parserTokenize(commandString);

The “commandString” parameter is any (single-quoted) string of text. This function will scan the string and break it up into tokens, which it returns as a list of strings. The token strings follow the same rules as the token list passed to the preparseCmd() function, including the conversion of special words such as “it” and “and”.

If the string contains any invalid characters (such as punctuation marks that the tokenizer doesn’t accept), the function returns nil, but doesn’t display any error messages.

Obtaining Token Types

After tokenizing a string into a list of words, you can obtain a list of the types of the tokens. The built-in function parserGetTokTypes looks up a list of words in the parser’s dictionary, and returns a list of the types of the tokens.

Call parserGetTokTypes 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)

Finding Objects for Words

The parser provides a function called parseDictLookup that lets you 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 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.

Parsing a Noun List

The parserDictLookup function is useful for writing your own entirely custom noun phrase parser, because it provides direct access to the parser’s internal dictionary. In many cases, though, you might want to use the parser’s normal noun phrase syntax, rather than doing all the work yourself. The parseNounList function provides access to the parser’s noun phrase parser, so that you can parse an entire noun phrase, or even a noun list, with a single function call.

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 first word in the phrase doesn’t appear to be part of a noun phrase (that is, the first word isn’t an article, adjective, noun, number, pronoun, or one of the special words that can be used in a noun phrase, such as “all” or “any”), then the parser returns a list containing the “startingIndex” value. The parser doesn’t display any error message in this case. The parser returns the “startingIndex” value as the first (and only) element of the return list to indicate that no words at all were used, but that no error occurred. The reason that the parser doesn’t display an error in this case is that there are times when it is possible that a noun phrase is present, but there are other possibilities as well, so your parsing code might merely be testing for the presence of a noun phrase; if the parser displayed an error here, it would prevent you from trying other possibilities.

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).

Summary of parseNounList() return values

No noun phrase (the first word isn’t a noun, adjective, article, etc.) Returns [number], where number is the starting index value in the arguments. Does not display any error message.
Syntactically invalid noun phrase Returns nil, and displays an error message
Syntactically valid noun phrase with no matching objects Returns [number], where number is the index of the first word in the word list after the end of the noun phrase. If “complainOnNoMatch” is true, the parser displays an error message indicating that there are no matching objects, otherwise it displays nothing.
Valid noun phrase with matching objects Returns a list of the matching objects, as described above.

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 parserResolveObjects() 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.

Resolving a Noun Phrase

After you’ve parsed a noun phrase, either through your own means or using parseNounList, you might want to resolve the noun phrase to one or more game objects. This is one of the most complicated parts of the TADS built-in parser; unless you need special custom behavior, you will probably want to use the standard mechanism for this step. Fortunately, a built-in function lets you call the built-in object resolver programmatically.

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 all of these 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” and takeVerb for “verb”, 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.

Note: It might seem strange to use takeVerb and &verDoTake to validate an actor, when most actors are fixeditem objects and hence cannot be taken by the player. However, in such cases, the parser is only validating the actor object, not verifying it. For validation purposes, takeVerb only requires that the player be able to reach the object, not that the player actually be able to take the object.

In most cases, though, the verb you specify won’t even be used. The parser always tries, when validating an actor, to use the actor’s validActor method instead of a verb-based validator. The parser only uses the verb-based validator for an actor when the actor object doesn’t define or inherit a validActor method. For any game written with a modern version of adv.t, validActor will be defined for all actors, and the parser will use this method instead of the verb validator. The only reason that the verb validator is still present is for compatibility with older games that pre-date the validActor mechanism.

“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 (insanely complex) 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 parser access 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;
    }
  }

Executing Recursive Commands: execCommand()

The built-in function execCommand() gives a game program direct access to the parser’s command execution system. This function doesn’t provide direct access to the string-parsing portion of the parser, but rather to the command execution portion. execCommand() takes the objects involved in the command and executes the command, performing object validation (validDo, validIo), verb notification (verbAction), actor notification (actorAction), room notification (roomAction), 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).

You call execCommand() 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 parserGetMe() 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 dobjCheck, iobjCheck, dobjGen, iobjGen 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 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 methods appropriately, then essentially duplicated the code in the SprayWith equivalents. Alternatively, you could have set up the SprayWith methods to call the SprayOn methods (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
  Youre 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 parserGetObj() built-in function, 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 (parserGetMe()) object.

Replacing the Current Command Line: parserReplaceCommand

In some cases, rather than executing a command recursively (as execCommand does), you might want to replace the current command line entirely and start over with a new string of text. The parser provides a built-in function that does just this.

The 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.

The parserReplaceCommand() function is especially useful in cases where you ask the player for an answer to a question, but the player responds with something that looks like a new command rather than a new question. This exact situation arises when the parser asks the player to disambiguate a noun phrase, but the player answers with a new command rather than an object name; parserResolveObjects() returns PRSERR_DISAMBIG_RETRY when this happens. You could also encounter this case if you read a response to a question with input(); if you want to let the player ignore your question and answer with a new command, you can use parserReplaceCommand() to switch to the new command.

Here’s an example of asking the player a yes/no question, treating any other answer as a new command.

  dragon: Actor
    // sdesc, ldesc, etc go here...

    verDoKill(actor) = { }
    doKill(actor) =
    {
      local response;
      local ret;

      /* ask what they want to do */
      "What, with your bare hands?\b>";
      response := lower(input());

      /* check to see if the response is "yes" or "no" */
      ret := reSearch(' *(yes|no) *', response);
      if (ret = nil)
      {
        /* it's not "yes" or "no" - it must be a new command */
        parserReplaceCommand(response);
      }

      /* check to see if it's "yes" or "no" */
      if (reGetGroup(1)[3] = 'yes')
      {
        "You grab the dragon by the neck and wrestle it to the ground!
        After a few moments of struggling, the dragon yells \"Uncle!\"
        and promises to let you cross the bridge.  You release the dragon, 
        and he slinks over to the corner to pout. ";

        self.isPouting := true;
      }
      else
      {
        "Undoubtedly a wise decision. ";
      }
    }
  ;


Regular Expression Searching

TADS provides a set of built-in functions for searching strings of text using “regular expressions.” Although this facility isn’t technically a part of the parser, you may occasionally find it useful in customizing your game’s command parsing. In particular, regular expression searching can be useful for parsing strings in places such as the preparse() and preparseCmd() functions.

A regular expression is a pattern that lets you describe a potentially large set of strings with a compact notation. Regular expressions are similar to “wildcard” patterns that many operating systems use for command-line utilities, but regular expressions are much more powerful.

TADS uses a regular expression syntax similar to that used by the Unix “grep” command. A regular expression is a string of characters. Most characters simply match themselves: the regular expression pattern “abc” matches the string “abc”, and nothing else. A number of characters have special meanings, though; the special characters are listed below.

|             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.
[ ] 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.
% Quotes the character that immediately follows, removing the special meaning of these characters: | . ( ) * ? + ^ $ % [ Also introduces the special sequences listed later.
%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, ‘%’ 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 ‘%’ 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”, and nothing else.

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’.

reSearch()

The built-in function reSearch() searches for the first occurrence of a regular expression pattern within a string.

  ret := reSearch(pattern, string_to_search);

The parameter “pattern” is a regular expression string, and “string_to_search” is the text string to search for the first occurrence of the pattern.

reSearch() 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 of which is the number of characters in the match, and the third element of which is a string giving the actual text of the match.

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". 

reGetGroup()

The built-in function reGetGroup() lets you retrieve the matching text for a parenthesized group within a regular expression match. 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() function:

  /* search for a telephone number */
  ret := reSearch('(%([0-9][0-9][0-9]%) *)?'
                  + '[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]', cmd);

  /* if we found a telephone number, convert it to a string */
  if (ret != nil)
  {
     /* get the telephone number group information */
     ret := reGetGroup(1);

     /* enclose the telephone number in quotes */
     cmd := substr(cmd, 1, ret[1] - 1) + ' "' + ret[3] + '" '
            + substr(cmd, ret[1] + ret[2], length(cmd));
  }  

Debugging Parsing Problems

Because of the complexity of the built-in parts of the parser, as well as the complicated interaction between the built-in parser system and the game-defined customization hooks, it’s sometimes difficult to predict the exact sequence of events that occurs when parsing a particular sentence. This can in turn make it difficult to figure out why the parser isn’t treating a command the way you want it to.

The most powerful method of tracking the interactions with customization hooks is to use the TADS Debugger. This tool lets you trace through your code line by line while inspecting the values of properties and variables.

At times, though, you may want to know what’s going on inside the built-in parser system. While the built-in parts of the parser are not accessible to TADS code or the TADS Debugger, the parser does provide a specialized trace facility of its own. To activate the trace facility, your game must call a built-in function:

  debugTrace(1, true); // turn on parser tracing

  // later...
  debugTrace(1, nil);  // turn off parser tracing

The function call debugTrace(1, true) tells the parser to start tracing. In trace mode, the parser displays numerous status messages as it analyzes each command; these messages, displayed to the main game text window, provide information on how the parser is internally interpreting the words in the player’s command.

Once you turn on tracing, the parser remains in trace mode until you turn it off with a call to debugTrace(1, nil).

Note that parser tracing is always available, regardless of whether you’re running your game with the TADS Debugger or the normal TADS Interpreter. debugTrace returns no value when called with these arguments.


 

Chapter Four Table of Contents Chapter Six