This file is part of the TADS Author's
Manual.
Copyright © 1987, 1996 by Michael J. Roberts.
All rights reserved.
The manual was converted to HTML by N. K. Guy, tela design.
TADS:
The Text Adventure Development System
Version 2.2 Release Notes
Part II
The New Book of the Parser
Chapter Four of the TADS Author's Manual describes the player command parser as it was in version 2.0. Since then, we've added substantial new functionality to the parser. For the past few releases, we described the new features as they were added, but until now there was no single source of information on the parser; as a result, it was often difficult to see how the new features and the original features worked together. This section covers all of the parser's features - new and old - and is intended to replace Chapter Four of the Author's Manual.
If you were going to write a text adventure game, and you didn't have a system such as TADS, you would be faced with the daunting prospect of designing a player command parser. The job is so large that you'd probably spend more time designing and implementing your parser than on any other part of the game.
Fortunately, if you're taking the time to read this, you're at least thinking about building your game with TADS, which means that you won't have to write your own parser. Using an existing parser will save you a vast amount of work, but won't eliminate your work entirely: you won't have to write your own parser, but you will have to learn enough about the parser you're using to write your game.
With TADS, the amount you need to learn depends on what you want to do. You can do quite a lot with TADS without learning anything at all about the parser - by using existing class libraries (such as the standard library adv.t supplied with TADS), you can create objects whose interactions with the parser have already been defined, so that all you need to do is fill in some simple information on the objects, such as their names and descriptions. Once you've become comfortable with the basics of writing TADS games, though, you'll want to create your own object classes, with their own interactions with existing verbs; to do this, you'll have to learn a little bit about how the parser processes commands, so that you can add code to your new objects that lets them respond to certain commands. You'll probably also want to be able to create your own verbs, which requires that you learn how to define your own commands. After you've done all of this, you may want to start making more complicated changes, which will require that you learn about some of the more esoteric aspects of the parser.
The first part of this chapter is an overview of what you need to know about parsing to get started with TADS: how to create objects and give them names the player can use in commands; how to make customize the way your objects respond to commands; and how to create your own new commands. One of the nice features of TADS is that you can write games almost immediately, and fill in your knowledge with more detail as you go. This section is intended to help you get started - we begin with the minimum information you need, and then move on to the more advanced features.
The very least you have to learn about the parser to write a game is: nothing! That's because you can write a game using only objects that have been defined already - for example, you can use the objects defined in adv.t, the library of common adventure objects included with TADS. To create a box, which can be opened and closed (but starts off open), you would only have to write this code:
box: openable sdesc = "box" noun = 'box' location = startroom isopen = true ;
The class openable (defined in adv.t) has already been set up to respond to the commands appropriate for a container that can be opened and closed - it automatically knows how to respond to all of these commands from a player:
>open the box >look in the box >put everything in the box >take the ball out of the box >put the book in the box, and close the box
Although we said you didn't have to know anything about parsing to use objects defined in adv.t, you actually have to know a little bit about parsing: you have to know how to give an object a name.
In the object above, we defined a property called noun, which we set to 'box'. The noun property is special - it's a vocabulary property. Vocabulary properties assign names to an object that the player can use in commands; the vocabulary properties are noun, adjective, verb, preposition, article, and verb. For the most part, you will define noun and adjective vocabulary properties; you won't need to use the others until you start defining new commands of your own. Note a very important feature about vocabulary properties: the value of a vocabulary property must always be a single-quoted string.
Vocabulary properties are different from normal properties. Their only function is to define words that the player can use to refer to the object. Unlike ordinary properties, vocabulary properties can't be used from within your game program. For example, this line of code won't display anything at all:
say( box.noun );
The property box.noun simply has no value at run-time. You can't use the vocabulary properties to get the vocabulary words for an object at run-time; however, you can get these properties using the built-in function getwords().
When you assign a vocabulary property to an object, you create a word that the player can use in a command, and you assign the word a particular "part of speech." A word's part of speech determines how the word can be used in a sentence, the same way it does in ordinary speech. When you define a word in a noun property, you're specifying that the word can be used in a player's commands wherever a noun is grammatically valid. You're also specifying that the word refers to this particular object.
The same word can be assigned to multiple objects. For example, you may have several objects in your game that all have the noun "box." In addition, the same word can be used as multiple parts of speech. For example, you might have one object in your game called a "paper towel," and another called a "term paper"; the word "paper" in this case can be used as either a noun and an adjective.
You can also define more than one of the same part of speech for an object. In many cases, you will want to provide several synonyms for an object's words. You can do this easily - simply list all of the synonyms in the vocabulary property for each part of speech. For example, if you have a booklet in your game, you may want to provide a number of similar words that the player can use to refer to the object:
booklet: item location = startroom sdesc = "booklet" ldesc = "It's a small booklet labeled \"TADS Release Notes\". " noun = 'book' 'booklet' 'notes' adjective = 'tads' 'release' 'small' ;
Notice that each word in each list is enclosed in single quotes. With this definition, you could refer to the booklet as "release notes," or "small tads booklet," or any other combination of these adjectives and nouns.
Adv.t defines many types of objects that are common in adventure games, and you will probably be able to write a substantial part of your game using only these objects. However, a game that only involved objects from adv.t would be very dull, so you'll soon want to be able to create objects of your own that respond in novel ways to certain commands. To do this, you'll need to understand how verbs are applied to objects.
Each command that the parser understands has a set of methods that it calls in the objects making up the command (a method is simply a function or subroutine that's associated with a particular object). When the player types a command that involves a verb and a noun, the parser figures out which methods to call based on the verb, and then calls these methods in the object specified by the noun. For example, the verb "open" has two methods that it calls in the object being opened: it has a verification method named verDoOpen, and an action method called doOpen. (The "do" in these names stands for "direct object" - it's not meant to be the verb "do.") The verification method is used to determine if the object is logical with the verb - for "open," an object is logical if it can be opened and closed in the first place, and if it's not already open. The verification method doesn't have to test to see whether the object is accessible to the player, because this testing is done separately; this method only has to check to determine if the object makes sense with the verb. The action method carries out the verb; it doesn't have to check to see whether the object makes sense with the command, since the verification method has already done this.
Suppose you wanted to make an object that does something unusual when opened. For example, you might want to have a can that contains a spring-loaded snake that jumps out when the can is opened. To do this, you'd customize the can's handling of the "open" command.
If we make the can an openable, as we did with the box, we'd get the automatic handling of opening and closing the can. However, we want some additional behavior - when the object is opened, and it contains the snake, we want the snake to jump out. To do this, we'll override the verDoOpen method that's defined by the openable:
snakeCan: openable sdesc = "can" noun = 'can' doOpen( actor ) = { inherited.doOpen( actor ); if ( snake.isIn( self ) ) { " As you open the can, a spring-loaded fake snake leaps out! You're so surprised that you drop the can. "; self.moveInto( actor.location ); snake.moveInto( actor.location ); } } ;
The first thing the new action routine does is to inherit the default doOpen that was defined by openable in adv.t. When overriding a method, it's often useful to be able to incorporate the original method from the class - the inherited keyword lets you do this. The inherited default doOpen opens the can as usual. After that's been done, we check to see if the snake is in the can; if so, we take our special action: display a message telling the player about the snake, then moving both the can and the snake to the player's location.
There are a couple more things to notice in this example. One is the parameter actor. Whenever a verification or action routine is called, the system includes the actor to whom the command was directed as an argument. When the player types a command without specifying an actor, the player's actor Me is used by default - Me is a special object that refers to the player. You can always find out what room the player is in with Me.location, and you can find out what the player is carrying with Me.contents. However, by writing our doOpen method in terms of the actor object passed as a parameter, we can make it possible for the player to direct commands to other characters in the game, and have the commands work properly. So, rather than moving the snake and the can to Me.location, we moved them to actor.location (which will be the same thing if the player was opening the can him or herself).
If you wanted to make the command even more adaptable to another character, you should replace some of the text in the message with special "format strings":
"As %you% open the can, a spring-loaded fake snake leaps out! %You're% so surprised that %you% drop%s% the can. ";
The special sequences in percent signs, such as "%you%", are converted by the system automatically to an appropriate word based on the current actor. If the command is being carried out by the player actor, the message displayed is the same as the original; however, if the command is directed to another character in the game, the terms are all replaced by something appropriate to that character. In most games, the non-player characters can only carry out certain selected commands, so you only need to worry about this when you're writing a command that can be directed to a character. All of the command handlers in adv.t are written this way, but you only need to worry about the ones you know can be carried out by a non-player character.
Note that we only had to change the action (doOpen) routine - we didn't do anything to the verification (verDoOpen) routine, because the one we inherited from container already does what we need.
Now suppose we wanted to handle another verb; but this time, it's one that the object doesn't inherit from its class. Let's suppose that we want to make it possible to read the can's label, but only after the can has been cleaned. For this, we'll need to new verbs, "read" and "clean," both of which are defined in adv.t. As with "open," these verbs each have a verification and an action method. We'll add the following code to the can:
isClean = nil verDoClean( actor ) = { if ( self.isClean ) "You can't make it much cleaner. "; } doClean( actor ) = { "You wipe off the label. It's much more readable now. "; self.isClean := true; } verDoRead( actor ) = { if ( not self.isClean ) "It's far too dirty. "; } doRead( actor ) = { "It's labeled \"Snake in a Can.\" "; }
The verification routine is a little strange, because it doesn't seem to do anything - all it does is check to see whether the command makes sense, and displays a message if not. This is, in fact, all that a verification routine should ever do. The most important thing to remember about verification routines is that they must never modify the game's state in any way - for example, they must never set a property's value. Only the action routine should change game state.
The reason that verification methods shouldn't change any game state is that these methods are sometimes called "silently" by the parser; that is, the parser turns off the output, and calls a verification method. The player will never know that the method has been called at all, because any text it displayed was suppressed - this is what we mean when we say that the method is called "silently." The parser makes these invisible calls to verification methods when it's trying to figure out which of several possible objects the player means. For example, suppose the player is in a room with two doors, one red and one blue, and the red door is open and the blue door is closed; if the player types "open door," the parser calls the verification routine on each door to determine which one makes the most sense. The red door's verification method will display a message saying that you can't open it because it's already open; the blue door's method will say nothing. These calls are made with the text output turned off, because the parser isn't really trying to open anything at this point - it's merely trying to figure out which door makes more sense with the command. Once it's decided which one to open, the parser will run the verification method with output turned on, and the n it will run the action method.
How does a verification method tell the parser that the command is not logical? Simple: it displays a message. So, the verification routine's only function is to display an error. If the verb is logical for the object, the verification method doesn't do anything at all - since the verb makes sense, there's no error to display. If the verb does not make sense, the verification method displays an error message telling the player why the verb can't be used. The parser sees the message, and knows that it indicates that the verb can't be used, so it won't try to run the action routine.
There's another important feature of the verification method: if an object doesn't have a verification method defined at all - which means that the object neither defines the verification method itself nor inherits the method from a superclass - the command automatically fails, and the parser generates the message "I don't know how to verb the object." So, if you want an object to be able to carry out a command, you must either define the verification method directly in the object, or make sure the object inherits the verification method from a class.
Once you start writing your game, you'll quickly want to start adding your own new commands. One of the features that makes TADS so powerful is that it has no built-in verbs - all of the verbs you've seen so far are defined in adv.t, not within TADS itself, so you can change, replace, or remove any of them, and, of course, add new verbs of your own.
We've already mentioned that there's a vocabulary property called verb; as you might guess, this is the vocabulary property that you use to add a new command. You may wonder what sort of object you attach the verb property to. The answer is that there's a special kind of object defined in adv.t, called deepverb, that you can use as a command object. This isn't an object that the player sees as part of your game - it's merely an abstract, internal object, whose purpose is to contain the data and methods that define a new command.
TADS allows you to create three basic types of commands: a command that consists simply of a verb, such as "look" or "jump"; a command that has a verb and a direct object, such as "take book" or "turn on the flashlight"; and a command that has a verb, a direct object, a preposition, and an indirect object, such as "put the ball in the box" or "clean the lens with the tissue." The same word can be used in more than one form of command; for example, you might define commands such as "lock door" and "lock door with key."
For the first type of sentence, which has only a verb, you define the entire command in the deepverb object. You specify the action that the verb carries out with a method called action that you define in the deepverb object. For example, to define a verb called "scream" that displays a message, you could do this:
screamVerb: deepverb verb = 'scream' sdesc = "scream" action( actor ) = { "Aaaaaiiiiieeeee!!!\n"; "(You feel much better now.) "; } ;
The presence of the action method in the object that defines the verb allows the command to be used without any objects. If you define a deepverb that doesn't have an action method, and the player types the verb without any objects, the parser asks the player to supply a direct object, because the parser can't execute the command without any objects when no action method is present.
For the second type of sentence, which has a verb and a direct object, you must set up a deepverb object - but, as you have already seen, the command is carried out by the direct object, not by the verb itself. The deepverb object serves to tell the parser about the verb and how it works - it contains the verb property, and also has the doAction property. The doAction property is a special property that tells the system that the command can be used with a direct object (the "do" in doAction stands for "direct object," just as it did with methods such as doOpen), and tells the system what the verification and action methods are called.
Here's how it works: you define the property doAction in the deepverb as a single-quoted string. The parser takes the string 'verDo' and puts it in front of your string - this becomes the name of the verification property. The parser then takes 'do' and puts it in front of your string - this becomes the name of the action property. For example, if you define a verb to scream at someone, and you specify doAction = 'Screamat', then the verification method for your new command is verDoScreamat, and the action method is doScreamat. Here's how your deepverb definition would look:
screamatVerb: deepverb sdesc = "scream at" verb = 'scream at' doAction = 'Screamat' ;
If the player types "scream at the phone," the parser first calls the verDoScreamat method in the phone object, to verify that the command is sensible; if the verification method is defined, and succeeds (which means that it doesn't display any messages), the parser proceeds to call the doScreamat method, which should carry out the action of the command.
There are two more things to notice in this example.
The first is that the verb property we defined has two words. This is a special ability of the verb property - you can't do anything similar for noun, adjective, or any of the other vocabulary properties. If you define a two-word verb, you must separate the two words by a space, and you must define the second word as a preposition. (In this case, "at " is already defined as a preposition in adv.t, as are most English prepositions. If you add a two-word verb, be sure that the second word is defined as a preposition; if it's not so defined in adv.t, you must define it in your game's source code.) Two-word verbs are useful, because many English verbs take this form. When a verb has a direct object, TADS lets the player use the second word immediately after the verb, or at the end of the sentence. With "scream at," the latter doesn't make much sense, but for many verbs it does - for example, you might say "pick up the book," but it would be equally valid to say "pick the book up." TADS understands both.
The second thing to notice is the use of capitalization in the doAction string. The convention used by adv.t is to capitalize the first letter of a preposition, but only for verbs that take an indirect object. For example, adv.t defines an ioAction of GiveTo for the command "give object to actor"; the To is capitalized because "to" is the preposition that introduces an indirect object. For our verb here, we don't have an indirect object - the preposition is part of the verb, rather than a word that introduces an indirect object - so we do not capitalize the "at" in Screamat. You don't have to use this convention, since the string that you define in a doAction is entirely up to you, but it may help you to make sense of adv.t if you know the convention it uses.
The third type of sentence has both a direct object and an indirect object, and has a preposition that introduces the indirect object. For example, you might want to create a command such as "burn paper with torch." To define this command, you use the ioAction property of the deepverb. This property is similar to doAction, but it defines three new methods, and also associates a preposition with the command:
burnVerb: deepverb sdesc = "burn" verb = 'burn' ioAction( withPrep ) = 'BurnWith' ;
Notice that 'BurnWith' follows the capitalization convention described earlier. Since the preposition introduces an indirect object, and isn't merely a part of the verb, we capitalized its first letter.
The association of the preposition is done with the withPrep in the parentheses - withPrep is an object, defined in adv.t, which specifies a preposition property that defines the vocabulary word "with." The withPrep object is of class Prep, which is similar to deepverb: it's an abstract type of object that isn't visible to a player, and its only function is to define the vocabulary word and associate it with an object that can be referenced from within your game program.
This definition tells the parser that sentences that start with the verb "burn" must have a direct object, and an indirect object, and that the objects are separated with the preposition "with." When a sentence matching this pattern is entered by the player, the parser will use this verb definition to execute the command.
We mentioned that an ioAction defines three methods. Recall that doAction defines two methods from its root string: a verification method (starting with verDo), and an action method (starting with do). The ioAction property similarly defines verification and action methods, but since two objects are involved, it must generate not one but two verification methods: one for the direct object, and another for the indirect object. The methods are called verIoBurnWith and verDoBurnWith. As you might guess from the names, verIoBurnWith is sent to the indirect object, and verDoBurnWith goes to the direct object - and they're called in that order, with the indirect object going first. If both verification methods are defined and successful, then the parser calls the action method, ioBurnWith - this is called only for the indirect object. The parser never calls an action method in the direct object for a two-object command.
The verification and action methods for verbs with two objects are a little different than those for single-object verbs. Whereas a single object verb's verification and action methods only take the actor as a parameter, two of the two-verb methods get an additional parameter: the other object. The remaining method only gets the actor. The definitions should look like this:
verIoBurnWith( actor ) = { ... } verDoBurnWith( actor, iobj ) = { ...} ioBurnWith( actor, dobj ) = { ... }
This may seem strange, but there's a reason. Since the parser calls verIoBurnWith first, it doesn't always know what the direct object is when it calls verIoBurnWith. As a result, it can't provide it as a parameter. By the time it calls verDoBurnWith, though, the indirect object is known, so the extra parameter is provided. ioBurnWith is called later still, so the extra object is also provided as a parameter there.
You may notice that there are some methods defined in adv.t that might make you think that the parser also calls doBurnWith. In fact, the parser will never call doBurnWith itself, but this is a perfectly valid method name which you can call from your own code. Sometimes, you will want the action of a two-object verb to be carried out by the indirect object, and other times you'll want to use the direct object. Which way is better depends entirely on the situation - in general, the better way is the one that requires you to write less code. If you find a situation where you want to handle a two-object command's action in the direct object rather than in the indirect object, simply write an indirect object action routine that looks like this:
ioBurnWith( actor, dobj ) = { dobj.doBurnWith( actor, self ); }
The parser will never call doBurnWith, but there's nothing to stop you from calling it.
If you find yourself in a situation where the standard order of verification for a two-object verb is wrong, TADS does let you change this default ordering. We're moving into advanced territory here, so you probably won't need to know this for quite a while, but you might at least want to note that it's possible. When you define an ioAction, you can include a special modifier that tells TADS to change the ordering:
ioAction = [ disambigDobjFirst ] 'BurnWith'
The modifier is the strange-looking word in square brackets; it tells TADS that you want to reverse the normal ordering. When you include this modifier, the parser will process the verification and action methods in a different order than usual:
verDoBurnWith( actor ) = { ...} verIoBurnWith( actor, dobj ) = { ... } ioBurnWith( actor, dobj ) = { ... }
Note that the parameters changed along with the order. Since the direct object is processed first for this command, the direct object verification routine is called before the indirect object is known, and hence no indirect object is passed to it; and since the indirect object verification routine is called after the direct object is already known, the direct object is supplied as a parameter to this routine.
As you add your own new commands, you will probably find that you want to augment the definition of one of the verbs defined in adv.t. For example, you may want to add a new command such as "open crate with crowbar." Since a verb for "open" is already defined in adv.t, you can't create another "open" verb in your own game. Fortunately, TADS lets you supplement an existing verb without having to change the original definition's source code.
To change an existing verb, you can use the TADS modify statement. This statement lets you add properties to an object that has already been defined elsewhere in your game (even in a separate file that you include, such as adv.t). For example, to modify the "open" verb defined in adv.t to add an "open with" command, you would do this:
modify openVerb ioAction( withPrep ) = 'OpenWith' ;
We strongly recommend that you use the modify mechanism whenever possible, rather than making source changes to adv.t. If you edit adv.t directly, you may find that you have a lot of work to do when you upgrade to a new version of TADS, because we will probably modify adv.t - if you also modify it, you will have to figure out how to apply your changes to the new version of adv.t If you use the modify mechanism instead, you should be able to use future versions of adv.t without any additional work.
As you build your game, you will probably add multiple objects that all go by the same name. When you give the same noun to two or more objects, you create the possibility that a player can type in a command with an ambiguous object name - that is, the words that the player uses refer to more than one object. For example, if you create several books in your game, you will probably give them all the noun "book"; a command such as "take book" could refer to any of these objects. Fortunately, the TADS parser has a powerful system that can resolve ambiguous object names; in many cases, the parser can disambiguate objects automatically, without needing any help from the player.
The first step in disambiguating an object name is to apply the visibility and access rules. These rules, which are defined by objects in your game program, determine whether an object is visible to the player, and if so whether it can be used with a particular verb. If an object is neither visible, nor accessible for use by the player with the given verb, the object isn't considered further. If it's visible but not accessible, it will be considered only to the extent that there isn't another object which is accessible.
Visibility is determined by the method isVisible, which is defined for each game object. The general-purpose class thing defined in adv.t provides a definition of this method suitable for most objects; you will probably not need to make any changes to this method unless you need some special effect. To determine if an object is visible to the player, the parser makes this call:
object.isVisible( Me )
Accessibility is determined by the deepverb object. For a direct object, the parser first calls the method validDoList, which returns a list of all objects which are possibly valid for the verb; the objects in the list aren't necessarily valid, but any objects not in the list are not valid. If validDoList returns nil, it means that all objects are possibly valid and should be considered. A similar method, validIoList, returns a list of possibly-valid indirect objects.
After eliminating any objects that aren't in the validDoList (or validIoList, as appropriate), the parser submits each surviving object to the method validDo, also defined in the deepverb object. This method takes the object as a parameter, and returns true if the object is valid for the verb, nil if not. The parser keeps only objects that are valid.
If any objects remain, the parser turns off output and calls the appropriate validation method in each object. This is the "silent" validation call that we mentioned earlier. If only one of the objects passes the validation test (that is, the validation method doesn't attempt to display any messages), that object is chosen. Otherwise, the parser gives up and asks the player which of the objects to use.
The silent validation test lets TADS be very intelligent about disambiguation. For example, if there's an open door and a closed door in the room, and the player types "open door," the validation test lets the system determine that the player means to open the closed door - since the verDoOpen method fails for the open door (it displays "It's already open" if the default verification method from adv.t is used), but succeeds for the closed door, the parser knows to choose the closed door.
The second half of this chapter, "The Parser in Detail," has full details on how disambiguation works.
The parser also tries to be intelligent about supplying defaults when the player provides only partial information. The parser supplies default direct and indirect objects for commands that omit one of these objects, using rules defined by the objects in your game program.
To determine a verb's default direct object, the parser calls the method doDefault in the deepverb object. This method returns a list of default direct objects. The verbs in adv.t generally return a list of all accessible objects; a couple of verbs return more restricted lists (for example, "take" returns a list of accessible objects that aren't already in the player's inventory). Similarly, the parser calls the deepverb method ioDefault to get a list of default indirect objects. When doDefault (or ioDefault, as appropriate) returns a list that consists of exactly one object, the parser uses this object as the default for the command. If the list is empty, or consists of more than one object, the parser will ask the player to supply the object, because the command doesn't imply a particular object.
The parser's automatic default system can be especially convenient for indirect objects. Certain commands imply the use of particular indirect objects; for example, "dig" implies a shovel or similar tool. For such commands, it's a good idea to define ioDefault methods that return a list of accessible objects that satisfy particular criteria; for "dig," you would return a list of accessible objects that are usable for digging.
The doDefault method has another use: the parser calls these methods to determine the meaning of "all." When the player uses "all" as a direct object, the parser replaces "all" by the list returned by doDefault. Since the parser doesn't allow using a command to include multiple indirect objects, "all" cannot be used as the indirect object, hence ioDefault will never be used to supply a list for "all." Note that you can disallow using "all" (and multiple direct objects in general) for a particular verb by defining the property rejectMultiDobj to be true in the deepverb. Here's an example of using rejectMultiDobj to prevent the player from being able to look at everything in the room all at once:
modify inspectVerb rejectMultiDobj( prep ) = { "You can only look at one thing at a time. "; return true; } ;
The rest of this chapter is devoted to the details of how the parser works. The sections are organized in roughly the same order as the steps the parser executes as it analyzes and executes a player's command.
When the player types a line of text into your game, the TADS parser goes through a series of operations to convert the command line into simple actions on objects in the game. In the course of these operations, the parser makes calls to your game program for advice; in most cases, the parser has default actions for these calls, so you don't need to include any code in your game program for the default behavior. You can, however, customize the parser by providing definitions for these "hooks" in your game. The main purpose of this chapter is to describe the way the parser interacts with your game program, so that you can customize the parser by overriding the defaults. We also describe many of the internal operations of the parser so that you can understand how to make it behave the way you want it to.
The 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.
After the player has typed a command, the parser will tell your game about it by calling your preparse function. Immediately after reading the command line, the parser calls this function with the original text of the player's command as the argument. If you don't provide a preparse function in your game program, the parser skips this step. If you do provide a preparse function, it can return one of three values: it can return true, in which case the command is processed as normal; it can return nil, in which case the command is skipped entirely, and the parser immediately asks the player for a new command; or it can return a string, in which case the returned string is used as the command instead of the line of text typed by the player.
Here's an example definition of commandPrompt that displays a long prompt for the first few commands, then changes to a shorter prompt. The longer prompt is not displayed when the parser asks the player a question.
commandPrompt: function( typ ) { if ( global.promptTurns = nil ) global.promptTurns := 0; "\b"; if ( typ = 0 or typ = 1 ) { global.promptTurns++; if ( global.promptTurns > 5 ) "What do you want to do now?\n"; else if ( global.promptTurns = 5 ) "Aren't you tired of this long prompt? I sure am. From now on, the prompt will just be like this:\n"; } ">"; }
If the player enters a blank line in response to a command prompt, the parser calls a function called pardon in your game program. You must provide a function with this name. This function takes no arguments; its only purpose is to provide an opportunity for you to display an error message when the player enters an empty command. Here's an example:
pardon: function { "I beg your pardon?"; }
Once the parser has read a line of text from the user, the next step is to break the command into individual words. The parser does this based entirely on the characters in the command line - at this stage, it doesn't pay any attention to whether it recognizes any of the words. People who write compilers call this step "lexical analysis" or "tokenization," because it involves breaking up a string of characters into individual units (or "tokens") by classifying the characters and grouping related characters.
First, the parser converts the entire command to lower-case. Next, the parser simply goes through the command line, and takes each group of letters and numbers to be a word, and keeps any strings enclosed in double quotes together.
For example, assume the player types this:
>Joe, Go West, then type "hello, world!" on the Acme-Tech computer's keyboard.
The parser converts this to a string of words and symbols:
joe
,
go
west
,
then
type
"hello, world!"
on
the
acme-tech
computer's
keyboard
.
Note that the punctuation marks are all treated as separate words, and the spaces are all ignored. Note also that apostrophes and dashes are considered to be equivalent to letters, so things like "Acme-Tech" and "computer's" are treated as single words.
After breaking the command into words, the parser checks the word list for the special words listed in your specialWords directive. If your game doesn't provide a specialWords directive, the parser has a built-in list that it uses (since adv.t provides a specialWords directive, your game probably has one whether you've entered one or not). Any word in the word list that matches one of the special words is converted to a flag indicating that it is that special word.
Note that this conversion doesn't apply to the special word in the "of" slot. This special word is matched later, and not converted along with the other special words, because it is often useful for "of" to be an ordinary preposition, which wouldn't be possible if it were converted at this point.
As with the previous step, where the command was broken up into words, this step is done automatically by the parser without any calls to your game.
The next step is to find the individual commands on the command line. The command line can be made up of multiple commands, separated by the words "then" or "and," or by various punctuation marks: commas, periods, exclamation points, and question marks.
The word "then," and the period, exclamation point, and question mark are unambiguous - they always separate commands. So, the first thing the parser does is scan through the command line, looking for one of these symbols. If it finds one or more at the beginning of the command, it simply discards them. Otherwise, it takes all of the words up to the first occurrence of one of these symbols, and treats that as a command. For example, consider this command:
>Joe, go west, then open the door and the window and go east.
The parser takes the part up to the "then" as the first command. It keeps doing the same thing through the rest of the command, which means that it will at this point break the line up into two commands:
Joe, go west
open the door and the window and go east
Note that the word "and" and the comma are ambiguous, since they may separate entire commands, or objects within a single command. The parser delays any decisions about these symbols until it has further analyzed the command; so, although the second line above actually consists of two separate commands, the parser at this point is considering it to be a single command.
The parser now processes these commands individually. It processes each part of the command in sequence, until either it has processed all of the commands, or an error occurs, or your game program executes an exit 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 actor's name followed by a comma at the very start of a command. The sentence "Joe, go west" starts with an actor's name and a comma, so the command "go west" is directed to Joe.
Checking for an actor is the first processing of the command that involves the vocabulary words defined by your game (other than the special words). To determine if the group of words in front of the comma is indeed the name of an actor, the parser looks at each word in turn, and applies the noun-checking rules; see the section on noun phrases (page 80) 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(Me) on each object in the list - this method returns true if the object is visible from the given vantage point (Me, the player actor), nil if not. This step is used entirely to determine how to report any errors that happen later; an object may be visible, but not valid as an actor, in which case a different message is required than if the object isn't present at all.
Next, the parser determines whether each object can be used as an actor. For each object, the parser calls the validActor method, which returns true if the object can be used as an actor. The default definition of this method in adv.t returns true for any object if the object can be reached by the player. You can override this method to achieve different effects; for example, if the player is carrying a walkie-talkie, you could make any actors that have the other walkie-talkie valid as actors, since they can hear the player through the radio, even though they may not be in the same room.
Note that validActor is intended to determine whether an object is valid as an actor, not necessarily whether the object is logical as an actor. Hence, the validActor method defined in adv.t applies to all objects, not just actors. This method is used in determining whether an object can be addressed by the player at all; if a head of lettuce is present in the same room, the player should be able to address it, even though doing so may not do any good.
For any object which passes the validActor test, the parser notes whether the object is a "preferred" actor, by calling the preferredActor method on each object. This method returns true if the object is generally suitable for use as an actor, nil otherwise. The default definitions in adv.t return true for this method for all objects of class Actor, nil for other types of objects.
After removing all objects that failed the validActor test, the parser looks at its list to see what's left.
If no objects remain, the player has tried to talk to something that is not currently accessible as an actor, or which doesn't exist at all. If none of the objects in the original list were visible, the parser issues error 9: "I don't see any %s here." (Note that the "%s" is replaced with the text of the player's noun phrase. So, if the original command was "Joe, Go West", the error message reads: "I don't see any joe here.") The reason the parser uses the player's original words in the error message is that the parser can't determine what object the player is referring to - it can only use the words the player originally entered. Note that the parser always converts the player's words to lower-case.
If more than one object remains, the parser goes back and looks at the results of the preferredActor method for each object remaining. If any objects returned true from this method, the parser considers only those objects; if all returned nil, the parser ignores the preferredActor results and keeps the original list. If exactly one object returned true from preferredActor, the parser uses that object as the actor. Otherwise, it must ask the player which of the remaining objects was intended; this process is the same as for any other object, and is described below.
If exactly one object remains, the parser uses that object as the actor. The process of determining the actor is completed.
Once the actor (or absence of an actor) has been determined, the parser finds the verb. This is one of the simpler parts of parsing, because the verb must always be the first word of the command. The parser takes the first word and checks to make sure it can be used as a verb; if not, message 17 ("There's no verb in that sentence!") is displayed, and the command is aborted.
When you define a verb, you can specify one word or two. For example, the verb "take" is specified with a single word, whereas "pick up" is specified with two words. In English (and some other languages), a preposition can be associated with a verb in such a way that it effectively becomes part of the verb - the preposition's presence changes the meaning of the verb so much that the verb-preposition combination is effectively a whole new verb: "throw away" has a meaning entirely different from "throw." TADS supports this construct with two-word verbs. Note that when you define a two-word verb, the second word in the two-word verb must be separately defined as a preposition - the parser does not automatically create a preposition for the word. For example, a verb defined as "pick up" requires that the preposition "up" be defined as well (these particular examples are defined in adv.t).
If the word following the verb is defined as a preposition, the parser checks to see if it's defined as part of a two-word verb with the verb in the sentence. If it is, the parser takes the pair of words as the verb - if the player types "pick up" as the first two words of a command, the parser takes the pair as the verb-preposition combination. If the preposition does not go with the verb, and the same word can be used as a noun or an adjective, the parser takes the verb to be the first word of the command only, and assumes the second word is being used as part of a noun phrase.
Once the verb is identified as the first one or two words of the sentence, the parser checks to see if anything remains. If the next word is a sentence separator ("and" or a comma), or if no more words follow, the parser takes it as the end of the sentence, and executes the verb without any objects.
If the next word starts a noun phrase, the reads the noun phrase that follows, and then checks to see what follows that. If the next word is a sentence separator, the parser takes it as the end of the sentence, and executes the verb with the noun phrase as the direct object.
If, instead, the next word is a preposition, the parser checks what follows the preposition. If the next word starts another noun phrase, the parser reads this second noun phrase, then executes the command with both a direct and an indirect object, with the preposition separating them.
If the word following the preposition is a sentence separator, the parser takes the preposition as part of the verb. With verb-preposition combinations, the preposition is sometimes placed immediately after the verb, as in "pick up the book," but can also be put at the end of the sentence, as in "pick it up." So, if the parser finds a preposition at the end of the sentence, it treats the preposition the same way it would have if the preposition had immediately followed the verb.
Note that many of these cases can be ambiguous - based on the parts of speech of the words in a sentence, more than one interpretation is possible. The parser's rules as described above are designed to choose the most sensible interpretation, but sometimes the results will not be exactly what you may have intended. For example, if your game has an object defined as an "up button" (in an elevator, for example), the word "up" will end up defined as a verb, preposition, and adjective. If the player types "push up button," and no verb is defined as "push up," the parser will know that "push" and "up" don't go together as a verb and will interpret this as applying the verb "push" to the object "up button." However, if the player types "pick up button," the parser will interpret this as applying the verb "pick up" to the object "button"; if you also have a down button, the parser will ask the player which button to take - which would be confusing if the player had intended to apply the verb "pick" to the object "up button."
Ambiguous word definitions are very difficult to anticipate, because there are so many possible combinations of words in even a small game. The best way to find these is to test your game, and have other people test it.
Note that in the course of identifying the verb, the parser has also identified the noun phrases that make up the direct and indirect objects, and the preposition that separates the direct and indirect objects. For the command "open the door and the window, " the parser identifies the following sentence components:
actor: Me
verb: open
direct object: the door and the window
preposition: none
indirect object: none
For the command "joe, pick up the ball with the tongs," the parser will identify the following elements:
actor: Joe
verb: pick up
direct object: the ball
preposition: with
indirect object: the tongs
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 on page 94). 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.
Now that the command has been identified, the parser begins to execute it.
The first step in execution is to call a user function called preparseCmd. This functional is optional; if your game doesn't define one, the parser skips this step. The parser calls the function with a single parameter: a list of (single-quoted) strings, one string per word in the command. For example, for the command "open the door and the window," the parser calls the function like this:
preparseCmd( [ 'open' 'the' 'door' ',' 'the' 'window' ] )
Note that the word "and" became a comma. "And," and certain other words, are transformed internally before this function is called:
and , all A but X it I them T him M her R any Y
Your preparseCmd function can return one of three values: true, nil, or a list. If the function returns true, processing continues as normal. If it returns nil, the command is abandoned - the parser discards the current command, and goes back to the beginning of the parsing process to get a new command. If the function returns a list, the list must consist entirely of single-quoted strings; the parser will go back to the step described above under "Checking for an Actor," substituting the returned list for the original command.
Note that the argument to preparseCmd doesn't include the actor specification. For example, if the player types "joe, go west," preparseCmd will receive only "go west." In addition, the list doesn't include the words separating the command from the previous or next command on the same line. For example, if the player types "go west, then pick up the box," the function is called first with simply "go west," then with "pick up the box."
In addition, this command is called for each command just prior to the steps described below. For example, if the player types "go west, then pick up the box," the parser first calls preparseCmd with "go west," and then proceeds through the rest of the processing for this command as described below; after that's all done, the parser comes back to this step, calls preparseCmd with "pick up the box," and then proceeds through the steps below once again.
The preparseCmd function is allowed to change a particular command only once. If preparseCmd returns a list value, the parser goes back and starts processing the list as a new command - as a result, the new command will itself be submitted to preparseCmd when the parser gets back to this step. On this second call, preparseCmd is not allowed to return a list; if it does, the parser displays an error that indicates the preparseCmd appears to be stuck in a loop (message 402) and cancels the command.
Here's a sample implementation of preparseCmd. This implementation converts commands in the format "tell actor to command" to the standard TADS notation for these commands, "actor, command."
#pragma C+ preparseCmd: function( cmd ) { local i, tot, to_loc, actor, the_rest; tot = length( cmd ); /* check to see if it starts with "tell" */ if ( tot > 3 && cmd[ 1 ] == 'tell' ) { /* see if there's a word "to" */ for ( i = 1, tot = length( cmd ) ; i <= tot ; ++i ) { if ( cmd[ i ] == 'to' ) to_loc = i; } /* if there's a "to", convert the command */ if ( to_loc != nil && to_loc > 2 ) { /* find the parts before and after the 'to' */ for ( i = 2, actor = [] ; i < to_loc ; ++i ) actor += cmd[ i ]; for ( the_rest = [], i = to_loc + 1 ; i <= tot ; ++i ) the_rest += cmd[ i ]; /* convert it to "actor, command" */ return actor + ',' + the_rest; } } /* otherwise, process the command as normal */ return true; }
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 Me. 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:
Me.roomCheck( takeVerb )
This method should return either true or nil: a return value of true indicates that the command is allowed to proceed. If roomCheck returns nil, the command is cancelled. The roomCheck method is expected to display a message indicating why the command is not allowed, so the parser doesn't display any additional information when roomCheck returns nil - it simply cancels the command without further comment.
The roomCheck method is intended to be a first, coarse check of whether a command is valid in a room. This method is called prior to any disambiguation, and once for the entire command (regardless of how many direct objects may be involved in the command). The main use of this method is to disallow entire verbs in a room based on the room's condition; for example, you could use roomCheck to prevent the player from being able to take any objects while in the dark. Since this method is called prior to disambiguation, you can prevent the disambiguation process from displaying any information about the room that the player should not be able to see.
The default roomCheck defined in the basicMe class in adv.t simply returns the value of the roomCheck method in the player's location. (The roomCheck defined in the movableActor class does the same thing for the actor's location.) The roomCheck defined in the room class in adv.t always returns true, and the roomCheck defined in the darkroom class in adv.t returns nil unless the location is lit, or the verb is a "dark verb" (i.e., its isDarkVerb property is true), which means that it can be used in the dark. All "system" verbs (such as "save" and "restore"), as well as a few other verbs that don't logically require light (such as "wait"), are defined in adv.t as dark verbs.
The parser must now determine how to execute the command. The first step is to identify the deepverb object associated with the word or words making up the verb; to do this, the parser simply looks up the verb phrase in its dictionary, and finds the object that defined the phrase as one of its verb property values.
A single deepverb can have multiple interpretations; these interpretations are called "templates," because they serve as models for the possible sentences that can be made with the verb.
TADS verb templates are a subtle concept, because templates are not explicitly stated in a deepverb; rather, they are implied by the presence of the properties action, doAction, and ioAction. If an action method exists for the verb, the template sentence that consists only of the verb is implicitly defined. If a doAction property exists for the verb, the template sentence that consists of the verb and a direct object is implicitly defined. If an ioAction property is defined for a particular preposition object, the verb implicitly defines the template sentence that consists of the verb, a direct object, the specified preposition, and an indirect object.
The "wait" verb defined in adv.t defines only the no-object template:
waitVerb: darkVerb verb = 'wait' 'z' action( actor ) = { "Time passes...\n"; } ;
The "lock" verb defined in adv.t defines both a one-object and two-object template, but doesn't have a no-object template:
lockVerb: deepverb verb = 'lock' sdesc = "lock" ioAction( withPrep ) = 'LockWith' doAction = 'Lock' prepDefault = withPrep ;
Note that the ioAction definition can contain certain special flags that affect the way commands matching the template are processed. For example, you could define a template as follows:
ioAction( aboutPrep ) = [ disambigDobjFirst ] 'TellAbout'
The [ disambigDobjFirst ] flag (which is the only flag currently defined) specifies that the parser is to disambiguate the direct object first when a sentence involving this template is executed. Normally, the parser disambiguates the indirect object first, then disambiguates the direct object with the indirect object known. For some commands this is undesirable, because it means that the direct object isn't known when the indirect object is being disambiguated. This flag provides control over the sequence of disambiguation, so that you can determine which object is known first, which allows you to use that object while disambiguating the other.
To choose a template, the parser uses the components of the command, which were determined earlier (see the section "Identifying the Verb" on page 78). If the only non-empty component of the command is the verb, the parser chooses the no-object template; if the deepverb defines an action method, it has a no-object template, and therefore allows no-object sentences.
If the sentence has a verb and a direct object, but no preposition or indirect object, the parser chooses the one-object template. If the deepverb has a doAction property, the verb has a one-object template, and thus allows single-object sentences. The doAction property specifies the verification and action methods for the command: TADS appends the string defined by doAction to the string "verDo" to form the verification method, and "do" to form the action method. For example, if doAction = 'Take', the verification method is called verDoTake, and the action method is called doTake.
If the sentence has a verb, direct object, preposition, and direct object, the parser uses a two-object template. A single deepverb object can define multiple two-object templates; these multiple templates are distinguished from one another by their prepositions. The templates are defined by the ioAction property - each ioAction specifies the preposition which identifies it. If the player types "dig in dirt with shovel," the parser looks in digVerb for an ioAction(withPrep) definition. (Note that digVerb. and withPrep could actually be called anything - we're following the naming conventions used in adv.t here, but you can call verb and preposition objects anything you want. The parser doesn't identify these objects based on their names, but rather based on their vocabulary words, defined by their verb and preposition properties, respectively.)
The parser must now match the components of the sentence entered by the player with the templates available for the verb. Before going on, the parser checks to make sure that the verb object is properly defined: it must define at least one sentence template. If the deepverb object fails to define any templates (that is, it has no action, doAction, or ioAction properties), the parser displays an error (message 23) and cancels the command.
If the player's command has no objects at all, but consists only of a verb, the parser checks to see if the deepverb has an action method; if so, the parser chooses the verb-only sentence template and proceeds with the execution.
If the command has no objects, and the deepverb object does not define an action method, the parser tries to provide a default direct object by calling the doDefault (which stands for "direct-object default") method in the deepverb object. This method is called with the actor, the preposition, and nil (since the indirect object is not yet known) as arguments. For example, if the player types simply "pick up," the parser chooses takeVerb as the deepverb object, sees that it has no action property, so attempts to obtain a default object:
takeVerb.doDefault( Me, nil, nil )
This method is meant to return a list of objects that should be used by default for the command; the definition of this method for the takeVerb defined in adv.t returns a list of all movable objects in the player's location. If this method fails to return a list, the parser simply asks the player to supply a direct object (see below). Otherwise, the parser now gets the value of the prepDefault property in the deepverb object: if this value is not nil, it must refer to a preposition object, which becomes the preposition for the command.
For example, if the player simply types "dig," the parser gets the default list from the doDefault method defined in digVerb, then gets the value of digVerb.prepDefault. Suppose that prepDefault has a value of withPrep: the command's components now look like this:
actor: Me
verb: dig
direct object: (the list returned by doDefault)
preposition: with
indirect object: nil (because it's not yet known)
The parser checks that the template specified by the new components exists for the verb. If it doesn't, the parser skips checking the doDefault return value, and goes on to ask for a direct object.
If a valid list resulted from doDefault, and the new template is valid, the parser goes through the doDefault list to check each item with the direct object verification method defined by the doAction or ioAction method for the new template. If the template is a single-object sentence, which means that the doAction definition is used, the parser calls the verification method with the actor as a parameter. If the template is for a two-object sentence, which means that the ioAction definition is used, the parser calls the verification method with the actor and nil (because the indirect object is not yet known) as parameters. If the ioAction is used, but the ioAction template specifies the [ disambigDobjFirst ] flag, the second (nil) parameter is omitted. Some examples:
object.verDoTake( Me )
object.verDoLockWith( Me, nil )
object.verDoTellAbout( Me )
In this third example, we're assuming that tellVerb's ioAction property has the [ disambigDobjFirst ] flag.
The verification methods are called silently, which means that any messages they display are hidden from the user (the same as during the disambiguation process). However, the parser notes whether these methods attempt to display any messages; any verification method that attempts to display a message is considered to fail. The parser discards any objects from the list whose verification method fails.
If exactly one object from the doDefault list passes the silent verification test, that object is chosen as the default direct object for the command. The parser displays the object to the player: if your game program defines the function parseDefault, the parser calls this function with the chosen default object as the parameter, and expects that the function will display a message to indicate that the object has been chosen as the default. Otherwise, the parser displays message 130 ("("), then calls the method thedesc in the default object, then displays message 131 (")"). It then starts this step over again with the new set of command components.
Here's a sample implementation of parseDefault. This definition generates the same message that the parser would if parseDefault weren't defined.
parseDefault: function( obj, prp ) { "("; if ( prp ) "prp.sdesc "; obj.thedesc; ")"; }
If no objects survive the verification test, the parser doesn't have any way to supply a default object. If more than one object survives, the parser still does not supply a default object, because the command doesn't imply a specific object. In either case, the parser now asks the player to supply a direct object; see the description below.
If the player's command has a direct object, but no preposition or indirect object, the parser checks the verb for a single-object sentence template. If the deepverb object has a doAction property, the single-object sentence is allowed. The parser first disambiguates the direct object list (see the section on resolving objects on page 94). If this succeeds, the parser saves the direct object (or list of direct objects) for future use as "it" or "them" (or "him" or "her," if appropriate), and proceeds to execute the command.
If a single-object sentence is not allowed, the parser checks the prepDefault property of the deepverb object; if it's nil, the sentence is not allowed, so the parser displays message 24 ("I don't recognize that sentence") and terminates the current command. Otherwise, the parser uses the prepDefault value as the preposition for the command, and calls the ioDefault method of the deepverb with the actor and preposition as arguments. For example, if the player types "put ball," and the verb has a default preposition of "in," the parser calls ioDefault like this:
putVerb.ioDefault( Me, inPrep )
If this method returns something other than a list, the parser ignores it, and asks the player to supply an indirect object (see below). Otherwise, the parser checks to see if the direct object must be disambiguated prior to the indirect object - if the ioAction property defines the [ disambigDobjFirst] flag, the direct object must be disambiguated first. If so, the parser disambiguates the direct object using the normal process (see the section on disambiguation on page 94). Next, the parser goes through the ioDefault list, and silently calls the verification method for each object. For normal verbs, these calls look like this:
object.verIoPutIn( Me )
When the ioAction property has the [ disambigDobjFirst ] flag, these calls look like this:
object.verIoPutIn( Me, directObject )
These calls are made silently - any messages they display are hidden from the player. However, the parser notes whether they attempt to display anything; any verification method that attempts to display a message is considered to have failed. The parser removes from the ioDefault any objects whose verification method fails at this point.
If exactly one object survives this test, the parser uses this object as the default indirect object and proceeds with the command. First, though, it shows the player that the object is being assumed. If your game defines a function called parseDefault, the parser calls this function with the verb and the preposition as arguments. Note that you must define parseDefault to take a variable argument list, because it can be called with either one or two arguments: when called with one argument, it means that the a default direct object is being assumed; when called with two arguments, it means that a default indirect object is being assumed. Note further that the preposition supplied as the second argument can be nil, which will be the case when a two-object command is entered that has no preposition (for example: "give joe the bottle"). If your game doesn't define a parseDefault function, the parser generates a message by displaying message number 130 ("("), then calling the preposition object's sdesc method, then displaying message 132 (a space), then calling the default indirect object's thedesc method, and then displaying message 131 (")").
If no objects survive the verification test, the parser can't supply a default indirect object. If more than one object survives, the parser doesn't supply a default indirect object, because the command doesn't imply a particular object. In either case, the parser asks the player to supply an indirect object (see below).
If the player specifies a command with two objects and no preposition, the parser treats the first object as the indirect object, and the second as the direct object, and uses the two-object sentence format. When this type of sentence is entered, the parser gets the value of the deepverb object's nilPrep property - this property should return a preposition object that is to be used for a sentence of this type. If nilPrep doesn't return a preposition object, the parser assumes that the preposition is "to," and finds the preposition object with that vocabulary word. In any case, the parser transforms the sentence from the format VERB IOBJ DOBJ to the standard two-object sentence format, VERB DOBJ PREP IOBJ, then proceeds with the command as though it had been entered that way in the first place.
If the player enters a command in the format VERB PREP OBJ1 OBJ2, the parser converts this to VERB OBJ2 PREP OBJ1 - in other words, the original sentence is interpreted as VERB PREP IOBJ DOBJ. After the conversion, the parser proceeds with the sentence as though it had originally been entered as VERB DOBJ PREP IOBJ. This conversion is most useful for some non-English languages; English speakers don't typically use this construction.
If the player specifies a command with a preposition and one or two objects, the parser finds the ioAction definition that matches the given preposition. If the verb doesn't have an appropriate ioAction property, the parser displays message 24 ("I don't understand that sentence"), and abandons the command.
If only one object was provided, the parser takes the object that was given as the indirect object, and attempts to find a direct object. The parser uses the same mechanism to find the direct object - first by trying to find a default direct object, then by asking the player - as described above in Case 1. This type of sentence is useful when the direct object is implied by the verb; for example, if a single actor is present in the room with the player, the direct object of "ask about the murder" would implicitly be the actor.
If two objects are provided, the parser disambiguates the two objects. If the ioAction has the [ disambigDobjFirst ] flag, the parser disambiguates the direct object first; otherwise, it disambiguates the indirect object first. It then disambiguates the other object. See the section on resolving objects (page 94) 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 doesn't specify one and the parser can't find a suitable default, the parser must ask the player to supply an object for the command. First, it must display a message asking for the object.
To do this, the parser checks to see if your game program defines a function called parseAskobjActor; if so, it 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 if you don't define a parseAskobjActor function, but you do define a function called parseAskobj, the function parseAskobj will be called 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.)
Note that the parseAskobjActor function is called with a third argument only when the parser is asking for an indirect object. The third argument is the preposition object, which may be nil. Because the parser calls parseAskobjActor with two or three arguments, depending on whether it's asking for a direct or indirect object, your parseAskobjActor function must be defined as taking variable arguments. You can determine if the function is being called with two or three arguments by inspecting the argcount pseudo-variable.
If your game does not have a parseAskobjActor (or parseAskobj) function, the parser generates its own message. If no actor (other than Me) is specified in the command, the parser displays message 140 ("What do you want to"); if an actor is specified, the parser displays message 148 ("What do you want"), then calls the actor's thedesc method, then displays message 149 ("to"). Next, the parser calls the sdesc method in the deepverb object. If an indirect object is required, the parser next displays an appropriate pronoun for the direct object, then calls the preposition's sdesc method (if the preposition is nil, the parser displays message 142, "to"). Finally, the parser displays message 143 ("?").
Here's a sample definition of parseAskobjActor, which generates the same prompt that the parser would if parseAskobjActor weren't defined.
parseAskobjActor: function( a, v, ... ) { if ( argcount = 3 ) { "What do you want "; if ( a <> Me ) a.thedesc; " to <<v.sdesc>> it <<getarg( 3 ).sdesc>>?"; } else { "What do you want "; if ( a <> Me ) a.thedesc; "to <<v.sdesc>>?"; } }
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), 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.
After displaying a message (either with parseAskobjActor 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. 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 new command does not appear to consist of only a noun phrase, 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 a nswer 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 in a command:
>type "hello, world!" on the terminal
Strings are always mapped to the special object strObj, which must be supplied by your game program (adv.t provides a class called basicStrObj, which provides a suitable definition that you can use a superclass of your own strObj object). In this case, the parser uses strObj as the direct object to the command, and sets the value property of strObj to the (single-quoted) string value 'hello, world!'.
Likewise, the player can use numbers:
>dial 8056 on the phone
Numbers are mapped to the special object numObj; as with strObj, this must be supplied by your game (adv.t provides a basicNumObj class). In this example, the direct object will be numObj, and its value property will be set to the numeric value 8056.
The special words for the pronouns (it, them, him, and her) are taken from the direct object of the previous command. (Note that you can also set the meaning of the pronouns from within game code, using the setit() built-in function. When you don't use setit() to set the pronouns yourself, the system will automatically set the pronouns to the direct object of the previous command.) Before accepting the pronoun, the system uses the access-checking function (validDo for a direct object, validIo for an indirect object) to ensure that the object is still accessible.
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.
First, we identify which objects are accessible. To do this, the parser calls validDoList (or validIoList, as appropriate) in the deepverb. This method is called with the actor, preposition, and other object as parameters; the preposition will be nil for one-object commands, and the other object may be nil if it's not known yet or isn't appropriate (when calling validDoList for a one-object command, the other object will always be nil). This method returns either a list of objects, or nil; if it returns a list, this list is intersected with the list of objects that the parser found for the noun phrase, otherwise the entire noun phrase object list is retained. Note that validDoList and validIoList must return every valid object, but they need not return only valid objects - since the remaining objects will be checked for validity with validDo or validIo, validDoList and validIoList can harmlessly include invalid objects in their results.
Next, we call validDo (or validIo) in the deepverb for each surviving object. The arguments to this method are the actor, the object whose validity is being tested, and a "sequence number." The sequence number starts off at 1, and is incremented for each object in the list. The sequence number can be used when you wish to arbitrarily select a single object from a list of ambiguous objects - simply return true only for the first object, and nil for all of the other objects. If this method returns true, it means that the object is valid for the current command - in other words, the object is accessible to the actor for the purposes of the command. If the player wishes to take an object, for example, the object should be valid for the command if and only if the player can reach the object. For other commands, accessibility may not necessitate that the object is reachable; for example, to look at an object, it need only be visible - if the object is inside a locked glass case, it will be visible but not reachable. The parser eliminates from consideration any objects for which validDo (or validIo) returns nil.
We now check each surviving object to see if it's logical with the verb. To do this, we make a "silent" call to the verification method. Recall that the verb template we chose earlier specifies a pair certain methods - a verification method, and an action method - that we will call in the direct and indirect objects. For example, the verb "take" may define a doAction = 'Take', which means that the verification method for the direct object is verDoTake, and the action method is doTake. The call is silent because we hide any messages that the method displays - the player will not be aware that we are making this call. However, we do take note of whether the method attempts to display any messages; if it does, the verification fails for the object. We make a list of all of the objects that survived the validity test and the verification test.
We now have two lists: a list of valid objects (objects which passed the validity test), and a list of logical objects (objects which passed the verification test).
If both lists are empty, there's no way to apply the verb to the player's noun phrase - we can't identify any objects for which the command is valid. We check to see if the player's noun phrase referred to any visible objects; if not, we simply display message 9 ("I don't see any %s here", where the "%s" is replaced by the words from the player's noun phrase) and abandon the command. Otherwise, we have a situation where the player referred to one or more visible objects, but those objects can't be accessed for the purposes of this command. In this case, we check to see if the verb object has a cantReach method - if so, we call the verb's cantReach method with four parameters: the actor, a list of inaccessible direct objects, a list of inaccessible indirect objects, and the preposition. Actually, only one of these lists will ever be supplied, and the other will always be nil; when the direct object is inaccessible, the first argument has the list of inaccessible (but visible) direct objects, and the second argument is nil. When the indirect object is inaccessible, the arguments are reversed. The preposition argument will be nil if the command is a one-object sentence.
For example, if a closed glass case contains a red book and a blue book, and the player types "take book," the word "book" will refer to the two books inside the case; neither of these books will be accessible for the "take" command, because they're inside a closed container, but they're both visible. The parser will call the cantReach method defined in the verb:
takeVerb.cantReach( Me, [ redBook blueBook ], nil, nil )
If the verb object doesn't define or inherit a cantReach method, the parser uses a different mechanism: for each visible object, it calls the object's sdesc method, then displays message 200 (":"), then calls the cantReach method in the object itself - not in the verb - with the actor as the single argument.
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.
At this point, the parser needs to determine whether the objects in the list are uniquely specified - in other words, it determines whether it has identified a single object for each singular noun phrase that the player entered. If the player types "take book," the parser must find a single object to which "book" refers. Likewise, if the player types "take book and box," the parser must identify a single object for "book," and a single object for "box."
If any of the noun phrases were plural, they're now finished - 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 object which matches the vocabulary word "books" in its plural property.
Similarly, if a noun phrase was specified with "any," as in "take any book" or "drop any of the coins," the player has explicitly asked us to choose one of the objects matching the phrase. The parser simply picks one of the remaining objects (it picks the first object in its internal list, but the order of objects in the internal list is unpredictable).
In addition, if the objects themselves are all indistinguishable from one another, the parser also picks one of the objects arbitrarily. Objects are indistinguishable if two conditions are true: first, that the objects are all derived from the exactly the same class - that is, every object's immediate superclass is the same; and second, that the objects all have the property isEquivalent set to true. If these two conditions hold, the parser considers the objects to be indistinguishable, and simply picks one from the list, exactly as though the user had used "any" with the noun phrase.
For a singular noun phrase, we must now have a single object. If so, we're done with the object. If more than one object remains, however, we have no way of knowing which of the remaining objects the player meant, so we must ask the player to tell us. To do this, we check to see if the game program provides a function called parseDisambig. If so, we call this function with two arguments: a (single-quoted) string giving the text of the noun phrase that the player typed; and a list composed of the surviving objects. If the game doesn't define a parseDisambig function, the parser generates a message: it displays message 101 ("Which %s do you mean," with the "%s" replaced by the text entered by the player for the ambiguous noun phrase), then calls thedesc for each surviving object, then displays message 104 ("?"). Between adjacent objects, the parse displays message 102 (","); and it also displays message 103 ("or") after the second-to-last object. Note one unusual case: if the ambiguous list contains a set of objects that are mutually indistinguishable, as described earlier, the parser displays only one description for the whole set of such objects, and uses adesc instead of thedesc for the object. For example, if the player types "take coin," and the room contains a gold coin and three silver coins which are all indistinguishable from one another, the message looks like this: "Which coin do you mean, the gold coin, or a silver coin?"
Here's an example implementation of parseDisambig, which generates the same message that the parser would generate if parseDisambig weren't defined.
parseDisambig: function( str, lst ) { local i, tot, cnt; "Which << str >> do you mean, "; for ( i := 1, cnt := length( lst ) ; i <= cnt ; ++i ) { lst[ i ].thedesc; if ( i < cnt ) ", "; if ( i + 1 = cnt ) "or "; } "?"; }
After displaying prompting message, the parser lets the user enter a command using commandPrompt code 2. If the player types something that looks like a noun phrase, the parser tries to use the new information to choose from the surviving objects. If the player simply types "all" or "both," the parser chooses all of the surviving objects. If the player types "any," the parser chooses one of the surviving objects arbitrarily. In either case, this completes disambiguation, since the parser has identified the objects the user wishes to use in the command.
If the player types adjectives and/or nouns, the parser limits the surviving list to objects that are associated with all of the new vocabulary words. If no objects remain, the parser displays message 16 ("I don't see that here"). If exactly one object remains, disambiguation is complete, because the noun phrase has been resolved to a particular object. Similarly, if the player typed a noun phrase that consists of multiple objects (for example: "the red one and the blue one"), and the remaining list has the same number of objects as the number of noun phrases the player entered, disambiguation is completed. Otherwise, the phrase is still ambiguous, so the parser goes back and asks the user once again which of the remaining objects is to be used. Note that if parseDisambig isn't defined, the parser displays message 100 ("Let's try it again:") before asking again.
If the player types something that doesn't look like a noun phrase, the parser abandons the current command, and starts over at the beginning, using the new input as the next command.
At this point, everything about the command is known. The parser has converted the player's words into a set of objects, with each object having a specific function in the sentence. Each component of the command - the actor, the verb, the direct object, the preposition, and the indirect object - has been resolved to an object or a list of objects. We have also identified the verb template, which specifies the methods that are to be called in the objects at certain points during execution. We now have all the information necessary to execute the command.
A command can refer to multiple direct objects; for example, the player can type "take the ball and the box." Fortunately, the details of handling multiple objects are entirely handled by the parser; you write the verb handlers in your game for single objects only, and the parser calls these handlers as many times as necessary.
When the player uses multiple direct objects, either by listing several objects or by using "all," the parser checks to make sure the verb allows multiple direct objects. (In certain cases, multiple objects will be disallowed before this point. The parser never allows multiple indirect objects, and it doesn't allow multiple direct objects for verb templates that specify the [ disambigDobjFirst ] flag.) To determine if the verb allows multiple direct objects, the parser calls the rejectMultiDobj method in the deepverb object, passing the preposition as the argument (the argument is nil for single-object commands). If this method returns true, the parser terminates the command without further processing. Note that the parser doesn't display anything if rejectMultiDobj returns true - the parser expects this method to display an appropriate error message if it returns true. You can use rejectMultiDobj if you want to discourage the player from guessing for certain verbs.
The remainder of the processing for the command is done for each object in the direct object list. The parser goes all the way through the rest of the command processing for the first direct object, then comes back to this step and goes through the same process for the next direct object, and so on.
Before proceeding, the parser displays the name of the current direct object if multiple direct objects are being used. If only a single object is used in the command, the parser doesn't display the object name - this is only done for multiple objects, so that the player can see the results of the command for each individual object. Note that the parser displays the object name when "all" is used even if "all" ended up referring to only a single object, because the player didn't say exactly which object he or she meant. For the same reason, we'll display the object name when the player used "any."
To display the object name, the parser first calls the multisdesc method in the current object (the parser calls sdesc instead if the object doesn't define or inherit multisdesc - this is for compatibility with games written with previous versions of TADS, since multisdesc is a recent addition); then, it displays message 120 (":").
Before going on, we check to see if the verb is "again," which requires special handling. This verb is used to repeat the last command. To identify this verb, TADS requires that you provide an object named againVerb - this allows you to define the vocabulary words that are to be used for this special internal command. When the deepverb object for the command is againVerb, the parser knows to perform the special handling necessary to repeat the last command.
If the player types "again" as the first command of the game, the parser displays message 26 ("There's no command to repeat"), and abandons the command. Otherwise, the parser gets the information from the last command - the actor, verb, direct object, preposition, and indirect object - and uses this as the new command information. Before proceeding, the parser calls the validDo method in the deepverb to ensure that the direct object is still valid for the command, and calls validIo to ensure that the indirect object is still valid. If either of these methods indicates that the objects are not valid, the parser displays message 27 ("You can't repeat that command") and terminates the command.
When the verb is something other than "again," the parser saves all of the information for the current command. If the player uses "again" for the next command, it is this information that is used to repeat the command. Note that this information is saved once for each object in a multi-object command, so if the player types "again" after typing "take the box and the ball," the command repeated is "take the ball."
The next step is to check to see if the actor will allow the command. Every command is directed to an actor - if the player doesn't explicitly direct a command to an actor, it implicitly goes to the player actor, Me. The parser calls the method actorAction in the actor object with four arguments: the deepverb object, the current direct object, the preposition, and the indirect object. For example, for the command "take the red book," the call would look like this:
Me.actorAction( takeVerb, redBook, nil, nil )
The actorAction method can either accept the command or disallow it. To accept the command, the method need not do anything at all. To reject the command, the method should use the exit or abort statement. If you use the exit statement, you will disallow the command, but the turn will still be counted - that is, daemons and fuses will run as though the turn succeeded. If you use abort, the turn doesn't count - the daemons and fuses will be skipped. In most cases, you should use exit if you want to disallow the command in actorAction, because the attempt to command the actor should still count as a turn. The abort statement is intended more for "system" commands, such as "save," that are intended to happen outside of the game's world, and so shouldn't count as turns within the game.
Note that the parser doesn't display any messages when actorAction uses exit or abort to disallow the command, so your actorAction method must display an appropriate error message before exiting or aborting. Note also that this method has no return value.
If the actor allows the command, the next step is to determine if the actor's current location allows the command. To do this, the parser get the value of the location property from the actor (if this isn't an object, it skips this step), then calls the roomAction method in that object. roomAction takes five arguments: the actor, the verb, the direct object, the preposition, and the indirect object. For example, if the player types "joe, put the ball in the box," the parser calls this:
joe.location.roomAction( joe, putVerb, redBall, inPrep, smallBox )
As with actorAction, the roomAction method can disallow the command by using the exit statement. This method must display a message if it disallows the command, because the parser will simply abandon the command if roomAction executes an exit statement. This method has no return value.
The next step is to call a general-purpose method in the indirect object, and then in the direct object. These general-purpose routines allow you to define certain behaviors for an object for any verb from within a single method. You will sometimes want an object to act a certain way for all (or nearly all) verbs; for example, if you want to create an object that's off in the distance, you will want to handle every verb except "inspect" the same way: "That's too far away." You can do this through the general-purpose methods.
First, if the command has an indirect object, the parser calls the method iobjGen (indirect object general processing) in the indirect object. This method takes four arguments: the actor, the verb, the direct object, and the preposition.
Next, if the command has a direct object, the parser calls dobjGen in the direct object. This method also takes four arguments: the actor, the verb, the indirect object, and the preposition.
These methods, like roomAction and actorAction, can cancel the command by using the exit statement. These methods should display an appropriate message if they do cancel the command, since the parser will simply abandon the command.
Note that the general-purpose handlers are not called if the specific verb "overrides" them. If the object defines - not inherits, but actually defines - either a verification method or an action method for the verb, the general-purpose method is not executed. These methods are intended to be "catch-all" methods; if a verification or action method for a particular verb is defined for the object, the parser assumes that the verb is not to be handled by the catch-all method, so it skips calling the general-purpose routine.
Note that a verification or action method will "override" the general-purpose handler if it's defined in the same object as the general-purpose handler, or if it's defined in any subclass of the object that defines the general-purpose handler. Consider this example:
distantItem: fixeditem dobjGen( a, v, iobj, p ) = { "It's too far away!"; exit; } verDoInspect( actor ) = { inherited.verDoInspect( actor ); } targetDistantItem: distantItem verIoThrowAt( actor ) = { inherited.verIoThrowAt( actor ); } ;
Now, any subclass of targetDistantItem will have verIoThrowAt override the presence of dobjGen. Since verIoThrowAt overrides dobjGen in targetDistantItem, the same applies to any subclass. The same is true of verDoInspect, because it overrides dobjGen by virtue of being defined in distantItem itself.
If the command survives all of the steps so far, the parser carries out the command on the objects involved.
The first step is to verify that the command is possible. We have already determined that the objects involved in the command are accessible for the purposes of the command, but we don't know that the command can actually be applied to the object. For example, if a door is already open, trying to "open the door" should fail.
Verification is done by calling the verification method, which is specified by the verb template as discussed earlier. If there are no objects in the command, there is no verification step; we move on immediately to the action step. Otherwise, we verify first the direct object, then, if the command has one, the indirect object.
If the command has no objects, we simply call the action method defined in the deepverb object. This method takes one argument: the actor. This method is responsible for carrying out the command; it should display an appropriate message - on either success or failure, since this is the entire command processor.
The action method has no return value. It can use the exit statement to skip any remaining commands on the same line, and it can use abort to terminate the turn and skip running fuses and daemons. Generally, you should only use abort with "system" commands that shouldn't count as turns. System commands, such as "save" or "restart," happen outside of the game world, so they shouldn't consume any time within the game world. For an ordinary command, even a failed command should usually count as a turn.
Note that the action method will generally use neither exit nor abort, even if the command fails. You should only use these statements when you want to skip the remaining commands on the same line, which should only be done if something really important happens in the command - ordinary failures in an action routine don't usually warrant cancelling the remainder of the command.
Another way of exiting an action routine is to use the askdo statement. Executing an askdo statement lets you ask the player for a direct object, using exactly the same mechanism that the parser uses when it determines that a direct object is required - askdo even supplies a default direct object using the same techniques as the parser. See the description of default objects (page 70) details.
For example, when the player types "wait," the parser makes this call:
waitVerb.action( Me )
If the command has one object, the parser starts by calling the verification method in the object. The verification method, which is named verDoVerb (where Verb is replaced by the doAction property value - for example, for "take," the verification method is called verDoTake), takes one parameter: the actor.
If the verification method is not defined or inherited for the object, the parser disallows the command and displays an error. To display the error, the parser checks to see if the game defines a function called parseError2. If so, it calls this function with four parameters: the verb, the direct object, the preposition, and the indirect object (note that the preposition and indirect object may be nil); this routine is responsible for displaying an appropriate message. If this function isn't defined, the parser generates a message: it displays message 110 ("I don't know how to"), then calls the sdesc method in the deepverb object. Then, if there's only a direct object, it displays message 111 (a space), then calls the thedesc method in the direct object; if there's an indirect object, the parser displays message 112 ("anything"), then calls sdesc in the preposition object (or displays message 113 ("to") if there's no preposition), then displays message 114 (a space), then calls thedesc in the indirect object.
Here's an example implementation of parseError2, which generates the same message that the parser would use if parseError2 were not defined.
parseError2: function( v, d, p, i ) { "I don't know how to << v.sdesc >> "; if ( d ) "<< d.thedesc >>. "; else "anything << p ? p.sdesc : "to" >> << i.thedesc >>. "; }
Apart from this extra message, the absence of a defined or inherited verification method is equivalent to the verification method failing by displaying a message.
If the verification method displays a message, the command is terminated; the parser proceeds with the next direct object in the same command, or moves on to the next command on the command line. The verification method doesn't have a return value - it indicates failure by displaying a message, and indicates success by displaying nothing.
Note that verification methods must not make any changes to game state - they must never set the object of a property, or call any built-in functions with side effects (for example, never call restart or askfile from within a verification method). Verification methods can be called "silently," which means that the parser hides any messages they generate from the player - see the section on resolving objects (page 94) for details. The parser is simply testing various possibilities when making these silent calls, and hasn't committed to a course of action yet, so these calls must not have any effect on the game state.
If the verification test succeeds - in other words, the verification method doesn't display any messages - the parser calls the action method in the direct object. The action method is named doVerb (for example, the action method for "take" is called doTake). Note that the do in doTake stands for "direct object" - it's not the word "do." The action method is called with one parameter: the actor.
The action method is responsible for carrying out the command. It should display an appropriate message - both on success and on failure, because this is the entire processing of the command.
Note that it's possible for the action method to reject the command, even though the verification method allowed it. If the action method wants to apply a tougher standard to the conditions under which the command can succeed, it can do so without any problem.
For example, when the player types "take red book," the parser calls these methods:
redBook.verDoTake( Me ) redBook.doTake( Me )
When both a direct and an indirect object are specified, the verification sequence involves both objects. First, the parser checks to make sure that the verification methods are defined or inherited for both objects; if not, the command fails, and the parser displays a message as described above for Case 2.
The parser first verifies the direct object. It does this by calling verDoVerb in the direct object. For a normal verb, whose the verb template does not define the [ disambigDobjFirst ] flag, this method takes two parameters: the actor, and the indirect object. Otherwise, the only parameter is the actor.
If the direct object verification is successful, we verify the indirect object by calling verIoVerb in the indirect object. For a normal verb, whose template does not specify [ disambigDobjFirst ], this method takes only one parameter: the actor. For verb templates that do specify the [ disambigDobjFirst ] flag, this method takes two parameters: the actor, and the direct object.
If either verification method displays a message, the command fails, and we proceed to the next direct object of the current command, or to the next command on the command line if there are no more direct objects for the current command.
If both verification methods succeed, we call the action method for the verb template. The action method for a two-object command is always called in the indirect object. The method is called ioVerb, and takes two parameters: the actor, and the direct object. (Note that the direct object is always included in this call, regardless of any special flags in the verb template.) As with a single-object action method, this action method is responsible for carrying out the command, and must display an appropriate message, whatever the outcome of the command.
Note that the parser will not call any action method in the direct object for a two-object command. Your action method in the indirect object is free to call a method in the direct object if you wish to have a direct object method process the command, but the parser will never call such a method directly.
For example, if the player types "put ball in box," the parser makes these calls:
ball.verDoPutIn( Me, box ) box.verIoPutIn( Me ) box.ioPutIn( Me, ball )
After processing the verification and action methods, the command is finished. The parser goes back and processes the next direct object in the same command, as described earlier. The steps above are repeated for each direct object in the list.
Once all of the direct objects have been processed for a particular command, the turn is over. Even if the command line has additional commands following the current command, the parser considers the current command to be a complete turn - the additional commands will be processed, but will be counted as additional turns. So, once all of the direct objects have been processed for a command, the parser executes the daemons and fuses.
The parser first executes the daemons. The order in which the daemons are executed is arbitrary; it depends on the (unpredictable) order in which the system builds its internal lists as daemons are added and removed. The parser executes all active daemons set with the setdaemon() built-in function, then executes all active daemons set with the notify() built-in function.
Next, the parser executes any active fuses that has "burned down." As with daemons, the order in which fuses are executed is arbitrary and unpredictable. The system first executes any fuses set with the setfuse() function, then executes any fuses set with the notify() function. Before executing each fuse, the parser removes it from the list of active fuses. Only fuses that have burned down are executed; others are left to be processed when they're ready.
Note that the fuse timer is entirely under the control of your game program. The only time that fuse timers are advanced is when you call the incturn() built-in function. This function advances all fuse timers by one turn (or by a specified number of turns, if you provide an argument), and marks any fuses that burn down as being ready for execution. The incturn() function doesn't actually execute any fuses, but merely advances their timers.
TADS has a couple of convenience features that make it easy to create certain common definitions.
First, TADS lets you define object-level verb synonyms. It's often desirable to make a number of verbs work the same way with a particular object, because different users will try to do the same thing with slightly different commands. For example, if you have a touch-pad on a control panel in your game, the player may try a number of different verbs with it: touch, push, tap, press. "Push" and "press" are already synonymous in adv.t (they both refer to pushVerb), but "touch" is a separate verb, and "tap" isn't even defined, so you would have to add this verb in your game program. Since "touch" and "tap" are separate verbs from "push," you will need to make the touch pad object respond to all three verification methods and all three action methods the same way. You could do this with code like this:
verDoPush( actor ) = { self.verDoTouch( actor ); } doPush( actor ) = { self.doTouch( actor ); }
This is tedious, though - especially if you have more than two or three verbs that you want to make synonymous. Instead of these lengthy definitions, you can use the TADS object-level verb synonym feature. To do this, you use the special pseudo-property doSynonym:
doSynonym( 'Touch' ) = 'Push'
This simple definition has the same effect as the much lengthier definitions above. The way to read this definition is: the direct-object verb synonym for "touch" is "push."
You can specify that more than one verb is a synonym for another verb. To do this, simply list all of the different verb synonyms on the right-hand side of the equals sign:
doSynonym( 'Touch' ) = 'Push' 'Tap'
Read this definition as: the direct-object verb synonyms for "touch" are "push" and "tap." Note that the fact that you can use multiple verbs on the right-hand side may make it easier to remember which way this direction works. All of the verbs in the command are redirected to a single verb, so the "target" - the verb handler that's actually called when any of these verbs are used - must be in the position where only one verb can go, which is inside the parentheses. So, if the player types "push pad," the parser will call verDoTouch and doTouch.
ioSynonym works the same way, but maps the verIoVerb and ioVerb handlers of one or more verbs to those of another verb. Whereas doSynonym makes one or more verbs synonymous when the object is used as a direct object, ioSynonym makes verbs synonymous when the object is used as an indirect object.
Note that verb synonyms created with doSynonym and ioSynonym apply only to the object in which they are defined. A verb synonym created for one object doesn't affect any other objects, so "touch" and "push" are still separate verbs for other objects. Of course, verb synonyms are inherited just like any other verb handler.
The second convenience feature lets you specify that when a verb is applied to a particular object, it should instead be applied to a different object. This is often convenient when you are composing a complex object in your game from several internal objects. For example, you might want to include a desk with a drawer. The desk and drawer are separate objects, but the player may type "open desk" to open the drawer. You could code this by redirecting the verb verification and action methods for "open" from the desk to the drawer:
desk: fixeditem noun = 'desk' sdesc = "desk" location = office verDoOpen( actor ) = { deskDrawer.verDoOpen( actor ); } doOpen( actor ) = { deskDrawer.doOpen( actor ); } ;
This works, but redirecting more verbs would be tedious - and you'd probably want to redirect at least "close," and probably "look in" and "put in" as well. To avoid the large amount of typing this would involve, you can use the verb handler redirection syntax:
doOpen -> deskDrawer
(Note that the symbol "->" is made up of a hyphen followed by a greater-than sign.) This single line replaces the verDoOpen and doOpen definitions above. It indicates that both the doOpen and verDoOpen methods, when called on the desk, should be redirected to the drawer.
For the most part, TADS doesn't treat the player actor's object, Me, any differently from any other actor in the game. When the player types a command that isn't explicitly directed to another actor, TADS assumes that the actor is Me, and from that point on acts the same as it would if the command were being directed to any other actor.
All of the command handling methods that the parser calls have the current actor object as a parameter, which allows them to be written independently of actor - or, if desired, to take special actions for certain actors. The verb handlers in adv.t generally treat all actors the same. For example, the definition of doTake in the class thing doesn't move the object being taken into the player's inventory, but rather into the current actor's inventory. This allows commands such as "take the box" and "joe, take the box" to be processed using the same code.
If a command handler is written independently of the actor, it needs to be able to display its messages in terms of the actor carrying out the command, rather than simply in terms of "you." It would be very inconvenient if you had to call actor.thedesc every time you wrote a message in a verb handler, so TADS provides a much more convenient mechanism called "format strings."
A format string is a special sequence of characters that signals the output formatter to substitute a property of the current command's actor. Instead of using "you" and related words, the messages in adv.t use format strings; your game program should do the same thing any place where an actor other than the player may be taking some action that results in a message.
A format string associates a special character sequence with a property name. When a message includes a format string, enclosed in percent signs (%), the output formatter removes the format string and replaces it by calling the property associated with the format string. For example, one of the format strings defined in adv.t is:
formatstring 'you' fmtYou;
This tells the system that whenever it sees "%you%" in a message being displayed, it removes the "%you%" sequence, and replaces it with the text that results from evaluating the property fmtYou in the current actor.
You can define new format strings of your own, although adv.t defines most of the format strings you'll probably need.
In adv.t, most messages are phrased in terms of format strings. For example, when the player tries to travel a particular direction but can't, adv.t doesn't display "You can't go that way," but instead displays "%You% can't go that way." When the player tries to go east but can't, the system displays "You can't go that way," just as it would have if the format string hadn't been used. But, when the player types "joe, go east," the system displays "Joe can't go that way," because joe.fmtYou displays "joe" rather than "you."
Note that the capitalization of the replacement text follows the capitalization of the format string itself. So, "%you%" becomes "you," while "%You%" becomes "You." ("Joe" is displayed capitalized in either case if the message displayed by joe.fmtYou is itself capitalized.)
Note that many format strings are defined for other parts of the sentence. When writing a message, you will usually have to use several format strings to keep the sentence grammatical. For example:
"%You% %are%n't wearing %your% spacesuit."
This message will become "You aren't wearing your spacesuit" when displayed for the player character, but will become "Joe isn't wearing his spacesuit" when displayed for Joe.
TADS can only handle prepositions that consist of a single word. Unfortunately, English and other human languages have constructions in which multiple words are used for the function of a single preposition in a sentence; for example, "take the book out of the bag" has two prepositions in a row, "out of," serving the function of a preposition in the sentence.
Although TADS doesn't have any way to specify a sentence template that uses multiple prepositions like this, it does provide a mechanism that lets you specify that certain pairs of words are "glued together" into a single word when they occur together. In the example above, you could define "out of" as such a pair of words: whenever the parser sees "out" followed directly by "of," it can be made to put these two words together and treat the pair as a single word.
To define a pair of words that should be treated as a single word, you use the compoundWord statement. For example:
compoundWord 'out' 'of' 'outof';
This specifies that when the parser sees the word "out," followed directly by the word "of," it should take the pair of words and replace it with the single word "outof." (Note this replacement word follows the convention in adv.t, which is to simply concatenate the two words to form the replacement word, but the replacement can be anything at all - you could have made the replacement word, for example, "out_of.")
Note that you must still define "outof" as a preposition. Even though "out" and "of" are separately defined as prepositions, the result of a compound word replacement must also be defined as a vocabulary word. Once the pair of words is replaced with the compound word, the parser forgets ever knowing about the pair as separate words.
You can't directly define three-word (or longer) compound words, but you can indirectly do so by constructing the longer words from two-word directives. For example, to convert "out from under" into a single preposition, you would use two compound word definitions:
compoundWord 'out' 'from' 'outfrom'; compoundWord 'outfrom' 'under' 'outfromunder';
When the parser finds the sequence "out from under," it first converts "out from" into "outfrom." Then, it checks the sentence again, and finds the pair "outfrom under," which it converts to "outfromunder."
The parser treats a number of words as special reserved words. These words aren't ordinary vocabulary words, because they don't fit in to the parts of speech used for normal vocabulary words - they aren't nouns, plurals, adjectives, articles, verbs, or prepositions. Since they aren't ordinary vocabulary words, you can't define them using the normal vocabulary definitions; instead, you use a separate command to specify these words: the specialWords statement.
This command can appear anywhere that one of the other special commands (such as formatstring or compoundWord) is allowed. Following the specialWords keyword, you list all of the special words in a fixed order. You must specify the words in the order shown below, and you must provide at least one word for every position. You can provide more than one word for each position, by listing each synonym separated by an equals sign (=). The default list, which matches the built-in list if no specialWords command is used in your game, looks like this:
specialWords 'of', 'and', 'then', 'all' = 'everything', 'both', 'but' = 'except', 'one', 'ones', 'it', 'them', 'him', 'her', 'any' = 'either' ;
Except for the words in the "of," "one," and "ones" positions, the words specified in the list can't be used as ordinary vocabulary words at all - they're always considered to have their special meaning as listed here. The slots for "of," "one," and "ones" are not reserved in ordinary use; their special meanings are in effect only when the parser expects them according to context.
Note that the slot occupied by "any" is a recent addition. You can omit this position in a specialWords, in which case the system will use the default ("any" and "either" will be used as synonyms for this position).
You can use the special replace and modify commands with specialWords. If your statement starts off with replace specialWords, any previous special words definitions are discarded, and the new set is used. (Note that the same thing happens if you specify a second specialWords directive in your game program with neither a replace nor modify prefix, but the compiler will warn you that you should use replace to make your intentions explicit.) If your statement starts off with modify specialWords, the compiler adds the special words in the new list to the words that have already been defined in previous specialWords statements. When using modify, you can use nil in any slot to indicate that you don't wish to add any words to that position. You can therefore use modify as a means of adding a new special word or words to one or more slots, without having to specify the entire list again.
When an error occurs during parsing, the parser tells the user what went wrong with as specific an error message as possible. One of the design goals of the parser is to be friendly to players; although error messages are inherently somewhat unfriendly, they certainly can't be eliminated entirely, so we at least try to make them informative. The player should always be able to understand why the game rejects a command, so that he or she can figure out what to say instead.
The parser lets your game program provide a custom message for every error. Most errors are handled through the function parseError - if your game defines this function, the parser will call it whenever it wants to display an error. A few parser messages are more specialized, and use different functions that you can optionally provide.
The parser calls the parseError function with two parameters: a message number, and the text of the default message. The message number identifies the reason the parser is displaying a message - each situation that requires a message is given its own message number. The default message is the text that the parser would have displayed if you did not define a parseError function in your game at all.
You may have noticed references to message numbers elsewhere in this chapter - these were referring to parseError message numbers.
Your parseError function should return either nil or a (single-quoted) string value. If your parseError function returns nil, it means that you want to use the default message - this is the same as not defining a parseError function at all. If your function returns a string value, the string is displayed in place of the parser's default message.
Note that a few of the default messages contain the sequence "%s". This special sequence is replaced in the actual message displayed by a string value of some sort; for example, in message 2, the "%s" is replaced with the unknown word. Similarly, the special sequence "%c" in message is replaced with a single character, and "%d" is replaced by a number. You can use a "%" sequence in any message you provide to replace a message where the default text uses the same special sequence.
Messages with numbers below 100 are complete messages - they're not part of more complex messages built out of pieces. Messages 100 and above are different: they're fragments of complete messages; several such fragments are combined (sometimes with other text as well) to form a complete message. So, if you want to do some special formatting, such as enclosing any parser error messages in parentheses, you can easily do so for any of the regular messages, and avoid doing so for the complex messages, you can simply check the message number to make sure it's less than 100, and apply your special formatting if so.
Most of the messages in the first group are self-explanatory, although a few require some explanation.
Messages 3, 10, and 11 are generated if a word the player uses refers to more than a fixed number of objects in the game. Note that the limit applies regardless of whether the objects are all present or not - the limit is for the total number of objects in the entire game with the same vocabulary word. (For this reason, it's probably not a good idea to define a noun or adjective in a very general class such as "thing" or "item".) The limit in version 2.2 is 200 objects; prior to 2.2 it was 100 objects.
Message 9 is generated if the words the player uses to refer to an object are not all defined for at least one object. For example, if your game has a blue book, and a red box, the words "red" and "blue" will both be recognized as adjectives, and "book" and "box" as nouns; if the player types "look at red book," the phrase "red book" will be recognized as a valid noun phrase in form, but it doesn't refer to any object defined in the game. The parser will respond with message 9.
Messages 13 and 14 are used for pronouns. When the player refers to one of the singular pronouns (it, her, and him), and the object that the pronoun represents is no longer accessible, message 13 is used. Message 14 is used with the pronoun "them."
Message 15 is used when the player uses "all" as the object of a command, but there are no suitable objects present.
Message 28 is displayed when the player enters a command with multiple direct objects for a verb that requires the direct object to be disambiguated first. Verbs that process the direct object first accept only one direct object.
Message 30 is displayed when the player enters a command like "take 3 balls," but fewer such items are present.
When the player addresses a command to an actor, and the actor is visible to the player (the actor's isVisible( Me ) returns true), but the actor is not a valid actor (the actor's validActor method returns nil), the parser displays message 31.
1 - I don't understand the punctuation "%c".
2 - I don't know the word "%s".
3 - The word "%s" refers to too many objects.
4 - I think you left something out after "all of".
5 - There's something missing after "both of".
6 - I expected a noun after "of".
7 - An article must be followed by a noun.
8 - You used "of" too many times.
Note: message 8 is no longer used.
9 - I don't see any %s here.
10 - You're referring to too many objects with "%s".
11 - You're referring to too many objects.
12 - You can only speak to one person at a time.
13 - I don't know what you're referring to with '%s'.
14 - I don't know what you're referring to.
15 - I don't see what you're referring to.
16 - I don't see that here.
17 - There's no verb in that sentence!
18 - I don't understand that sentence.
19 - There are words are your command I couldn't use.
20 - I don't know how to use the word "%s" like that.
21 - There appear to be extra words after your command.
22 - There seem to be extra words in your command.
23 - internal error: verb has no action, doAction, or ioAction
24 - I don't recognize that sentence.
25 - You can't use multiple indirect objects.
26 - There's no command to repeat.
27 - You can't repeat that command.
28 - You can't use multiple objects with this command.
29 - I think you left something out after "any of".
30 - I only see %d of those.
31 - You can't talk to that.
32 - Internal game error: preparseCmd returned an invalid list
33 - Internal game error: preparseCmd command too long
34 - Internal game error: preparseCmd loop
The next set of messages is used to ask questions when the player refers to an object with words that could refer to more than one object. The parser must in these cases ask the player which of the possible objects was meant. Note that these messages will only be used if your game does not define a parseDisambig function, which will be used instead if defined.
100 - Let's try it again:
101 - Which %s do you mean,
102 - ,
103 - or
104 - ?
The next set is used when an object is not suitable for a verb. These messages are needed when a player uses a verb with an object, but the object does not define (or inherit from a superclass) an appropriate "verification" method (verDoVerb or verIoVerb). Messages 111 and 114 consist of a single space.
Note that these messages are not used if your game defines a parseError2 function, since that function will be called instead to display this error.
110 - I don't know how to
111 -
112 - anything
113 - to
114 -
116 - .
The next message is used after each object when multiple objects are listed. For example, when the player enters the command "take all," the parser will apply the verb "take" to each accessible object in sequence; before applying the verb to each object, the parser displays the name of the object, followed by this message, so that the player can see the results of the command for each object.
120 - :
The next messages are used to note objects being used by default. When the player types a command that omits an object, and the parser can determine that the verb implies that a particular object is being used, the parser will display the name of the object with these messages. For example, if the player types "dig", the parser might determine that the direct object is implicitly the ground, the preposition is "with," and the indirect object is implicitly the shovel; in this case, it will display message 130, then the name of the ground, then message 131; then it will display message 130 again, then the name of the assumed preposition, then message 132, then the name of the shovel, then message 131. Message 132 consists of a single space.
Note that these messages won't be used if your game defines a parseDefault function.
130 - (
131 - )
132 -
When the player leaves out the object in a command, but the parser is not able to find a suitable default object, the parser will ask the player to supply an object using these messages.
Note that these messages aren't used if your game defines a parseAskobj or parseAskobjActor function.
140 - What do you want to
141 - it
142 - to
143 - ?
144 - them
145 - him
146 - her
147 - them
148 - What do you want
149 - to
When the player uses words that could refer to more than one object, and the objects in question are visible but not accessible, the parser will call the cantReach method in each of the objects after displaying the name of the object and message 200.
200 - :
Here's a sample of a parseError function that encloses any of the regular messages in square brackets. It ignores any messages that aren't in the simple message range (below 100), since messages outside this range are fragments of more complicated messages, so can't be formatted as though they were entire messages.
parseError: function( str, num ) { if ( num < 100 ) return '[' + str + ']'; else return nil; }
As described throughout this chapter, the parser calls certain methods that objects in your game can define, and calls certain functions that your game can provide. All of these methods and functions are described in detail in this chapter. This section summarizes these properties and function; for full information, refer to the appropriate section earlier in this chapter.
Called in a deepverb object during command execution to execute a command with no objects.
Called in the actor object during command execution.
Used to display an object's short description with an indefinite article.
Defines words as adjectives, and associates them with the object.
Defines words as articles.
Called in an object (only if cantReach isn't defined in the verb) when the object is visible but not accessible; this method is meant to display an explanation of this condition.
Called in a verb when the list of objects (only one of dolist or iolist will be non-nil) is visible but not accessible; this method is meant to display an explanation.
Called immediately after an object is created with the new operator. Minimally, this method should install the object into its container's contents list.
The list of an object's contents.
Called immediately before an object is destroyed with the delete operator. Minimally, this method should remove the object from its container's contents list, and should make sure that the object is not referenced from any other lists or properties.
Direct Object Action; associated with a deepverb object. Defines the verb template for a single-object command for a verb, and specifies the suffix for to be used for the action and verification methods for the verb (doSuffix and verDoSuffix, respectively).
General direct object handler, called during command execution on the direct object prior to processing the verb's verification and action routines.
Direct Object Default. Associated with a deepverb object. Returns the list of default objects for the verb.
Indirect Object Action; associated with a deepverb object. Defines the verb template for a two-object command for a verb with the given preposition, and specifies the suffix for to be used for the action and verification methods for the verb (io Suffix and verIoSuffix, respectively).
General indirect object handler, called during command execution on the indirect object prior to processing the verb's verification and action routines.
Indirect Object Default; associated with a deepverb object. Returns the list of default objects for the verb.
Used to determine if an object should be treated as equivalent to other objects of its immediate superclass. If this property is true, the parser treats all objects of the same immediate superclass as this object as interchangeable during disambiguation.
If true, the object can be the antecedant for the pronoun "him."
If true, the object can be the antecedant for the pronoun "him."
Used to determine if an object is visible from the given object. The parser uses this to determine how to complain when an object isn't accessible: if it's visible, but not accessible, the parser will use cantReach to complain, otherwise it will simply say "I can't see that here."
The object which contains the object.
If this property is true, it tells the compiler that you know that the location property is not an object constant, and the compiler therefore suppresses the warning it would otherwise generate.
Used by the parser to display the current object when iterating through a list of direct objects in a command.
Associated with a deepverb object; used by the parser to determine the preposition to use when a command of the form VERB IOBJ DOBJ is entered. If undefined, the preposition object associated with the word "to" is used by default.
Defines words as nouns, and associates them with the object.
Defines words as plurals, and associates them with the object.
Used by the parser to determine if an object is a "preferred" actor; if this property is true, the object is chosen as the actor in cases where the actor is ambiguous and none of the other objects are preferred actors.
Associated with a deepverb object. The parser uses this property to find the default preposition object to use when the player enters a partial command that starts with the verb and a direct object, but omits the preposition and indirect object.
Defines words as prepositions, and associates them with the object.
Associated with a deepverb object. If this method returns true, the verb doesn't allow multiple direct objects or "all."
Called in the actor's location during command execution.
Called in the actor at the beginning of execution, before object disambiguation.
Short description. Used by the parser to display an object's name.
Called in the actor's location to update the statue line.
Displays the object's name with a definite article.
Called by the parser to determine if the object is valid as an actor. This method doesn't check to see if the object is logical to use as an actor, but simply if the object is accessible as the target of a command.
Associated with a deepverb object. Returns true if the object is accessible as a direct object for the verb.
Associated with a deepverb object. Returns a list of possibly-valid direct objects for the verb. The objects returned will be tested for validity with validDo, so the list can contain invalid objects as well, but it must contain allof the valid direct objects for the verb.
Associated with a deepverb object. Returns true if the object is accessible as an indirect object for the verb.
Associated with a deepverb object. Returns a list of possibly-valid indirect objects for the verb. The objects returned will be tested for validity with validIo, so the list can contain invalid objects as well, but it must contain all of the valid indirect objects for the verb.
The parser sets this property of the strObj or numObj object to the actual value of the string or number (respectively) entered in a command.
Defines words as verbs, and associates them with the object. Strings in this list can consist of one or two words; the second word must also be separately defined as a preposition.
The deepverb object with the vocabulary for "again." The parser internally handles the "again" processing when this verb's vocabulary word is entered as the command.
The player actor object. When a command is not explicitly directed to another actor, this object is the actor to which the command is sent.
A special object which is used as the direct object when a number is entered in a command (such as in "enter 30 on calculator," in which numObj is the direct object). The value property is set to the number actually entered in the command.
A special object which is used as the direct object when a quoted string is entered in a command (such as in 'type "hello" on keyboard,' in which strObj is the direct object). The value property is set to the string actually entered in the command.
Formerly required, but no longer directly used by the parser.
The parser calls this function, if defined, to display the prompt before asking for a command. The type code specifies the type of input being requested.
The system calls this immediately after loading the game, and again whenever the game is restarted. This function can perform any startup operations, such as setting fuses and daemons, and displaying an introductory message.
The parser calls this function whenever the player enters an empty command. You can display an error message, if you wish.
The parser calls this function, if defined, to prompt for a missing direct or indirect object. If parseAskobjActor is defined, parseAskobj is ignored. If two arguments are provided, an indirect object is being requested, and the second argument is a preposition (which may be nil); otherwise, a direct object is being requested.
The parser calls this function, if defined, to prompt for a missing direct or indirect object. If three arguments are provided, an indirect object is being requested, and the third argument is a preposition (which may be nil); otherwise, a direct object is being requested.
The parser calls this function, if defined, to display a message indicating that the object (and the preposition, if not nil) are being used by default.
The parser calls this function, if defined, to prompt for more information when the player enters an ambiguous object name. The nameString is the string of words entered by the player (given as a single-quoted string), and the objList is a list of the objects matching the name.
The parser calls this function, if defined, to get the text of an error message. num is the message number, and str is the text of the message that the parser would display by default if this function weren't defined.
The parser calls this function, if defined, to display a message indicating that the verb can't be applied to these objects (because one of the objects doesn't define or inherit a verification method for the verb).
Called by the compiler, after compilation is complete, to perform any desired initialization. This function can't set any fuses or daemons, or display any messages; these functions are not saved in the compiled game file, so they must wait until the init function, which is invoked at run-time. The preinit function is intended to perform any time-consuming computation - for example, setting up lists of objects or initializing property values - that would otherwise be done in init every time the game is started. By performing these functions in preinit, the work can be done at compile-time, saving players from having to wait for it each time they run the game. Note that this function won't be called until run-time if the game is compiled for debugging; while this doesn't affect the behavior of the game (apart from slowing down run-time startup), it allows you to use the debugger to step through your preinit function.
The parser calls this function with a (single-quoted) string giving the full text of the command line each time the player enters a new command.
The parser calls this function with a list of (single-quoted) strings giving the words of the current command. This function is called for each individual command on a command line.
TADS 2.2 Part I | Table of Contents | TADSVER.MAC |