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 Eight


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 several typographical conventions to describe the TADS language in an unambiguous way.

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.


Version Information

TADS has gained many new functions and features over the years, but tracking what features were available with which version of the software was problematic in the past. Since the release of version 2.2.4, however, TADS has had the ability to check to see what version of the interpreter (runtime) software is being used. For that reason all new functions and features added with 2.2.4 and higher are marked in this documentation. If no version number is listed you can assume that the item in question was available to all versions of TADS prior to 2.2.4.


Components of TADS

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

When you write a TADS program, you use a text editor (such as BBEdit, UltraEdit, emacs, etc., which is not included with TADS) to create and edit a source file, and then you compile the file using the TADS Compiler for your computer. The compiler checks the entire program for syntax errors andthen creates another file containing a binary representation of your source file. This form, known as a game file, iis more efficient to execute, so your game will require less memory and run faster.

After you have compiled your program, you use a TADS interpreter to run the game. The interpreter contains the player command parser and other code for user interaction.


Input File Format

The TADS compiler accepts plain text (standard ASCII) files as input. The compiler considers any amount of whitespace to be the same as a single space, and 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 number or hash or 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.

Note once again that the #include directive cannot be preceded by a space character. The # symbol must be the first character on the line. If you copy and paste sample code from a Web browser window be sure to check for this common problem, or else your code simply will not compile.


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.

\t Tab to the next stop. Tab stops are every four spaces. This is useful if you want stretches of blank space within a line. The multimedia TADS equivalent is the tag <TAB MULTIPLE=4>.
\n

A newline (carriage return). Ends the current line, and skips to a new one. Note that repeating this sequence has no effect, since the output formatter ignores redundant \n sequences. This is normally convenient, because it means you don’t have to worry about several newline sequences being displayed by different objects filling the display with unwanted blank lines. If you want to force one or more blank lines, use \b. The multimedia TADS equivalent is the tag <BR HEIGHT=0>.

\b

Ends the current line, and then outputs a blank line. Multiple \b sequences will result in multiple blank lines. The multimedia TADS equivalent is the tag <P>. Almost. Multiple <P>s don’t insert multiple blank lines.

