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.


Chapter Five


Language Reference

This section describes in detail all of the keywords, operators, syntactic elements, built-in functions, and special considerations of the TADS language.


Typographical Conventions

This section uses abstract syntax diagrams to describe the language. In these diagrams, several typographical conventions are used:

italics are used to represent a description of an item that must be replaced with an actual instance of the item. [Square Brackets] are used to indicate that the enclosed item or items are optional. Typewriter text is to be used verbatim. The same applies to any other punctuation shown.


Components of TADS

The TADS development system includes a compiler, a run-time system, and several TADS source files containing basic adventure definitions.

When you write a TADS program, you use a text editor (which is not included with TADS) to prepare a source file, then you compile the file. The compiler checks the entire program for syntax errors, then creates another file containing a binary representation of your source file; this form is more efficient to execute, so your game will require less memory and run faster.

After you have compiled your program, you use the run-time module to execute the game. The run-time system contains the player command parser and other code for user interaction.


Input File Format

The TADS compiler accepts standard ASCII text files as input. The compiler considers any amount of whitespace to be the same as a single space; spaces, tabs, and newlines all count as whitespace. Whitespace can occur between any two syntactic elements, but need only appear between two identifiers or keywords that have no other intervening punctuation. The compiler knows that punctuation is never part of a keyword or identifier, and can manage to break apart punctuation when it's all run together without spaces. For readability, liberal use of whitespace is recommended.


Including Other Files

The #include directive allows one file to insert the contents of another:

  #include "file.t"
  #include <file.t> 

Here, file.t is the name of an operating system file to be included. An included file can include another, and so forth, down to ten levels of nested files. Note that the pound sign, #, must be in the first column of the line; no leading spaces are allowed. The include file adv.t\ is normally included at the start of a game file to define the many items that are needed in a normal adventure. These definitions are quite general, but specific games might modify them to customize the game.

Note that when the filename is enclosed in double quotes, the compiler searches for the file first in the current directory, then in the directories in the include path (as set with the -i compiler switch on most operating systems). When the filename is enclosed in angle brackets, the compiler searches only in the directories in the include path - the current directory is not searched. Generally, a system include file (such as adv.t) should be enclosed in angle brackets, while your files should be in the current directory and enclosed in double quotes.


Multiple Inclusions of the Same File

The TADS compiler automatically keeps track of each file you have included, and ignores redundant #include directives. Note that this feature is based on the filename as specified in your program; if you refer to the same file by different names (for example, by specifying a path on one inclusion, but using angle brackets to let TADS find the file on the second), the compiler will not be able to identify the duplicate file, and will include it twice, resulting in countless unnecessary errors. For this reason, you are encouraged to use angle brackets to specify include files, rather than using specific paths in your source file. Doing so will help ensure that your source files are portable to different computers, as well, since each operating system uses its own conventions for specifying file paths.

Note that the include files that make up a pre-compiled binary file, loaded with the compiler's -l option, are recorded in the binary file. Thus, there is no need for you to remove the #include directive in your source file just because you are using the pre-compiled version of that include file. For more information on using pre-compiled include files, see the section on using the compiler.


Comments

Outside of a quoted string, two consecutive slashes, //, indicate that the rest of the line is a comment. Everything up to the next newline is ignored.

Alternatively, C-style comments can be used; these start with /* and end with */; this type of comment can span multiple lines.

Examples:

  // This line is a comment. 
  /*
  This is a comment
  which goes across
  several lines.
  */ 


Identifiers

Identifiers must start with a letter (upper or lower case), and may contain letters, numbers, dollar signs, and underscores. Identifiers can be up to 39 characters long. Upper and lower case letters are distinct.


Scope of Identifiers

All objects and functions are named by global identifiers. No identifier may be used to identify different things; that is, no two objects can have the same name, an identifier naming a function can't also be used for an object, and so forth.

Property names are also global identifiers. A name used for a property can't be used for a function or object, or vice versa. However, unlike functions and objects, the same property name can be used in many different objects. Since a property name is never used alone, but always in conjunction with an object, the TADS compiler is able to determine which object's property is being referenced even if the same name is used in many objects.

Function arguments and local variables are visible only in the function in which they appear. It is permissible to re-use a global identifier as a function argument or local variable, in which case the variable supersedes the global meaning within the function. However, this is discouraged, as it can be a bit confusing.


Object Definitions

A basic object definition has the form:

  identifer: object [property-list] ; 

This defines an object which has no superclass. An object can be defined as having a superclass with the alternative form of object definition:

  identifier: class-name [, class-name [...]] [property-list] ; 

Here, the class-name is an identifier which is defined elsewhere in the program as an object or class (either with or without a superclass), and is the new object's superclass; if more than one superclass is present, a comma separates each superclass in the list. The new object named by identifier inherits all the properties of the superclass or superclasses. If a property in the optional property-list is also in the property list of the superclass (or its superclass, and so forth), the new property overrides the inherited one.

