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 Three


Language Overview

The Text Adventure Development System offers game authors a versatile and powerful language, well suited to creating the world models that underlie text adventure games.

The TADS language is a powerful object-oriented language based on C. TADS uses most of the same keywords and operators as C, but has a few changes that make it easier to use. TADS also uses “run-time typing,” which means that you don’t have to declare in advance the datatypes of your variables, functions, and properties. In addition, TADS has high-level datatypes, such as lists and strings, that make memory management totally automatic.

This chapter provides an overview of the general features of the language.


Functions

Functions will be familiar to users of languages such as C and Pascal. A function is a bit of code that is grouped together and given a name; another part of your program can cause a function to carry out its code by calling it. This is an example of a function in TADS:

  showSum: function( arg1, arg2, arg3 )
  {
    "The sum is: ";
    say( arg1 + arg2 + arg3 );
    "\n";
  }

This function is called showSum. A function name can be any combination of letters and numbers, but must start with a letter. Capital and lower-case letters are different in TADS, so showSum is a distinct function from showsum and ShowSum.

The curly braces indicate where the function starts and ends; the code inside is the body of the function, and is executed each time the function is called.

The items in parentheses, arg1, arg2, and arg3, are the arguments to the function. Some functions have no arguments at all; for these functions, the parentheses and argument list are not present. When the function is called, values are specified for the arguments. For example, if the following code is executed:

  showsum( 1, 2, 3 );
  showsum( 9, 8, 7 ); 

then the function is called twice: the first time, the values of arg1, arg2, and arg3, respectively, are 1, 2, and 3; the second time, they are 9, 8, and 7. The output is thus:

  The sum is: 6
  The sum is: 24 

The arguments are entirely “local” to the function; arg1 is meaningful only within the function showSum. The function can also define local variables of its own with the local statement; this is explained in more detail later.

Functions can do a great deal more than just display the sum of three numbers. The if statement allows statements to be executed conditionally, and the while statement allows statements to be executed repeatedly. TADS provides full integer arithmetic, and it also provides operations on strings and lists (which will be described shortly). In addition, a function can return a value to its caller. And, of course, one function can call another (or even itself). In short, most programs you could write with a language such as C or Pascal or BASIC could be written with TADS functions.


Objects

Objects are the primary elements of any TADS game. Each real-world entity that a text adventure models is described by a TADS object.

Note: The term “object” has a specific meaning in the context of TADS programming, and is different from its usual use in text adventures. While playing a game, you think of objects as those things you can pick up and manipulate. In a TADS program, these are indeed represented as objects, but so are many other things that the player doesn’t directly manipulate, such as rooms, actors, verbs, and many other things.

A TADS object is something like a C structure or Pascal record, which are collections of related data (such as numbers and character strings) gathered together under a single name for convenience of manipulation. An object has properties, which are data items associated with the object, but it also has methods, which are bits of code tied to the object. Methods are very much like functions; the difference is that a method is part of an object, whereas a function stands alone, and is not part of any object.


Properties

In the following example, only properties, not methods, are defined for an object.

  robot: object
    name = 'Lloyd'
    weight = 350
    height = 72
    speed = 5
  ;

This defines robot as an object, and specifies a list of properties and their values. The line name = 'Lloyd' says that the property name has the value 'Lloyd', a character string. Likewise, weight = 350 says that the property weight has the numeric value 350, and so forth. (Note that only integral numbers are supported in TADS; you can’t use floating point numbers such as 3.1415926. The maximum size of the numbers that TADS allows is about 2 billion.) The semicolon ends the object definition.

To get at an object’s properties, you use the name of the object, then a dot, then the property name. For example, robot.name evaluates name property of the object robot. The function below prints out the value of an object’s name property.

  showName: function( obj )
  {
    say( obj.name );
  } 