\" A double quotation mark. Note that this is a neutral quotation mark. If you want curved (typographical) quotation marks you need to use multimedia TADS.
\' A single quotation mark. Note that this is a neutral apostrophe. If you want curved (typographical) apostrophes or single quotes you need to use multimedia TADS.
\\ A backslash.
\< A left angle bracket. This is not required to produce a single angle bracket (although it will do so), but you will need to use this on second and subsequent angle brackets in a stream of contiguous angle brackets, to prevent TADS from thinking you want to embed an expression in your string (see below). Hence, if you want to display <<<<<, you will have to type this in your program: “<\<\<\<\<”. Note also that is displayed as a left angle bracket in the multimedia TADS system, and will not be interpreted as a tag.
\^ (That is, a backslash followed by a circumflex or “up arrow” or “hat,” entered with the shifted “6” key on most keyboards.) This sequence causes the next character in the output to be capitalized. This sequence is generally used just before invoking a function or method that will display text that will be at the start of a sentence. By using this sequence prior to the text, you ensure that the first letter of the sentence is capitalized, even if the function or method displaying the text doesn’t know it should be capitalized. For example: “\^<< obj.adesc >> is sitting on the table.” Note that the caps() built-in function has the same effect.
\v This sequence is the opposite of “\^”. Whereas “\^” converts the next character displayed to a capital letter, “\v” converts the next character to a small letter. Displaying “\v” is equivalent to calling the nocaps() built-in function.
\space (That is, a backslash followed by a space.) A quoted space. This is useful in certain cases to achieve special formatting effects, because it overrides the output formatter’s suppression of multiple spaces, and it also suppresses the double-space that sometimes follows punctuation marks such as periods in certain versions of the TADS interpreter. For example, if you want to print someone’s name with a middle initial, you don’t want two spaces after the initial. Since some versions of the runtime print the two spaces automatically you would thus enter: “Michael J.\ Roberts” to ensure that the name is displayed correctly with all versions of the interpreter. Note also that some versions of the TADS interpreter will print either one or two spaces after a punctuation mark, depending on a preferences setting.
\( Begin highlighting. Text after the \( is displayed with a special attribute, which varies by system. On some machines it will appear in a different color than ordinary text, while on others it may appear in a boldfaced font. On some systems it may have no effect at all. This is intended to be used for emphasizing text the same way that boldface or italics would emphasize text in a book. Note that highlighting does not “nest”; that is, if a \( sequence occurs while highlighting is already active, the second sequence has no additional effect. This functionality predates the multimedia TADS software, which offers richer and more flexible text-formatting options.
\) End highlighting. Text after the \) without the special highlighting attribute. Note that a single \) sequence turns off highlighting, no matter how many \( sequences preceded it.
\- Pass two bytes. The sequence is a backslash followed by a hyphen, and tells the formatter to pass the following two bytes as-is, without any interpretation. This is useful for multi-byte character sets (such as on a Japanese-localized Macintosh), where a single character may be represented by two bytes. For the most part, you can freely mix single- and double-byte characters within text strings without any special work. However, some double-byte characters contain the ASCII code for a backslash as one of their two bytes; in these cases, the output formatter incorrectly interprets the byte whose code is “\” as introducing an escape sequence. By preceding such characters with “\-”, you can prevent the parser from interpreting the byte with the backslash code as an escape sequence, so the character will be displayed correctly.
\H+ Turn on the HTML parser. This character sequence is used to enable the HTML functionality of multimedia TADS. For more information consult the multimedia TADS documentation.
\H- Turn off the HTML parser.

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.

Multimedia TADS supports many more formatting options than regular TADS, but does so using HTML tags rather than TADS character sequences. Consult the multimedia TADS documentation for more information.


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.

Note that this feature is fully compatible with multimedia TADS and does not cause any problems, even though it uses the < and > symbols. This is because these symbols are interpreted by the compiler at compile-time and never appear in the actual game text, where they could be misinterpreted by the runtime.


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. Another way of looking at it is that a method is code inside an object. When the method is evaluated or triggered, 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 is a standalone piece of code that, unlike a method, isn’t part of an object. A function definition has this form:

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

The argument-list is optional; functions need not take arguments if there’s no need to send any data to it. But 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 initializers 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 bloc. In other words, the only thing that can appear 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.

&

Takes the “address” of a function or property. In other words, this expression allows you to refer to a function or property without evaluating (triggering) it.

The & operator must be immediately followed by the name of a property or the name of a function. You can assign this value to a local variable or a property, or you can pass it to a function or method. The address value can then be used to call the function or property. See “Indirect Function and Method Calls” later in this chapter. (Note: older versions of TADS used the # operator to take the address of a function. This operator is still understood by the system, but we recommend that you use & in new code instead.)


.

(Period or full stop.) Takes a property of an object. On the left of the dot is an expression that evaluates to an object, and on the right is a property name. If the property is a method that requires arguments, these arguments are listed in parentheses after the property name, just as with function arguments. A local variable or an expression enclosed in parentheses may appear on the right hand side; the local or expression must evaluate to a property pointer.

[]

List indexing; applied to the right side of a list-valued expression. An expression which evaluates to a number must appear between the square brackets. If the index (i.e., the value between the square brackets) is n, the nth element of the list is returned by this operator. The first element in the list is number 1. For example, ['one' 'two' 'three'][1+1] evaluates to the string 'two'. Note that a run-time error results if n is outside the bounds of the list; that is, n is less than 1 or greater than the number of elements in the list (as returned by the length() built-in function.

++

Increment; adds one to a numeric variable or property. The expression to which ++ is applied must be suitable for assignment (i.e., it must be something that you can use on the left-hand side of the assignment operator, “:=”). The ++ operator can be either a prefix or postfix operator - that is, it can be placed either before or after its operand. If it’s placed before the operand, as in ++i, the operand is incremented, and the value of the expression is the value of the operand after the increment. If it’s placed after the operand, as in i++, the value of the expression is the value of the operand before the increment. So, if the variable i has the value 3, ++i has the value 4, while i++ has the value 3. Think about it by reading the expression left-to-right, and noting that the value of the expression is the value of i when you read it, and the increment is applied when you read the ++ operator. So, with ++i, you first read the increment operator, which adds 1 to i, then you note the value of i, which by now is 4. With i++, you first read i and note its value, which is still 3, then you see the ++ operator, leaving 4 in i.

--

Decrement; subtracts one from a numeric variable or property. This acts exactly like the ++ operator, except that it decrements its operand rather than incrementing it.


not Logical negation. Turns true into nil and vice versa. This operator can only be applied to the values true and nil.
-

Arithmetic negation. As a unary prefix operator (that is, an operator that precedes its single operand), the minus sign negates the number following it.


* Numeric multiplication. Multiplies the numbers on either side of the asterisk together.
/ Numeric division. The number on the left of the slash is divided by the number on the right. Note that TADS numbers are all integers; the remainder of the division is discarded. Hence, 7/2 has a value of 3.

+ Adds numbers, concatenates lists, concatenates strings. If both the left and right are numbers, the result is numeric addition. If one or the other is a list, but not both, the non-list is added as the last element of the list. If both operands are lists, the items from the list on the right side are appended to the list on the left; for example, [1 2 3] + [4 5] yields the list [1 2 3 4 5]. If both are strings, the right string is concatenated to the left string. Other datatypes are illegal.
-

Subtracts numbers, removes items from lists. If both operands are numbers, the right operand is subtracted from the left. If the left element is a list, the right item is removed from the list if it appears in the list on the left (nothing happens if not); if the right item is also a list, each element from the right list that appears in the left list is removed from the left list. Other datatypes are illegal.


=

Equality. The datatypes on either side must be the same, but can’t be lists. One exception: anything can be compared for equality to nil. Evaluates to true if the items are the same, nil otherwise.

<>

Inequality. True if the operands on both sides are not the same (although the datatype of each operand must be the same).

>

Greater than. Like the other comparison operators, this may only be applied to numbers and strings. Evaluates to true or nil. Note that, when applied to strings, the comparison is based on the collation sequence of the character set of the computer you are using, such as ASCII.

< Less than.
>= Greater than or equal to.
<=

Less than or equal to.


and Logical product: both the left and right sides must be true, in which case the value is true; otherwise, the value is nil. Note that if the left operand is nil, the right operand is not evaluated at all, since the value of the expression will be nil regardless of the value of the right operand.

or Logical sum: one or the other of the left or right operators must be true for the value to be true; otherwise the value is nil. Note that if the left operand is true, the right operand is not evaluated at all, since the value of the expression will be true regardless of the value of the right operand.

? : Conditional. This is a tertiary operator; that is, it takes three operands, of the form cond ? true-expr : false-expr. First, the cond is evaluated; if it evaluates to true or a non-zero number, then the true-expr is evaluated, and its value is the value of the entire conditional expression. If the cond is false, the false-expr is evaluated, and its value is the value of the entire expression. Note that either true-expr or false-expr is evaluated - not both. This allows you to use this operator when the expressions have side effects, such as displaying strings.

:= Assignment. The variable or property on the left of the operator is assigned the value on the right side. This is the lowest priority operator, and operates right to left, unlike the other operators. Hence, a := b := 3 assigns the value 3 to b, then to a. Note that an assignment is an expression, which returns the assigned value; hence, the value of (a := 3) + 4 is 7, with the side effect that the variable a has been set to 3.
+= Add and assign. This is simply short-hand notation. The expression a += b has exactly the same effect as the expression a := a + b, except that the += operator makes the expression easier to write. The value of the expression is a + b, which is the same as the value of a after the assignment.
-= Subtract and assign. This is short-hand notation. The expression a -= b has the same effect as a := a - b.
*= Multiply and assign. The expression a *= b has the same effect as a := a * b.
/= Divide and assign. The expression a /= b has the same effect as a := a / b.

, Conjunction. The comma operator simply allows you to start a new expression within an expression. On the left hand side is an expression, and on the right hand side is another expression. The two expressions are independent; the comma operator performs no computation on either expression. The value of a pair of expressions separated by a comma is the value of the second (right-hand) expression. The comma operator is useful mostly in contexts where an expression is required, but you wish to evaluate several otherwise independent expressions for their side effects. For example, in the initialization portion of a for statement, you often wish to initialize several variables; you can do this by separating the initializations with commas. See the description for statement later in this chapter. Note: the comma operator is different from a comma that appears in a function’s or method’s argument list. In the argument list, the comma simply separates different expressions that are the arguments; in a normal expression, however, the comma separates parts of the same expression.

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 */
  }


C-Style Operators

TADS users who are also C programmers often find the substantial similarity between TADS and C to be convenient, but also find the slight differences to be a source of confusion when switching between the two languages. TADS offers the option to use C-style operators. Note that if you’re not an experienced C programmer you probably won’t need to read this section.

First, TADS supports the full complement of C operators.

a % b Returns the remainder of dividing a by b
a %= b Assigns (a % b) to a
a != b

Equivalent to (a <> b)

!a

Equivalent to (not a)

a & b Bitwise AND
a &= b sets a to (a & b) (the bitwise AND of a and b)
a | b bitwise OR
a |= b sets a to (a | b) (the bitwise OR of a and b)
a && b equivalent to (a and b)
a || b equivalent to (a or b)
a ^ b bitwise XOR of a and b
a ^= b sets a to (a ^ b) (the bitwise XOR of a and b)
~a bitwise negation of a
a << b a shifted left by b bits
a <<= b sets a to (a b) (shifts a left by b bits)
a >> b a shifted right by b bits
a >>= b sets a to (a b) (shifts a right by b bits)

Some of these operators, such as !, &&, and ||, are merely synonyms for existing operators. The “bitwise” operators act on numeric values rather than logical values; they treat their operands as bit vectors, and apply the operation to each bit of the numbers. For example, 3 & 2 has the value 2, since the bit patterns are “011” and “010,” respectively. The bit-shift operations are equivalent to multiplying or dividing by a power of 2: 1 << 5 has the value 32, since it’s equivalent to multiplying 1 by 2 raised to the 5th power.

Second, TADS has a mode which uses the C-style assignment operator. Normally, the TADS assignment operator is :=, and the equality operator is =. In C, these operators are = and == respectively. If you prefer, you can tell TADS to use the C-style operators instead of the TADS version. By default, TADS still uses its own version of the operators. There are two ways to switch into C-style operator mode: by using a command-line option, or by using a #pragma compiler directive in your source code.

To compile an entire game in C mode, use the -C+ command line option (Macintosh users will find a menu item for C-style operators under the “Options” menu; check this item to enable C operators, and uncheck it to use standard TADS operators). Using the -C+ compiler option enables C operator mode for the entire game’s source code. (The -C- option explicitly turns off C operator mode. This is the default mode.)

To specify that a particular file is to be compiled in C mode, you can use the directive #pragma C+. The similar directive #pragma C- specifies that TADS operator mode is to be used. These directives can appear anywhere in a source file (outside of comments and strings); they must be alone on the line, and must not be preceded by any whitespace on the line.

A #pragma setting affects only the current source file, and any files it includes. The header files included with TADS (adv.t and std.t) both use TADS operators, so they explicitly specify #pragma C-. However, because these directives are limited to the header files, you can freely include adv.t from a file that uses C operator mode without having to worry about setting the mode to or from TADS mode. Simply #include adv.t exactly as you did before - even if your source file uses C mode, adv.t will compile correctly, because it sets the operator mode back to TADS for its own contents, and TADS automatically restores the enclosing file’s mode at the end of adv.t.

Note that the C-style operator mode setting affects only the assignment and equality operators. You can use all of the other C operators (such as the bitwise operators) in either mode - all of these symbols were invalid in previous versions of TADS, so there’s no danger that they’ll be misinterpreted for old games.

When the compiler is using C-style assignment operators, it issues a warning, “possibly incorrect assignment,” whenever it finds a statement in this format:

  if ( a = 1 ) ...

While this statement is legal, with C-style operators it has the effect of assigning the value 1 to a; since the value of an assignment is the value assigned, this if will always succeed. It’s a common error for C programmers (even highly experienced ones) to write this type of statement when they really want to compare the values. In fact, I originally chose to use “:=” as the assignment operator in TADS to reduce the likelihood of this type of error. Now that TADS can be switched to C syntax for assignments and comparisons, I’ve added the “possibly incorrect assignment” warning to help catch these. The compiler will flag assignments made in if, while, and do statements, and in the condition clause of for statements. To suppress this warning, you can explicitly test the value of the assignment like this:

  if ( ( a = 1 ) != 0 ) ...

There are a couple of minor complications with some of the C-style operators.

First, the >> operator can’t be used in an expression embedded in a string with the << >> construct, because it would be taken for the >> that terminates the embedded expression. Even adding parentheses won’t help, because the compiler recognizes the << >> construct before it looks at any expression in progress. So, this type of code won’t work:

  myprop = "x divided by 128 is << (x >> 7) >>! "     // wrong

You would have to code this instead as:

  myprop = { "x divided by 128 is "; x >> 7; "! "; }  // right

Second, the & operator now has a binary interpretation in addition to its unary interpretation. For the most part, this won’t create any confusion, but there’s one situation in which it might: in lists. You might have lists in your games that look like this:

  mylist = [ &prop1 &prop2 &prop3 ]

In past versions, since the & operator could only be a unary operator, this construct was unambiguous. However, now that & can be a binary operator, this could be interpreted either as three expressions involving unary & operators, or as a single expression involving one unary & operator and two binary & operators.

For compatibility with past versions, TADS will interpret the & operators as unary operators. When it finds this construct, though, it will warn you that it is ambiguous. (The new warning is TADS-357, “operator ‘&’ interpreted as unary in list.”) You can suppress this warning in one of two ways. First, you can render the list unambiguous. To do this, use a comma between each pair of list elements:

  mylist = [ &prop1, &prop2, &prop3 ]

Note that if you actually want the binary interpretation, you should simply enclose the expression in parentheses:

  mylist = [ ( 2 & 3 & 4 ) ]

The other way you can suppress this message is with the new -v-abin compiler option, which tells the compiler not to generate the warning. The compiler still interprets the operator the same way when you specify -v-abin - it just doesn’t tell you about it.

Note that TADS will treat any operator which has both a unary and binary interpretation as a unary operator when it finds it within a list, and will generate the TADS-357 warning. For the - operator, this is a change from past versions, which used the binary interpretation when in a list. I don’t anticipate that this will be a compatibility problem, because the old binary interpretation was almost never desirable, and I think users avoided it. However, if you have an older game, you may wish to compile without the -v-abin option at least once, and check any lines where the TADS-357 warning is generated for - or + operators, to determine if your game’s behavior will change with the new version. Any TADS-357 warnings generated for the & operator can be safely ignored for a game written with a previous version of TADS.


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 at any time 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. Another way of looking at it is you can refer to the function or method without actually evaluating (triggering it). 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);
  }