If a property is inherited from more than one of its superclasses (and is not overridden in the object's own property list), the property is inherited from the superclass that appears earliest in the list. For example, suppose you define an object like this:

  vase: container, fixeditem
  ;

If both container and fixeditem define a method named m1, and vase itself doesn't define an m1 method, then m1 is inherited from container, because it appears earlier in the superclass list than fixeditem.

There is a more complicated case that can occur, but it is very unusual. You will probably never encounter this, so skip this section if you find it confusing. Suppose that in the example above, both container and fixeditem have the superclass item, and that item and fixeditem define method m2, and that neither container nor vase define m2. Now, since container inherits m2 from item, it might seem that vase should inherit m2 from container and thus from item. However, this is not the case; since the m2 defined in fixeditem overrides the one defined in item, vase inherits the m2 from fixeditem rather than the one from item. Hence, the rule, fully stated, is: the inherited property in the case of multiple inheritance is that property of the earliest (leftmost) superclass in the object's superclass list that is not overridden by a subsequent superclass.


Property Lists

A property list takes on this form:

  property-definition [property-list] 

(This is a formal way of saying that you can string together any number of property definitions in a property list, one after the other.) A property definition looks like this:

  identifier = data-item 

Note that no semicolon comes after the property definition. A semicolon terminates the entire object definition, not individual property definitions.


Property Data Items

A data item can be a number, a double-quoted string, a single-quoted string, a list, an object, nil, or true.


Numbers

A number is just a string of digits. The default base is decimal; you can also enter octal and hexadecimal numbers. An octal number simply starts with a leading zero, so 035 is an octal number having the decimal value 29. A hexadecimal number starts with 0x, as in 0x3a9.

Numbers can vary from -2147483647 to 2147483647 (decimal), inclusive. Only integers are allowed; numbers cannot have a decimal point or a fractional part.


Double-quoted Strings

A double-quoted string is an arbitrary string of characters enclosed in double quotation marks, such as,

  "This is a double-quoted string." 

Stretches of whitespace in such strings are compressed to single spaces. This includes newlines; double-quoted strings can go on past the end of a line, and keep going for several lines. For example, this is perfectly legal:

  "This is a string
  that goes on for

  several lines." 

Note that TADS converts all of that blank space, including the blank lines, down to a single space between each word. Thus, you can enter your text without having to worry about formatting it; TADS will do all the work of filling up each output line when your program runs.

Sometimes, you will want to format your output in a specific way, overriding the standard output formatting. Since TADS converts all whitespace in your strings (including newlines) to spaces, you have to specify any special formatting you want explicitly. TADS provides several special character sequences you can use to obtain these effects.

There is no specific limit on the length of a double-quote string, although the compiler may impose some limits depending on the context in which the string appears.

A double-quoted string is always displayed whenever evaluated. When a double-quoted string appears in code, executing that code results in displaying the string. Likewise, when a property has a double-quoted string as its value, the string is displayed whenever the property is evaluated.


Embedding Expressions in Strings

TADS has a convenient feature which allows you to embed expressions in strings, without actually stopping the string. Any time TADS encounters two consecutive left angle brackets in a string, it will momentarily stop the string, and evaluate what comes after the angle brackets as an expression whose value is to be displayed. The expression ends, and the string resumes, when two right angle brackets are encountered. This makes property definitions that need to look up another property very convenient to define; for example,

  itsHere = "<< self.thedesc >> is here." 

The statement above is equivalent to this more verbose version:

  itsHere =
  {
    self.thedesc; " is here.";
  }

An embedded expression can evaluate to a number, a single-quoted string, or a double-quoted string. Other datatypes are not allowed. A string may have any number of embedded expressions within it. An embedded expression may only contain an expression; statements (such as if) are not allowed, and semicolons may not be used to separate multiple expressions - use commas instead.

This feature has certain limitations. It is illegal to use a string in an embedded expression which itself has an embedded expression. This feature can be used only in double-quoted strings; single-quoted strings cannot contain embedded expressions.

Because some game authors may wish to use two or more consecutive left angle brackets in their strings, the special sequence `\<' is provided. See the section on special character sequences, above, for more information.


Single-quoted Strings

Single-quoted strings are essentially the same as double-quoted strings in appearance, except, of course, that they are enclosed in single quote marks. The sequence \' is available to produce a single quote in the string itself.

Single-quoted strings do not display when evaluated; instead, they are treated as values. Certain built-in functions are available to manipulate these strings, such as say, which displays such a string value.

Note also that all vocabulary words appear as single-quoted strings (or lists of single-quoted strings).


Lists

A list is an aggregate of other data items. It is entered as a set of data items enclosed in square brackets (note that these square brackets are the actual punctutation, not indicators of optionality):

  [ data-list ] 

A data-list can be nothing at all, in which case the list is empty, as in

  [] 

Or, it can be one or more data items (numbers, strings, objects, lists). Generally, it is only useful to construct lists with the same datatype for all members, but this is not required by the language.

Examples of lists:

  [ 1 2 3 ]
  [ 'hello' 'goodbye' ]
  [ [1 2 3] [4 5 6] [7 8 9] ]
  [ vase goldSkull pedestal ] 

List elements need not be constants, unless the entire list needs to be a constant. For example, a list used as a property value must be a constant, so all of the elements must be constants (hence, local variables and non-constant expressions cannot be used). However, a list used in code, such as a list assigned to a local variable, can contain non-constant expressions within the list elements. For example, the following function dynamically constructs a list from three expressions:

  f: function(x, y, z)
  {
    return([x+1 y+1 z+1]);
  }

The non-constant list elements are legal because the list construction appears in code. Note that the routine above has the same effect as this code:

  f: function(x, y, z)
  {
    return((([] + (x+1)) + (y+1)) + (z+1));
  }

The second example starts with an empty list, then adds an element whose value is (x+1), then adds a second element whose value is (y+1), and so on. The two examples construct exactly the same list. However, you should use the first construct where possible, because it is much more efficient: the first example constructs only a single list, whereas the second must construct four lists, one at a time. The first example is faster and consumes less memory at execution time.


nil and true

Two special datatypes, nil and true, are pre-defined. These are generally used as "truth values"; nil means false and true means true. For example, 1 > 3 evaluates to nil, whereas 1 < 3 is true. In addition, nil means the absence of a value; taking the car of an empty list returns nil, as does evaluating a property of an object when the property is neither defined nor inherited by the object.

For truth values, you should use nil and true rather than numeric equivalents, since nil and true provide much more explicit self-documentation of your program.


Expressions

A property definition can contain an expression rather than a simple constant. When a property value is an expression, the expression must be enclosed in parentheses. The expression is evaluated each time the property is evaluated. An example of using an expression for a property value:

  exprObj: object
    x = 1
    y = 2
    z = (self.x + self.y)
  ;

Whenever exprObj.z is evaluated, the sum of the current values of exprObj.y and exprObj.z is returned.

Note that a property defined in this manner can take parameters, just like methods (described below). For example:

  exprObj2: object
    w(a, b) = (a * b)
  ;

When exprObj2.w is evaluated, it requires two parameters, which are multiplied together to give the value of the property. For example, evaluating exprObj2.w(3, 4) produces a value of 12.

Note that the property z above could have been written without the "self." prefix on x and y:

  z = (x + y) 

This is because properties, if used without an object name, are assumed to belong to the current self object.


Methods

Code can be used in a property definition just like any other datatype; when you associate code with an object, the code is called a method. When the method is evaluated, the code is executed; the return value is the value of the method, but often it is useful for the code to produce side effects as well.

Method code is enclosed in braces, { and }. Anything valid in a function may appear in code associated with a method. The method may have local variables, and it can receive arguments. If the method has arguments, the argument list (which is identical to a function's argument list) appears after the method name. For example:

  obj1: object
    f(x) =
    {
      return(x + 1);
    }
  ;

When such a method is evaluated, the argument list is specified after the method name, as in:

  f1: function
  {
    say(obj1.f(123));
  }


Functions

A function definition has this form:

  identifier: function [ ( argument-list ) ]
  {
    function-body
  }

The argument-list is optional; functions need not take arguments. If the function does take one or more arguments, the list looks like this:

  identifier [, argument-list ] 

The identifiers may be used just like local variables within the function.

The punctuation in the above syntactic description may be a little much, so an example might be helpful:

  addlist: function(list)          // add up the numbers in the list
  {
    local sum, count, i;
    i := 1;                           // index the first item in the list
    sum := 0;                               // initialize the sum to zero
    count := length(list);         // get the number of items in the list
    while (i < count)              // as long as there's more to do...
    {
      sum := sum + list[i];                            // add next element
      i := i + 1;                        // go on to the next list element
    }
    return(sum);
  }


Functions with Variable Argument Lists

It is possible to define a function that takes a variable number of arguments. In the function definition, you can specify a minimum number of arguments that are always passed to the function (which can be no arguments at all, if you wish), and then specify that more arguments can optionally follow. This is done with the "ellipsis" token, "...", as the final "argument" in the function's argument list. For example, to define a function that takes any number of arguments:

  f: function(...)
  {
  }

To define a function that always takes at least one argument, but could take additional arguments:

  g: function(fmt, ...)
  {
  }

In a function taking a variable number of arguments, you can determine how many arguments were actually passed to the function by inspecting the pseudo-variable argcount. This pseudo-variable's value is simply the number of arguments to the current function.

To retrieve an argument, use the getarg(argnum) built-in function. The argument argnum is the number of the argument you want to retrieve; getarg(1) returns the first argument, getarg(2) returns the second, and so forth. Note that in a function which has some explicit arguments, followed by an ellipsis, getarg(1) still returns the first argument, even though it has a name in the argument list. For example, in the function g above, getarg(1) returns the value of the argument fmt.

Another built-in function, datatype(value), can be used to determine the datatype of an argument. See the description of the datatype function later in this chapter for more information.

As an example, the function below displays any number of values.

  displist: function(...)
  {
    local i;
    for (i := 1 ; i <= argcount ; i++)
    {
      say(getarg(i));
      " ";
    }
    "\n";
  }


Forward-Declaration of Functions

An alternative form of the function statement allows you to forward-declare a name as a function, without actually defining the function. The format of a forward declaration is:

  identifier: function; 

Note that this does not define the function; it merely tells the compiler that a function definition for the specified identifier will appear later in the file. This reserves the identifier for use as a function name, and prevents the compiler from assuming the identifier refers to an object.

Forward declarations are not necessary except where setdaemon() and the like will be used. When a function is actually called, the syntax of the call tells TADS that the name refers to a function even when TADS hasn't seen the function definition yet. In setdaemon() and similar calls, though, no special syntax is present to tell TADS what the identifier refers to, so the compiler assumes it will refer to an object.


Writing Code

In this section we cover the details of the language that goes inside functions and methods. Function and method code consists of a series of statements; the statements are executed sequentially in the order they appear in the source program. The following pages provide details about the statements that may be used.

Each statement in TADS code is terminated with a semicolon.


local

At the start of a function or property definition, you can define local variables for the current code block. This is done with a statement such as this:

  local identifier-list ; 

The identifier-list has the form:

  identifier [ initializer ] [, identifier-list ] 

An initializer, which is optional, has the form:

  := expression 

where the expression is any valid expression, which can contain arguments to the function or method, as well as any local variables defined prior to the local variable being initialized with the expression. The expression is evaluated, and the resulting value is assigned to the local variable prior to evaluating the next initializer, if any, and prior to executing the first statement after the local declaration. Local variables with initializers and local variables without initializers can be freely intermixed in a single statement; any local variables without initailizers are automatically set to nil by the run-time system.

The identifiers defined in this fashion are visible only inside the function in which the local statement appears. Furthermore, the local statement supersedes any global meaning of the identifiers within the function.

A local statement can occur in any block (that is, any statements grouped together with braces), and a single block can have multiple local statements; all local statements in a block must precede the first executable statement of the block (that is, the only thing that can occur before a local statement is another local statement.

An example of declaring local variables, using multiple local statements, and using initializers is below.

  f: function(a, b)
  {
    local i, j;                                           /* no initializers */
    local k := 1, m, n := 2;         /* some with initializers, some without */
    local q := 5*k, r := m + q;        /* OK to use q after it's initialized */
    for (i := 1 ; i < q ; i++)
    {
      local x, y;                     /* locals can be at start of any block */
      say(i);
     }
   }


Expressions

TADS expressions are entered in algebraic notation. Operators have different meanings on different datatypes. The basic list follows.

The operators are shown above in their order of evaluation, from first to last. Hence, conjunctions are done last, after all higher-precedence operators have been evaluated. Note that there are some groups of operators in the list above; for example, all of the comparison operators are grouped together. This indicates that these operators have the same precedence. Operators of the same precedence associate left to right; for example, 3-4+5 is evaluated as (3-4)+5. The exception is the assignment operator, which groups right to left; that is, in the expression a := b := 2, b is first set to 2, then a is set to b.

You can use parentheses to force a different order of evaluation of your expression.


Assignments

An assignment is not a special type of statement; it is merely an expression which uses an assignment operator:

  item := expression; 

The item is a local variable, a list element, or an object's property. When assigning to a property of an object, the object reference may itself be an expression. Likewise, when assigning to a list element, the list and index may both be expressions. Note, however, that lists cannot be expanded by assigning to an element past the end of the list; the index must refer to an existing member of the list to be replaced. A few examples of assignments:

  a: function(b, c)
  {
    local d, e, lst;
    d := b + c;                                     // assign a local variable
    obj3.prop1 := 20;                                // assign obj3's property
    prop1 e := obj3;                   // assign an object to a local variable
    e.prop1 := obj2;                // assign obj3's property prop1 the object
    obj2 e.prop1.prop2 := 20;    // assign obj2's property prop2 the number 20
    fun1(3).prop3 := 1 + d;     // assign prop3 of the object returned by fun1
    lst := [1 2 3 4 5];                 // set up a local variable with a list
    lst[3] := 9;                                     // lst is now [1 2 9 4 5]
    lst[5] := 10;                                 // and it's [1 2 9 4 10] now
    /* lst[6] := 7 would be illegal - the list is only 5 elements long */
  }


Function Calls

A function call is simply an expression involving a call to a function:

  function-name( [ argument-list ] ); 

The function-name is the name of a function defined elsewhere. The argument list, if provided, is passed to the function for its parameters. (Naturally, the arguments passed to a function should match in number those defined in the function's definition.) Each parameter can be an arbitrary expression, and the individual arguments are separated by commas.

Whether there's an argument list or not, the parentheses are required; they tell the compiler that you wish to call the function. (You may wonder where you would ever want to use a function's name other than when calling the function. A few special built-in functions, such as setdaemon, which will be discussed in detail later, allow you to specify a function that is to be called eventually, but not right away. In these cases, you need to be able to refer to a function without actually calling it. In these cases, you use the function's name without parentheses.)

If the function returns a value, the value is discarded in this form of the call. A function called in this manner is invoked for its side effects rather than its return value.

An example:

  showlist(a+100, 'The list is: ', list1); 


Indirect Function and Method Calls

You can call a function or method "indirectly"; that is, you can use a pointer to a function or method to call the function or method. This can sometimes be useful in setting up a general routine which calls other routines based on parameters passed into it.

To call a function with a function pointer, simply use an expression yielding a function pointer, within parentheses, where you'd normally use a function name. For example:

  g: function(a, b, c)
  {
    return(a + b + c);
  }

  f: function
  {
    local fptr, x;
    fptr := &g;             /* get address of function g */
    x := (fptr)(1, 2, 3);      /* call function with pointer */
  }

A property pointer is used in essentially the same way.

  f: function(actor, obj)
  {
    local propPtr := &doTake;          /* get pointer to doTake property */
    obj.(propPtr)(actor);                   /* call property through pointer */
  }

For example, suppose you wish to implement a way of asking characters in a game about various objects in the game. One way you could do this is by defining a general "ask" routine that takes the character and the object as arguments. Set things up so that each object defines a property saying what each actor knows about that object. Then, each actor specifies via a "property pointer" which property to evaluate in an object to find out what the actor knows about the object. If an object doesn't define this property, the actor doesn't know anything about that object.

So, our general "ask" routine is very simple (or concise, anyway):

  ask: function(actor, obj)
  {
    local propPtr;

    /* find out what property to evaluate in the object */
    propPtr := actor.askPropPtr;

    /* see if it's defined in the object */
    if (defined(obj, propPtr))
    {
      /* it is defined - call the property indirectly */
      obj.(propPtr);
    }
    else
    {
      /* it's not defined - use default message */
      actor.dontKnow;
    }
  }

Now, each actor simply has to define a property that each object uses to specify what the actor knows about the object, and place the address of this property the actor's askPropPtr property. The actor also needs to define the default message in the dontKnow property. Here's an example:

  joe: Actor
    sdesc = "joe"
    noun = 'joe'
    dontKnow = "Joe just scratches his head and shrugs."
    askJoe = "You probably don't want to get Joe started on his life story."
    askPropPtr = askJoe
  ;

Finally, each object must define an appropriate askJoe property if Joe knows anything about that object. Likewise, it will define other properties for what other actors know about it. This way, all of the information about an object, including what the various characters in the game know about it, can be kept with the object itself. In addition, the general "ask" routine is extremely simple. The overall concept behind the mechanism is somewhat complicated, but the finished product is very simple and easy to use and expand.


return

A function can return a value to its caller by using a statement such as:

  return [ expression ]; 

With or without the expression, execution of the function is terminated and the caller resumes execution where it left off. If the expression is provided, the caller receives the expression as the function's value.

Here's an example of a function that computes the sum of the elements of a list, and returns the sum as the value of the function.

  listsum: function(lst)
  {
    local i, sum, len := length(lst);
    for (i := 1, sum := 0 ; i <= len ; i++)
      sum += lst[i];
    return(sum);
  }


if

The general form of the conditional in TADS is:

  if ( expression ) statement
  [ else statement ] 

Here and elsewhere, a statement can be either a single statement or a series of statements enclosed in braces. The expression should evaulate to either a number, in which case zero counts as false and anything else counts as true, or to a truth value, true or nil.

Note that the optional else clause is grouped with the most recent if statement when if statements are nested. For example,

  if (self.islit)
    if (film.location = Me)
      "Oops! You've exposed the film!";
    else
      "It's dark in here.";

The author of this code obviously intended the else clause to go with the first if, but remember that an else goes with the most recent if, so it actually is grouped with the second if statement. This problem can be easily rectified by using braces to make the grouping explicit:

  if (self.islit)
  {
    if (film.location = Me)
    {
      "Oops! You've exposed the film!";
    }
    else
    {
      "It's dark in here.";
    }
  }


switch

The switch statement lets you set up the equivalent of a large if-else tree, but is considerably easier to read and is more efficient to execute. A switch statement allows you to test a particular value against several alternatives, and execute a group of statements accordingly.

The form of the switch statement is:

  switch ( expression )
  {
    [ case-list ]
    [ default-clause ]
  }

The form of the case-list is:

  case constant-expression :
    [ statements ]
    [ case-list ]

The form of the default-clause is:

  default:
    [ statements ]

In the diagrams, statements means that zero or more statements can follow a case or default. You do not need to supply any case labels at all, and the default is also optional.

The expression evaluates to a number, string, object, list, or true or nil. The value of the expression is then tested against each case value. If the value matches one of the case values, the statements following the matching case are executed. If the value does not match any case value, and a default case is defined, the statements following the default are executed. If the value does not match any case value, and there is no default case, the entire switch statement is skipped, and execution resumes following the closing brace of the switch.

Note that execution is not interrupted when another case is enountered. Instead, it just continues into the statements following the case label. If you wish to stop executing statements in the switch at the end of the statements for a single case, you must use the break statement.

The break statement has a special meaning within a switch statement: it indicates that execution should break out of the switch statement and resume following the closing brace of the switch.

Here's an example of a switch statement.

  f: function(x)
  {
    switch(x)
    {
      case 1:
        "x is one";
        break;
      case 2:
      case 3:
        "x is either 2 or 3";
        break;
      case 4:
        "x is 4";
      case 5:
        "x is either 4 or 5";
      case 6:
        "x is 4, 5, or 6";
        break;
      case 7:
        "x is 7";
        break;
      default:
        "x is not in 1 through 7";
      }
    }


while

The while statement defines a loop: a set of statements that is executed repeatedly as long as a certain condition is true.

  while ( expression ) statement 

As with the if statement, the statement may be a single statement or a set of statements enclosed in braces. The expression should be a number (in which case 0 is false and anything else is true), or a truth value (true or nil).

The expression is evaluated before the first time through the loop; if the expression is false at that time, the statement or statements in the loop are skipped. Otherwise, the statement or statements are executed once, and the expression is evaluated again; if the expression is still true, the loop executes one more time and the cycle is repeated. Once the expression is false, execution resumes at the next statement after the loop.


do-while

The do-while statement defines a slightly different type of loop than the while statement. This type of loop also executes until a controlling expression becomes false (0 or nil), but evaluates the controlling expression after each iteration of the loop. This ensures that the loop is executed at least once, since the expression isn't tested for the first time until after the first iteration of the loop.

The general form of this statement is:

  do statement while ( expression ); 

The statement may be a single statement or a set of statements enclosed in braces. The expression should be a number (in which case 0 is false and anything else is true), or a truth value (true or nil).


for

The for statement defines a very powerful and general type of loop. You can always use while to construct any loop that you can construct with for, but the for statement is often a much more compact and readable notation for the same effect.

The general form of this statement is:

  for ( init-expr ; cond-expr ; reinit-expr ) statement 

As with other looping constructs, the statement can be either a single statement, or a block of statements enclosed in braces.

The first expression, init-expr, is the "initialization expression." This expression is evaluated once, before the first iteration of the loop. It is used to initialize the variables involved in the loop.

The second expression, cond-expr, is the condition of the loop. It serves the same purpose as the controlling expression of a while statement. Before each iteration of the loop, the cond-expr is evaluated. If the value is true (a non-zero number, or true), the body of the loop is executed; otherwise, the loop is terminated, and execution resumes at the statement following the loop body. Note that, like the while statement's controlling expression, the cond-expr of a for statement is evaluated prior to the first time through the loop (but after the init-expr has been evaluated), so a for loop will execute zero times if the cond-expr is false prior to the first iteration.

The third expression, reinit-expr, is the "re-initialization expression." This expression is evaluated after each iteration of the loop. Its value is ignored; the only purpose of this expression is to change the loop variables as necessary for the next iteration of the loop. Usually, the re-initialization expression will increment a counter or perform some similar function.

Any or all of the three expressions may be omitted. Omitting the expression condition is equivalent to using true as the expression condition; hence, a loop that starts "for ( ;; )" will iterate forever (or until a break statement is executed within the loop). A for statement that omits the initialization and re-initialization expressions is the same as a while loop.

Here's an example of using a for statement. This function implements a simple loop that computes the sum of the elements of a list.

  sumlist: function(lst)
  {
    local len := length(lst), sum, i;
    for (sum := 0, i := 1 ; i <= len ; i++)
      sum += lst[i];
  }

Note that an equivalent loop could be written with an empty loop body, by performing the summation in the re-initialization expression. We could also move the initialization of len within the initialization expression of the loop.

  sumlist: function(lst)
  {
    local len, sum, i;
    for (len := length(lst), sum := 0, i := 1 ; i <= len ;
      sum += lst[i], i++);
  }


break

A program can get out of a loop early using the break statement:

  break; 

This is useful for terminating a loop at a midpoint. Execution resumes at the statement immediately following the innermost loop in which the break appears.

The break statement also is used to exit a switch statement. In a switch statement, a break causes execution to resume at the statement following the closing brace of the switch statement.


continue

The continue statement does roughly the opposite of the break statement; it resumes execution back at the start of the innermost loop in which it appears. The continue statement may be used in for, while, and do-while loops.

In a for loop, continue causes execution to resume at the re-initiailzation step. That is, the third expression (if present) in the for statement is evaluated, then the second expression (if present) is evaluated; if the second expression's value is non-nil or the second expression isn't present, execution resumes at the first statement within the statement block following the for, otherwise at the next statement following the block.


goto

The goto statement is used to transfer control unconditionally to another point within the same function or method. The target of a goto is a label; a label is defined by placing its name, followed by a colon (:), preceding a statement.

Note that labels have function- or method-scope; that is, they are visible within the entire function or method in which they are defined. This is different from local variables, which are visible only within the block (the group of statements enclosed in braces) in which they are defined. Labels are not visible outside the function or method in which they are defined.

An example of using goto:

  f: function
  {
    while (true)
    {
      for (x := 1 ; x < 5 ; x++)
      {
        /* do some stuff */
        if (myfunc(3) < 0)           /* did an error result? */
          goto exitfunc;                    /* error - quit now */
        /* do some more stuff */
      }
    }
    /* come here if something goes wrong */
    exitfunc: ;
  }

This use of goto avoids the need for testing a flag in the outer (while) loop, which makes the code a little simpler and easier to understand.

The goto statement is widely considered among civilized computer scientists to be an evil and malevolent feature of ancient and unusable languages, and the esteem of TADS within serious computer language design circles has undoubtedly been fatally injured by the inclusion of this construct. So, you may wish to use this statement sparingly, if at all, especially if you're hoping to impress a civilized computer scientist with your coding efforts. However, many software engineers look upon goto as a highly useful statement when in the hands of a seasoned professional, and scoff at the blanket indictment by the more-elegant-than-thou academic establishment, most of whom probably haven't written a line of code since their TA's were chastising them for using goto in their Pascal programs, excepting perhaps some algorithms written in pseudo-code that always end in "the rest is left as an exercise for the reader" anyway. The author of TADS doesn't wish to take sides in this heated controversy, but hopes that both camps will be pleased, by gaining either the utility of using goto with wild abandon or the sense of virtue of knowing they could have used it but overcame the unclean temptation. With TADS, the choice is yours.


pass

A method can decide to inherit the behavior of its parent class by using the pass statement:

  pass method-name; 

The method-name must be the same as the name of the method that the pass statement occurs in. When a pass statement is executed, the method that would have been inherited if the object had not overridden it is called with the same arguments, if any, as the method called in the first place. The self object is unchanged; that is, the superclass method is run, but self is the object that was originally sent the message being passed.


exit and abort

During processing of a player's command, the game can terminate the normal sequence of events and return to the get another command from the player in two different ways. The abort statement stops all processing, and gets the next command; it is normally used by "system" functions, such as saving the game, that should not count as a turn, and therefore shouldn't run any daemons or fuses. The exit statement, on the other hand, skips everything up to the fuses and daemons; exit is used when an actor wishes to stop further processing, and other similar cases.

Note that messages to objects scheduled with the notify() function are treated the same as other daemons and fuses, so these are also skipped by the abort statement.


askdo and askio

These statements interrupt processing of a turn like abort, skipping fuses and daemons, but allow the user to enter more information. The askdo command asks the user to enter a direct object; the system displays a message asking the user for an object. The user can either enter an object in response to the request, or can simply type a new command. For example, if the verb is "take," executing an askdo command will cause the system to display "What do you want to take?" and wait for an object or a new command. The askio command is similar, but it takes a preposition as a parameter; this preposition is added to the command, and the system prompts the player for an indirect object. For example, if the verb is "unlock," and the following command is executed:

  askio(withPrep); 

then the system displays "What do you want to unlock it with?" and prompts the player for an object or a new command. The askio command can only be used when a direct object is already present.

In either case, if the player responds to the request with an object, the command is tried again from the start. If the player types a new command, the command that resulted in the askdo or askio is discarded. Note that control never comes back to the statement after the askdo or askio command, regardless of user input; in either case, command processing starts from the top.

Note that, in some cases, the system won't actually ask the player for a new object. Instead, the system will attempt to find a default object, using exactly the same mechanisms that it uses to find default objects normally (see the section on the parser's default object mechanisms in chapter four).


self

When code associated with a property is being executed, a special object is defined, called self. This special object refers to the object whose property is being evaulated. This may not sound too useful, but consider the case of an object whose superclass defines a property which refers to other properties of the object:

  class book: object
    description =
    {
      "The book is << self.color >>.";
    }
  ;

  redbook: book
    color = "red"
  ;

  bluebook: book
    color = "blue"
  ;

In this example, the general object, book, knows how to describe a book given its color. The books that are defined, the redbook and bluebook objects, take advantage of this by simply defining their color, and letting the description property of their superclass be used to describe them. So, when you attempt to evaluate redbook.description, you get

  The book is red. 


inherited

A special pseudo-object called inherited allows you to call a method in the current self object's superclass. This pseudo-object is similar to the pass statement, but much more useful in several ways. First, with inherited, you can simply call the superclass method, and regain control when it returns; with pass, the current method never regains control. Second, you can use inherited in an expression, so any value returned by the superclass method can be determined and used by the current method. Third, you can pass arguments to the property invoked with the inherited pseudo-object.

You can use inherited in an expression anywhere that you can use self.

Here is an example of using inherited.

  myclass: object
    sdesc = "myclass"
    prop1(a, b) =
    {
       "This is myclass's prop1.  self = << self.sdesc >>,
        a = << a >>, and b = << b >>.\n";
        return(123);
    }
  ;

  myobj: myclass
    sdesc = "myobj"
    prop1(d, e, f) =
    {
        local x;
        "This is myobj's prop1.  self = << self.sdesc >>,
        d = << d >>, e = << e >>, and f = << f >>.\n";
        x := inherited.prop1(d, f) * 2;
        "Back in myobj's prop1.  x = << x >>\n";
    }
  ;

When you call myobj.prop1(1, 2, 3), the following will be displayed:

  This is myobj's prop1. self = myobj, d = 1, e = 2, and f = 3.
  This is myclass's prop1. self = myobj, a = 1, and b = 3.
  Back in myobj's prop1. x = 246.

Note one feature of inherited that is the same as pass: the self object that is in effect while the superclass method is being executed is the same as the self object in the calling (subclass) method. This makes inherited very different from calling the superclass method directly (i.e., by using the superclass object's name in place of inherited).


argcount

The pseudo-variable argcount returns the number of arguments to the current function. This can be used for functions that take a variable number of arguments to learn the number of arguments that need to be processed. Note that argcount isn't really a variable, so you can't assign a value to it, but otherwise you can use it as though it were an ordinary variable.


Built-in Functions

The system has a set of built-in functions to facilitate writing programs. The functions are described in this section. They operate just like ordinary functions, with the exception that built-in functions which don't take arguments don't require parentheses; this simplifies coding of some special cases.


askfile

Call: askfile(prompt)

This function asks the user to enter a filename, in a system-dependent manner. The system's standard file dialog, if the computer has one, will be used; otherwise, the user may simply be prompted to type a filename. The prompt (a single-quoted string value) may or may not be used, depending on system conventions; for systems without any defined standard file dialog, it will be displayed to prompt the user for a filename.

This function is primarily useful for operations such as saving and restoring games which require that the user enters a system filename.


caps

Call: caps()

Forces the next non-space character to be displayed to be capitalized. This is useful for formatting output when it is not known in advance whether an item will be displayed at the start of a sentence or not.

Note that displaying the sequence "\^" has the same effect as calling caps().

Do not confuse this function with the upper(string) built-in function, which converts all of the letters in a string to upper-case. The caps() function takes no arguments, and affects only the next character output.


car

Call: car(list)

Returns the first element of a list, or nil if the list is empty.

Note that the same value can be retrieved with the expession list[1], which uses the list indexing operator to retrieve the first element of list. The primary difference between using car() and the list indexing operator is the style of your program; using car() and cdr() to decompose a list, you can iterate or recurse until you run out of list to process, at which time car() will return nil. With the list indexing operator, however, you need to know in advance how many elements are in the list; this information can be learned with the length() built-in function. The choice of one of these methods over another is a matter of personal preference.


cdr

Call: cdr(list)

Returns the end of a list; that is, the list of everything after the list's car(). With car() and cdr() you can decompose a list into its individual elements.


cvtnum

Call: cvtnum(string)

This function converts a string that contains the text version of a number into a numeric value. For example, cvtnum('1234') returns the number 1234. Note that the special strings 'true' and 'nil' are also accepted by the function, and are converted to the logical values true and nil, respectively.


cvtstr

Call: cvtstr(value)

This function converts a numeric or logical value into its string representation. For example, cvtstr(1234) returns the string '1234', cvtstr(true) returns the string 'true', and cvtstr(nil) returns the string 'nil'.


datatype

Call: datatype(value)

This function returns the type of a value. It is useful primarily with functions that take a variable number of arguments, but could also be useful for inspecting lists whose contents vary.

This function returns a numeric value based on the datatype.


defined

Call: defined(object, propPointer)

This function allows you to determine if a property is defined or inherited by an object, or if the object doesn't have any value for the property. This function returns true if the the object has a definition for the property (either explicitly in the object, or inherited from a superclass), or nil if the object doesn't have any definition for the property.

Note that the propPointer argument must be a property pointer. You can obtain a property pointer using the & operator with a property name. For example, to determine if the player's current location has an ldesc property defined, you would do this:

  x := defined(Me.location, &ldesc); 

In this example, x is true if the current location has an ldesc property defined, and nil otherwise.


find

Call: find(value, target)

If value is a list; the function returns the offset (starting at 1 for the first element) in the list of the target item within the value list. If the target is not found, nil is returned. For example, find([4 5 6], 5) returns 2.

If value is a string, in which case target must also be a string, the function will return the offset within the string value of the substring target, or nil if the substring is not found. The offset of the first character in the target string is 1. For example, find('abcdefghij', 'cde') returns 3.


firstobj

Call: firstobj()

Alternative Call: firstobj(class)

This function is used in conjunction with nextobj(object) to loop over all non-class objects in the game. The firstobj() function returns the first non-class object, and nextobj(object) is used to retrieve subsequent objects in a loop. The order of the objects returned by these functions is arbitrary, but calling firstobj() and then sequentially calling nextobj(object) until nil is returned will guarantee that each non-class object is retrieved exactly once.

These functions are useful primarily during initialization to construct lists of all objects satisfying certain search criteria; these lists can be used later in the game to expedite searches for objects with such criteria. For example, code that determines if a room is dark will have to always check to see if a light-providing object, such as a lamp or a burning candle, is present. It will be faster during game play to check only those objects known to be light-providing than to check all objects in the game; to accomplish this, we could set a property called islamp to true in every potential light-providing object, then construct a list of all such objects during initialization with the example code below.

  getlamps: function
  {
    local obj, l;
    l := [];
    obj := firstobj();
    while(obj <> nil)
    {
      if (obj.islamp) l := l + obj;
      obj := nextobj(obj);
    }
    global.lamplist := l;
  }

After initialization, then, it is only necessary to check for the presence of one of the objects in the list global.lamplist, rather than checking all objects in the game, to determine if a room is lit or not.

Note that firstobj() will return nil if the game has no non-class objects. Likewise, nextobj(object) returns nil when the last non-class object has been retrieved.

The alternative form of this function, with a single argument giving a class object, allows you to restrict the objects returned by firstobj() and nextobj() to those that are subclasses of a particular class. This can save a great deal of time by ignoring objects that are not important to your search. For the example above, you could make the loop execute much more quickly by rewriting it as follows.

  getlamps: function
  {
    local obj, l;
    l := [];
    obj := firstobj(lampitem);
    while(obj <> nil)
    {
      l := l + obj;
      obj := nextobj(obj, lampitem);
    }
    global.lamplist := l;
  }

Note that the test for obj.islamp is no longer necessary, because only objects that are subclasses of the class lampitem will be used in the loop. By iterating over a much smaller set of objects, the loop will execute substantially faster.


getarg

Call: getarg(argnumber)

This function returns the argument given by argnumber, which is a number from 1 to the number of arguments to the function (which can be learned with the pseudo-variable argcount). getarg(1) returns the first argument to the current function, getarg(2) returns the second argument, and so forth, up to getarg(argcount). This function can be used to retrieve the arguments to functions taking a variable number of arguments.


incturn

Call: incturn()

Increments the turn counter. Normally, a daemon is present to call this function once per player command. The turn counter is used to time the execution of fuses, so the user must call incturn() once per turn.

This function is not called automatically by the system, since the turn counter should not be incremented after certain events. For example, most system commands, such as saving a game, should not count as a turn. This function is provided so that the game program can decide when to increment the counter.


input

Call: input()

This function stops and waits for the user to enter a line of text (terminated by the return key and edited as normal for command lines), and returns a string containing the text the user enters. This function does not display any prompt, so it is up to the game program to prompt the user before calling this function.

As a stylistic point, this function should normally be avoided except for special system functions, since the game will present a more consistent interface if the command line is used for most player input. One possible use is given by the example below. (Note that this code is somewhat simplified; in an actual game, you would also want to call setscore() at the appropriate points, and provide other useful feedback.)

die: function
  {
    "*** You have died ***
    \bDo you wish to RESTART, RESTORE, or QUIT? >";
    while (true)
    {
      local response;
      response := upper(input());
      if (response = 'RESTART') restart();
      else if (response = 'RESTORE')
      {
        response := askfile();
        if (restore(response)) "Failed. ";
        else abort;
      }
      else if (response = 'QUIT')
      {
        quit();
        abort;
      }
      else "Please enter RESTORE, RESTART, or QUIT: >";
    }
  }


isclass

Call: isclass(object, class)

This function determines if an object is a subclass of a given class. It returns true if object is a subclass of class, nil otherwise. Note that the function scans the entire class tree, so class need not be in the immediate superclass list for object, but can be a superclass of a superclass of object, or a superclass of a superclass of a superclass, and so on.

This function should be used to determine if an object is a member of a class, rather than using a special property value that the object inherits from the class. Using isclass is more efficient than using a special inherited property value, because the property value doesn't need to be stored, and because it is much faster to scan the inheritance tree than to check each object in the tree for the property value.


length

Call: length(item)

If the item is a string, length() returns the number of characters in the string. If item is a list, length() returns the number of elements in the list.


logging

Call: logging(value)

If the value is a string, it specifies the name of an operating system file which will be created (or truncated to zero length if it already exists) and to which subsequent information displayed on the screen is duplicated. That is, a complete log of the play will be copied to the file for later inspection.

If the value is nil, it indicates that a log file, if in effect, should be closed and logging halted.

Logging is automatically terminated when the player quits the game, but other operations (including saving, restoring, and restarting a game) do not affect logging.

This function has no return value.


lower

Call: lower(string)

This function returns a copy of string with all of the upper-case letters converted to lower-case. See also the upper(string) function.


nextobj

Call: nextobj(object)

Alternative Call: nextobj(object, class)

This function is used to retrieve the next object in a search started with firstobj(). The object is the value returned by the call to firstobj() or by the previous nextobj(object) call. The next non-class object after object is returned, and nil is returned if object was the last non-class object.

The alternative form, with a second argument giving a class object, returns the next object that is a subclass of the given class. This can be used to restrict the search to a particular class of objects.

For an example of the use of this function, see the description of the firstobj() built-in function.


notify

Call: notify(object, &message, turns)

This function sets up to send the message to the object after the given number of turns has elapsed, or, if turns is zero, after each turn. Note the & before the message parameter; this is required to indicate that the message is to be stored rather than being sent immediately.

The message is simply the name of a method defined by the object. The method receives no parameters when it is called.

There is a limit of 100 simultaneously pending notifications.

With a non-zero number of turns, the notify() function is similar to the setfuse() function, in that it waits a given number of turns and then sends the specified message to the given object. With turns set to zero, notify is similar to setdaemon(), in that the message is sent to the object after each turn. Note that the notify() function is generally the better function to use, since most fuses and daemons are directed at objects, and it is better to keep all code that affects the object in the object's definition, rather than move some into a separate function.

See also the unnotify() function, which cancels the effect of the notify() function.

An example of the notify() function is shown below. In the example, we define two objects: a bomb, and a button on the bomb. When the button is pushed, it calls notify() to specify that the explode method in the object bomb is to be called in three turns.

  bomb: item
    location = bombroom
    sdesc = "bomb"
    noun = 'bomb'
    ldesc =
    {
      "The bomb seems to have a small button marked
      \"detonator\" on it. ";
      if (self.isActive) "It's ticking loudly. ";
    }
    explode =
    {
      "The bomb explodes! ";
      self.moveInto(nil);
    }
  ;

  bombButton: buttonItem
    location = bomb
    sdesc = "detonator button"
    adjective = 'detonator'
    doPush(actor) =
    {
      "The bomb starts ticking. ";
      notify(bomb, &explode, 3);              // Bomb explodes in three turns
      bomb.isActive := true;
    }
  ;


proptype

Call: proptype(object, propPointer)

This function determines the datatype of a property of an object, without actually evaluating the property. This can be useful if you want to find out what kind of value the property has, without activating any side effects of evaluating the property (for example, displaying a double-quoted string contained by the property).

The return value is a number, and the possible values are a superset of those returned by the datatype() built-in function.

Note that the propPointer argument must be a property pointer. You can get a property pointer by using the & operator on a property name. For example, to determine the type of the north property of the player's current location, you would do this:

  x := proptype(Me.location, &north); 

Note that using proptype() is very different from using the datatype() built-in function on the value of the property, because proptype() does not evaluate the property in order to determine its type. This has several implications. First, and most importantly, it allows you to avoid side effects of evaluating the property (such as displaying a double-quoted string value) if you don't desire the side effects. Second, it means that you will find out if a property contains code, but you will not learn what type of value (if any) the code will return.


quit

Call: quit()

Terminates the game. Actually, this function flags that the game is to be ended the next time a command will be parsed, so normal return codes should be passed back when using quit(). Hence, after calling quit(), you should always immediately execute an abort statement. See the example given in the description of the input() built-in function for an illustration of the use of quit().


rand

Call: rand(upper-limit)

This function returns a random number from 1 to the upper-limit given. (The upper-limit must be a positive number.) See also the discussion of randomize(), below.


randomize

Call: randomize()

This function "seeds" the random number generator with a new value. The value is implementation-defined, and comes from such a source as the system clock. Until randomize() is called, the random number generator will always produce a fixed sequence of random numbers; once randomized, however, the sequence is unpredictable. For testing purposes, you can leave the call to randomize() out of your program, because the game will always run repeatably. Once the game is working, you can simply put a call to randomize() in your init() function in order to make the game unpredictable for your players.


remdaemon

Call: remdaemon(function, value)

Removes a daemon set previously with setdaemon(). The daemon will no longer be called. Note that if a given function has been set as a daemon multiple times, a call to remdaemon() only removes the first such daemon; hence, you must call remdaemon() as many times as you called setdaemon() on a particular function if you wish to cancel all calls to it. Note also that the value given must match the value used in the original call to setdaemon(); this allows you to set a daemon several times, with different contexts (the context being given by the value), and selectively remove the daemons later.


remfuse

Call: remfuse(function, value)

See also the setfuse() built-in function. This function removes a fuse previously set, or does nothing if the named function has not been set as a fuse. The fuse will not be executed if removed prior to burning down. Note that if a function has been set as a fuse multiple times, remfuse() will remove only the first such fuse set; hence, you must call remfuse() on a function as many times as you called setfuse() on the function if you wish to cancel all calls to it. Note also that the value given must match the value used in the original call to setdaemon(); this allows you to set a fuse several times, with different contexts (the context being given by the value), and selectively remove the fuses later.


restart

Call: restart()

This function starts the game over again from the beginning. It does not return.


restore

Call: restore(filename)

The filename is a string specifying an operating system file which was created with the save() function. The state of the previously saved game is restored, so that the players can continue where they left off.

If successful, nil is returned; otherwise, an error occurred restoring the game from the file. Note that the game to be restored must have been saved by the identical version of the TADS software and of the game itself. The system will refuse to restore a game saved by a different version of the TADS software, or by a different version of your game program (as determined by a timestamp stored in the game by the compiler).


save

Call: save(filename)

The filename is a string specifying an operating system file to be used to save the current status of the game. Everything about the game is saved in the file, so that the exact point in the game can be later restored with the restore() function.

If successful, nil is returned; otherwise, an error occurred saving the file.


say

Call: say(value)

The value can be either a number or a string. If it's a number, its decimal value is printed. A string is displayed as it is.

There is no return value.


setdaemon

Call: setdaemon(function, value)

Sets a daemon. Each turn, all outstanding daemons are called once, after all player command processing is completed. The value given is passed to the function as its sole parameter each time the function is called.

Note: the function must be known when the compiler reads this statement, which means the function must have been defined or at least called prior to the setdaemon() call in the source file. You can forward-declare a function by using a statement like this:

  test: function; 

Note that the actual definition of the function test will appear later in the file; the statement above only declares it as a function so it can be used in the setdaemon() or similar call.

At most 100 daemons can be running simultaneously; a run-time error will result if a call to setdaemon() is made when 100 daemons are already running. There is no return value.

See also the notify() function.


setfuse

Call: setfuse(function, time, value)

Sets a fuse of the specified length. The function is the identifier naming a function. The time is a positive number specifying the number of turns before the fuse burns down. After the specified number of turns, function is called with value as its sole parameter.

Note: the function must be known as a function when the compiler reads this statement, which means the function must have been defined or at least called prior to the setfuse() call in the source file. See the note in the description of setdaemon(), above, for more information.

There is limit of 100 simultaneously pending fuses; a run-time error results if setfuse() is called when 100 fuses are already pending. There is no return value from setfuse().

Note that fuses differ from daemons in that a fuse is only called once, at a time determined when the fuse is set; once called, it is automatically removed from the list of pending fuses. Daemons, on the other hand, are called once per turn until explicitly removed.


setit

Call: setit(object)

Sets the antecedant of "it," as used in a player's command, to the specified object. This is useful if a complaint refers to an object which will draw the player's attention; consider the following dialogue:

  >go north
  The metal door is closed.

  >open it
  There is a padlock preventing you from opening the door.

  >unlock it
  What do you want to unlock the padlock with?

  >the key
  The padlock swings open.

In this example, setit() has been used each time a complaint refers to an object which the user might wish to manipulate due to the complaint.

There is no return value.


setscore

Call: setscore(score, turns)

Alternative Call: setscore(stringval)

This function is used to inform the system of the current score and turn count. On versions of TADS that have a status line, this function updates the values on the status line. On versions of TADS without a status line, this call is ignored.

Your game should call this function whenever the score or turncounter change, so that the status line is always kept up to date. Note that this means that it should be called after using restore() to load an old game, and before calling restart() to start over from the beginning, in addition to being called whenever the player earns points or completes a turn. Generally, if you use the function incscore() defined in adv.t to add points to the score, and you use a standard turn counter function (such as the turncount() function defined in adv.t), you will not have to worry about calling setscore() too often.

The alternative form of the setscore() call allows you to display any text string in the score/turn counter area of the status line. In older versions of TADS, only the first form could be used, and the status line display was fixed with the score/turn counter format. In new versions, however, you can place whatever string you want on the right portion of the status line, simply by passing the string you wish to display to the setscore() function. You could, for example, display the time of day (within your game), or you could display the player's rank, experience, or other statistics rather than the score. The text string used in setscore() is displayed right-justified on the status line.

Note that several routines in adv.t (about four in all) call the numeric form of setscore(). If you use the string version, you should change the setscore() calls in adv.t to call a routine that you provide that displays the status line in the format you prefer.


setversion

Call: setversion(version)

Note: This function is no longer needed, although TADS continues to support it for compatibility with existing games written with older versions of TADS.

In older version of TADS, this function was used to identify the version of your game that created a saved game file. It was necessary to use setversion so that a saved position file created by one version of your game couldn't accidentally be used with another version of your game, because the information in a saved position file is only usable by the same version of a game that created it. The compiler now automatically generates a unique version identifier whenever your game is compiled, eliminating the need for you to do this manually. You no longer need to call this function, and it has no effect if you do.


substr

Call: substr(string, offset, length)

This function returns the substring of string starting at the character position given by offset (the first character's position is 1) and containing up to length characters. For example, substr('abcdefghi', 4, 3) returns the string 'def'. If the given offset is past the end of string, a null string is returned; hence, substr('abcdef', 10, 3) returns a null string. (Note that a null string, specified by two single quotes, ", is not the same as nil.) If the string is too short to satisfy the requested length, the substring from the given offset to the end of the string is returned; for example, substr( 'abcdefg', 4, 10 ) returns the string 'defg'.


undo

Call: undo()

This function undoes all changes made since the last "savepoint" in the undo records. A savepoint is entered at the beginning of each turn automatically by the player command parser. Calling undo() once restores the game to its state at the very beginning of the current turn. Calling undo() again returns it to the state at the beginning of the previous turn, and so forth.

You should call undo() once in any situation where you only want to undo the current turn. For example, in a routine that gives the player options after being killed by an action taken on the current turn, you'd only want to call undo() once if the player elects to undo the action that killed him, since it's just the current turn that needs to be taken back.

You should call undo() twice in your verb that implements an "undo" command, if you have one in your game. The first call undoes the current turn (the "undo" command itself), and the second call undoes the previous turn - this is appropriate behavior, since the "undo" command should back up one turn. The default undoVerb defined in adv.t implements exactly this behavior.

The undo() function returns true if the undo operation was successful, or nil if no more undo information is available. A return value of nil means either that the player has already undone turns all the way back to the beginning of the game, or that he has undone turns as far back as the undo records go. The default undo area size will hold about a hundred turns in a typical game, but the actual number of turns that can be undone will vary by game, depending on the amount of work that needs to be undone. The undo mechanism will always keep as many of the most recent turns as possible, discarding the oldest turns in the undo log when it runs out of space.


unnotify

Call: unnotify(object, &message)

This function cancels the effect of a previous call to notify() with the same object and message. Note that the & before message is required.

See also the notify() function.


upper

Call: upper(string)

This function returns a copy of string with all of the lower-case letters converted to upper-case. Do not confuse this function with the caps() built-in function, which affects only the next character displayed.


yorn

Call: yorn()

Waits for the user to type a character, and returns 1 if the player typed "Y", 0 if the player typed "N", and -1 for anything else. A suitable prompt should be issued prior to calling this function.




Our life is frittered away by detail Simplify, simplify.
HENRY DAVID THOREAU, Where I Lived, and What I Lived For (1854)

The statements was interesting but tough.
MARK TWAIN, Adventures of Huckleberry Finn (1884)


Chapter Four Table of Contents Chapter Six