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 Six


The Parsing Sequence

The rest of this chapter is devoted to the details of how the parser works. The sections are organized in roughly the same order as the steps the parser executes as it analyzes and executes a player’s command.

When the player types a line of text into your game, the TADS parser goes through a series of operations to convert the command line into simple actions on objects in the game. In the course of these operations, the parser makes calls to your game program for advice; in most cases, the parser has default actions for these calls, so you don’t need to include any code in your game program for the default behavior. You can, however, customize the parser by providing definitions for these “hooks” in your game. The main purpose of this chapter is to describe the way the parser interacts with your game program, so that you can customize the parser by overriding the defaults. We also describe many of the internal operations of the parser so that you can understand how to make it behave the way you want it to.

The TADS parser divides the process of interpreting a player’s command into essentially three phases:


Phase 1: Parsing Words and Phrases

The TADS parser begins interpreting the player’s wishes by reading a command line, then breaking the command down into words and phrases.


Reading a Command Line

The first step in parsing is getting a command line from the player. The parser does this automatically, without any action by your game program, at the beginning of each turn. The parser displays its prompt, then waits for the user to enter a line of text.

Prompting: commandPrompt() and commandAfterRead()

The prompt is the first parser default action that you can override: if your game defines a function called commandPrompt, the parser will call this function when it needs to display a prompt; otherwise, it will display its default prompt, which is a greater-than sign (>). The commandPrompt function is called with an argument that lets you display distinct prompts for different situations, if you wish; the default prompt is always the same, regardless of the type of input being requested, but you can use different prompts for the different types of input if you prefer. The commandPrompt argument values are:

0 - Used for normal commands.

1 - The player typed an unknown word, and the parser is reading a new command that can be an “oops” command. If the player types a command line that begins with the word “oops” (or “o”), the parser will substitute the next word for the unknown word in the previous command, and go back to parsing the previous command. Otherwise, the parser treats the new line as a brand new command.

2 - The player has referred to an ambiguous object, and the parser wants the user to provide more information. The parser displays a message such as “Which book do you mean, the red book, the blue book, or the green book?”, and then reads a command with prompt 2. If the player types something that looks like an answer to the question (for example, “the blue one” or “green book” or simply “red”), the parser uses that information to disambiguate the object; otherwise, it treats the new line as a brand new command .

3 - The player has typed a command that has no direct object, but the verb requires a direct object. The parser displays a message such as “What do you want to open?”, then prompts with prompt 3. If the player responds with an object name, it is used as the direct object of the original command, otherwise the text is treated as a new command.

4 - The same as 3, but used to prompt for indirect objects.

Immediately after the calling commandPrompt, the parser reads a line of text from the player. After the player presses the Enter or Return key to enter the command, the parser calls another game-defined function called commandAfterRead; this function takes the same argument as the immediately preceding commandPrompt call. You can use commandAfterRead to reverse any text effects that you established in commandPrompt; for example, if you set a special font with the <FONT> tag in HTML mode, you can turn off the font with </FONT>.

Here’s an example definition of commandPrompt that displays a long prompt for the first few commands, then changes to a shorter prompt. The longer prompt is not displayed when the parser asks the player a question.

  commandPrompt: function(typ)
  {
    if (global.promptTurns = nil)
       global.promptTurns := 0;
   
    "\b";
    if (typ = 0 or typ = 1)
    {
      global.promptTurns++;
      if (global.promptTurns > 5)
         "What do you want to do now?\n";
      else if (global.promptTurns = 5)
         "Aren't you tired of this long prompt?
          I sure am.  From now on, the prompt
          will just be like this:\n";
    }
    ">";
  }

preparse()

After calling commandAfterRead, the parser will tell your game about the new command by calling your preparse function. The parser calls this function with the original text of the player’s command as the argument. If you don’t provide a preparse function in your game program, the parser skips this step. If you do provide a preparse function, it can return one of three values: it can return true, in which case the command is processed as normal; it can return nil, in which case the command is skipped entirely, and the parser immediately asks the player for a new command; or it can return a string, in which case the returned string is used as the command instead of the line of text typed by the player.

preparse() Return Values

true - process the command as normal

nil - skip the command entirely

string - process the string as the command line, instead of the player’s original entry

You can use the preparse() function to completely replace the standard parser, if you want. TADS provides several built-in functions that can help with this; refer to the sections on programmatic parser access and regular expression searching for additional information.

Blank Lines: pardon()

If the player enters a blank line in response to a command prompt, the parser calls a function called pardon in your game program. You must provide a function with this name. This function takes no arguments; its only purpose is to provide an opportunity for you to display an error message when the player enters an empty command. Here’s an example:

  pardon: function
  {
    "I beg your pardon? ";
  }


Breaking the Command into Words

Once the parser has read a line of text from the user, the next step is to break the command into individual words. The parser does this based entirely on the characters in the command line - at this stage, it doesn’t pay any attention to whether it recognizes any of the words. People who write compilers call this step “lexical analysis” or “tokenization,” because it involves breaking up a string of characters into individual units (or “tokens”) by classifying the characters and grouping related characters.

First, the parser converts the entire command to lower-case. Next, the parser scans through the command line, and takes each group of letters and numbers to be a word, and keeps any strings enclosed in double quotes or single quotes together.

For example, assume the player types this:

  >Joe, Go West, then type "hello, world!" on the Acme-Tech computer’s keyboard.

The parser converts this to a string of words and symbols:

joe

,

go

west

,

then

type

"hello, world!"

on

the

acme-tech

computer's

keyboard

.

Note that the punctuation marks are all treated as separate words, and the spaces are all ignored. Note also that apostrophes and dashes are considered to be equivalent to letters, so things like “Acme-Tech” and “computer's” are treated as single words.


Checking for Special Words

After breaking the command into words, the parser checks the word list for the special words listed in your specialWords directive. If your game doesn’t provide a specialWords directive, the parser has a built-in list that it uses (since adv.t provides a specialWords directive, your game probably has one whether you’ve entered one or not). Any word in the word list that matches one of the special words is converted to a flag indicating that it is that special word.

Note that this conversion doesn’t apply to the special word in the “of” slot. This special word is matched later, and not converted along with the other special words, because it is often useful for “of” to be an ordinary preposition, which wouldn’t be possible if it were converted at this point.

As with the previous step, where the command was broken up into words, this step is done automatically by the parser without any calls to your game.


Dictionary Look-up

After the parser has broken up the sentence into words and found any special words in the command, the parser looks up each word in the dictionary. The dictionary is an internal table of strings that the parser maintains; each string is associated with the objects that define a vocabulary property (verb, preposition, noun, adjective, plural, or article) using the string. The dictionary provides a very fast way to find all of the objects that define a particular vocabulary word.

For each word in the command that exists in the dictionary, the parser flags each word with all of the parts of speech (noun, adjective, etc) under which the word is listed in the dictionary. For example, if you define “light” as a noun for one object, but as an adjective for another object (a light switch, perhaps), the word appears twice in the dictionary, so the parser will tag the word “light” in a player’s command as being both a noun and an adjective.

Unknown Words

For each word that is not found in the dictionary, the parser flags the word as unknown. In general, TADS does not accept commands with unknown words; however, at this stage in the parsing process, TADS merely flags the word and proceeds with the command.

Later, in the course of processing the command, TADS will encounter the unknown word, and will know more about the context in which the word was used. At that point, TADS will take an appropriate action that depends on the context:


Assembling the Words into Commands

The next step is to find the individual commands on the command line. The command line can be made up of multiple commands, separated by the words “then” or “and,” or by various punctuation marks: commas, periods, exclamation points, and question marks.

The word “then,” and the period, exclamation point, and question mark are unambiguous - they always separate commands. So, the first thing the parser does is scan through the command line, looking for one of these symbols. If it finds one or more at the beginning of the command, it simply discards them. Otherwise, it takes all of the words up to the first occurrence of one of these symbols, and treats that as a command. For example, consider this command:

  >Joe, go west, then open the door and the window and go east.

The parser takes the part up to the “then” as the first command. It keeps doing the same thing through the rest of the command, which means that it will at this point break the line up into two commands:

  Joe, go west
  open the door and the window and go east

Note that the word “and” and the comma are ambiguous, since they may separate entire commands, or objects within a single command. The parser delays any decisions about these symbols until it has further analyzed the command; so, although the second line above actually consists of two separate commands, the parser at this point is considering it to be a single command.

The parser now processes these commands individually. It processes each part of the command in sequence, until either it has processed all of the commands, or an error occurs, or your game program executes an exit, exitobj, or abort command.


Checking for an Actor

Now the parser starts processing the individual commands making up the command line. The first thing it does is to check for an actor prefix. The player can specify that a command is to be given to a particular actor by putting the actor’s name followed by a comma at the very start of a command. The sentence “Joe, go west” starts with an actor’s name and a comma, so the command “go west” is directed to Joe.

Checking for an actor is the first processing of the command that involves the vocabulary words defined by your game (other than the special words). To determine if the group of words in front of the comma is indeed the name of an actor, the parser looks at each word in turn, and applies the noun-checking rules; see the section on noun phrases for details on these rules.

If the words do form a noun phrase, the parser will next attempt to determine if the noun phrase refers to an object that can be used as an actor. To do this, it first forms a list of all of the objects in the game that match all of the words in the noun phrase. For example, if the sentence starts with “black knight,” the parser builds a list of all of the objects in the game that have both the adjective “black” and the noun “knight.” If the list has more than one object, the noun phrase is ambiguous, so the parser must “disambiguate” the object to determine what the player intended.

First, the parser goes through the list and notes which objects in it are visible. To do this, it calls the method isVisible(parserGetMe()) on each object in the list - this method returns true if the object is visible from the given vantage point (parserGetMe(), the current player character object), nil if not. This step is used entirely to determine how to report any errors that happen later; an object may be visible, but not valid as an actor, in which case a different message is required than if the object isn’t present at all.

Next, the parser determines whether each object can be used as an actor. For each object, the parser calls the validActor method, which returns true if the object can be used as an actor. The default definition of this method in adv.t returns true for any object if the object can be reached by the player. You can override this method to achieve different effects; for example, if the player is carrying a walkie-talkie, you could make any actors that have the other walkie-talkie valid as actors, since they can hear the player through the radio, even though they may not be in the same room.

Note that validActor is intended to determine whether an object is valid as an actor, not necessarily whether the object is logical as an actor. Hence, the validActor method defined in adv.t applies to all objects, not just actors. This method is used in determining whether an object can be addressed by the player at all; if a head of lettuce is present in the same room, the player should be able to address it, even though doing so may not do any good.

For any object which passes the validActor test, the parser notes whether the object is a “preferred” actor, by calling the preferredActor method on each object. This method returns true if the object is generally suitable for use as an actor, nil otherwise. The default definitions in adv.t return true for this method for all objects of class Actor, nil for other types of objects.

After removing all objects that failed the validActor test, the parser looks at its list to see what’s left.

If no objects remain, the player has tried to talk to something that is not currently accessible as an actor, or which doesn’t exist at all. If none of the objects in the original list were visible, the parser issues error 9: “I don't see any %s here.” (Note that the “%s” is replaced with the text of the player’s noun phrase. So, if the original command was “Joe, Go West”, the error message reads: “I don't see any joe here.”) The reason the parser uses the player’s original words in the error message is that the parser can’t determine what object the player is referring to - it can only use the words the player originally entered. Note that the parser always converts the player’s words to lower-case.

If more than one object remains, the parser goes back and looks at the results of the preferredActor method for each object remaining. If any objects returned true from this method, the parser considers only those objects; if all returned nil, the parser ignores the preferredActor results and keeps the original list. If exactly one object returned true from preferredActor, the parser uses that object as the actor. Otherwise, it must ask the player which of the remaining objects was intended; this process is the same as for any other object, and is described below.

If exactly one object remains, the parser uses that object as the actor. The process of determining the actor is completed.


Identifying the Verb