Note that the brackets are optional. Thus the following form is perfectly legal:

  return nil;

In fact, recent versions of adv.t use the bracketless style as the preferred form.


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


abort, exit and exitobj

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 three 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 and so is used when an actor wishes to stop further processing, and other similar cases. Finally, the exitobj function does what exit does except that it skips the remaining processing only for the current object in a command and proceeds to the next object.

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.

You can use the abort statement within a daemon or fuse, and it will work as expected, by terminating the current routine and skipping any remaining fuses or daemons on this turn. (this wasn’t allowed in older versions of TADS)


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

TADS 2.2.4 added a new syntax that lets you specify the name of the superclass after the ‘inherited’ keyword, but is otherwise similar to the normal ‘inherited’ syntax:

  inherited fixeditem.doTake(actor);

This specifies that you want the method to inherit the doTake implementation from the fixeditem superclass, regardless of whether TADS might normally have chosen another superclass as the overridden method. This is useful for situations involving multiple inheritance where you want more control over which of the base classes of an object should provide a particular behavior for the subclass.


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.


replace and modify

Most game authors find that, when writing a substantial game, they can’t avoid modifying adv.t. While there’s nothing intrinsically wrong with this, it creates a problem when a new version of TADS is released, because you must either continue to use the old version of adv.t, which means that any bug fixes or enhancements in the new version are not available, or take the time to reconcile your changes to your custom adv.t with those made in the standard version. The replace and modify mechanism can help you deal with this problem.

