This file is part of the TADS 2 Authors Manual.
Copyright © 1987 - 2002 by Michael J. Roberts.
All rights reserved.
Edited by NK Guy, tela
design.
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 players 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 dont 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 players command into essentially three phases:
The TADS parser begins interpreting the players wishes by reading a command line, then breaking the command down into words and phrases.
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.
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>.
Heres 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"; } ">"; }
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 players command as the argument. If you dont 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.
true - process the command as normal
nil - skip the command entirely
string - process the string as the command line, instead of the players 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.
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. Heres an example:
pardon: function { "I beg your pardon? "; }
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 doesnt 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 computers 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.
After breaking the command into words, the parser checks the word list for the special words listed in your specialWords directive. If your game doesnt 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 youve 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 doesnt 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 wouldnt 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.
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 players command as being both a noun and an adjective.
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:
- If the unknown word appears in a context where the parser expects a noun phrase, the parser will invoke parseUnknownDobj or parseUnknownIobj, depending on whether the noun phrase is in the correct position for a direct or indirect object. These methods are defined in the deepverb object for the commands verb. If the verb doesnt define the appropriate method, the parser proceeds to the final case below by default. Refer to resolving noun phrases with unknown words for details.
- If the unknown word appears in a position where the parser expects a verb or preposition, the parser invokes the parseUnknownVerb function.
- In any other case, the parser will tell the player that it doesnt understand the word (via parser message 2). The parser will then read a new command from the player; if the command starts with the word o or oops, the parser will try the command again, substituting the new word or words for the original unknown word. If the new command doesnt start with o or oops, the parser will forget the old command and start over with the new command.
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 westopen the door and the window and go eastNote 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.
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 actors name followed by a comma at the very start of a command. The sentence Joe, go west starts with an actors 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 isnt 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 whats left.
If no objects remain, the player has tried to talk to something that is not currently accessible as an actor, or which doesnt 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 players 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 players original words in the error message is that the parser cant 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 players 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.
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 prepositions 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 its 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 parsers 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 dont 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
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 its 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 doesnt have a doAction, or an indirect object is present, but the deepverb doesnt 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 cant handle a command directly, which means that parseUnknownVerb doesnt 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 doesnt 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.
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.
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.
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.
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 dont 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 commands 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 objects 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 copys 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 players 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 dont 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 dont 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, youll 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 dont 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, its 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 doesnt start with 1, you should override this method to return a number in your objects valid range.
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 players 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 its 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 dont 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. Its 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 youd add entries for all of the items in the except list, with each additional entrys 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, youd 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, its easy:
[4 book 0 candle 0]The parser also lets you omit the flags entirely, if you dont 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 dont 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 doesnt 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 didnt 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 doesnt 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 doesnt try to resolve the objects at this stage is that the parser doesnt 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, youll 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 dont see a special case, you should simply return PNP_USE_DEFAULT to use the built-in parsing system.
Now that the command has been parsed into phrases, TADS must resolve the phrases into objects defined by the game.
Before the parser begins resolving objects, the system calls a game-defined function called preparseCmd(). This functional is optional; if your game doesnt 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 players 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 dont 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 itIn 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 doesnt include the actor specification. For example, if the player types joe, go west, preparseCmd will receive only go west. In addition, the list doesnt 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 thats 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.
Heres 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; }
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.
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 doesnt 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 rooms 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 players location. (The roomCheck defined in the movableActor class does the same thing for the actors 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 dont 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 commands targeted actor, is that roomCheck is meant to check to see if the player can even issue such a command right now.
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.
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 northSuppose that taking the box magically transports the player to a new location. Clearly, because the player wasnt 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 shouldnt count as turns, because they happen outside of the game world and hence outside of the games internal timeline.
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 dont 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.
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 dont 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.
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 doesnt 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 isnt 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 - were following the naming conventions used in adv.t here, but you can call verb and preposition objects anything you want. The parser doesnt 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.
If the players 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 players 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 commands components now look like this:
actor: Me
verb: dig
direct object: (the list returned by doDefault)
preposition: with
indirect object: nil (because its not yet known)
The parser checks that the template specified by the new components exists for the verb. If it doesnt, 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, were assuming that tellVerbs 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.
Heres a sample implementation of parseDefault. This definition generates the same message that the parser would if parseDefault werent defined.
parseDefault: function(obj, prp) { "("; if (prp) "<< prp.sdesc>> "; obj.thedesc; ")"; }If no objects survive the verification test, the parser doesnt 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 doesnt imply a specific object. In either case, the parser now asks the player to supply a direct object; see the description below.
If the players 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 its 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 doesnt define a parseDefault function, the parser generates a message by displaying message number 130 ((), then calling the preposition objects sdesc method, then displaying message 132 (a space), then calling the default indirect objects thedesc method, and then displaying message 131 ()).
If no objects survive the verification test, the parser cant supply a default indirect object. If more than one object survives, the parser doesnt supply a default indirect object, because the command doesnt imply a particular object. In either case, the parser asks the player to supply an indirect object (see below).
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 objects nilPrep property - this property should return a preposition object that is to be used for a sentence of this type. If nilPrep doesnt 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 dont 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 doesnt 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.
If the verb requires a direct or indirect object, but the command doesnt specify one and the parser cant 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.
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 cant provide you with a resolved list of direct objects when calling this function - the parser obviously cant have resolved the indirect object when it doesnt 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 players 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 players 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 well 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 players 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.
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 its 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.
Heres a sample definition of parseAskobjActor(), which generates the same prompt that the parser would if parseAskobjActor() werent 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>>?; } }
If you dont 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.
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 actors 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 prepositions 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?
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 players 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 shovelIf 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.
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.
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 terminalStrings 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 phoneNumbers 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.
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 dont 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.
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 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 its not known yet or isnt 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 its 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 players noun phrase - we cant identify any objects for which the command is valid. We check to see if the players 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 players noun phrase) and abandon the command. Otherwise, we have a situation where the player referred to one or more visible objects, but those objects cant 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 verbs 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 theyre inside a closed container, but theyre both visible. The parser will call the cantReach method defined in the verb:
takeVerb.cantReach(parserGetMe(), [redBook blueBook], nil, nil)If the verb object doesnt define or inherit a cantReach method, the parser uses a different mechanism. For each visible object, if theres more than one object in the list, the parser first calls the multisdesc method, if defined, or the sdesc method, if multisdesc isnt 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, theyre 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 objects 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, were 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 commands 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 wouldnt 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 players 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 users 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 players noun phrase matches the leading substring of a dictionary word, and is at least six characters long, but doesn