This function’s single statement calls another function, say, with the argument obj.name. say is a built-in function that displays a number or a string. (Note that say will determine when it is called which type of data it should display, and act accordingly. Many built-in functions will perform their action based on the type of data they are sent.)

In robot, we defined a number and a string. There is an additional special type of string in TADS, enclosed in double quotes. This type of string is displayed automatically whenever it is evaluated. This is a convenient shorthand, because it removes the need to call say every time you want to print a string. Since text adventure games display a lot of text, this feature proves to be very convenient.

  newobj: object
    greeting = "Hello!\n"
  ; 

The \n at the end of the string prints a newline (that is, it moves the cursor to the start of a new line). There’s a similar special code, \b, that prints a blank line. You might wonder why \b is needed, when you could just use two \n’s; the reason is that TADS will ignore redundant newlines. Most of the time, this makes output formatting easier, because you don’t have to worry about having too many newlines. Once in a while, though, you really want a blank line; you can use \b at these times.

Property greeting, when evaluated, will simply print the string Hello! and a newline. So, rather than calling the built-in say routine to display the value of newobj.greeting, you would simply evaluate the property itself:

  printGreeting: function( obj )
  {
    obj.greeting;
  }

Properties can also have list values. A list is a set of values enclosed in square brackets, such as this:

  listobj: object
    mylist = [ 1 2 3 ]
  ; 

Elements of a list need not be of the same datatype, but they generally are. Lists are convenient when you need to keep related items together in one place, especially when the group of items changes from time to time.

Several operators and built-in functions perform list operations. Operators are provided to add items to and remove items from a list, and to reference an individual item in a list. Built-in functions are provided to scan through a list, and to find an item within a list.

As an example of how to scan through a list, the function below displays all the elements of the list contained in listobj.mylist. This example uses the list indexing operator, [index], to obtain the individual entries in the list. It also uses the built-in function length() to determine how many elements are in the list.

  showList: function
  {
    local ind, len;
    len := length( listobj.mylist );            // Find the list's length
    ind := 1;                                   // start at first element
    while ( ind <= len )                        // loop over each element
    {
      say( listobj.mylist[ ind ] );                 // display this entry
      "\n";                                          // Display a newline
      ind := ind + 1;                      // move on to the next element
    }
  }

If you’re familiar with C, you would probably want to write the loop in the example above using the for statement, which would allow you to put the loop initialization, condition test, and loop variable increment together in a single statement. You can use the for statement in TADS the same way you use it in C, so you can rewrite the showList function with a for loop as shown below.

  showList: function
  {
    local ind;
    local len := length(listobj.mylist);           // save list's length
    for ( ind := 1 ; ind <= len ; ind++ )
    {
      say( listobj.mylist[ ind ] );                // display this entry
      "\n";                                         // Display a newline
    }
  } 

An alternative way of scanning items in a list is to use the built-in functions car() and cdr(). The example below demonstrates these functions.

  showList2: function
  {
    local cur;                 // A variable for the remainder of the list
    cur := listobj.mylist;                    // Start with the whole list
    while ( car ( cur ) )      // car(list) is the first element of a list
    {
      say( car ( cur ) );     // Display the first element of rest of list
      "\n";                                           // Display a newline
      cur := cdr( cur );             // cdr(list) is the rest of the list;
                            // that is, everything but the car of the list
    }
  }

This function loops through the list element by element. Each time through the while loop, cur is replaced by the cdr of cur, which is the list minus its first element; the loop finishes when car(cur) is nil, which means that nothing is left in the list (nil is a special datatype which means, effectively, the absence of a value). A while loop continues until its condition is either zero or nil.

Note that we assigned the list to a local variable before starting the loop, because we want to take the list’s cdr() each time through the loop; if we assigned this to listobj.mylist each time, listobj.mylist would end up with nothing left when the function finished. By assigning the list to the local variable, we leave listobj.mylist intact.