These keywords allow you to make changes to objects that have been previously defined. In other words, you can #include the standard adv.t file, and then make changes to the objects that the compiler has already finished compiling. Using these keywords, you can make three types of changes to previously-defined objects: you can replace a function entirely, you can replace an object entirely, or you can add to or change the methods already defined in an object.

To replace a function that’s already been defined, you simply preface your replacement definition with the keyword replace. Following the keyword replace is an otherwise normal function definition. The following example replaces the scoreStatus function defined in adv.t with a new function that customizes the status line score display.

       #include <adv.t>

       replace scoreStatus: function( points, turns )
       {
          setscore( cvtstr( pts ) + ' points/' + cvtstr( turns ) + ' moves' );
       }

You can do exactly the same thing with objects. For example, you can entirely replace the fastenVerb defined in adv.t:

       #include <adv.t>

       /* we don't want "buckle", so replace adv.t's fastenVerb */
       replace fastenVerb: deepverb
          verb = 'fasten'
	  sdesc = "fasten"
	  prepDefault = toPrep
	  ioAction( toPrep ) = 'FastenTo'
       ;

Replacing an object entirely deletes the previous definition, including all inheritance information and vocabulary. The only properties of a replaced object are those defined in the replacement; the original definition is entirely discarded.

You can also modify an object, retaining its original definition (including inheritance information, vocabulary, and properties). This allows you to add new properties and vocabulary. You can also override properties, simply by redefining them in the new definition.

