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


Chapter Four


Parsing Fundamentals

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 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 chapter is intended to help you get started - we begin with the minimum information you need, and then move on to the more advanced features.

Theorems

Throughout this chapter you’ll find points which we jokingly refer to as “theorems,” which emphasize facts about the parser that are especially important and subtle. In most cases, the theorems point out features that frequently confuse game authors who are first learning TADS; if you’re skimming this manual for the first time, you should pay special attention to these points. The theorems are by no means a complete summary, but are simply meant to highlight the most subtle topics.

Quick Reference

Experienced game authors might find the section Summary of Parsing and Execution Sequence to be a useful quick reference to the parser’s operations.


What the Parser Does

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 at least a little 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.


Built-in vs. Game-Programmed Features

When we talk about “the parser,” we’re talking about a collection of program code that reads commands from the player, interprets the commands, and carries out the specified game actions. This collection of code is divided into two parts: a portion that’s built in to the TADS interpreter, and a portion that’s defined in TADS code in the game. Furthermore, the part defined in TADS code is usually further divided: part of this code usually comes from a standard library, such as adv.t, and part is unique to the game.

The Built-in Parser and Customization Hooks

The built-in parser handles most of the “syntactic analysis” part of parsing: dividing a command into individual words (or “tokens”), determining the sentence boundaries of command lines with multiple commands, figuring out which words specify the verb and prepositions, and identifying noun phrases. The built-in portion also defines the execution process by calling a series of methods in the objects involved in a command to carry out the action of the command.

The portion of the parser that is built in to the TADS interpreter obviously cannot be changed directly by the TADS game program. However, the built-in parser has numerous customization “hooks,” which are specific places in the parsing process where the game can define custom behavior. The number of customization hooks has increased over time, as has their power; with modern versions of TADS, there’s very little of the built-in parser that you can’t override.

The parser customization hooks take several forms. Some of the hooks let you define strings that the parser uses for certain special words, such as “all” and “except”. Most of the hooks let you define a function or an object method that the parser invokes at a certain stage in the parsing process, which you can use to modify the way that stage works. In most cases, a customization hook function lets you completely override an aspect of the built-in parser.

Library Code

The built-in parser does not provide any of its own verbs, prepositions, or objects; this is the job of the game program. Since most games have a large set of basic commands and object classes in common, TADS includes in its standard library (defined in the file adv.t) numerous verbs and prepositions.

Your game program can change anything in the library; for that matter, you can throw the library away entirely and create your own, or use a library someone else has designed (WorldClass, Alt or Pianosa, for example).

Game Code

Most games define at least a few verbs of their own, because the pre-defined libraries are designed to be general and so don’t include every possible verb. In addition, some games use one or more customization hooks to override the default behavior of the built-in parser, in order to achieve some special game-specific effect.


Using Pre-Defined Objects

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

Theorem 1: A vocabulary string must be in single quotes.

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.


Responding to Commands

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 doOpen 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 method 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 method 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 current player character actor, given by parserGetMe(), is used by default. You can always get the player’s current location with parserGetMe().location (note that this is the player’s immediate container, so it may be a nested object, such as a bed within a room, rather than a room); and you can find out what the player is carrying with parserGetMe().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 parserGetMe().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%s% 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) method - we didn’t do anything to the verification (verDoOpen) method, 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 method 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 method should ever do. The most important thing to remember about verification methods 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 method should change game state.

Theorem 2: Never modify any game state in a verification method.

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

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. It’s always a good idea to add a verification method to the thing class for each new verb you define, even if your method only says “I don’t know how to do that”; at the very least, this lets you customize the default response to the verb.


Creating New Commands

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.

Theorem 3: verIoXxxx does not have a dobj parameter.

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

Theorem 4: The parser never calls doXxxx for a two-object verb.

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 method is called before the indirect object is known, and hence no indirect object is passed to it; and since the indirect object verification method is called after the direct object is already known, the direct object is supplied as a parameter to this method.


Adding to an Existing Verb

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.

Theorem 5: Don’t edit adv.t directly - use modify instead.


Visibility, Reachability, Accessibility, Validity

The TADS parser and the standard library (adv.t) define several related concepts that control which objects the player can use in a command. The objects that are available to the player vary from turn to turn, since the player’s location in the game and the state of objects in the game can change on each turn, and can also depend on which command the player is issuing, since different commands have different criteria.

Visibility

An object is visible if the player can see the object from the current location (or, more precisely, from the current location of the player character object, as indicated by parserGetMe()).

The parser sees what the player sees. When the player enters a noun phrase, the parser relates the noun phrase to the objects that are visible to the player character object.