Once the actor (or absence of an actor) has been determined, the parser finds the verb. This is one of the simpler parts of parsing, because the verb must always be the first word of the command. The parser takes the first word and checks to make sure it can be used as a verb; if not, message 17 (“There's no verb in that sentence!”) is displayed, and the command is aborted.

When you define a verb, you can specify one word or two. For example, the verb “take” is specified with a single word, whereas “pick up” is specified with two words. In English (and some other languages), a preposition can be associated with a verb in such a way that it effectively becomes part of the verb - the preposition’s presence changes the meaning of the verb so much that the verb-preposition combination is effectively a whole new verb: “throw away” has a meaning entirely different from “throw.” TADS supports this construct with two-word verbs. Note that when you define a two-word verb, the second word in the two-word verb must be separately defined as a preposition - the parser does not automatically create a preposition for the word. For example, a verb defined as “pick up” requires that the preposition “up” be defined as well (these particular examples are defined in adv.t).

If the word following the verb is defined as a preposition, the parser checks to see if it’s defined as part of a two-word verb with the verb in the sentence. If it is, the parser takes the pair of words as the verb - if the player types “pick up” as the first two words of a command, the parser takes the pair as the verb-preposition combination. If the preposition does not go with the verb, and the same word can be used as a noun or an adjective, the parser takes the verb to be the first word of the command only, and assumes the second word is being used as part of a noun phrase.

Once the verb is identified as the first one or two words of the sentence, the parser checks to see if anything remains. If the next word is a sentence separator (“and” or a comma), or if no more words follow, the parser takes it as the end of the sentence, and executes the verb without any objects.

If the next word starts a noun phrase, the parser reads the noun phrase that follows, and then checks to see what follows that. If the next word is a sentence separator, the parser takes it as the end of the sentence, and executes the verb with the noun phrase as the direct object.

If, instead, the next word is a preposition, the parser checks what follows the preposition. If the next word starts another noun phrase, the parser reads this second noun phrase, then executes the command with both a direct and an indirect object, with the preposition separating them.

If the word following the preposition is a sentence separator, the parser takes the preposition as part of the verb. With verb-preposition combinations, the preposition is sometimes placed immediately after the verb, as in “pick up the book,” but can also be put at the end of the sentence, as in “pick it up.” So, if the parser finds a preposition at the end of the sentence, it treats the preposition the same way it would have if the preposition had immediately followed the verb.

Note that many of these cases can be ambiguous - based on the parts of speech of the words in a sentence, more than one interpretation is possible. The parser’s rules as described above are designed to choose the most sensible interpretation, but sometimes the results will not be exactly what you may have intended. For example, if your game has an object defined as an “up button” (in an elevator, for example), the word “up” will end up defined as a verb, preposition, and adjective. If the player types “push up button,” and no verb is defined as “push up,” the parser will know that “push” and “up” don’t go together as a verb and will interpret this as applying the verb “push” to the object “up button.” However, if the player types “pick up button,” the parser will interpret this as applying the verb “pick up” to the object “button”; if you also have a down button, the parser will ask the player which button to take - which would be confusing if the player had intended to apply the verb “pick” to the object “up button.”

Ambiguous word definitions are very difficult to anticipate, because there are so many possible combinations of words in even a small game. The best way to find these is to test your game, and have other people test it.

Note that in the course of identifying the verb, the parser has also identified the noun phrases that make up the direct and indirect objects, and the preposition that separates the direct and indirect objects. For the command “open the door and the window,” the parser identifies the following sentence components:

actor: Me

verb: open

direct object: the door and the window

preposition: none

indirect object: none

For the command “joe, pick up the ball with the tongs,” the parser will identify the following elements:

actor: Joe

verb: pick up

direct object: the ball

preposition: with

indirect object: the tongs


Unknown Verbs and Syntax: parseUnknownVerb()

If the parser is unable to find a valid verb for the command, or is unable to fit the sentence into a valid syntax pattern (because too many or too few objects are present, the preposition is not valid for the verb, or there is an unknown word where the preposition would normally go), the parser tries invoking a game-defined function called parseUnknownVerb. This function is defined as follows:

  parseUnknownVerb: function(actor, wordlist, typelist, errnum);

“actor” is the current actor object. The “wordlist” parameter is a list with the strings of the words in the command, in the same format as the list that is passed to preparseCmd. The “errnum” parameter is the parser error number for the condition that caused the call to parseUnknownVerb; this is the same error number that is passed to parseError (and related functions).

The “typelist” argument is a list of the types of the words in the “wordlist” parameter. Each element of “typelist” gives the word type of the corresponding element of “wordlist” (so typelist[3] gives the type of the word in wordlist[3], for example). Each type is a number, which can contain any number of the values below combined with the bitwise OR operator (“|”). To test for a particular type, use an expression like this: ((typelist[3] & PRSTYP_NOUN) != 0). The type values, defined in adv.t, are:

PRSTYP_ARTICLE - the word is defined as an article

PRSTYP_ADJ - adjective

PRSTYP_NOUN - noun

PRSTYP_PLURAL - plural

PRSTYP_PREP - preposition

PRSTYP_VERB - verb

PRSTYP_SPEC - special word (“.”, “of”, “and”, etc.)

PRSTYP_UNKNOWN - the word is not in the dictionary

This function can return, true, nil, or a number, or it can use abort to abort the command.

Returning true indicates that the function has successfully handled the entire command itself; the parser does not display any error messages, it executes fuses, daemons, and the endCommand function (passing nil as the verb object to endCommand), and it proceeds to continue parsing any remaining text on the command line (after a period or “then”).

Returning a number (greater than zero) indicates success, just as true does, but also indicates that the function parsed only the words before the returned index, and that the remaining words (starting with the word at the index value returned) are to be considered an additional command. The parser will run fuses, daemons, and the endCommand function as normal, and then will resume its normal parsing, starting with the word at the index given by the return value. You can use this if you find “and” following a noun phrase that you parse, or if for any other reason you find that another sentence follows and should be parsed separately. For example, if you succesfully parse the first three words of the list, you should return the value 4 to indicate that you want the parser to apply the default parsing starting with the fourth word in the list.

Returning nil indicates that the function has failed to handle the command, and wants the parser to display the default error message. The parser will display the message in the normal fashion, using parseErrorParam or parseError as appropriate, and will abort the command. No fuses or daemons will be executed, the endCommand method will not be invoked, and any remaining text on the command line will be discarded.

If this function uses abort to end the command, the parser will not execute any fuses or daemons, and it will ignore any remaining text on the command line. The parser will, however, call the endCommand function, passing the EC_ABORT status code to indicate that the command was aborted. The difference between returning nil and executing an abort statement is that the parser will display the default message and will not execute endCommand when this function returns nil; the parser will not display anything, and will invoke endCommand, if the function uses abort.

The parseUnknownVerb function is currently called with the following error codes:

17 - There's no verb in that sentence!

18 - I don't understand that sentence.

19 - There are words after your command I couldn't use.

20 - I don't know how to use the word "%s" like that.

21 - There appear to be extra words after your command.

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

24 - I don't recognize that sentence.

Error code 17 indicates that the first word in the sentence is not defined as a verb (which may mean that the word is entirely uknown, or that it’s defined as another part of speech but not as a verb). 18 means that a noun phrase was not formed properly, or that the combination of the verb and verb preposition (“pick up,” for example) is not defined. 19 means that a preposition occurred at the end of a sentence, but it could not be combined with the verb. 20 indicates that the word separating the indirect object is not defined as a preposition. 21 means that another word follows what the parser thinks should be the last word in the sentence (for example, the sentence ends with two prepositions). 23 means that the deepverb object has no defined templates (action, doAction, or ioAction). 24 indicates that too many objects were used with the sentence: a direct object is present, but the deepverb doesn’t have a doAction, or an indirect object is present, but the deepverb doesn’t have an ioAction.

The purpose of this function is to let you defined your own parsing for commands outside of the bounds of the built-in parser. Although this function is similar to preparseCmd, it differs in that parseUnknownVerb runs only when the parser can’t handle a command directly, which means that parseUnknownVerb doesn’t have to decide whether or not to pass the command to the parser. In addition, parseUnknownVerb integrates into the turn-handling mechanism, in that it can control fuse and daemon execution and endCommand execution, as well as the handling of remaining text on the command line.

If the game doesn’t define a parseUnknownVerb function, the parser simply displays the appropriate error message and aborts the command.

The parseUnknownVerb function lets you take control of the entire parsing process for a command. In some cases, you will want to do everything yourself based on the strings in the command. In other cases, though, you might want to perform portions of the normal parsing process; for example, you might want to parse or resolve a noun phrase. The parser provides a number of built-in functions that let you invoke specific parts of the built-in parser; refer to the section on programmatic parser access for details.


Noun Phrases and Objects

TADS provides a built-in noun phrase parser; this section describes how this works. However, before performing the standard built-in parsing, TADS calls a game-defined function called parseNounPhrase(), if it exists, to allow the game to perform custom noun phrase parsing.

A noun phrase is made up of an optional article, one or more optional adjectives, a noun or a plural, and optionally the word “of” (or an equivalent defined with specialWords) and another noun phrase. A word is a noun if it matches a word defined in the noun property of an object in your game; likewise for adjectives, plurals, and articles.

Certain special words can be used as noun phrases. The word “all” is by itself a valid noun phrase, as are the pronouns (it, him, her, and them). “All” or “all of” followed by a plural noun phrase (which is a noun phrase whose last word is a plural rather than a noun) is a noun phrase, equivalent to the plural noun phrase without the “all” or “all of.” Similarly, “both” and “both of” can be used in exactly the same way. “Any” followed by a noun phrase, or “any of” followed by a plural noun phrase, can also be used; these tell the parser to arbitrarily pick one of the objects indicated.

Numbers: As Counts

The player can also specify a count with a plural or with “any.” For example, phrases such as “3 books,” “any 3 books,” “3 of the books,” and “any 3 of the books” work the same as “any book,” but the parser will (arbitrarily) choose three of the named objects in this case, rather than just one. If the number is “1,” the parser allows this format with a singular noun phrase as well: “1 book” or “any 1 book,” which are equivalent to “any book.”

The player can also use multiple noun phrases, by separating each with “and” or a comma.

During the first phase of parsing, before starting to execute the command, the parser identifies all of the possible objects for each noun phrase. After finding the words involved in the noun phrase (which is done entirely on the basis of their defined parts of speech, as described above), the parser makes a list of all of the objects which match all of the words in the noun phrase. For example, if the noun phrase is “pile of red paper,” the parser finds every object which has “pile” defined as a noun, “red” defined as an adjective, and “paper” defined as a noun. The parser intersects these lists of objects to arrive at a list of objects that have all three words defined.

In most circumstances, a number entered as part of a command serves as a noun, and is assigned to numObj (see the section on object resolution). In some cases, though, you may want to use a number as part of the name of an object. For example, you might have a five dollar bill, in which case you would want “5” to serve as an adjective for the object. Similarly, if you have an elevator with a button for each floor, the buttons will be called “button 3,” and so on.

Numbers: As Adjectives

The parser allows you to enter numbers as adjectives. For example:

  button3: floorButton
    adjective = '3' 'three'
    floor = 3
  ;

When a number is defined as an adjective, and the object is present, the parser allows the number to be used as either a prefix or a suffix in the noun phrase: “button 3” and “3 button” (as well as “three button”) are valid noun phrases referring to the button.

Numbers: As Adjectives, Part 2

The parser also lets you use any number as an adjective for certain objects. This can be useful when you want to present the player with a large collection of numbered objects, but you don’t want to create a separate TADS object for each game object.

For example, suppose your game has a post office lobby, which has ten thousand post office boxes numbered 10000 through 20000. It would be inconvenient to code all ten thousand boxes manually; fortunately, TADS has a way to code this conveniently.

To define an object that can accept any number as an adjective, define the object with the special adjective value ‘#’:

  postOfficeBox: fixeditem, container
    noun = 'box'
    plural = 'boxes'
    adjective = 'po' 'post' 'office' 'p.' 'o.' '#'
    location = postOfficeLobby
    sdesc = "post office box";

The special adjective '#' tells the parser that the object can be used with any number. The parser accepts the syntax “box 11000” and “11000 box” as equivalent, just as for an object with a specific number as an adjective.

When the player refers to the object in a command, the parser requires a number to be used. If the player refers to the object without a number (as in “open po box”), the parser responds with error 160, “You'll have to be more specific about which %s you mean” (where “%s” is replaced by the phrase the player entered that referred to the object). You can customize this message with parseErrorParam or parseError as with any other default parser error message.

If the player enters a number with the object name (“box 11000”), the parser calls the newNumbered method in the object:

  postOfficeBox.newNumbered(actor, verb, num);

The actor parameter is the actor object for the command, verb is the deepverb object for the command’s verb, and num is the number the player typed. For example, if the player types “open box 11000”, the call would look like this:

  postOfficeBox.newNumbered(Me, openVerb, 11000);

The num parameter can also be nil: this indicates that the player referred to the object in the plural (“look in boxes”). This indicates that the player wants to perform the command on the entire collection of objects represented by the numbered object.

The newNumbered method must return either an object or nil. If it returns nil, the method should first display an error message, because the parser will simply cancel the command without any further messages when the method returns nil. If the method returns an object, the parser uses this object, instead of the original object itself, as the numbered object.

The standard library, adv.t, includes a class called numberedObject that is helpful to implement numbered objects. This class provides a newNumbered method that creates a copy of the object, using operator new, and sets the new object’s value property to the number the player typed. For example, if the player types “box 99”, newNumbered creates a new copy of the object, sets the copy’s value property to 99, and returns the new object. numberedObject also defines another method, num_is_valid(num), that you can override in your subclasses of this object. numberedObject.newNumbered calls num_is_valid with the player’s number as the argument to determine if the number is valid for the object. By default, this method returns true; you can override this method if you want to restrict the range of valie numbers. For example, for the post office boxes of our earlier example, we want to allow only numbers from 10000 through 20000:

  postOfficeBox: fixeditem, container, numberedObject
    noun = 'box'
    plural = 'boxes'
    adjective = 'po' 'post' 'office' 'p.' 'o.' '#'
    location = postOfficeLobby
    sdesc = "post office box"

    num_is_valid(num) =
    {
      if (num >= 10000 && num <= 20000)
      {
        /* it's in our valid range */
        return true;
      }
      else
      {
        /* it's outside our range */
        "The boxes are numbered from 10000 through 20000. There doesn't
        seem to any box numbered <<num>>. ";
        return nil;
      }
    }
  ;

The newNumbered method defined in numberedObject returns the original object by default if the player refers to the object with a plural. If you want to override this to use a special object for plural references, override the method newNumberedPlural(actor, verb) to return a different object. If you don’t want to allow plural references at all (so something like “open post office boxes” would not be allowed), override newNumberedPlural to display an appropriate error message and return nil.

The numberedObject class defines dobjGen and iobjGen methods that don’t allow any action to be taken on the object in the plural; they do this by checking to see if the value property is nil, which indicates that the object is the original (plural) object. These methods simply display “You'll have to be more specific about which one you mean” and otherwise ignore the command. You may want to override this behavior for some verbs; for example, in most cases, you’ll want to provide a generic description of the collection of objects in response to “examine” commands, which you could do by adding this to the postOfficeBox definition above:

    dobjGen(actor, verb, iobj, prep) =
    {
      if (self.value != nil or verb != inspectVerb)
        inherited.dobjGen(actor, verb, iobj, prep);
      else
      {
        "The boxes are lined up in a neat grid, and completely fill
        the north wall.  The boxes are numbered 10000 to 20000. ";
        exitobj;
      }
    }

When you create an object with new, you must always take care of deleting the object when it is no longer needed. Fortunately, numberedObject takes care of this itself, by automatically setting a fuse to delete the newly created object at the end of the turn. While this means that you don’t need to worry about deleting the object that newNumbered creates to represent the individual numbered object, it also means that you must be careful not to refer to the new object after the end of the turn, because the new object will not be valid after it is deleted at the end of the turn.

This also means that any state that you set in the newly-created object will not persist after the end of the turn, because any subsequent reference to the same number by the player on a new turn will create another brand new object. If you want the state of a particular number to persist beyond the end of the turn, you must use another mechanism to keep track of the state. The easiest approach is to avoid this problem entirely, with something like this:

  doOpen(actor) =
  {
    "You slip your master key into the lock on
    box <<self.value>>, open the box, and look inside,
    but the box is empty.  You close the door and re-lock the box. ";
  }

The parser uses one additional method in some circumstances. When the player uses “any” with an object whose adjective list contains ‘#’ the parser evaluates the property anyvalue(n) for the object. The parameter “n” is a number indicating how many of these objects has been requested so far for this command. Currently, it’s always 1; in the future, a command such as “push any 3 buttons” may call this method three times, with “n” set to 1, 2, and 3, respectively, for the three calls. For the time being, “push any 3 buttons” is treated by the parser as identical with “push buttons”. The default implementation of anyvalue in numberedObject in adv.t simply returns the same number “n” that it received as a parameter; if your valid number range doesn’t start with 1, you should override this method to return a number in your object’s valid range.


Custom Noun Phrase Parsing: parseNounPhrase()

Before performing the standard built-in parsing described above, the parser calls a game-defined function, if it exists, that lets you do custom noun-phrase parsing. The function is called parseNounPhrase(), and you define it as follows:

  parseNounPhrase: function(wordlist, typelist, current_index,
                            complain_on_no_match, is_actor_check)

The parameter “wordlist” is a list of strings, where each string is a token in the player’s command. This is the same type of list that preparseCmd() receives.

“typelist” is a list of word types for the tokens in the list. The types are bit flag values, so each element may have multiple types combined with OR. To test to see if a particular type flag is set, use the bitwise AND operator, “&”; for example, to test the second element to determine if it’s a noun, use this expresion:

  ((typelist[2] & PRSTYP_NOUN) != 0)

The type flag values, defined in adv.t, are:

PRSTYP_ARTICLE - the word is defined as an article

PRSTYP_ADJ - adjective

PRSTYP_NOUN - noun

PRSTYP_PLURAL - plural

PRSTYP_PREP - preposition

PRSTYP_VERB - verb

PRSTYP_SPEC - special word (“.”, “of”, “and”, etc.)

PRSTYP_UNKNOWN - the word is not in the dictionary

“current_index” is the index in the word list of the start of the noun phrase. This function can look at the previous words in the list if desired, but the parser has already determined that words before “current_index” are part of the verb or of another part of the command. This function should start parsing at “current_index”.

“complain_on_no_match” is a boolean value (true or nil) indicating whether the function should display an error message if the noun phrase has no matching objects. If this parameter is true, you should display an appropriate message on this type of error; otherwise, you should not display any message in this case. You should always display an error if the noun phrase is not syntactically correct; the “complain_on_no_match” parameter applies only to error messages for syntactically correct noun phrases that don’t refer to any objects.

“is_actor_check” is a boolean value indicating whether the function is being called to check for an actor noun phrase. When this is true, the function should not allow syntax that is obviously inappropriate for an actor, such as the word “all,” or a string or number; in such cases, the function should simply return an empty object list to indicate that no valid actor is present.

parseNounPhrase() can do one of four things: it can parse the noun phrase, and return a list of matching objects; it can determine that no noun phrase is present; it can indicate that a noun phrase is present but contains a syntax error; or it can let the parser perform the default noun phrase parsing.

If this function parses a noun phrase successfully, it should return a list. The first element of the list must be a number, which is the index of the next token in the word list after the noun phrase. For example, if “current_index” is 3 when the function is called, and the noun phrase consists of one word, the first element of the returned list should be 4. This tells the parser where it should resume parsing.

The remaining elements of the returned list are pairs of elements; the first of each pair is a game object matching the noun phrase, and the second is a number giving flags for the object. It’s not necessary to determine whether or not the objects are accessible, reachable, visible, or anything else; the parser will disambiguate the list later, when it knows more about the sentence structure. parseNounPhrase() should simply return a list of all of the objects that match the vocabulary words.

For the flags value in each object/flag pair in the returned list, multiple flag values can be combined with the bitwise OR operator, “|”. The flags, defined in adv.t, are:

PRSFLG_ALL - the entry is for the word “all” or equivalent

PRSFLG_EXCEPT - the entry is excluded from the “all” list

PRSFLG_IT - the entry matched the pronoun “it”

PRSFLG_THEM - the entry matched the pronoun “them”

PRSFLG_HIM - the entry matched the pronoun “him”

PRSFLG_HER - the entry matched the pronoun “her”

PRSFLG_NUM - the entry is a number

PRSFLG_STR - the entry is a string

PRSFLG_PLURAL - the entry matched a plural usage

PRSFLG_COUNT - the entry has a numeric count as the first word

PRSFLG_ANY - the entry was qualified with “any”

PRSFLG_UNKNOWN - the entry contains an unknown word

PRSFLG_ENDADJ - the entry ends with an adjective

PRSFLG_TRUNC - the entry uses a truncated word

Some examples might be helpful, since the return value is complicated.

For “all” and for the pronouns (it, him, her, them), you should return a list containing nil as the object, and the appropriate flag value. For example, if the noun phrase is simply the word “everything”, you would return this (assuming that the index of the word “everything” was 2):

  [3 nil PRSFLG_ALL]

Similarly, if the noun phrase was simply “her”, you would return:

  [3 nil PRSFLG_HER]

The construction “all except” is also special. You would return a list whose first entries are nil and PRSFLG_ALL, just as though you were parsing a simple “all” phrase, but then you’d add entries for all of the items in the “except” list, with each additional entry’s flag including PRSFLG_EXCEPT. For example, if the player typed “take all except book and candle”, you might return something like this:

  [7 nil PRSFLG_ALL book PRSFLG_EXCEPT candle PRSFLG_EXCEPT]

Strings and numbers work the same way: return nil for the object, and set the appropriate flag. For example, if the player typed “type 'hello' on keypad”, you’d return this:

  [3 nil PRSFLG_STR]

If you encounter an unknown word in the noun phrase, and you want to let the parser resolve the unknown word using its normal mechanisms, you should return a nil object with the PRSFLG_UNKNOWN flag set:

  [4 nil PRSFLG_UNKNOWN]

If you simply want to return a list of objects that match the noun phrase, it’s easy:

  [4 book 0 candle 0]

The parser also lets you omit the flags entirely, if you don’t need to include any flags with an object. If an element that follows an object is another object (or nil), the parser will assume that the flag value for the preceding object is zero. So, for the example above, this list is equivalent:

  [4 book candle]

If the function parses a noun phrase successfully, but can find no objects matching the words in the noun phrase (in other words, the words form a valid noun phrase syntactically, but don’t actually refer to any object in the game), it should return a list that contains only the index of the next word after the noun phrase.

If the function determines that a noun phrase appears to be present, but is not syntactically correct, you should display an error message and return PNP_ERROR. You should not display an error if it doesn’t look like a noun phrase is present at all; instead, you should simply return a list consisting of the original “current_index” value to indicate that you didn’t parse any words at all. You should only display an error and return PNP_ERROR if you determine that a noun phrase is actually present, but is syntactically incorrect.

If your function determines that it doesn’t want to parse the noun phrase after all, it should simply return PNP_USE_DEFAULT. This tells the parser to proceed with the default parsing for the noun phrase.

The default noun phrase parser (built in to the TADS interpreter) does not attempt to resolve or disambiguate objects at this stage. Instead, it simply creates a list of all of the objects that match every word in the noun phrase. The reason that the parser doesn’t try to resolve the objects at this stage is that the parser doesn’t have enough information at this point in the parsing sequence. So, the parser merely determines the syntactic structure of the noun phrase, and ensures that at least one object in the game can match all of the words; later, after the parser has fully analyzed the sentence structure and knows the verb, prepositions, and number of objects, the parser resolves and disambiguates the noun phrase. If you write an implementation of this function, keep this design in mind.

In most cases, you will probably not want to write a parseNounPhrase() function that completely replaces the built-in noun phrase parser. Instead, you’ll probably want to check for special cases, and let the standard parser handle everything else. When you see a special case, you should perform your parsing and return an appropriate object list; when you don’t see a special case, you should simply return PNP_USE_DEFAULT to use the built-in parsing system.


Phase 2: Object Resolution

Now that the command has been parsed into phrases, TADS must resolve the phrases into objects defined by the game.

preparseCmd()

Before the parser begins resolving objects, the system calls a game-defined function called preparseCmd(). This functional is optional; if your game doesn’t define one, the parser skips this step. The parser calls the function with a single parameter: a list of (single-quoted) strings, one string per word in the command. For example, for the command “open the door and the window,” the parser calls the function like this:

  preparseCmd(['open' 'the' 'door' ',' 'the' 'window'])

Note that the word “and” became a comma. “And,” and certain other words, are transformed internally before this function is called:

   and       ,   (comma)
   then      .   (period)
   all       A
   but       X
   it        I
   them      T
   him       M
   her       R
   any       Y
   both      B
   one       N
   ones      P

(Note that “one” and “ones” are not always converted to “N” and “P” (respectively), but are usually left as the original text. The only time these transformations apply is when the parser reads the response to a disambiguation question (“which book do you mean...”). In all other cases, “one” and “ones” are left with their original text, so you will never see “N” or “P” in a preparseCmd() list.)

In addition, any double-quoted string in the player’s command is passed to preparseCmd() as the text of the string enclosed in double quote marks. Even when the player enters a string with single quotes, the preparseCmd() list entry will be enclosed in double quotes; this makes it easier to check for a quoted string, since you don’t have to worry about checking for all the possible ways the player might have entered one.

For example, suppose the player enters this command:

  >type 'hello' on it

In this case, the parser will pass the following list preparseCmd():

  ['type' '"hello"' 'on' 'I']

Your preparseCmd function can return one of three values: true, nil, or a list. If the function returns true, processing continues as normal. If it returns nil, the command is abandoned - the parser discards the current command, and goes back to the beginning of the parsing process to get a new command. If the function returns a list, the list must consist entirely of single-quoted strings; the parser will go back to the step described above under “Checking for an Actor,” substituting the returned list for the original command.

Note that the argument to preparseCmd doesn’t include the actor specification. For example, if the player types “joe, go west,” preparseCmd will receive only “go west.” In addition, the list doesn’t include the words separating the command from the previous or next command on the same line. For example, if the player types “go west, then pick up the box,” the function is called first with simply “go west,” then with “pick up the box.”

In addition, this command is called for each command just prior to the steps described below. For example, if the player types “go west, then pick up the box,” the parser first calls preparseCmd with “go west,” and then proceeds through the rest of the processing for this command as described below; after that’s all done, the parser comes back to this step, calls preparseCmd with “pick up the box,” and then proceeds through the steps below once again.

The preparseCmd function is allowed to change a particular command only once. If preparseCmd returns a list value, the parser goes back and starts processing the list as a new command - as a result, the new command will itself be submitted to preparseCmd when the parser gets back to this step. On this second call, preparseCmd is not allowed to return a list; if it does, the parser displays an error that indicates the preparseCmd appears to be stuck in a loop (message 402) and cancels the command.

Here’s a sample implementation of preparseCmd. This implementation converts commands in the format “tell actor to command” to the standard TADS notation for these commands, “actor, command.”

  #pragma C+
  preparseCmd: function(cmd)
  {
    local i, tot, to_loc, actor, the_rest;

    tot = length(cmd);

    /* check to see if it starts with "tell" */
    if (tot > 3 && cmd[1] == 'tell')
    {
      /* see if there's a word "to" */
      for (i = 1, tot = length(cmd) ; i <= tot ; ++i)
      {
        if (cmd[i] == 'to')
          to_loc = i;
      }

      /* if there's a "to", convert the command */
      if (to_loc != nil && to_loc > 2)
      {
        /* find the parts before and after the 'to' */
        for (i = 2, actor = [] ; i < to_loc ; ++i)
          actor += cmd[i];
        for (the_rest = [], i = to_loc + 1 ; i <= tot ; ++i)
          the_rest += cmd[i];

        /* convert it to "actor, command" */
        return actor + ',' + the_rest;
      }
    }

    /* otherwise, process the command as normal */
    return true;
  }

deepverb Identification

After preparseCmd returns true, the parser makes sure it has a valid deepverb object for the verb phrase. If no object is found that defines the verb phrase as one of its verb property values, the parser displays an error (message 18, “I don't understand that sentence”) and cancels the commands.

roomCheck

Next, the parser runs the roomCheck method in the object given by parserGetMe(). The roomCheck method is called with a single argument: the deepverb object. For example, if the player types “take book,” the parser calls this method:

  parserGetMe().roomCheck(takeVerb)

This method should return either true or nil: a return value of true indicates that the command is allowed to proceed. If roomCheck returns nil, the command is cancelled. The roomCheck method is expected to display a message indicating why the command is not allowed, so the parser doesn’t display any additional information when roomCheck returns nil - it simply cancels the command without further comment.

The roomCheck method is intended to be a first, coarse check of whether a command is valid in a room. This method is called prior to any disambiguation, and once for the entire command (regardless of how many direct objects may be involved in the command). The main use of this method is to disallow entire verbs in a room based on the room’s condition; for example, you could use roomCheck to prevent the player from being able to take any objects while in the dark. Since this method is called prior to disambiguation, you can prevent the disambiguation process from displaying any information about the room that the player should not be able to see.

The default roomCheck defined in the basicMe class in adv.t simply returns the value of the roomCheck method in the player’s location. (The roomCheck defined in the movableActor class does the same thing for the actor’s location.) The roomCheck defined in the room class in adv.t always returns true, and the roomCheck defined in the darkroom class in adv.t returns nil unless the location is lit, or the verb is a “dark verb” (i.e., its isDarkVerb property is true), which means that it can be used in the dark. All “system” verbs (such as “save” and “restore”), as well as a few other verbs that don’t logically require light (such as “wait”), are defined in adv.t as dark verbs.

Note that the parser always calls roomCheck for the current player character object, regardless of any actor mentioned in the command. So, the parser calls parserGetMe().roomCheck for “take book” as well as for “joe, take book.” The reason that roomCheck is always applied to the player character, rather than to the command’s targeted actor, is that roomCheck is meant to check to see if the player can even issue such a command right now.


Stopping the Command: exit, exitobj, and abort

Throughout command execution, your methods and functions have the ability to terminate processing of the current command. There are generally two possible reasons you would want to terminate the command:

TADS provides three different ways to stop the current command, each of which has slightly different effects.

Complete Termination: abort

The abort statement completely stops execution of the current command, and in addition discards any remaining commands on the current command line. abort also skips any fuses or daemons for the current turn, so the effect is as though the turn never happened. However, the parser does call the postAction and endCommand functions when abort is executed.

For example, suppose the player enters this:

  >take the box and go north

Suppose that taking the box magically transports the player to a new location. Clearly, because the player wasn’t expecting such an abrupt change, any additional commands on the current line will no longer be applicable. The abort statement is useful for this situation, because it discards any remaining commands.

abort is also useful for “system” commands, such as “save” and “restore,” because it skips fuses and daemons. System commands generally shouldn’t count as turns, because they happen outside of the game world and hence outside of the game’s internal timeline.

Single-Command Termination: exit

The exit statement terminates any remaining execution of the current command, and skips immediately to the postAction function, if any, then to the fuses and daemons for the command. After the fuses and daemons run, the parser calls the endCommand function, then continues to the next command on the line.

exit differs from abort in two important respects. First, fuses and daemons run after exit but not after abort. Second, abort discards any remaining commands on the current command line, whereas exit keeps the remaining commands, and continues execution with the next command on the line.

exit is useful when an error of some kind occurs. In this situation, you usually don’t want to proceed with any further execution of the current command, but you still want the command to consume time in the game (thus you want fuses and daemons to run), and you want to allow any remaining commands on the current line to execute.

Single-Object Termination: exitobj

exitobj is similar to exit, but rather than skipping to the next command, exitobj calls the postAction function, then skips to the next object in the current command if the player specified multiple objects.

exitobj is useful for situations where you have finished processing a command “early” (in the sense that you don’t want the parser to finish making the full sequence of method calls for the command). For example, if you finish executing a command in the actorAction method, you can use exitobj to skip any remaining action processing for the current object, but continue executing the command for any remaining objects.


Verb Templates

The parser must now determine how to execute the command. The first step is to identify the deepverb object associated with the word or words making up the verb; to do this, the parser simply looks up the verb phrase in its dictionary, and finds the object that defined the phrase as one of its verb property values.

A single deepverb can have multiple interpretations; these interpretations are called “templates,” because they serve as models for the possible sentences that can be made with the verb.

TADS verb templates are a subtle concept, because templates are not explicitly stated in a deepverb; rather, they are implied by the presence of the properties action, doAction, and ioAction. If an action method exists for the verb, the template sentence that consists only of the verb is implicitly defined. If a doAction property exists for the verb, the template sentence that consists of the verb and a direct object is implicitly defined. If an ioAction property is defined for a particular preposition object, the verb implicitly defines the template sentence that consists of the verb, a direct object, the specified preposition, and an indirect object.

The “wait” verb defined in adv.t defines only the no-object template:

  waitVerb:  darkVerb
    verb = 'wait' 'z'
    action(actor) =
    {
      "Time passes...\n";
    }
  ;

The “lock” verb defined in adv.t defines both a one-object and two-object template, but doesn’t have a no-object template:

  lockVerb:  deepverb
    verb = 'lock'
    sdesc = "lock"
    ioAction(withPrep) = 'LockWith'
    doAction = 'Lock'
    prepDefault = withPrep
  ;

Note that the ioAction definition can contain certain special flags that affect the way commands matching the template are processed. For example, you could define a template as follows:

  ioAction(aboutPrep) = [disambigDobjFirst] 'TellAbout'

The [disambigDobjFirst] flag (which is the only flag currently defined) specifies that the parser is to disambiguate the direct object first when a sentence involving this template is executed. Normally, the parser disambiguates the indirect object first, then disambiguates the direct object with the indirect object known. For some commands this is undesirable, because it means that the direct object isn’t known when the indirect object is being disambiguated. This flag provides control over the sequence of disambiguation, so that you can determine which object is known first, which allows you to use that object while disambiguating the other.

To choose a template, the parser uses the components of the command, which were determined earlier (see “Identifying the Verb”). If the only non-empty component of the command is the verb, the parser chooses the no-object template; if the deepverb defines an action method, it has a no-object template, and therefore allows no-object sentences.

If the sentence has a verb and a direct object, but no preposition or indirect object, the parser chooses the one-object template. If the deepverb has a doAction property, the verb has a one-object template, and thus allows single-object sentences. The doAction property specifies the verification and action methods for the command: TADS appends the string defined by doAction to the string “verDo” to form the verification method, and “do” to form the action method. For example, if doAction = 'Take', the verification method is called verDoTake, and the action method is called doTake.

If the sentence has a verb, direct object, preposition, and direct object, the parser uses a two-object template. A single deepverb object can define multiple two-object templates; these multiple templates are distinguished from one another by their prepositions. The templates are defined by the ioAction property - each ioAction specifies the preposition which identifies it. If the player types “dig in dirt with shovel,” the parser looks in digVerb for an ioAction(withPrep) definition. (Note that digVerb. and withPrep could actually be called anything - we’re following the naming conventions used in adv.t here, but you can call verb and preposition objects anything you want. The parser doesn’t identify these objects based on their names, but rather based on their vocabulary words, defined by their verb and preposition properties, respectively.)

The parser must now match the components of the sentence entered by the player with the templates available for the verb. Before going on, the parser checks to make sure that the verb object is properly defined: it must define at least one sentence template. If the deepverb object fails to define any templates (that is, it has no action, doAction, or ioAction properties), the parser displays an error (message 23) and cancels the command.


Case 1: A Command with No Objects

If the player’s command has no objects at all, but consists only of a verb, the parser checks to see if the deepverb has an action method; if so, the parser chooses the verb-only sentence template and proceeds with the execution.

If the command has no objects, and the deepverb object does not define an action method, the parser tries to provide a default direct object by calling the doDefault (which stands for “direct-object default”) method in the deepverb object. This method is called with the actor, the preposition, and nil (since the indirect object is not yet known) as arguments. For example, if the player types simply “pick up,” the parser chooses takeVerb as the deepverb object, sees that it has no action property, so attempts to obtain a default object:

  takeVerb.doDefault(parserGetMe(), nil, nil)

This method is meant to return a list of objects that should be used by default for the command; the definition of this method for the takeVerb defined in adv.t returns a list of all movable objects in the player’s location. If this method fails to return a list, the parser simply asks the player to supply a direct object (see below). Otherwise, the parser now gets the value of the prepDefault property in the deepverb object: if this value is not nil, it must refer to a preposition object, which becomes the preposition for the command.

For example, if the player simply types “dig,” the parser gets the default list from the doDefault method defined in digVerb, then gets the value of digVerb.prepDefault. Suppose that prepDefault has a value of withPrep: the command’s components now look like this:

actor: Me

verb: dig

direct object: (the list returned by doDefault)

preposition: with

indirect object: nil (because it’s not yet known)

The parser checks that the template specified by the new components exists for the verb. If it doesn’t, the parser skips checking the doDefault return value, and goes on to ask for a direct object.

If a valid list resulted from doDefault, and the new template is valid, the parser goes through the doDefault list to check each item with the direct object verification method defined by the doAction or ioAction method for the new template. If the template is a single-object sentence, which means that the doAction definition is used, the parser calls the verification method with the actor as a parameter. If the template is for a two-object sentence, which means that the ioAction definition is used, the parser calls the verification method with the actor and nil (because the indirect object is not yet known) as parameters. If the ioAction is used, but the ioAction template specifies the [disambigDobjFirst] flag, the second (nil) parameter is omitted. Some examples:

object.verDoTake(Me)

object.verDoLockWith(Me, nil)

object.verDoTellAbout(Me)

In this third example, we’re assuming that tellVerb’s ioAction property has the [disambigDobjFirst] flag.

The verification methods are called silently, which means that any messages they display are hidden from the user (the same as during the disambiguation process). However, the parser notes whether these methods attempt to display any messages; any verification method that attempts to display a message is considered to fail. The parser discards any objects from the list whose verification method fails.

If exactly one object from the doDefault list passes the silent verification test, that object is chosen as the default direct object for the command. The parser displays the object to the player: if your game program defines the function parseDefault, the parser calls this function with the chosen default object as the parameter, and expects that the function will display a message to indicate that the object has been chosen as the default. Otherwise, the parser displays message 130 (“(”), then calls the method thedesc in the default object, then displays message 131 (“)”). It then starts this step over again with the new set of command components.

Here’s a sample implementation of parseDefault. This definition generates the same message that the parser would if parseDefault weren’t defined.

parseDefault: function(obj, prp)
  {
    "(";
    if (prp) "<< prp.sdesc>> ";
    obj.thedesc;
    ")";
  }

If no objects survive the verification test, the parser doesn’t have any way to supply a default object. If more than one object survives, the parser still does not supply a default object, because the command doesn’t imply a specific object. In either case, the parser now asks the player to supply a direct object; see the description below.


Case 2: A Command with One Object

If the player’s command has a direct object, but no preposition or indirect object, the parser checks the verb for a single-object sentence template. If the deepverb object has a doAction property, the single-object sentence is allowed. The parser first disambiguates the direct object list (see the section on resolving objects). If this succeeds, the parser saves the direct object (or list of direct objects) for future use as “it” or “them” (or “him” or “her,” if appropriate), and proceeds to execute the command.

If a single-object sentence is not allowed, the parser checks the prepDefault property of the deepverb object; if it’s nil, the sentence is not allowed, so the parser displays message 24 (“I don't recognize that sentence”) and terminates the current command. Otherwise, the parser uses the prepDefault value as the preposition for the command, and calls the ioDefault method of the deepverb with the actor and preposition as arguments. For example, if the player types “put ball,” and the verb has a default preposition of “in,” the parser calls ioDefault like this:

  putVerb.ioDefault(Me, inPrep)

If this method returns something other than a list, the parser ignores it, and asks the player to supply an indirect object (see below). Otherwise, the parser checks to see if the direct object must be disambiguated prior to the indirect object - if the ioAction property defines the [ disambigDobjFirst] flag, the direct object must be disambiguated first. If so, the parser disambiguates the direct object using the normal process (see the section on disambiguation). Next, the parser goes through the ioDefault list, and silently calls the verification method for each object. For normal verbs, these calls look like this:

  object.verIoPutIn(Me)

When the ioAction property has the [disambigDobjFirst] flag, these calls look like this:

  object.verIoPutIn(Me, directObject)

These calls are made silently - any messages they display are hidden from the player. However, the parser notes whether they attempt to display anything; any verification method that attempts to display a message is considered to have failed. The parser removes from the ioDefault any objects whose verification method fails at this point.

If exactly one object survives this test, the parser uses this object as the default indirect object and proceeds with the command. First, though, it shows the player that the object is being assumed. If your game defines a function called parseDefault, the parser calls this function with the verb and the preposition as arguments. Note that you must define parseDefault to take a variable argument list, because it can be called with either one or two arguments: when called with one argument, it means that the a default direct object is being assumed; when called with two arguments, it means that a default indirect object is being assumed. Note further that the preposition supplied as the second argument can be nil, which will be the case when a two-object command is entered that has no preposition (for example: “give joe the bottle”). If your game doesn’t define a parseDefault function, the parser generates a message by displaying message number 130 (“(”), then calling the preposition object’s sdesc method, then displaying message 132 (a space), then calling the default indirect object’s thedesc method, and then displaying message 131 (“)”).

If no objects survive the verification test, the parser can’t supply a default indirect object. If more than one object survives, the parser doesn’t supply a default indirect object, because the command doesn’t imply a particular object. In either case, the parser asks the player to supply an indirect object (see below).


Case 3: A Command with Two Objects

If the player specifies a command with two objects and no preposition, the parser treats the first object as the indirect object, and the second as the direct object, and uses the two-object sentence format. When this type of sentence is entered, the parser gets the value of the deepverb object’s nilPrep property - this property should return a preposition object that is to be used for a sentence of this type. If nilPrep doesn’t return a preposition object, the parser assumes that the preposition is “to,” and finds the preposition object with that vocabulary word. In any case, the parser transforms the sentence from the format VERB IOBJ DOBJ to the standard two-object sentence format, VERB DOBJ PREP IOBJ, then proceeds with the command as though it had been entered that way in the first place.

If the player enters a command in the format VERB PREP OBJ1 OBJ2, the parser converts this to VERB OBJ2 PREP OBJ1 - in other words, the original sentence is interpreted as VERB PREP IOBJ DOBJ. After the conversion, the parser proceeds with the sentence as though it had originally been entered as VERB DOBJ PREP IOBJ. This conversion is most useful for some non-English languages; English speakers don’t typically use this construction.

If the player specifies a command with a preposition and one or two objects, the parser finds the ioAction definition that matches the given preposition. If the verb doesn’t have an appropriate ioAction property, the parser displays message 24 (“I don't understand that sentence”), and abandons the command.

If only one object was provided, the parser takes the object that was given as the indirect object, and attempts to find a direct object. The parser uses the same mechanism to find the direct object - first by trying to find a default direct object, then by asking the player - as described above in Case 1. This type of sentence is useful when the direct object is implied by the verb; for example, if a single actor is present in the room with the player, the direct object of “ask about the murder” would implicitly be the actor.

If two objects are provided, the parser disambiguates the two objects. If the ioAction has the [disambigDobjFirst] flag, the parser disambiguates the direct object first; otherwise, it disambiguates the indirect object first. It then disambiguates the other object. See the section on resolving objects for details.

The parser never allows a command to use multiple indirect objects. If the command contains more than one indirect object, the parser displays message 25 (“You can't use multiple indirect objects”) and abandons the command. When the direct object is disambiguated first (which will happen only when the ioAction has the [disambigDobjFirst] flag), the parser also restricts the direct object to a single object; if multiple direct objects are specified in this case, the parser displays message 28 (“You can't use multiple objects with this command”) and abandons the command.

Once the objects have been disambiguated, the parser saves the direct object or objects as “it” or “them” (or “him” or “her,” as appropriate), then executes the command.


Asking for an Object

If the verb requires a direct or indirect object, but the command doesn’t specify one and the parser can’t find a suitable default, the parser must ask the player to supply an object for the command. First, it must display a message asking for the object.

To do this, the parser checks to see if your game program defines a certain function:

parseAskobj() is called only for compatibility with older games that were written before parseAskobjActor() and parseAskobjIndirect() were added to TADS. If your game defines these other functions, the parser ignores any parseAskobj() function that you define.

parseAskobjIndirect()

When asking for an indirect object, the parser calls parseAskobjIndirect(), if your game defines it. This function receives extra arguments that describe the noun phrase or phrases that the player used to describe the direct object in the command, allowing you to display a message that uses this information.

Remember that the normal parsing sequence does not resolve the direct objects in a command until the indirect object is known. This means that the parser can’t provide you with a resolved list of direct objects when calling this function - the parser obviously can’t have resolved the indirect object when it doesn’t even have a noun phrase for the indirect object yet. So, instead of passing you a resolved direct object list, the parser passes you the “raw” noun phrases from the player’s command. In particular, the parser gives you information on the exact words that the player typed in the noun phrase for the direct object, and the list of objects that match the noun phrase. This object list will not be disambiguated yet.

parseAskobjIndirect takes four arguments:

  parseAskobjIndirect(actor, verb, prep, noun_info);

The “actor” parameter specifies the actor object for the command; “verb” is the deepverb object; and “prep” is the preposition object.

“noun_info” is a list that provides information on the noun phrases that the player typed in the command. This argument is a list of sublists; each sublist contains information on one noun phrase in the player’s command. The list contains one sublist for each distinct noun phrase in the command, so each time the player uses a conjunction (“and” or a comma) to add another noun phrase, the parser will add another sublist to the list.

Each sublist in turn contains three sub-sublists:

This is pretty complicated, so we’ll provide some recipes for common things you might want to do with this parameter.

To determine how many distinct noun phrases the player entered in the command, use length(noun_info). To get the string list, object list, and flags list for the nth noun phrase in the player’s command, use this:

  strs := noun_info[n][1];
  objs := noun_info[n][2];
  flags := noun_info[n][3];

To display the nth noun phrase that the player entered (not taking into account special word translation, such as changing “A” to “all”), use something like this:

  strs := noun_info[n][1];
  for (i := 1 ; i <= length(strs) ; ++i)
    "<<strs[i]>> ";

If you want to traverse the list of every object that matches the nth noun phrase, you can do this:

  objs := noun_info[n][2];
  for (i := 1 ; i <= length(objs) ; ++i)
  {
    if (objs[i] = nil)
      "nil ";
    else
      "<<objs[i].sdesc>> ";
  }

Note that the objs[i] entries can be nil; this happens when the player uses “all”, a pronoun, or a string or number.

parseAskobjActor()

If your game defines a function called parseAskobjActor(), the parser calls this function with the actor as the first argument, the deepverb object as the second argument, and the preposition as an optional third argument - this third argument is present only if an indirect object is required, and even then may be nil. Your parseAskobjActor() function is expected to display a message asking the player to supply a direct object for the verb.

Note that the parseAskobjActor() function is called with a third argument only when the parser is asking for an indirect object. The third argument is the preposition object, which may be nil. Because the parser calls parseAskobjActor() with two or three arguments, depending on whether it’s asking for a direct or indirect object, your parseAskobjActor() function must be defined as taking variable arguments. You can determine if the function is being called with two or three arguments by inspecting the argcount pseudo-variable.

Here’s a sample definition of parseAskobjActor(), which generates the same prompt that the parser would if parseAskobjActor() weren’t defined.

  parseAskobjActor: function(a, v, ...)
  {
     if (argcount = 3)
     {
        ”What do you want ”;
        if (a <> parserGetMe()) a.thedesc;
        ” to <<v.sdesc>> it <<getarg(3).sdesc>>?”;
     }
     else
     {
        ”What do you want ”;
        if (a <> parserGetMe()) a.thedesc;
        ”to <<v.sdesc>>?”;
     }
  }

parseAskobj()

If you don’t define a parseAskobjActor() function, but you do define a function called parseAskobj(), the parser calls parseAskobj() instead. parseAskobj() has the same purpose as parseAskobjActor(), but does not receive the actor as a parameter. parseAskobj() is called for compatibility with games written prior to version 2.2; you should use parseAskobjActor() for new games, since this function gives you more information.

Default Messages

If your game does not have define any of the functions described above, the parser generates its own default message. If no actor (other than parserGetMe()) is specified in the command, the parser displays message 140 (“What do you want to”); if an actor is specified, the parser displays message 148 (“What do you want”), then calls the actor’s thedesc method, then displays message 149 (“to”). Next, the parser calls the sdesc method in the deepverb object. If an indirect object is required, the parser next displays an appropriate pronoun for the direct object, then calls the preposition’s sdesc method (if the preposition is nil, the parser displays message 142, “to”). Finally, the parser displays message 143 (“?”).

If a pronoun for the direct object is required, the parser checks each object in the direct object list. If the player explicitly specified multiple direct objects (by using “all” or a list of noun phrases) or if (in the case of TADS 2.5.2 and higher) the matching objects all have isThem set to true then the parser displays message 144 (“them”). Otherwise, if every object in the list returns consistent values from isHim and isHer, the parser displays message 147 (“them”) if all objects return true for both isHim and isHer; message 145 (“him”) if only isHim returned true; message 146 (“her”) if only isHer returned true; or message 141 (“it”) if neither isHim nor isHer were true for all objects.

For direct objects, the default message will look something like this:

   >take
   What do you want to take?

   >bob, take
   What do you want Bob to take?

For indirect objects, the default message will like these examples:

   >unlock door
   What do you want to unlock it with?

   >hit bob
   What do you want to hit him with?

Reading the Response

After displaying a message (with parseAskobjIndirect(), parseAskobjActor(), parseAskobj(), or through a set of messages, as described above), the parser allows the user to enter a new command, using commandPrompt() code 3 if asking for a direct object and code 4 for an indirect object, and using the same code in a call to commandAfterRead() after the player finishes entering the new command line. If the new command appears to consist only of a noun phrase, the parser uses that noun phrase for the missing object, and goes back to the beginning of this step to process the command with the new noun phrase filling in the missing component. Note that the player can use anything in this noun phrase that would have been allowed in the command itself, including multiple objects and special words such as “all.”

If the parser is asking for an indirect object, the player’s response can optionally start with the preposition that introduces the indirect object. For example:

   >dig in the dirt
   What do you want to dig with?

   >with the shovel

If the new command does not appear to be a simple noun phrase (or the appropriate preposition plus a noun phrase for an indirect object), the parser abandons the current command and starts over from the beginning with the new command. So, when the parser asks the player to supply an object, the player can choose either to answer the question, or to type a brand new command.


Resolving the Noun Phrases

After the parser has made a final determination of the form of the sentence, and identified the verb template, it must resolve the noun phrases in the command to game objects. Up to this point, the parser has been keeping a list of the objects matching each noun phrase; these lists include all objects in the entire game that match each noun phrase. At this point, the parser must determine which specific object or objects the player meant for each noun phrase. This process is called “resolving” or “disambiguating” the object references.

Strings and Numbers

Certain special noun phrases can be resolved with very little work. The player can include a string enclosed in double quotes or single quotes in a command:

  >type "hello, world!" on the terminal

Strings are always mapped to the special object strObj, which must be supplied by your game program (adv.t provides a class called basicStrObj, which provides a suitable definition that you can use a superclass of your own strObj object). In this case, the parser uses strObj as the direct object to the command, and sets the value property of strObj to the (single-quoted) string value ‘hello, world!’.

Likewise, the player can use numbers:

  >dial 8056 on the phone

Numbers are mapped to the special object numObj; as with strObj, this must be supplied by your game (adv.t provides a basicNumObj class). In this example, the direct object will be numObj, and its value property will be set to the numeric value 8056.

Pronouns

The special words for the pronouns (it, them, him, and her) are taken from the direct object of the previous command. (Note that you can also set the meaning of the pronouns from within game code, using the setit() built-in function. When you don’t use setit() to set the pronouns yourself, the system will automatically set the pronouns to the direct object of the previous command.) Before accepting the pronoun, the system uses the access-checking function (validDo for a direct object, validIo for an indirect object) to ensure that the object is still accessible.

“All”

The special word for “all” is replaced with the object list returned by the doDefault property of the verb. The parser calls doDefault, and includes all of the objects from the resulting list in the direct object list. If a list of objects is excluded with “but” (for example: “take everything except the key and the idol”), the parser removes the objects in the exception list from the doDefault list.

Ordinary Noun Phrases

Ordinary noun phrases require more analysis. In particular, we must identify the actual objects to which the noun phrases refer. At first glance, this process seems simple: all we need to do is match the words in the command to the object that defines all of the same words. In practice, though, noun phrase resolution is complicated by the fact that several objects in a game could define the same words; the trick is to figure out which objects the player could be referring to in the current context.

Access Validation:
First, we identify which objects are accessible. To do this, the parser calls validDoList (or validIoList, as appropriate) in the deepverb. This method is called with the actor, preposition, and other object as parameters; the preposition will be nil for one-object commands, and the other object may be nil if it’s not known yet or isn’t appropriate (when calling validDoList for a one-object command, the other object will always be nil). This method returns either a list of objects, or nil; if it returns a list, this list is intersected with the list of objects that the parser found for the noun phrase, otherwise the entire noun phrase object list is retained. Note that validDoList and validIoList must return every valid object, but they need not return only valid objects - since the remaining objects will be checked for validity with validDo or validIo, validDoList and validIoList can harmlessly include invalid objects in their results.

Next, we call validDo (or validIo) in the deepverb for each surviving object. The arguments to this method are the actor, the object whose validity is being tested, and a “sequence number.” The sequence number starts off at 1, and is incremented for each object in the list. The sequence number can be used when you wish to arbitrarily select a single object from a list of ambiguous objects - simply return true only for the first object, and nil for all of the other objects. If this method returns true, it means that the object is valid for the current command - in other words, the object is accessible to the actor for the purposes of the command. If the player wishes to take an object, for example, the object should be valid for the command if and only if the player can reach the object. For other commands, accessibility may not necessitate that the object is reachable; for example, to look at an object, it need only be visible - if the object is inside a locked glass case, it will be visible but not reachable. The parser eliminates from consideration any objects for which validDo (or validIo) returns nil.

Usage Verification:
We now check each surviving object to see if it’s logical with the verb. To do this, we make a “silent” call to the verification method. Recall that the verb template we chose earlier specifies a pair certain methods - a verification method, and an action method - that we will call in the direct and indirect objects. For example, the verb “take” may define a doAction = 'Take', which means that the verification method for the direct object is verDoTake, and the action method is doTake. The call is silent because we hide any messages that the method displays - the player will not be aware that we are making this call. However, we do take note of whether the method attempts to display any messages; if it does, the verification fails for the object. We make a list of all of the objects that survived the validity test and the verification test.

We now have two lists: a list of valid objects (objects which passed the validity test), and a list of logical objects (objects which passed the verification test).

No Matches:
If both lists are empty, there's no way to apply the verb to the player’s noun phrase - we can’t identify any objects for which the command is valid. We check to see if the player’s noun phrase referred to any visible objects; if not, we simply display message 9 (“I don't see any %s here”, where the “%s” is replaced by the words from the player’s noun phrase) and abandon the command. Otherwise, we have a situation where the player referred to one or more visible objects, but those objects can’t be accessed for the purposes of this command. In this case, we check to see if the verb object has a cantReach method - if so, we call the verb’s cantReach method with four parameters: the actor, a list of inaccessible direct objects, a list of inaccessible indirect objects, and the preposition. Actually, only one of these lists will ever be supplied, and the other will always be nil; when the direct object is inaccessible, the first argument has the list of inaccessible (but visible) direct objects, and the second argument is nil. When the indirect object is inaccessible, the arguments are reversed. The preposition argument will be nil if the command is a one-object sentence.

For example, if a closed glass case contains a red book and a blue book, and the player types “take book,” the word “book” will refer to the two books inside the case; neither of these books will be accessible for the “take” command, because they’re inside a closed container, but they’re both visible. The parser will call the cantReach method defined in the verb:

  takeVerb.cantReach(parserGetMe(), [redBook blueBook], nil, nil)

If the verb object doesn’t define or inherit a cantReach method, the parser uses a different mechanism. For each visible object, if there’s more than one object in the list, the parser first calls the multisdesc method, if defined, or the sdesc method, if multisdesc isn’t defined, followed by parser message 120 (“:”). This produces the same prefix text that the parser uses when actually carrying out a command on multiple objects; see Processing Multiple Objects for more information. Next, the parser calls the cantReach method in the object itself - not in the verb - with the actor as the single argument. The parser repeats these steps for each object in the list. (The verb.cantReach mechanism is newer than the per-object cantReach mechanism, which is why the verb version is used if it exists, and the per-object version is used otherwise.)

Valid Matches or Verified Matches:
If the list of logical objects is empty, but the list of valid objects is not empty, the parser considers only the list of valid objects. Otherwise, it considers only the list of logical objects.

Determining the Required Object Count:
At this point, the parser needs to determine whether the objects in the list are uniquely specified - in other words, it determines whether it has identified a single object for each singular noun phrase that the player entered. If the player types “take book,” the parser must find a single object to which “book” refers. Likewise, if the player types “take book and box,” the parser must identify a single object for “book,” and a single object for “box.”

If any of the noun phrases were plural, they’re now fully resolved, no matter how many valid objects matched - the player explicitly asked us to use every object matching the phrase by using a plural. For example, if the player types “take books,” we will apply the command to every valid object which matches the vocabulary word “books” in its plural property.

Similarly, if a noun phrase was specified with “any,” as in “take any book” or “drop any of the coins,” the player has explicitly asked us to choose one of the objects matching the phrase. The parser simply picks one of the remaining objects (it picks the first object in its internal list, but the order of objects in the internal list is unpredictable).

In addition, if the objects themselves are all indistinguishable from one another, the parser also picks one of the objects arbitrarily. Objects are indistinguishable if two conditions are true: first, that the objects are all derived from the exactly the same class - that is, every object’s immediate superclass is the same; and second, that the objects all have the property isEquivalent set to true. If these two conditions hold, the parser considers the objects to be indistinguishable, and simply picks one from the list, exactly as though the user had used “any” with the noun phrase.

For a singular noun phrase, we must now have a single object. If so, we’re done with the object. If exactly one object remains in the verified list, we choose that object. If the verified list is empty, but the valid list has exactly one object, we use that object. If more than one object remains in both lists, however, we have no way of knowing which of the remaining objects the player meant, so we must “disambiguate” the noun phrase to determine which of the several possible objects we should choose.

Summary of Determining the Required Object Count

If we have this kind of noun phrase... ...then we will choose these objects:
Singular usage (“the log book”) One object is required, and we must be able to tell exactly which one the user meant to refer to
Plural usage (“the log books”) All of the matching objects
“any” (“any coin”) Any one of the matching objects; we will pick one object arbitrarily
Explicit count N (“5 coins”) Any N of the matching objects; we will pick the objects arbitrarly
Indistinguishable objects (all matching objects have isEquivalent = true and have a common superclass) Any one of the matching objects; we will pick one arbitrarliy

Disambiguation:
When the noun phrase has a singular usage, but we find more than one matching object in both the valid list and the verified list, we must figure out which object the player meant.

First, the parser tries invoking a user method called disambigDobj (to disambiguate a direct object) or disambigIobj (to disambiguate an indirect object) in the command’s deepverb object. The method is invoked like this:

  verb.disambigDobj(actor, prep, iobj, verprop, 
                    wordlist, objlist, flaglist,
                    numberWanted, isAmbiguous, silent);

Note that the parser calls this method, if defined, every time it resolves an ordinary noun phrase to a concrete list of objects, whether or not the noun phrase is ambiguous. This allows you to perform special object resolution on specific noun phrases or for specific verbs, even when the parser wouldn’t normally think the phrases require disambiguation. The parser does not call this method, however, when resolving “all”, or when resolving a noun phrase that consists entirely of a number (unless the number is a vocabulary word matching an object accessible in the current context, in which case the number is treated as an ordinary noun phrase), a string, or a pronoun.

The “actor” parameter is the actor involved in the command.

“prep” is the preposition object associated with the word that introduces the indirect object, if present; if there is no preposition, “prep” will be nil.

“iobj” is the indirect object, if available; if there is no indirect object, or the indirect object has not yet been resolved when disambigDobj is invoked, “iobj” will be nil.

“verprop” is the property address of the verification method (verDoVerb) defined for the direct object for the verb; this parameter is included because a single verb object could define several verification methods that vary by preposition (“put x in y” usually has a different verification method than “put x on y”).

“wordlist” is a list of strings giving the tokens of the player’s command that make up the noun phrase. This is the same type of list that preparseCmd receives.

“objlist” is a list of the objects that the parser found with dictionary entries matching all of the words in the word list.

“flaglist” is a list of numbers giving flags for the corresponding “objlist” entries; for example, flaglist[3] gives the flags associated with objlist[3].

The flag values for “flaglist” are defined in adv.t. Three flags are bit-field values, so multiple flags can be combined with OR into a single value. To test if a particular flag is set, use the bitwise AND operator, “&”; for example, to test the second element to see if the PLURAL flag is set, use this expression:

  ((flaglist[2] & DISAMBIG_PLURAL) != 0) 

The flags are:

PRSFLG_COUNT - the object matched with a numbered count. For example, if the noun phrase is “3 gold coins,” objlist will contain one or more objects matching the plural phrase “gold coins,” and the VOCS_COUNT flag will be set for each object to indicate that a count is present. In these cases, the first element of wordlist should always be the string with the user’s number (the string '3' in the example).

PRSFLG_PLURAL - the object matched a plural usage.

PRSFLG_ANY - the noun phrase started with “any”

PRSFLG_ENDADJ - the object matched with an adjective at the end of the noun phrase. For example, suppose the noun phrase is “letter”, and the game defines parchmentLetter with noun = 'letter', and defines letterOpener with adjective = 'letter'. In this case, objlist would contain both parchmentLetter and letterOpener, and the flaglist entry corresponding to letterOpener would have the PRSFLG_ENDADJ flag set. This flag allows the disambiguator to select in favor of the noun interpretation in case of ambiguity, to avoid having to ask a stupid disambiguation question (“which letter do you mean, the parchment letter, or the letter opener?” should clearly not be asked).

PRSFLG_TRUNC - the object matched with one or more truncated words. This will be set when a word in the player’s noun phrase matches the leading substring of a dictionary word, and is at least six characters long, but doesn’t match the entire dictionary word. For example, “flashlig” matches “flashlight” because “flashlig” is at least six characters long and matches “flashlight” in its first eight characters (i.e., the length of “flashlig”), but the parser will flag the word with PRSFLG_TRUNC to indicate that it wasn’t an exact match.

“numberWanted” is the number of objects that the parser wants in the resolved list. For a definite singular noun phrase (“take the box”), this will be 1. For a plural noun phrase, this will be the number of objects in the proposed resolution list (in the 'objlist' parameter). When the player specifies a count (“take 3 boxes”), this will be the give number. For an “any” phrase (“take any box”), this will be 1. You don’t have to obey the “numberWanted” parameter, but this information may be helpful in some cases.

“isAmbiguous” is true if the parser thinks the noun phrase is ambiguous, nil if not. For example, if the player specified a singular definite noun phrase, but the parser found two matching objects, “isAmbiguous” will be true. You can always deduce whether or not the list is ambiguous by examining the “numberWanted” value and all of the object flags, but doing so is complicated, so the parser provides “isAmbiguous” for your convenience. If your disambigDobj or disambigIobj is interested only in resolving ambiguity, as opposed to performing special noun phrase resolution, you can simply return DISAMBIG_CONTINUE immediately if “isAmbiguous” is nil.

“silent” specifies whether the disambiguation is in interactive or non-interactive mode. If “silent” is true, it means that you should not display any messages or ask the player to help resolve any ambiguity. If “silent” is nil, you can display messages or prompt for more information if you wish.

The disambigIobj method is essentially the same:

  verb.disambigIobj(actor, prep, dobj, verprop,
                    wordlist, objlist, flaglist,
                    numberWanted, isAmbiguous, silent);

The only difference from disambigDobj is that disambigIobj receives the direct object in the “dobj” parameter. Note that the direct object is not normally available during indirect object disambiguation, so “dobj” will usually be nil. The only time the direct object will be available will be for verb templates with the [disambigIobjFirst] flag. For verbs without this flag, the indirect object is resolved before the direct object, hence the direct object is not yet known.

The parser’s built-in disambiguation system calls these methods after it has done everything it can, short of asking the player, to resolve and disambiguate a noun list. The parser will apply the normal validation checks (validDo, validDoList, etc.) to the objects before calling this method, eliminating any that don’t pass; and it will apply the silent verification checks (verDoVerb, verIoVerb) as well. The parser will next call disambigDobj or disambigIobj, if the verb object defines it.

These methods can return a status code from the list below, or they can return a list (see below). The status codes (defined in adv.t) are:

DISAMBIG_CONTINUE - continue through the remainder of the disambiguation process as normal.

DISAMBIG_ERROR - abort the current command entirely. This can be used when the method encounters an error and displays an error message to the player. The parser will simply terminate the current command. Note that the parser does not display an error of its own, so the method must display an appropriate message before returning this status code.

DISAMBIG_PROMPTED - continue through the process as normal, but do not show a prompt for an interactive response (“which foo do you mean...”), because the disambigDobj or disambigIobj function already displayed an appropriate prompt of its own. This allows the function to display a customized prompt (using more information than the parseDisambig function has available), but still use the parser’s default response reader and parser.

DISAMBIG_PARSE_RESP - this indicates that your code has asked the player a question and read a response (via the input() built-in function, for example), but that you want to use the default response parser in the normal disambiguation mechanism to parse the response. Do not return this as a raw status code; instead, return a list containing this value as the first element, and the string to be parsed as the second element.

DISAMBIG_DONE - consider the object list fully resolved. This skips any additional checking the disambiguator would normally perform and uses the current list as-is. This should generally never be returned directly, but is used when returning a list of objects.

The method can also return a list, which contains objects that replace the original input list (in the “objlist” parameter). The first element of the returned list is a status code, from the list above. Subsequent elements are the objects to use as the result of disambiguation.

You can optionally specify a flag value for each object value. To specify a flag for an object, simply place the flag value after the object in the list:

  return [DISAMBIG_DONE redBox PRSFLG_PLURAL blueBox PRSFLG_PLURAL];

Each flag value pertains to the object immediately preceding it.

If you omit a flag value, zero is assumed. So, the following two return lists are equivalent:

  [DISAMBIG_CONTINUE redBox 0 blueBox 0]
  [DISAMBIG_CONTINUE redBox blueBox]

You can optionally omit the status code from the list. If the first element of the list is an object, the parser uses DISAMBIG_CONTINUE as the default status code, so it takes the list you returned and proceeds to the next step in the disambiguation process.

Note that the parser doesn’t call disambigDobj or disambigIobj for certain cases: resolving pronouns (it, her, him, them); strings; numbers that don’t map to vocabulary words, but simply resolve to numObj; and “all” phrases. However, the parser does call these methods to resolve the items in an “except” phrase.

Asking the Player for Help:
The parser tries all of the steps above to find a unique object matching the player’s noun phrase: validation, verification, and the game-defined disambigDobj or disambigIobj method. If, after all of this, the parser still doesn’t have a unique object, it reaches its last resort: the parser asks the player to choose an object.

To do this, we check to see if the game program provides a function called parseDisambig. If so, we call this function with two arguments: a (single-quoted) string giving the text of the noun phrase that the player typed; and a list composed of the surviving objects. If the game doesn’t define a parseDisambig function, the parser generates a default message using message 101 (“Which %s do you mean,” with the “%s” replaced by the text entered by the player for the ambiguous noun phrase), then calls thedesc for each surviving object, then displays message 104 (“?”). Between adjacent objects, the parse displays message 102 (“,”); and it also displays message 103 (“or”) after the second-to-last object. Note one unusual case: if the ambiguous list contains a set of objects that are mutually indistinguishable, as described earlier, the parser displays only one description for the whole set of such objects, and uses adesc instead of thedesc for the object. For example, if the player types “take coin,” and the room contains a gold coin and three silver coins which are all indistinguishable from one another, the message looks like this: “Which coin do you mean, the gold coin, or a silver coin?”

Here’s an example implementation of parseDisambig, which generates the same message that the parser would generate if parseDisambig weren’t defined (except that this implementation doesn’t handle indistinguishable objects).

parseDisambig: function(str, lst)
{
   local i, tot, cnt;
   "Which << str >> do you mean, ";
   for (i := 1, cnt := length(lst) ; i <= cnt ; ++i)
   {
      lst[i].thedesc;
      if (i < cnt) ", ";
      if (i + 1 = cnt) "or ";
   }
   "?";
}

After displaying prompting message, the parser lets the user enter a command using commandPrompt and commandAfterRead code 2. If the player types something that looks like a noun phrase, the parser tries to use the new information to choose from the surviving objects. If the player simply types “all” or “both,” the parser chooses all of the surviving objects. If the player types “any,” the parser chooses one of the surviving objects arbitrarily. In either case, this completes disambiguation, since the parser has identified the objects the user wishes to use in the command.

If the player types adjectives and/or nouns, the parser limits the surviving list to objects that are associated with all of the new vocabulary words. If no objects remain, the parser displays message 16 (“I don't see that here”). If exactly one object remains, disambiguation is complete, because the noun phrase has been resolved to a particular object. Similarly, if the player typed a noun phrase that consists of multiple objects (for example: “the red one and the blue one”), and the remaining list has the same number of objects as the number of noun phrases the player entered, disambiguation is completed. Otherwise, the phrase is still ambiguous, so the parser goes back and asks the user once again which of the remaining objects is to be used. Note that if parseDisambig isn’t defined, the parser displays message 100 (“Let's try it again:”) before asking again.

Note that the parser re-invoked disambigDobj or disambigIobj, if defined, after limiting the list of surviving objects.

If the player types something that doesn’t look like a noun phrase, the parser abandons the current command, and starts over at the beginning, using the new input as the next command.


Resolving Noun Phrases with Unknown Words: parseUnknownXobj

If the player’s command contains an unknown word in a position where the parser expects to find a noun phrase, the parser will attempt to invoke a game-defined method to resolve the unknown word.

During the dictionary look-up step, the parser marks unknown words but doesn’t generate an error at this step. Instead, it simply ignores the unknown words, and continues parsing. The parser then attempts to determine the verb and general structure of the command as normal; if this fails, the parser goes back and reports the unknown word (or tries to parse the verb through the parseUnknownVerb() function). However, if the parser is able to find a valid sentence structure, it continues to the next step, which is noun phrase resolution.

Note that the parser considers an unknown word that appears to be part of a noun phrase to have a completely neutral part of speech, which means that unknown words are merged with any known nouns and adjectives to which the unknown word is adjacent to form the noun phrase. Since the parser doesn’t know the word, it can’t decide whether to treat it as a noun or adjective, so it simply considers it to be neutral and allows it to combine with whatever other words are present.

Noun phrase resolution continues as normal until the parser once again encounters one of the unknown words. So, the parser will call the verification (verDoVerb) and validation (validDo, validDoList) methods as usual for any noun phrases containing recognized words, in the usual order (for most verbs, this means that the indirect object is resolved first, then the direct objects).

Upon encountering the unknown word during the noun phrase resolution process, however, the parser checks to see if the game program defines the method parseUnknownDobj (for a direct object) or parseUnknownIobj (for an indirect object) in the deepverb object for the command’s verb. If the appropriate method is not defined, the parser displays the usual unknown word error message (parser message 2) and aborts the command. If the appropriate method is defined in the verb object, though, the parser invokes it as follows:

  parseUnknownDobj(actor, prep, iobj, wordlist)
  parseUnknownIobj(actor, prep, dobj, wordlist) 

In both cases, “actor” is the actor object to which the command is directed; “prep” is the preposition that introduces the indirect object, or nil if there is no preposition; and “wordlist” is a list of single-quoted strings containing all of the words - both known and unknown - making up the noun phrase that contains the unknown word or words.

The “iobj” argument in parseUnknownDobj is the indirect object, if present and known; similarly, the “dobj” argument in parseUnknownIobj is the direct object, if present and known. The “iobj” or “dobj” argument will be nil if there was no other object in the command, or if that object has not been resolved by the time this method is called. When the indirect object is resolved first, as it is with most verbs, parseUnknownIobj will always receive nil for the direct object, since the direct object cannot be resolved until the indirect object is resolved, which is what parseUnknownIobj is doing.

These methods can return the following values:

nil - This indicates that the method did not successfully resolve the object, and the system should use the default handling. The parser reports the unknown word error as usual.

true - This indicates that the method has fully processed the command for this noun phrase, and no further parser action is necessary. The parser simply continues processing any other objects in the command, but treats this noun phrase as having no corresponding objects in the command and does no further processing on it.

an object - The methods can return an object value, which the parser treats as the resolution of the noun phrase. The parser proceeds to apply all normal processing to the object as though the parser had found an appropriate set of dictionary words for the noun phrase matching the returned object. So, all of the normal verification, validation, and action methods are called for the object.

a list of objects - The methods can return a list containing object values. The parser uses all of the objects in the list as the resolution of the noun phrase, and applies all of the normal processing to each object in the list. The effect is the same as if the user had entered the objects in the command separated by commas.

If your game doesn’t define the appropriate parseUnknownDobj or parseUnknownIobj method for the verb, the parser simply uses the default handling, and reports the unknown word error.

Here’s a simple example that changes the ASK ABOUT command so that we always let the actor respond, even if a word that the player is asking about isn’t defined anywhere in the game. To accomplish this, we add a parseUnknownIobj to askVerb. This method will return a special object, unknownAskIobj, that we define solely as a placeholder for ASK ABOUT with an unknown word. We’ll set unknownAskIobj’s wordlist property to the list of unknown words, and the object uses the list of words to construct its sdesc.

  #pragma C-

  /*
   *   Special object that we use as the indirect object of any
   *   ASK ABOUT command that refers to unknown words 
   */
  unknownAskIobj: thing
      wordlist = []
      sdesc =
      {
          local i;

          for (i := 1 ; i <= length(wordlist) ; ++i)
          {
              if (i != 1)
                  " ";
              say(self.wordlist[i]);
          }
      }
  ;

  /*
   *   For "ask about," use special handling for unknown words so
   *   that the actor can respond directly to the unknown words. 
   */
  modify askVerb
      parseUnknownIobj(actor, prep, dobj, words) =
      {
          /* if we're asking about something, have the actor respond */
          if (prep = aboutPrep)
          {
              /* use our special ASK ABOUT object for the unknown words */
              unknownAskIobj.wordlist := words;
              return unknownAskIobj;
          }
          else
          {
              /* 
               *   it's not ASK ABOUT, return nil to use the
               *   default system handling 
               */
              return nil;
          }
      }
  ; 


Phase 3: Execution

At this point, everything about the command is known. The parser has converted the player’s words into a set of objects, with each object having a specific function in the sentence. Each component of the command - the actor, the verb, the direct object, the preposition, and the indirect object - has been resolved to an object or a list of objects. We have also identified the verb template, which specifies the methods that are to be called in the objects at certain points during execution. We now have all the information necessary to execute the command.


“Again”

Before starting execution, the parser checks to see if the verb is “again,” which requires special handling. This verb is used to repeat the last command. To identify this verb, TADS requires that you provide an object named againVerb - this allows you to define the vocabulary words that are to be used for this special internal command. When the deepverb object for the command is againVerb, the parser knows to perform the special handling necessary to repeat the last command.

If the player types “again” as the first command of the game, the parser displays message 26 (“There's no command to repeat”), and abandons the command. Otherwise, the parser gets the information from the last command - the actor, verb, direct object, preposition, and indirect object - and uses this as the new command information. Before proceeding, the parser calls the validDo method in the deepverb to ensure that the direct object is still valid for the command, and calls validIo to ensure that the indirect object is still valid. If either of these methods indicates that the objects are not valid, the parser displays message 27 (“You can't repeat that command”) and terminates the command.

When the verb is something other than “again,” the parser saves all of the information for the current command. If the player uses “again” for the next command, it is this information that is used to repeat the command. Note that this information is saved once for each object in a multi-object command, so if the player types “again” after typing “take the box and the ball,” the command repeated is “take the ball.”


Starting Execution: preCommand

The parser starts execution by giving the game a chance to examine the command, with the entire list of direct objects, by calling the game-defined function preCommand(). The parser calls your preCommand() function like this:

  preCommand(actor, verb, dobj_list, prep, iobj);

The “dobj_list” parameter is a list, with one entry per direct object in the command, in the same order as the objects appear in the command, which is the same order that the parser uses to execute the command. “actor” is the actor object, “verb” is the deepverb object, “prep” is the preposition object (or nil if there is no indirect object), and “iobj” is the indirect object, or nil if there is no indirect object.

This function can simply return to proceed with the processing of the command, it can use exit to skip to the post-execution phase, or it can use abort to cancel the command entirely. If the function uses abort, the endCommand() function is still invoked, but fuses and daemons will not be executed.

Note that using exit within preCommand() is slightly different than using it elsewhere, in that exit will skip all of the direct objects when used in preCommand. This is because preCommand() works on the entire list of direct objects, not just on a single direct object; using exit from preCommand thus skips the entire direct object list.

If you use exit or abort from preCommand(), the postAction() function is not invoked. This is because you’re terminating the command before the parser begins execution on a single direct object, so there’s no current direct object for which to invoke postAction().

One possible use for preCommand() is to consolidate two or more direct objects in the direct object list into a single composite object for processing as a unit. To do this, you can build your new list of direct objects, then use the execCommand() to execute the command on your substituted list, and finally use exit to cancel further processing of the original command.


Processing Multiple Objects

A command can refer to multiple direct objects; for example, the player can type “take the ball and the box.” Fortunately, the details of handling multiple objects are entirely handled by the parser; you write the verb handlers in your game for single objects only, and the parser calls these handlers as many times as necessary.

Allowing or Disallowing Multiple Objects

When the player uses multiple direct objects, either by listing several objects or by using “all,” the parser checks to make sure the verb allows multiple direct objects. (In certain cases, multiple objects will be disallowed before this point. The parser never allows multiple indirect objects, and it doesn’t allow multiple direct objects for verb templates that specify the [disambigDobjFirst] flag.) To determine if the verb allows multiple direct objects, the parser calls the rejectMultiDobj method in the deepverb object, passing the preposition as the argument (the argument is nil for single-object commands). If this method returns true, the parser terminates the command without further processing. Note that the parser doesn’t display anything if rejectMultiDobj returns true - the parser expects this method to display an appropriate error message if it returns true. You can use rejectMultiDobj if you want to discourage the player from guessing for certain verbs.

Iterating through the Objects

The remainder of the processing for the command is done for each object in the direct object list. The parser goes all the way through the rest of the command processing for the first direct object, then comes back to this step and goes through the same process for the next direct object, and so on.

Displaying the Object Name Prefix

Before proceeding, if the command has multiple direct objects, the parser displays the name of the current direct object as a prefix to the results of executing the command. This keeps the player informed of the command’s progress as the parser works through the list of direct objects, which is important because the results of the command might not otherwise mention the object involved. If only a single object is used in the command, the parser doesn’t display the object name, because it should be obvious that the results apply to the single object.

Note that the parser does display the object name when “all” is used even if “all” ends up referring to a single object, because the player didn’t say exactly which object he or she meant. For the same reason, the parser displays the object name when the player uses “any” to qualify the direct object, or uses the pronoun “them” as the direct object.

Here’s an example of the normal way that the parser displays the multiple-object prefix. You can override this standard style of display using the mechanisms described below.

   >take bedistor and fromitz board
   bedistor: Taken.
   fromitz board: Your hands are too full. 

The parser has three different ways to display the object name prefix; two of these are older methods for compatibility with older games, so the parser chooses the newest method that the object implements.

The Newest Prefix Mechanism: prefixdesc

The newest approach is to call a method in the object called prefixdesc. The parser calls prefixdesc, if defined, for all cases, whether the command uses a single direct object, multiple direct objects, “all,” or any other special words. This method takes four arguments:

   prefixdesc(show, current_index, count, multi_flags);

The “show” argument indicates whether or not the parser would ordinarily display a prefix for the object at all; this flag is true in the cases described above where the parser would ordinarily display a prefix (multiple objects were used in the command, or one of the words “them”, “any”, or “all” was used to specify the object), nil if the parser would not ordinarily show a prefix.

The “current_index” argument indicates the index of the current object in a list of multiple objects, starting with 1 for the first object, and “count” gives the total number of objects in the list.

Note that the parser won’t always invoke prefixdesc for every “current_index” value from 1 to “count”; sometimes the parser will only invoke the method with a subset of these values. In particular, when the parser finds that some objects matching the word “them” are not accessible, it will call prefixdesc only with the index values for the inaccessible objects; the parser doesn’t call the method for the accessible objects at this point because it doesn’t need to display error messages for these objects. After displaying error messages for the inaccessible objects, the parser will consider only the objects that were accessible: it will start counting again from index value 1, and it will use a “count” value that includes only the accessible objects.

The “multi_flags” argument indicates the special word flags associated with the direct object noun phrase. This value uses the same PRSFLG_xxx definitions that parseNounPhrase() uses, but only the PRSFLG_ALL, PRSFLG_THEM, and PRSFLG_ANY flags are used in the “multi_flags” value. This argument provides more detail about why the parser decided to show or hide the prefix description.

A simple implementation of prefixdesc, which provides the same behavior that the parser uses by default, would look like this:

   prefixdesc(show, curidx, count, flags) =
   {
      if (show)
         "<<self.sdesc>>: ";
   }

The Older Prefix Mechanism: multisdesc

The next approach, in historical order, is to call a method in the object called multisdesc, then to display parser message 120 (“:”). The parser uses this method only if the object doesn’t define or inherit a prefixdesc method; furthermore, the parser only calls this method if it has decided that it must display a prefix, as described above.

The Oldest Prefix Mechanism: sdesc

Finally, if the object doesn’t defines or inherit either prefixdesc or multisdesc, the parser calls the object’s sdesc method, then displays parser message 120 (“:”). As with multisdesc, the parser makes this call only if it has decided that it must display a prefix.

Comparison of the Prefix Mechanisms

The newest prefix mechanism, prefixdesc, gives your game program the most control over how prefixes are displayed, so you will probably want to use prefixdesc for new games.

The prefixdesc mechanism behaves differently from multisdesc and sdesc in two important ways. First, prefixdesc is called in all cases, whereas the others are called only when the parser decides to display a multiple-object prefix; prefixdesc receives a flag argument indicating whether or not the parser would ordinarily display a prefix, allowing the method to decide for itself whether to display anything or not. Second, the parser doesn’t display anything else after calling prefixdesc, whereas it displays parser message 120 (“:”) after the others; this means that prefixdesc must itself display any desired punctuation after the name of the object, whereas the others must display only the object name.


Re-Validation

In a command with multiple direct objects, it is possible for the game state to change in the course of the command in such a way that an object which starts out being valid for the command becomes inaccessible. Consider the following:

  >look at box
  The large box is closed.

  >open it
  Opening the large box reveals a small box.

  >look at small box
  It's closed.

  >open it
  Opened.

  >inventory
  You have a large box.  The large box seems to contain a
  small box.

  >close large box, small box
  Large box: Closed.
  Small box: You don't see that here.

Before the last command, both the large box and the small box are accessible: the large box is accessible because the player is carrying it, and the small box is accessible because it’s in the large box, which is open and is itself accessible.

The parser resolves all of the objects in the player’s command before executing any of the command, so it finds that “large box” and “small box” both refer to accessible objects. However, after executing the command on the first direct object (the large box), the second direct object (the small box) becomes inaccessible.

To detect this situation, the parser re-validates each direct and indirect object after the first. This isn’t necessary for the first direct object in the command, because nothing in the game state has changed since the parser validated the objects during noun phrase resolution; so the parser skips this step for the first direct object. For the second and all subsequent direct objects, though, the parser invokes the validation methods again:

  verb.validDo(actor, directObject, 1)
  verb.validIo(actor, indirectObject, 1)

If one of these methods returns nil, the parser displays an error message and aborts the command. Otherwise, it proceeds through the rest of the command for the current objects.

To report a validation failure, the parser checks to see if the inaccessible object is visible, using the isVisible method as usual. If the object is visible, the parser uses the cantReach method to report the problem, in the same manner as when finding no matches during object resolution. If the object is not visible, the parser displays parser error 38 (“You don't see that here any more”).


Checking with the Verb: verbAction

Next, the parser checks the command with the deepverb object, by calling the game-defined method verbAction:

  verb.verbAction(actor, dobj, prep, iobj);

All of the parameters other than “actor” may be nil. If the command has no direct or indirect object, “dobj”, “prep”, and “iobj” will all be nil. If the command has only a direct object, prep and iobj will be nil.

If this method wants to terminate further processing of the command, it should use abort, exit, or exitobj to terminate the command. Reasons you may want to terminate the command include:

In any case, if the method terminates the command, it should display some sort of message telling the player what happened, because the parser will simply stop executing the command if one of abort, exit, or exitobj is invoked.

If the verbAction method isn’t defined in the deepverb object for the command, the parser skips this step entirely.

Using verbAction to redirect command syntax

One use to which you can put verbAction is to redirect command syntax, so that you handle one phrasing of a command as though the player had entered a different phrasing. In many cases, this can greatly simplify your game coding, by allowing you to code a particular action only once, and redirect alternative ways of phrasing the action to the single handler.

To redirect command syntax, you can use the execCommand() built-in function to execute a different command than the one the player issued.

For example, suppose you want to process the command “fill x with y” as though the player had typed “put y in x.” (Thus, “fill bottle with water” would be interpreted as though the player had typed “put water in bottle.”)

One way to implement this equivalence would be to put the same code in your ioFillWith method as in your doPutIn method, and likewise for all of the other FillWith and PutIn methods. This would work, but it would involve a lot of confusing copying of code.

A much less tedious approach would be to catch all “fill with” commands in the verbAction method for your fillVerb object, and redirect these calls to “put in” commands instead. Here’s how you might code this:

  fillVerb: deepverb
    verb = 'fill'
    sdesc = "fill"
    ioAction(withPrep) = 'FillWith'
    verbAction(actor, dobj, prep, iobj) =
    {
      /* check for fill-with syntax */
      if (prep = withPrep)
      {
        /* handle "fill x with y" exactly like "put y in x" */
        execCommand(actor, putVerb, iobj, inPrep, dobj);

        /*
         *   We're done with the command - do not process it further, but
         *   do continue with any other objects in a multi-object command
         */
        exitobj;
      }
    }
  ;

Note that you’d still want to provide some simple verIoFillWith and verDoFillWith methods, probably in the thing class, to ensure that the object disambiguation process for “fill with” commands will work properly.


Checking with the Actor: actorAction

The next step is to check to see if the actor will allow the command. Every command is directed to an actor - if the player doesn’t explicitly direct a command to an actor, it implicitly goes to the current player actor, given by parserGetMe(). The parser calls the method actorAction in the actor object with four arguments: the deepverb object, the current direct object, the preposition, and the indirect object. For example, for the command “take the red book,” the call would look like this:

  parserGetMe().actorAction(takeVerb, redBook, nil, nil)

The actorAction method can either accept the command or disallow it. To accept the command, the method need not do anything at all. To reject the command, the method should use the exit, exitobj, or abort statement. If you use the exit statement, you will disallow the command, but the turn will still be counted - that is, daemons and fuses will run as though the turn succeeded. exitobj is similar to exit, but continues with the next object in a multi-object command; exit skips any remaining objects and goes directly to the postAction function. If you use abort, the turn doesn’t count - the daemons and fuses will be skipped (but the postAction function will still be called). In most cases, you should use exit or exitobj if you want to disallow the command in actorAction, because the attempt to command the actor should still count as a turn. The abort statement is intended more for “system” commands, such as “save,” that are intended to happen outside of the game’s world, and so shouldn’t count as turns within the game.

Note that the parser doesn’t display any messages when actorAction uses exit, exitobj, or abort to disallow the command, so your actorAction method should always display an appropriate error message before exiting or aborting. Note also that this method has no return value.


Checking with the Room: roomAction

If the actor allows the command, the next step is to determine if the actor’s current location allows the command. To do this, the parser get the value of the location property from the actor (if this isn’t an object, it skips this step), then calls the roomAction method in that object. roomAction takes five arguments: the actor, the verb, the direct object, the preposition, and the indirect object. For example, if the player types “joe, put the ball in the box,” the parser calls this:

  joe.location.roomAction(joe, putVerb, redBall, inPrep, smallBox)

As with actorAction, the roomAction method can disallow the command by using the exit or exitobj statement. This method must display a message if it disallows the command, because the parser will simply abandon the command if roomAction executes an exit or exitobj statement. This method has no return value.


General Object Processing: xobjCheck and xobjGen

The next step is to call a general check method in the indirect object, then a general-purpose method in the indirect object; after that, the parser calls a general check method in the direct object, then a general-purpose method in the direct object. These general check and general-purpose methods allow you to define certain behaviors for an object for any verb from within a single method. You will sometimes want an object to act a certain way for all (or nearly all) verbs; for example, if you want to create an object that’s off in the distance, you may want to handle every verb except “inspect” the same way: “That's too far away.” You can do this through the general-purpose methods.

First, if the command has an indirect object, the parser calls the method iobjCheck (indirect object check) in the indirect object. This method takes four arguments: the actor, the verb, the direct object, and the preposition.

Second, if the command has an indirect object, and the indirect object does not directly define a verIoVerb or ioVerb method for the verb, the parser calls the method iobjGen (indirect object general processing) in the indirect object. This method takes the same four arguments as iobjCheck: the actor, the verb, the direct object, and the preposition.

Next, if the command has a direct object, the parser calls dobjCheckin the direct object, passing as arguments the actor, the verb, the indirect object, and the preposition.

Next, if the command has a direct object, and the direct object does not directly define a verDoVerb or doVerb method, the parser calls dobjGen in the direct object. This method also takes the same four arguments as dobjCheck: the actor, the verb, the indirect object, and the preposition.

These methods, like roomAction and actorAction, can cancel the command by using the exit statement or exitobj function. These methods should display an appropriate message if they do cancel the command, since the parser will simply abandon the command.

Note that the general-purpose handlers dobjGen and iobjGen are not called if the object “overrides” them for the specific verb. If the object defines - not inherits, but actually defines - either a verification method or an action method for the verb, the general-purpose method is not executed. These methods are intended to be “catch-all” methods; if a verification or action method for a particular verb is defined for the object, the parser assumes that the verb is not to be handled by the catch-all method, so it skips calling the general-purpose method.

Note that a verification or action method will “override” the general-purpose handler if it’s defined in the same object as the general-purpose handler, or if it’s defined in any subclass of the object that defines the general-purpose handler. Consider this example:

 distantItem: fixeditem
   dobjGen(a, v, iobj, p) =
   {
      "It's too far away!";
      exitobj;
   }
   verDoInspect(actor) =
   {
      inherited.verDoInspect(actor);
   }

 targetDistantItem: distantItem
   verIoThrowAt(actor) =
   {
      inherited.verIoThrowAt(actor);
   }
 ;

Now, any subclass of targetDistantItem will have verIoThrowAt override the presence of dobjGen. Since verIoThrowAt overrides dobjGen in targetDistantItem, the same applies to any subclass. The same is true of verDoInspect, because it overrides dobjGen by virtue of being defined in distantItem itself.

The parser always calls the dobjCheck and iobjCheck methods, regardless of whether the object also defines handlers for the verb. This is really the only difference between dobjCheck and dobjGen, and between iobjCheck and iobjGen: whereas the xobjGen methods can be overridden by a verb handler, the xobjCheck methods cannot. So, if you have code that you want to run for every verb, put it in xobjCheck; if you have code that you want to run except for verbs otherwise specifically handled by a normal verb handler for the object, put it in xobjGen.


Verification and Action

If the command survives all of the steps so far, the parser carries out the command on the objects involved.

The first step is to verify that the command is possible. We have already determined that the objects involved in the command are accessible for the purposes of the command, but we don’t know that the command can actually be applied to the object. For example, if a door is already open, trying to “open the door” should fail.

Verification is done by calling the verification method, which is specified by the verb template as discussed earlier. If there are no objects in the command, there is no verification step; we move on immediately to the action step. Otherwise, we verify first the direct object, then, if the command has one, the indirect object.


Case 1: No Objects

If the command has no objects, we simply call the action method defined in the deepverb object. This method takes one argument: the actor. This method is responsible for carrying out the command; it should display an appropriate message - on either success or failure, since this is the entire command processor.

For example, when the player types “wait,” the parser makes this call:

  waitVerb.action(parserGetMe())

The action method has no return value. It can use the exit statement to skip any remaining commands on the same line, and it can use abort to terminate the turn and skip running fuses and daemons. You can also use exitobj to skip any remaining processing for the current object, but proceed with the next object in a multi-object command (this has the same effect as exit in a single-object command). Generally, you should only use abort with “system” commands that shouldn’t count as turns. System commands, such as “save” or “restart,” happen outside of the game world, so they shouldn’t consume any time within the game world. For an ordinary command, even a failed command should usually count as a turn.

Note that the action method generally will not use exit or abort, even if the command fails. You should only use these statements when you want to skip the remaining commands on the same line, which should only be done if something really important happens in the command - ordinary failures in an action method don’t usually warrant cancelling the remainder of the command.

Another way of exiting an action method is to use the askdo statement. Executing an askdo statement lets you ask the player for a direct object, using exactly the same mechanism that the parser uses when it determines that a direct object is required - askdo even supplies a default direct object using the same techniques as the parser. See the description of default objects for details.


Case 2: Direct Object Only

If the command has one object, the parser starts by calling the verification method in the object. The verification method, which is named verDoVerb (where Verb is replaced by the doAction property value - for example, for “take,” the verification method is called verDoTake), takes one parameter: the actor.

If the verification method is not defined or inherited for the object, the parser disallows the command and displays an error. To display the error, the parser checks to see if the game defines a function called parseError2(). If so, it calls this function with four parameters: the verb, the direct object, the preposition, and the indirect object (note that the preposition and indirect object may be nil); parseError2() is responsible for displaying an appropriate message. If this function isn’t defined, the parser generates a message: it displays message 110 (“I don't know how to”), then calls the sdesc method in the deepverb object. Then, if there’s only a direct object, it displays message 111 (a space), then calls the thedesc method in the direct object; if there’s an indirect object, the parser displays message 112 (“anything”), then calls sdesc in the preposition object (or displays message 113 (“to”) if there’s no preposition), then displays message 114 (a space), then calls thedesc in the indirect object.

Here’s an example implementation of parseError2, which generates the same message that the parser would use if parseError2 were not defined.

  parseError2:  function(v, d, p, i)
  {
    "I don't know how to << v.sdesc >> ";
      if (d)
        "<< d.thedesc >>. ";
      else
        "anything << p ? p.sdesc : "to" >> << i.thedesc >>. ";
  }

Apart from this extra message, the absence of a defined or inherited verification method is equivalent to the verification method failing by displaying a message.

If the verification method displays a message, the command is terminated; the parser proceeds with the next direct object in the same command, or moves on to the next command on the command line. The verification method doesn’t have a return value - it indicates failure by displaying a message, and indicates success by displaying nothing.

Note that verification methods must not make any changes to game state - they must never set the object of a property, or call any built-in functions with side effects (for example, never call restart or askfile from within a verification method). Verification methods can be called “silently,” which means that the parser hides any messages they generate from the player - see the section on resolving objects for details. The parser is simply testing various possibilities when making these silent calls, and hasn’t committed to a course of action yet, so these calls must not have any effect on the game state.

If the verification test succeeds - in other words, the verification method doesn’t display any messages - the parser calls the action method in the direct object. The action method is named doVerb (for example, the action method for “take” is called doTake). Note that the do in doTake stands for “direct object” - it's not the word “do.” The action method is called with one parameter: the actor.

The action method is responsible for carrying out the command. It should display an appropriate message - both on success and on failure, because this is the entire processing of the command.

Note that it’s possible for the action method to reject the command, even though the verification method allowed it. If the action method wants to apply a tougher standard to the conditions under which the command can succeed, it can do so without any problem.

For example, when the player types “take red book,” the parser calls these methods:

  redBook.verDoTake(Me)
  redBook.doTake(Me)  

Case 3: Direct and Indirect Objects

When both a direct and an indirect object are specified, the verification sequence involves both objects. First, the parser checks to make sure that the verification methods are defined or inherited for both objects; if not, the command fails, and the parser displays a message as described above for Case 2.

The parser first verifies the direct object. It does this by calling verDoVerb in the direct object. For a normal verb, whose the verb template does not define the [disambigDobjFirst] flag, this method takes two parameters: the actor, and the indirect object. Otherwise, the only parameter is the actor.

If the direct object verification is successful, we verify the indirect object by calling verIoVerb in the indirect object. For a normal verb, whose template does not specify [disambigDobjFirst], this method takes only one parameter: the actor. For verb templates that do specify the [disambigDobjFirst] flag, this method takes two parameters: the actor, and the direct object.

If either verification method displays a message, the command fails, and we proceed to the next direct object of the current command, or to the next command on the command line if there are no more direct objects for the current command.

If both verification methods succeed, we call the action method for the verb template. The action method for a two-object command is always called in the indirect object. The method is called ioVerb, and takes two parameters: the actor, and the direct object. (Note that the direct object is always included in this call, regardless of any special flags in the verb template.) As with a single-object action method, this action method is responsible for carrying out the command, and must display an appropriate message, whatever the outcome of the command.

Note that the parser will not call any action method in the direct object for a two-object command. Your action method in the indirect object is free to call a method in the direct object if you wish to have a direct object method process the command, but the parser will never call such a method directly.

For example, if the player types “put ball in box,” the parser makes these calls:

  ball.verDoPutIn(Me, box)
  box.verIoPutIn(Me)
  box.ioPutIn(Me, ball) 

Post-Action Processing

Starting with TADS version 2.5, the parser calls another function, postAction, after the appropriate action method for the command has finished.

The parser calls the postAction for every command that begins execution (i.e., every command for which the verb’s verbAction method is invoked). The parser invokes postAction even when the command is terminated by exit, exitobj, or abort. If a command never begins execution (for example, because object resolution fails, or because re-validation just prior to execution fails), the parser will not call postAction.

The parser invokes postAction just after the appropriate action method (verb.action for a command with no objects, dobj.doAction for a command with a direct object, or iobj.ioAction for a command with both a direct and an indirect object). If the command is terminated with exit, exitobj, or abort, the parser calls postAction immediately after the terminating statement is executed.

postAction is part of the per-object processing sequence that starts with verbAction, so this method is called once per direct object in a command that involves multiple direct objects.

The parser calls the game’s postAction function, if one is defined, like this:

    postAction(actor, verb, dobj, prep, iobj, status);

The first five parameters identify the current command’s objects; any of the objects except “actor” and “verb” can be nil. The “status” parameter has the same meaning as the return codes from the execCommand built-in function; it can be one of the following values, defined in adv.t:

EC_SUCCESS   The command executed successfully, which indicates that everything up through and including the command’s “action” method (verb.action, dobj.doAction, or iobj.ioAction, as appropriate).
EC_EXIT   An exit statement was executed at some point before the parser reached the action method.
EC_EXITOBJ   An exitobj statement was executed at some point before the parser reached the action method.
EC_ABORT   An abort statement was executed at some point before the parser reached the action method.

The postAction function returns no value.


Phase 4: End-of-Turn Processing

Once the parser has finished going through each object in the command, executing the sequence of function and method calls from verbAction to postAction on each object in turn, the parser begins the end-of-turn phase.

Before the end-of-turn phase, the parser considers how the execution phase ended:

The end-of-turn phase consists of two steps: running daemons and fuses, and calling the endCommand() function. The parser determines which of these steps to perform based on the way the execution phase ended:

Fuses and daemons executed? endCommand() invoked?
Command ended successfully Yes Yes
Command ended with exit or exitobj Yes Yes
Command ended with abort No Yes
Command did not begin execution phase No No

In a command with multiple direct objects, the turn ends after the parser has applied the command in turn to each direct object. In a command line that contains multiple commands, each individual command counts as one turn. To illustrate, suppose the player enters this command:

    >put the book and ball in the box. go north.

This command counts as two separate turns:

    >put the book and ball in the box
    >go north

The end-of-turn phase is run twice: once after the book and ball have been put in the box, and again after going north.

Note that the first command involves two direct objects, so it goes through the execution phase twice (once for the book, once for the ball) before the first end-of-turn phase. So, breaking this down into the individual execution and end-of-turn phases, we have this sequence:

    execute "put the book in the box"
    execute "put ball in the box"
    end-of-turn
    execute "go north"
	end-of-turn 

Daemons and Fuses

The first part of the end-of-turn phase consists of running the daemons and fuses. As described above, the parser executes fuses and daemons at the end of a turn only if the command entered the execution phase and the turn did not end with an abort statement.

A fuse is set with the setfuse() built-in function, or with the notify() built-in function called with a non-zero number for the third argument. A daemon is set with the setdaemon() built-in function, or with notify() called with zero as the third argument.

The order in which the parser executes fuses and daemons is not defined, and you should not rely on these functions or methods being executed in any special order. You should in particular not assume that an observed order of execution will hold true for all versions of TADS, for all systems, or even for every time you run the game.

The parser runs each active daemon once during each end-of-turn phase.

During the end-of-turn phase, for each fuse whose countdown timer has reached zero, the parser executes the fuse and then deletes it. The parser at this point simply ignores each fuse whose countdown timer has not reached zero. The parser does not advance any fuse time counters during the end-of-turn phase; instead, the fuse counters are advanced (i.e., the timers are decremented) only when you call the incturn() or skipturn() built-in functions.


The endCommand Function

After running daemons and fuses, the parser invoked the endCommand() function. The parser calls endCommand() on each turn that the parser entered the execution phase.

The parser calls endCommand() with the same arguments that it passed to preCommand at the beginning of the turn, plus an extra “status” parameter:

    endCommand(actor, verb, dobj_list, prep, iobj, status);

The “status” parameter has the same meaning as the status code parameter to postAction.

The endCommand() function returns no value.

Note that endCommand() was introduced with TADS version 2.5; the parser didn’t call this function in earlier versions.


Chapter Five Table of Contents Chapter Seven