The most common addition to an object from adv.t will probably be new verb associations and added vocabulary.

       modify pushVerb
          verb = 'nudge'
          ioAction( withPrep ) = 'PushWith'
       ;

Note several things about this example. First, no superclass information can be specified in a modify statement; this is because the superclass list for the modified object is the same as for the original object. Second, note that vocabulary has been added. The additional vocabulary does not replace the original vocabulary, but simply adds to the previously-defined vocabulary. Further note that verb association pseudo-properties, such as doAction and ioAction, are legal in a modify definition. Any new doAction or ioAction definitions are added to the original set of definitions.

In a method that you redefine with modify, you can use pass and inherited to refer to the replaced method in the original definition of the object. In essence, using modify renames the original object, and then creates a new object under the original name; the new object is created as a subclass of the original (now unnamed) object. (There is no way to refer to the original object directly; you can only refer to it indirectly through the new replacement object.) Here’s an example of using pass with modify.

     class testClass: object
	    sdesc = "testClass"
	    ;

     testObj: testClass
        sdesc =
	    {
	        "testObj...";
		pass sdesc;
	    }
        ;

     modify testObj
        sdesc =
	    {
	        "modified testObj...";
		pass sdesc;
	    }
	    ;

Evaluating testObj.sdesc results in this display:

  modified testObj...testObj...testClass