Visibility is controlled by the isVisible(vantage) method defined for each object. In adv.t, object.isVisible(vantage) is defined in the thing class to return true if object is in the same location as vantage, or object is in a container whose contents are visible (as indicated by the container’s contentsVisible property) and the object.container.isVisible(vantage).

adv.t provides a helper function called visibleList(object, actor). This function returns a list of the objects contained within object that are visible to actor.

Reachability

An object is reachable if the player can touch the object.

The method isReachable(actor) determines whether an object is reachable. This method is defined for each object. In adv.t, the thing class defines object.isReachable(actor) to return true if object is in actor’s location’s reachable list (reachable is a property that can be defined for each room as a list of objects that are explicitly reachable to any actor in that location), or if actor is in the same location as object, or if object’s location’s contents are reachable (as defined by the container’s contentsReachable property) and object’s location is reachable to the actor object.

Note that the parser never directly requires that an object be reachable. The parser merely requires that an object be valid for a verb in order to apply the verb to the object; the game may in turn require the object to be reachable in its definition of a validity method, but this requirement is created by the game and not by the parser.

Accessibility and Validity

The parser considers an object accessible or valid for a verb if the object passes the validDo (for a direct object) or validIo (for an indirect object) test for the verb. validDo and validIo are defined on the deepverb object for the verb.

An object may be valid for one verb, but not valid for a different verb, because each verb has its own requirements for validation. Most verbs fall into one of these categories:

At any given time, an object may be valid for one verb but not for another. For example, if an object is locked inside a glass case in the player’s current location, the object would be visible but not reachable; hence, the player could examine the object, but couldn’t take it, move it, or open it.

Note that the validDoList and validIoList methods are related to the validDo and validIo methods, but are less precise. The validDoList and validIoList methods exist to make the parser run faster: rather than slogging through the entire list of objects in the game, the parser concerns itself when resolving a noun phrase only with the objects returned by the appropriate validDoList or validIoList method. All of these objects are then further validated through validDo or validIo, so validDoList and validIoList can (and do) err on the side of returning too many objects: these methods often return objects that are not, in fact, valid for the verb. The reason these methods don’t return precise lists is that doing so would require exact duplication of the logic in validDo and validIo; it’s much easier and more efficient for these methods to make a rough guess that overestimates the set of valid objects, then let the parser sort things out using validDo and validIo as final checks.

Verification

The parser has one more level of object suitability: an object is logical for a command if it passes the appropriate verDoVerb or verIoVerb method. An object passes this test if the method doesn’t display any text; if the method displays text, the parser assumes that the text is an error message, and considers the object to be illogical for the verb.

We usually refer to this logic test as verification.

Interactions among Visibility, Validity, and Verification

If an object is not visible, the parser doesn’t allow the player to refer to the object at all. The parser responds to any mention of the object with error 16, “I don’t see any noun phrase here.”

If an object is visible but isn’t valid for a verb, the parser doesn’t allow the player to use the verb on the object. However, because the object is visible, the parser can’t say that it doesn’t see the object here; instead, the parser uses the cantReach method defined by the game to explain the problem.

If an object is visible and valid, but fails verification, the parser simply relies on the message that the verification method displays to explain the problem.


Overview of Disambiguation

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.

Version 2.4 adds a customization hook that lets you write your own disambiguation code, if you want to override the built-in processing. This is described in detail in the disambiguation discussion.

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(parserGetMe()) 

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 (so that any text displayed is hidden from the player) 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.

When the parser decides that it can’t disambiguate a noun phrase without help from the user, it displays a question like this:

Which book do you mean, the blue book, or the red book?

The player can respond with something like “the red one” (or simply “red”) to tell the parser which object to choose.

The object names in the question (“blue book” and “red book”) are displayed by the sdesc properties of the possible matching objects. For this reason, it’s important that you give each object a distinguishing sdesc property; if both of those books simply had an sdesc of “book”, your player would see a message like this:

Which book do you mean, the book, or the book?

This obviously isn’t good. Whenever you create more than one object with the same noun, be sure to give each object some kind of distinguishing feature, and describe it in the object’s sdesc.

Theorem 6: Always give your objects unique sdescs.

The disambiguation discussion has full details of how disambiguation works.


Default objects and “All”

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;
    }
  ;


Daemons and Fuses

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.

Controlling Time Explicitly: incturn and skipturn

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.

The function skipturn is similar to incturn, but rather than marking expiring fuses for execution, skipturn simply deletes any fuses that burn down in the specified interval. Deleted fuses are never executed; this allows you to entirely bypass any timed events that would normally have happened in the given number of turns. skipturn requires one argument, which is the number of turns to skip.



Chapter Three Table of Contents Chapter Five