These are some of the basic datatypes of TADS: numbers, strings (enclosed in single quotes, which are simply values passed around), printing strings (enclosed in double quotes, which have no value but are printed whenever evaluated), lists (values enclosed in square brackets), nil and true (returned by expressions such as 1 < 2), and objects. A property can have any of these datatypes.


Methods

This is where the object-oriented nature of this language becomes more visible: a property can contain code instead of a simple data item. When a property contains code, it is called a method.

  methodObj: object
    c =
    {
      local i;
      i := 0;
      while ( i < 100 )
      {
        say( i ); " ";
        i := i + 1;
      }
      return( 'that is all' );
    }
  ;

This object is considerably more complicated than those we have seen so far. The method c is a set of statements, which are executed whenever methodObj.c is evaluated. The method in this case has returned a value, but this is not necessary; quite often, a method is evaluated strictly for its side effects, such as a message it prints.

Note that a property whose value is a double-quote string acts exactly like a method with the same double-quote string as its only statement, so the following three definitions are synonymous:

  string1: object
    myString = "This is a string."
  ;

  string2: object
    myString =
    {
      "This is a string.";
    }
  ;

  string3: object
    myString =
    {
      say( 'This is a string.' );
    }
  ; 

Note that double-quote strings do not have any value; their only function is that their evaluation displays the string.

Like functions, methods can take arguments. To specify arguments, simply list them as you would with a function after the method name; they act as local variables inside the method just as function arguments do.

  argObj: object
    sum( a, b, c ) =
    {
      "The sum is: ";
      say( a + b + c );
      "\n";
    }
  ; 

To invoke a method with arguments, the argument values are enclosed in parentheses and placed after the method name, just as with a function call:

  argObj.sum( 1, 2, 3 ); 


Inheritance

Objects can inherit properties and methods from other objects. The object keyword defines the most general kind of object; in its place, you can use the name of another object. For example,

  book: object
    weight = 1                                    // Books are fairly light
  ;

  redbook: book
    description = "This is a red book. "
  ;

  bluebook: book
    weight = 2                                 // A heavier-than-usual book
    description = "This is a big blue book. "
  ;

The first object, book, defines a general category. redbook defines a particular book, which means it has all the properties of a book, plus any that are specific to it. Likewise, bluebook defines a different book. Again, it has all the general properties of a book; however, since it has its own weight property, the weight property of the more general book object is ignored. Hence, redbook.weight is 1, whereas bluebook.weight is 2.


Classes

When an object inherits properties from a second object, the second object is called the first object’s superclass. When an object is the superclass of other objects, the object is called a class. Some object-oriented languages make a strong distinction between objects and classes; in TADS, there is very little difference between the two. However, a class keyword is provided that specifies that an object is serving strictly as a class; book could thus have been defined as follows:

  class book: object
    noun = 'book' 'text'
    weight = 1              // Books are fairly light
  ;

The only time that the class keyword is required is when a class that is not itself an object has vocabulary word properties or a location property. The class specification prevents the player command parser from mistakenly believing that the player might be referring to the class with his commands.


Multiple Inheritance

An object can inherit properties from more than one other object. This is called multiple inheritance. It complicates things considerably, primarily because it can be confusing to figure out exactly where an object is inheriting its properties from. In essence, the order in which you specify an object’s superclasses determines the priority of inheritance if the object could inherit the same property from several of its superclasses.

  multiObj: class1, class2, class3
  ;

Here we have defined multiObj to inherit properties first from class1, then from class2, then from class3. If all three classes define a property prop1, multiObj inherits prop1 from class1, since it is specified first.

Multiple inheritance is used only rarely, but you will find that once in a while it is a very useful feature. For example, suppose you wanted to define a huge vase; it should be fixed in the room, since it is too heavy to carry, but it should also be a container. With multiple inheritance, you can define the object to be both a fixeditem and a container (which are classes defined in adv.t).



To change your language, you must change your life.
DEREK WALCOTT, Codicil (1965)


Chapter Two Table of Contents Chapter Four