You can also replace a property entirely, erasing all traces of the original definition of a property. The original definition is entirely forgotten - using pass or inherited will refer to the method inherited by the original object. To do this, use the replace keyword with the property itself. In the example above, we could do this instead:

      modify testObj
        replace sdesc =
	    {
	        "modified testObj...";
		pass sdesc;
	    }
	    ;

This would result in a different display for testObj.sdesc:

  modified testObj...testClass

The replace keyword before the property definition tells the compiler to completely delete the previous definitions of the property. This allows you to completely replace the property, and not merely override it, meaning that pass and inherited will refer to the property actually inherited from the superclass, and not the original definition of the property.


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.


addword

Call: addword(obj, &prop, word)

Adds the word (a single-quoted string value) to the object as the given part of speech. The prop parameter can be noun, adjective, plural, verb, article, or preposition. You can add words to any object, including objects defined statically in your game, as well as objects created dynamically at run-time.

For examples of using this function, see the section on Dynamic Vocabulary.


askfile

Call: askfile(prompt_text, prompt_type_code, file_type_code, flag)

Added: Type code parameters added with TADS 2.3.0. Flag value added with TADS 2.5.0

This function asks the user to enter a filename, in a system-dependent manner. The system’s standard file dialogue, 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 dialogue, 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. Prompt strings passed to askfile can contain \n and \t sequences. These sequences are converted properly for display in the dialogue.

TADS 2.3 added two additional, optional parameters that let you specify what type of prompt to show and what type of file to request. These arguments are hints to the system-specific code that displays the “open file” dialogue; by specifying this new information, you help the system code show the correct type of dialogue.

The prompt_type_code tells the open-file dialogue whether you’re opening an existing file or saving a file. On some systems (Windows and Macintosh included), the user interface uses one type of dialogue for opening an existing file, and a different type of dialogue for saving a file; you can use this parameter to select the appropriate dialogue type on systems that make this distinction. This parameter can have one of the following values, defined in adv.t:

ASKFILE_PROMPT_OPEN - open an existing file for reading
ASKFILE_PROMPT_SAVE - open a file for saving information

On some systems, the open-file dialogue will filter the files it displays so that the player only sees files of the particular type being requested. The file_type_code parameter lets you specify the type of file you’re interested in, so that the dialogue can use the appropriate filtering on systems that support this. The file_type_code can be one of the following values, defined in adv.t:

FILE_TYPE_GAME - a game data file (.gam)
FILE_TYPE_SAVE - a saved game (.sav)
FILE_TYPE_LOG - a transcript (log) file
FILE_TYPE_DATA - general data file (used for fopen())
FILE_TYPE_CMD - command input file
FILE_TYPE_TEXT - text file
FILE_TYPE_BIN - binary data file
FILE_TYPE_UNKNOWN - unknown file type

If you leave out the type code arguments in a call to askfile(), the function will behave as it did with previous versions of TADS. This means that your prompt string must contain the word “save” or “write” in order to show a “save file” dialogue rather than an “open file” dialogue on those systems that differentiate between these dialogue types.

The optional fourth argument added with TADS 2.5.0 lets you specify additional flags to the askfile function. The possible flag values, defined in adv.t, are:

ASKFILE_EXT_RESULT   Return extended result codes (described below). If this flag is provided, the function returns extended results; if this flag is not specified, the function returns the traditional results.

In order to specify the new flag value argument, you must specify the prompt type and file type arguments as well; if you omitted the prompt or file type argument, the askfile function would not be able to tell that you meant the last argument as the flags value.

If you omit the flags argument, askfile uses a default value of zero, which makes the function behave the same as in past versions. Because older code never specifies a flags value, the function will always behave compatibly with past versions when called from older code.

Before the release of 2.5.0, askfile returned a string on success, or nil for any type of failure. However, this didn’t permit the caller to determine exactly what kind of failure occurred, and in particular did not allow the caller to distinguish between an actual error and the player cancelling the file selector dialogue. When ASKFILE_EXT_RESULT is specified, the function will return additional information that allows the caller to distinguish these cases.

When the ASKFILE_EXT_RESULT flag is specified, askfile returns a list that contains two elements. The first element is a number which indicates the status of the file selection; the second element is a string if a file was successfully chosen, or nil if not. The possible values for the first element of the returned list, defined in adv.t, are:

ASKFILE_SUCCESS   A file was successfully chosen. The second element of the list contains a string giving the chosen filename.
ASKFILE_FAILURE   An error occurred prompting for a filename. This usually indicates that the file selector dialogue could not be shown for some reason (insufficient memory, for example).
ASKFILE_CANCEL   The user canceled the file selector dialogue. On the Macintosh, for example, this means that the user clicked the “Cancel” button. This indicates that the user does not wish to proceed with whatever operation is in progress, so the operation should be aborted. Since the user explicitly chose to cancel the operation, the program should not indicate that an error occurred, but simply that the operation will be terminated in accordance with the user’s request.

Here’s an example, from the “restore” command’s implementation in adv.t, of using the extended results.

    local savefile;

    savefile := askfile('File to restore game from',
                        ASKFILE_PROMPT_OPEN, FILE_TYPE_SAVE,
                        ASKFILE_EXT_RESULT);
    switch(savefile[1])
    {
    case ASKFILE_SUCCESS:
        return mainRestore(savefile[2]);

    case ASKFILE_CANCEL:
        "Cancelled. ";
        return nil;

    case ASKFILE_FAILURE:
    default:
        "Failed. ";
        return nil;
    }


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.


clearscreen

Call: clearscreen()

Clears the screen. This function may have no effect when called under some versions of the TADS interpreter. For example, when the DOS runtime is operating in plain ASCII mode, clearscreen() will have no effect. Other interpreters clear the screen by displaying a screenful of blank spaces, allowing you to view previously-displayed text in the scrollback. Others may simply reset the text window, so you lose everything that was in the scrollback.

This function has a special meaning in multimedia-enabled versions of the TADS interpreter. The function clears the screen but retains the contents of the screen as a chapter. You can thus flip from one chapter to the next by using the appropriate menu items.


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.

1 - Number
2 - Object
3 - String
5 - nil
7 - List
8 - true
10 - Function Pointer
13 - Property Pointer


debugTrace

Call: debugTrace(1, flag)

This form of debugTrace lets you turn a new player command parser diagnostic mode on and off. If flag is true, this function activates the diagnostic mode; if flag is nil, it turns the diagnostic mode off.

When the diagnostic mode is active, the parser generates a series of progress messages as it analyzes the player’s command. These messages provide information on how the parser interprets the words in the command. When first reading a sentence, the parser displays all possible parts of speech for each word. As the parser further analyzes the sentence, it displays information on each noun phrase: the words involved, the part of speech that the parser uses for each word in the noun phrase (when a word can be used as multiple parts of speech, the parser chooses one part of speech as it reads the noun phrase), and the objects that match the words in the noun phrase.

The parser diagnostic mode may help you track down problems in which the parser refuses to recognize certain noun phrases that you would expect to be valid. Since the parser chooses among ambiguous interpretations of words, it’s frequently helpful to understand exactly how the parser is interpreting your commands; this new debugging mode should make it easier to gain this understanding.

This mode is available in the standard runtime as well as the debugger, so debugTrace(1, flag) always succeeds. The function returns no value.


defined

Call: defined(object, propPointer, flag)

Added: Flag argument added with TADS 2.5.1.

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.

The third argument is optional. If provided, it returns more specific information about the property definition, and it can be one of the following values, defined in adv.t:

DEFINED_ANY   This is the default, and has the same effect as omitting the flag argument. The function returns true if the object defines or inherits the property, nil if not.
DEFINED_DIRECTLY   The function returns true only if the object directly defines the property. If the object doesn’t define the property at all, or merely inherits the definition from a superclass, the function returns nil.
DEFINED_INHERITS   The function returns true only if the object inherits the property. If the object doesn’t define the property, or defines the property directly rather than inheriting it from a superclass, the function returns nil.
DEFINED_GET_CLASS   The function returns the class where the property is defined. If the object directly defines the property, the function returns the object itself. If the object inherits the property from a superclass, the function returns the superclass from which the property is inherited. If the object doesn’t define or inherit the property, the function returns nil.

For example, to determine if the object redBook directly defines verDoTake, you could use this code:

   if (defined(redBook, &verDoTake, DEFINED_DIRECTLY))
      "verDoTake is overridden directly in redBook. ";


delword

Call: delword(obj , &prop, word)

Deletes the word (a single-quoted string value) from the object’s vocabulary for the given part of speech. The prop parameter can be noun, adjective, plural, verb, article, or preposition. You can delete words from any object, including objects defined statically in your game, as well as objects created dynamically at run-time. Furthermore, you can delete words that were added dynamically, as well as words that were statically defined in your game.

For examples of using this function, see the section on Dynamic Vocabulary.


endCommand

Call: endCommand(actor, verb, dobj_list, prep, iobj, status)

Added: TADS 2.5.0

This function lets you write code that the parser calls at the end of a turn, just after running all of the fuses and daemons for the turn.

The parser invokes the endCommand after all of the fuses and daemons have finished running at the end of a turn. The function is called once per command, not per object. In a command with multiple direct objects, this function is called only once, just as fuses and daemons are called only once for the entire command.

The “status” parameter has the same meaning as the status code parameter to postAction. The other parameters have the same values as they did in the call to preCommand that the parser makes at the start of the execution phase for the command.

endCommand is always invoked at the end of a turn. If an abort statement is executed in the course of a turn, the parser skips directly to endCommand, because abort skips the daemons and fuses. This means that endCommand is executed at the end of a turn even when fuses and daemons are skipped.

The endCommand function returns no value.


execCommand

Call: execCommand(actor, verb, dobj, prep, iobj, flags)

Added: TADS 2.4.0

This function gives a game program direct access to the parser’s command execution system. The function doesn’t provide direct access to the string-parsing portion of the parser, but to the command execution portion, which takes the objects involved in the command and executes the command, performing object validation (validDo, validIo), room notification (roomAction), actor notification (actorAction), direct and indirect object checks (dobjCheck and iobjCheck), general object handling (dobjGen and iobjGen), object validation (verIoVerb and verDoVerb), and object action processing (ioVerb, doVerb, or verb.action, as appropriate).

For full documentation on execCommand consult Chapter Five.


exitobj

Call: exitobj()

Added: TADS 2.4.0

This function provides a new method for skipping the remaining processing for a command at any point. It’s very similar to the “exit” statement, but differs in one respect: whereas the “exit” statement terminates all further processing for a command and skips directly to the fuses and daemons, “exitobj” skips the remaining processing only for the current object in a command and proceeds to the next object.

This difference is significant when the player types in a command involving multiple objects. For example, suppose that you define a roomAction method in the current room as follows:

      roomAction(actor, verb, dobj, prep, iobj) =
      {
        /*
         *  when the player touches anything not already in their inventory,
         *  make it vanish
         */
        if (dobj != nil && !dobj.isIn(actor))
        {
          "\^<<dobj.thedesc>> disappears in a flash of light!\n";
          dobj.moveInto(nil);
          exit;
        }
      }

Now consider the following transcript:

>take ball
  The ball disappears in a flash of light!
>take hammer and chisel
  The hammer disappears in a flash of light!

The first response makes sense, but the second isn’t exactly what you wanted. The problem is that the “exit” statement tells the parser to skip processing of any objects other than the current one.

To change this, you can simply change the “exit” statement in the code listing above to “exitobj”. The result will be more sensible:

>take hammer and chisel
  The hammer disappears in a flash of light!
  The chisel disappears in a flash of light!

“exitobj” is useful when you want to skip the remaining processing for a command for the current object, but you still want the command to be considered successful. “exit” is more suited for situations where the outcome of the command is something less than total success, and you want to skip further processing of other objects involved in the command. “exitobj” is particularly useful with the execCommand() built-in function (see above), because it allows you to completely redirect the processing of a command, skipping all or part of the normal processing for the original command without telling the parser that the original command was unsuccessful.

You an use exitobj anywhere you can use exit.


fclose

Call: fclose(filehandle)

Closes the file indicated by filehandle. Once the file is closed, no further operations on filehandle are valid. For more information see the section on file operations.


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)