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)

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

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

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

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

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

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

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

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


firstsc

Call: firstsc(obj)

Returns the first superclass of the given object. Returns nil if the object has no superclass (which will only be the case if the object was defined as being of type object).

This function is provided primarily to facilitate handling equivalent objects. In conjunction with the isEquivalent property, this function lets you determine if two objects are indistinguishable from one another. If two objects have the same immediate superclass, and they both have the isEquivalent property set to true, the two objects are equivalent.


fopen

Call: fopen(filename, mode)

Added: Text mode compatibility added with TADS 2.2.4

Opens the file, either text or binary, whose name is given by the single-quoted string value filename. Files written in text mode can be used by other applications (text editors, Web browsers, etc.) as ordinary text files, but binary mode files should only be used by TADS. The file is opened in a manner according to the mode argument, which is a single-quoted string value:

‘b’ - open as a binary file. If you don’t specify the file mode, binary is assumed as the default mode, for compatibility with past versions.

‘r’ - open file for reading; file must already exist.

‘r+’ - open file for reading and writing; the file is created if it doesn’t already exist. Not currently allowed in text mode.

‘t’ - open as a text file. You can use the text mode in conjunction with ‘r’ (read) and ‘w’ (write) modes - ‘t’ is not currently allowed with ‘r+’ or ‘w+’ modes. Note that text file functionality was added with version 2.2.4 of TADS.

‘w’ - create a new file for writing; the file is deleted if it already exists.

‘w+’ - create a new file for reading and writing; the file is deleted if it already exists. Not currently allowed in text mode.

The function returns a file handle that is used in subsequent file operations (fwrite(), fread(), fclose(), and the like) to refer to the open file.

If the operation fails, fopen() returns nil. This function can fail for a number of reasons; for example, if you attempt to open a file that doesn’t exist with mode ‘r’, the operation will fail because this mode can only be used to open an existing file.

For more information on file operations, consult the section at the end of this chapter.


fread

Call: fread(filehandle)

Added: Text mode compatibility added with TADS 2.2.4

Reads the next data item from the text or binary file and returns its value. The value will be of the same type as that originally written at the current position in the file with fwrite(). If an error occurs, this function returns nil; this usually indicates that you are attempting to read past the end of the file.

When operating in text mode (see fopen() ), fread() always reads a line of text from the file. If the end of the file is not reached, the line returned will end with a ‘\n’ sequence (as with fwrite(), fread() translates newlines according to local conventions, and always returns the TADS ‘\n’ sequence to represent a newline in the file). If fread() encounters the end of the file in the middle of a line, it will return the text up to the end of the file, with no trailing newline. The subsequent call will return nil to indicate that the end of the file has been reached.


fseek

Call: fseek(filehandle, byteposition)

Seeks to a byte position in the file. The byteposition value should be a value previously returned by ftell(), since other values may not correspond to a value in the file.


fseekeof

Call: fseekeof(filehandle)

Positions the file at its end.


ftell

Call: ftell(filehandle)

Returns the current seek position in the file, given as a number of bytes from the beginning of the file.


fwrite

Call: fwrite(filehandle, value)

Added: Text file compatibility added with TADS 2.2.4

Writes the given value to the text or binary file. The value argument must be a number, a single-quoted string, or true. Returns nil on success, true on failure; a true return value usually indicates that the disk is full.

When a file is opened in text mode, (see fopen()), fwrite() can only be used with string values. Strings passed to fwrite() can contain the escape characters ‘\t’, ‘\n’, and ‘\\’; other escapes are not allowed. ‘\t’ is translated to a tab, ‘\n’ is translated to a newline (using the appropriate local conventions for the current system), and ‘\\’ is translated to a single backslash. fwrite() does not add any newlines to the text you provide, so you must explicitly include any newlines you want to write to the file.

Because TADS obeys local newline conventions, fwrite() always produces the correct sequence of characters for the current machine when you include ‘\n’ in a string, so you don’t have to worry about how newlines are handled on each platform.

See the section on file handling at the end of this chapter for more information about security modes when writing files in TADS.


getarg

Call: getarg(argnumber)

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


getwords

Call: getwords(obj, &prop)

This function returns a list of single-quoted strings giving the vocabulary words defined for the given part of speech for the specified object. The prop parameter can be noun, adjective, plural, verb, article, or preposition.

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


getfuse

Call: getfuse(funcptr, parm)

This function lets you determine if the indicated fuse is still active, and if so, how many turns are left until it is activated. If the fuse is not active (either it has already fired, or it has been removed with a call to remfuse), this function returns nil. Otherwise, it returns the number of turns before the fuse is activated.

Alternative Call: getfuse(obj, &msg)

This form of getfuse lets you check on a fuse set with the notify function. If the fuse has already been fired, or has been removed with unnotify, this function returns nil. Otherwise, it returns the number of turns before the fuse is activated.


gettime

Call: gettime(optional argument)

Added: Additional information support added with TADS 2.3.0

Returns the current system clock time. The time is returned as a list of numeric values for easy processing.

year calendar year (e.g., 1992).
month month number (January = 1, February = 2, etc.)
day number of the day within the current month
weekday day of the week (1 = Sunday, 2 = Monday, etc.)
yearday day of the year (1 = Jan 1)
hour hour of the day on 24-hour clock (midnight = 0, noon = 12, 3 PM = 15, etc.)
minute minute within the hour (0 to 59)
second second within the minute (0 to 59)
elapsed the number of seconds since January 1, 1970, 00:00:00 GMT. This last value is useful for computing the difference between two points in time.

In addition, TADS 2.3 added the ability to return additional system real-time clock information. The function optionally takes an argument specifying what type of information to return. Constants for the argument values are defined in adv.t:

GETTIME_DATE_AND_TIME - this returns the traditional date and time information that gettime() returned in the past. This is the same information that the function returns if called with no arguments (thus ensuring that existing code that calls gettime() will continue to work unchanged).

GETTIME_TICKS - this returns the number of milliseconds since an arbitrary zero point, which is usually some system event, such as starting the current session of the TADS interpreter, or turning on the computer. The actual zero point is arbitrary, but it will remain fixed for a particular session, so you can use this form of gettime() to compute relative times between events over a short period of time. For example, if you’re reading events with the new inputevent() function, you can use this time value to set a limit on how long you read events. For example:

local max_time, cur_time, evt;

   /* process events for no more than 5 seconds (5000 milliseconds) */
   max_time := gettime(GETTIME_TICKS) + 5000;
   for (;;)
   {
      /* check to see if we've reached our time limit */
      cur_time := gettime(GETTIME_TICKS);
      if (cur_time >= max_time)
          break;

      /* get events, but time out if we exceed our time limit */
      evt := inputevent(max_time - cur_time);

      /* process the event */
      switch(evt[1])
         // and so on
   }


incturn

Call: incturn()

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

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

Alternative Call: incturn(num)

This form allows you to run a series of turns all at once. You can specify a numeric argument to incturn(); the argument gives the number of turns that should pass. An argument of 1 is equivalent to calling incturn() with no arguments.

When an argument higher than 1 is specified, the function runs all of the fuses that are set to turn down within the number of turns specified, but not after that number of turns. Note that the normal incturn() doesn’t actually execute any fuses, but simply burns all fuses down by one turn.

For example, if you call incturn(2), the system will first run any fuses that are set to burn down after 1 turn, then will shorten all remaining fuses by one turn. Similarly, incturn(3) first runs any fuses that are set to burn down after 1 turn, then runs any fuses set to burn down after 2 turns, then shortens any remaining fuses by 2 turns.

Fuses set with setfuse() and notify() are both affected by this routine. Note that this function has no effect on daemons.


input

Call: input()

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

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

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


inputdialog

Call: inputdialog(icon, prompt, response_list, default_idx, cancel_idx)

Added: TADS 2.5.0

This function lets you ask the player a multiple-choice question. On graphical systems, inputdialog() displays a system dialogue box, and lets the user respond by clicking a button. On text systems, this function displays a textual prompt and lets the user respond with the keyboard.

The “icon” parameter indicates which icon, if any, to display. The icon will only be displayed on graphical systems; text-only systems ignore this parameter. These icon constants are defined in adv.t:

INDLG_ICON_NONE   Do not use any icon.
INDLG_ICON_WARNING   Show a “warning” icon. This indicates a potential or minor problem. (On Windows, this displays an exclamation-point icon.)
INDLG_ICON_INFO   Show an “information” icon. This indicates to the user that the dialogue is being displayed to inform them of the status of an operation. (On Windows, this displays an icon with a small letter “i” in a circle.)
INDLG_ICON_QUESTION   Show a “question” icon. This indicates that additional information from the user is required. (On Windows, this displays a question-mark icon.)
INDLG_ICON_ERROR   Show an “error” icon. This indicates that a problem has occurred. (On Windows, this displays a stop-sign icon.)

The “prompt” parameter is the message string to display. For graphical systems, this message is displayed in a dialogue box; for text systems, it’s simply displayed on the terminal.

The “response_list” is a list of strings giving the valid responses. Each entry in the list is a string giving one possible response. On graphical systems, one button is displayed in the dialogue for each response string; the response string is the button’s label. On text systems, the responses are displayed to the player after the prompt string.

Each string in the response list can optionally include an ampersand character (“&”) before the character that serves as a keyboard short-cut for the response. The ampersand is not displayed in the button label or response list displayed to the player. For example, the response list string ‘&Yes’ makes the “Y” key a short-cut for the button, which is labeled “Yes” in the dialogue. On some systems the short-cut key will be indicated visually in the dialogue; on Windows, for example, the “Y” in the “Yes” button would be underlined to indicate that the letter “Y” is the short-cut for the button. If no ampersand appears in a response list item, the item has no short-cut.

On text-only systems, the keyboard short-cut will be indicated visually by enclosing the short-cut letter in parentheses when displaying the list of possible responses to the player. If a response item has no short-cut key, the player must enter a sufficiently long leading substring of the response item so that the response is unambiguous with the other valid responses.

Each element of the list can be a number, rather than a string. If an element is a number, it specifies that the button should use a pre-defined standard label. You should use standard labels when possible, because these labels will follow local system conventions and will be localized to the player’s language settings; these labels are read from external resources on platforms with appropriate operating system support, so they can be localized easily. To select a standard label, use one of the following values, defined in adv.t:

INDLG_LBL_OK   “OK”, or local system or language equivalent
INDLG_LBL_CANCEL   “Cancel”
INDLG_LBL_YES   “Yes”
INDLG_LBL_NO   “No”

The strings shown above do not necessarily reflect the actual button text that the player will see, because the actual label will vary by platform and by language. Whatever label is displayed, though, will convey to the user the same meaning.

You can also select an entire standard set of buttons, rather than specifying each button individually. If the response_list parameter is a number, rather than a list, it indicates that a standard set of buttons is to be used, selected from a pre-defined list. The advantage of using one of these pre-defined button sets when possible is that the buttons will automatically follow local system conventions and be localized to the player’s language settings, on platforms with appropriate operating system support. To select a pre-defined button set, use one of the following values, defined in adv.t, for the response_list parameter:

INDLG_OK   The dialogue will display an “OK” button, properly localized.
INDLG_OKCANCEL   The dialogue will display an “OK” button and a “Cancel” button, properly localized.
INDLG_YESNO   The dialogue will display an “Yes” button and a “No” button, properly localized.
INDLG_YESNOCANCEL   The dialogue will display a “Yes” button, a “No” button, and a “Cancel” button, properly localized.

The “default_idx” parameter gives the index in the response list of the default response. If the user presses the “Return” key, or performs any other action appropriate to the system user interface that by local convention accepts the default response to a dialogue, this response will be used. The first list entry is at index 1. Pass nil in this parameter if there is no default response, in which case TADS will require the user to select a specific button. (Note that, on some systems, passing nil for this parameter will not make a noticeable difference; on Windows, for example, one of the buttons will always have keyboard focus, so pressing the Return key will always select one of the buttons.)

The “cancel_idx” parameter gives the index in the response list of the cancellation response. Most GUI systems have a standard way of cancelling a dialogue; the Escape key has this effect on Windows, for example, as does the Command-Period key combination on the Macintosh. If the user performs the appropriate system-specific action to cancel the dialogue, this response is used. The first list entry is at index 1. Pass nil in this parameter if there is no cancel response, in which case TADS will not allow the player to cancel the dialogue.

The dialogue returns the index of the response that the player selects: 1 for the first response in the response list, 2 for the second entry in the list, and so on. For the standard response lists (INDLG_YESNO and so on), the response are in the order described for the constant name: INDLG_YESNO has a “Yes” button at index 1 and a “No” button at index 2, for example.

Here’s an example of using this function.

    ret := inputdialog(INDLG_ICON_WARNING, 'What would you like to do next?',
                       ['&Restore', 'Re&start', '&Quit'],
                       nil, 3);
    switch(ret)
    {
    case 1:
      /* restore a game... */
      break;

    case 2:
      /* restart the game */
      restart();
      break;

    case 3:
      /* quit */
      quit();
      break;
    }

On a graphical system, this would display a dialogue with the message text “What would you like to do next?”, and three buttons: one with the label “Restore”, one with the label “Restart”, and one with the label “Quit”. If the user presses the “R” key, the “Restore” button would be selected; if the user presses “S”, the “Restart” button would be selected; if the user presses “Q”, or cancels the dialogue (by pressing the Escape key on a Windows machine, for example), the “Quit” button would be selected.

On a text-only system, TADS would display this text on the terminal, on a new line (TADS would output a “\n” sequence to start a new line):

    What would you like to do next? (R)estore/Re(s)tart/(Q)uit >

TADS would then wait for the player to enter a line of text (as with the input() built-in function). If the player enters one letter, TADS would check the letter against each response’s short-cut, and return the one that matches. If the player enters more than one letter, TADS would check the string against the leading substring of each possible response; if the string matches one of the responses unambiguously, TADS would return that response. If the player enters something invalid or ambiguous, TADS would redisplay the prompt and await another response.

inputdialog() has certain limits. The prompt string can be no longer than 256 characters. There can be no more than ten responses, and the total length of the text in all of the responses must not exceed 256 characters. In addition, to ensure portability, you should choose a reasonably short label for each button; some systems use buttons of a fixed size, so a long label name might not fit in the available space on some systems. Whenever possible, use a single word for each button label.


inputevent

Call: inputevent(timeout)

Alternative call: inputevent()

Added: TADS 2.3.0

This function multiple types of events and can also apply a timeout to limit the how long it waits for an event to occur.

The inputevent() function takes zero or one arguments. With no arguments, inputevent() simply waits until an event occurs. With one argument, which must be a number, inputevent() waits until an event occurs, or until the number of milliseconds specified by the argument has elapsed without an event occurring, in which case the function “times out” and returns without any event having occurred.

Note that the timeout value, if given, may not always be obeyed to the exact millisecond. Different types of computers have different system clock resolutions, and additionally multi-user and multi-tasking systems often have unpredictable latencies for event processing. As a result, if you specify a timeout value, the actual time that elapses before the function times out and returns may be slightly longer than the specified timeout value. Any additional latency should be no more than a few hundred milliseconds in most cases, so this shouldn’t be noticeable for most purposes.

The function returns a list value describing the event that occurred. The first element of the list is a number that specifies the type of the event. The rest of the list varies according to the event type. Constants for the event codes are defined in adv.t. The possible event codes are:

INPUT_EVENT_KEY The user pressed a key. The second element of the list returned by inputevent() in this case is a string containing the key that the user pressed. The string is the same that would be returned by inputkey() for the same keystroke.
INPUT_EVENT_HREF The user clicked on an link. This event is only returned by a multimedia TADS interpreter, never by a character-mode TADS interpreter. The second element of the return list is a string containing the text of the HREF that the user clicked.
INPUT_EVENT_TIMEOUT No event occurred before the specified timeout elapsed. The return list contains no additional elements.
INPUT_EVENT_EOF This indicates that the TADS interpreter is terminating or an error occurred reading an event.
INPUT_EVENT_NOTIMEOUT This is not actually an event, but an error indicating that the current system does not support the timeout feature of inputevent(). If this occurs, you can still use inputevent(), but you cannot specify a timeout. The DOS TADS interpreters (TR, TRX, TR32) all support timeouts, as does HTML TADS for Windows. Interpreters on most systems should be able to support this feature, but a few systems may not be able to.


inputkey

Call: inputkey()

Added: Extended keys added with TADS 2.3.0, others added 2.5.0

Reads a single keystroke from the keyboard, and returns a string consisting of the character read. inputkey() takes no arguments. When called, the function first flushes any pending output text, then pauses the game until the player hits a key. Once a key is hit, a string containing the character is returned. inputkey() takes its input directly from the keyboard. It does not look at the command line

In addition, the function returns a portable representation for certain extended keys. Before TADS 2.3, keys outside of the normal ASCII character set were almost impossible to use with inputkey(), because TADS returned a useless “raw” format for extended keys, such as cursor navigation keys and function keys. TADS now uses a portable string format to represent many common keys.

Each extended key is represented as a string containing a key name enclosed in square brackets. The key name are:

[up] Up arrow
[down] Down arrow
[right] Right arrow
[left] Left arrow
[end] End of line
[home] Home
[del-eol] Delete to end of line
[del-line] Delete line
[del] Del (delete character)
[page up] Page up
[page down] Page down
[top] Top of file
[bottom] Bottom of file
[fN] Function key N (N is replaced by a number, 1 through 10)
[word-left] Word left
[word-right] Word right
[del-word] Delete word
[bksp] Backspace
[esc] Escape
[ctrl-X] Control-X (X is replaced by a lower-case letter: Control-C is [ctrl-c])
[alt-X] Alt-X or Meta-X (X is replaced by a lower-case letter: Alt-F is [alt-f])

In addition, “control” keys (i.e., keys entered by holding down the “control” or “ctrl” key on the keyboard and pressing an alphabetic key) are returned as “[ctrl-X]”, where “X” is the lower-case letter key; and “alt” keys are returned as “[alt-X]”. Finally, the “Return” and “Enter” keys are returned as “\n”, and the tab key is returned as “\t”.

Even though these key names are portable, be aware that not every computer has all of these keys, so you can’t count on the player actually being able to enter them. The only keys that you can always count on being present are the regular ASCII keys, Enter/Return, Tab, and Backspace. So, if you’re using these extended keys, you should always be sure to provide an alternative for each extended key using an ordinary key. For example, if you want to implement a menu system that uses the up and down arrow keys to navigate through a set of choices, you could use “N” (for “next”) and “P” (for “previous”) as synonyms for [down] and [up], respectively.

The arrow keys ([up], [down], [left], and [right]) are probably the most portable of the extended keys, since most computers and terminals have some sort of arrow keys. The function keys ([f1] through [f10]) are also available on many systems, although some systems use some or all of the function keys for special purposes; for example, Windows uses the F10 key to begin navigating the menu bar, so your game will never receive the [f10] extended key when running on Windows. The ALT and CONTROL keys are also very non-portable.

Here’s an example of using the arrow keys.

numberVerb: deepverb
  verb = 'number'
  action(actor) =
  {
      local num;
      local done;
      local changed;

      "Press the Up or Down arrow keys, or the + or - keys,
      to change the number.  Press Enter when finished.\b";

      num := 5;
      changed := true;
      for (done = nil ; !done ; )
      {
          if (changed)
          {
              "\nCurrent value = <<num>>";
              changed := nil;
          }

          switch(inputkey())
          {
            case '\n':
              done := true;
              break;

            case '+':
            case '[up]':
              ++num;
              changed := true;
              break;

            case '-':
            case '[down]':
              --num;
              changed := true;
              break;
          }
      }

    "\bThe final value was <<num>>. ";
  }
  ;


intersect

Call: intersect( list1, list2 )

Returns the intersection of two lists - that is, the list of items in both of the two lists provided as arguments. For example:

  intersect( [ 1 2 3 4 5 6 ], [ 2 4 6 8 10 ] )

yields the list [ 2 4 6 ]

Note that the behavior for lists with repeated items is not fully defined with respect to the number of each repeated item that will appear in the result list. In the current implementation, the number of repeated items that is present in the shorter of the two source lists will be the number that appears in the result list; however, this behavior may change in the future, so you should try not to depend on it.


isclass

Call: isclass(object, class)

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

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


length

Call: length(item)

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


logging

Call: logging(value)

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

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

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

This function has no return value.


lower

Call: lower(string)

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


morePrompt

Call: morePrompt()

Added: TADS 2.2.4

This function allows your game to explicitly display the system MORE prompt. You can use this for such special effects as a dramatic pause prior to a chapter change. The function takes no arguments and returns no value.


nextobj

Call: nextobj(object)

Alternative Call: nextobj(object, class)

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

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

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


nocaps

Call: nocaps()

This function is the opposite of caps() - it specifies that the next character displayed will be converted to lower case. Note that you can use the escape sequence “\v” to achieve the same effect.

Note that calls to caps() and nocaps() override one another; if you call caps(), and then immediately call nocaps(), the next character displayed will be lower-case - the call to caps() is forgotten after the call to nocaps().


notify

Call: notify(object, &message, turns)

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

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

There is a limit of 200 simultaneously pending notifications.

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

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

An example of the notify() function is shown below. In the example, we define two objects: a bomb, and a button on the bomb. When the button is pushed, it calls notify() to specify that the explode method in the object bomb is to be called in three turns. Notice that we call the bomb’s ‘explode’ method with an ampersand. That means we’re simply taking its address - we aren’t evaluating it at this point. It will actually be evaluated when the notifier is called, in three turns.

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

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


objwords

Call: objwords( num )

Provides a list of the actual words the user typed to refer to an object used in the current command. The argument num specifies which object you’re interested in: 1 for the direct object, or 2 for the indirect object. The return value is a list of strings; the strings are the words used in the command (converted to lower case, stripped of any spaces or punctuation). If a special word such as “it,” “them,” or “all” was used to specify the object, the list will have a single element, which is the special word used.

For example, if the player types “take all,” then objwords(1) will return [ 'all' ] and [ objwords2 ] will return []. Note that objwords(1) will return [ 'all' ] even if the player typed a variation on “all,” such as “take everything” or “take all but the book.”

If the player types “put all in red box,” the objwords(1) returns [ 'all' ] and objwords(2) returns [ 'red' 'box' ].

If the player uses multiple direct objects, the function will return the current object’s words only. For example, if the player types “put blue folder and green book in red box,” objwords( 1 ) will return [ 'blue' 'folder' ] while the first direct object is being processed, and [ 'green' 'book' ] while the second object is being processed.

This function could potentially be useful in such cases as “ask actor about object,” because it allows you to determine much more precisely what the player is asking about than would otherwise be possible.

You can call objwords() during a verb verification or action method (verDoVerb, verIoVerb, doVerb, ioVerb), and during a doDefault method. Note that the return value is slightly different during a doDefault method: if the word“all” is used in the command, the function will return the list [ 'A' ] rather than [ 'all' ], due to the internal order of processing of the word list.

objwords( 1 ) words with doDefault

You can also call objwords( 1 ) from within the doDefault method, as described above. Until version 2.2, you could call objwords( 1 ) only from verb verification and action methods.


outcapture

Call: outcapture( stat )

TADS lets you capture the text displays that are generated by double-quoted strings and by the say() built-in function. This feature allows you to examine the values of methods such as ldesc and sdesc. Since these methods display text, there is no direct way of manipulating their text as strings. The output capture feature makes it possible for you to examine and manipulate any text that ordinarily would simply be displayed.

To use this feature, you first tell TADS that you wish to begin capturing output by calling the built-in outcapture function:

  stat := outcapture( true );

This begins capturing output. The return value is a status code that you use in a subsequent call to turn off output capturing. This status code allows output capturing calls to be “nested,” since the status code allows the capturing status that was in effect on a call to begin capturing to be restored on the subsequent call to end capturing. This status code is for use by TADS only - the only thing you do with it is use it in the subsequent call to end capturing.

While output is being captured, any text that would normally be displayed is instead saved by the system. The user does not see any text displayed while capturing is in effect. After you begin capturing, simply call any methods whose displays you want to examine. For example, you could call redbook.sdesc if you want to examine the short description of the object redbook.

After you’ve finished calling the methods whose displays you want to examine, end capturing by calling outcapture() again, passing as the argument the status code returned by the first call:

  str := outcapture( stat );

This second call tells TADS to stop capturing output. The return value of this function is a (single-quoted) string containing all of the text that was displayed since the corresponding call to outcapture(true).

When text is being captured, TADS expands all format strings (strings such as “%you%”), and processes “\^” and “\v” sequences (which convert the next character to upper- and lower-case, respectively). However, all other escape sequences (such as “\n” and “\t”) are left in the string intact.

You can display the string returned from the call to outcapture(stat) using the say() built-in function. This should result in the same text display that would have occurred if you hadn’t turned on capturing.

You can nest calls to outcapture(). As explained above, the status code returned from the first call is used to restore the capturing status of the previous call. The string returned by a call to turn off capturing includes only text that was generated after the corresponding call to turn on capturing. Because calls to outcapture() can be nested, you don’t have to worry when using text capturing about whether any methods you’re calling also use the function.

Note that the system automatically turns off output capturing whenever it prompts the user for a command, for a missing direct or indirect object, or for disambiguation information. When the system turns off capturing, it clears the captured text, so any subsequent calls to turn off capturing return empty strings. You therefore can only capture text within a single command line.


outhide

Call: outhide( flag )

Turns hidden output on or off, simulating the way the parser disambiguates objects. The parameter flag is either true or nil. When you call outhide(true), the system starts hiding output. Subsequent output is suppressed - it is not seen by the player. When you call outhide(nil), the system stops hiding output - subsequent output is once again displayed. outhide(nil) also returns a value indicating whether any (suppressed) output was generated since the call to outhide(true), which allows you to determine whether any output would have resulted from the calls made between outhide(true) and outhide(nil).

This is the same mechanism used by the parser during disambiguation, so it should not be called by a verDoVerb or verIoVerb method. This function is provided to allow you to make calls to verDoVerb and verIoVerb to determine if they will allow a particular verb with an object, just as the parser does.

There is no way to recover the text generated while output is being hidden. The only information available is whether any text was generated.

Alternative Call: outhide(flag) - nested calls

When you call outhide(true), the function returns a status indicator, which is a value that can be used in a subsequent call to outhide() to restore output hiding to the state it was in before the outhide(true). This allows you to nest text hiding - you can hide output in a subroutine or method call, without interfering with the routine that called you or any routines you call.

To use the nested form, save the return value of outhide(true), and then use the saved value as the parameter - in place of nil - to the subsequent call to outhide(). The value returned by the second outhide() indicates whether any text output occurred between the nested calls. For example:

  local old_stat1, old_stat2;
  local new_stat1, new_stat2;
  old_stat1 := outhide(true);
  "This is some hidden test.";
  old_stat2 := outhide(true);
  // don't write any text here
  new_stat2 := outhide(old_stat2);
  new_stat1 := outhide(old_stat1);

Because outhide(old_stat2) indicates whether any output occurred during the nested outhide(true), new_stat2 is nil. However, new_stat1 is true, since output occurred after the first outhide(true). Consider another sequence:

  old_stat1 := outhide(true);
  // write no text here
  old_stat2 := outhide(true);
  "This is some hidden text.";
  new_stat2 := outhide(old_stat2);
  new_stat1 := outhide(old_stat1);

In this case, both new_stat1 and new_stat2 will be true, because hidden output occurred within both nested sections (even though only a single message was displayed, it was within both nested sections).

The general form of a nested outhide() section is:

{
   local old_stat;
   local new_stat;

   old_stat := outhide(true);
   // do whatever you want here
   // output will be hidden
   new_stat := outhide(old_stat);
}

The new_stat will indicate whether any output occurred between the outhide(true) and the outhide(old_stat). In addition, output hiding will be restored to the same state as it was prior to the outhide(true).


parseAskobjIndirect

Call: parseAskobjIndirect(actor, verb, prep, noun_info)

Added: TADS 2.5.1

This function provides more control over the message that the parser uses to ask the player to supply an indirect object when none is provided in the command but the verb requires one. It supplements the existing parseAskobj() and parseAskobjActor() functions, and is fully documented in Chapter Six.


parseNounList

Call: parseNounList(wordlist, typelist, startingIndex, complainOnNoMatch, multi, checkActor)

Added: TADS 2.4.0

This function allows the game to invoke the parser's internal function to parse a noun list. This can be used in conjunction with the parseUnknownVerb to allow the function to interpret part of the word list as a noun list. The function is documented in Chapter Five.


parseNounPhrase

Call: parseNounPhrase(wordlist, typelist, current_index, complain_on_no_match, is_actor_check)

Added: TADS 2.4.0

This parser hook lets the game program parse noun phrases and perform the initial resolution to possible matching objects. For more information on this function consult the parseNounPhrase section in Chapter Six.


parserDictLookup

Call: parserDictLookup(tokenList, typeList)

Added: TADS 2.4.0

This function lets you perform a parser dictionary look-up to obtain the list of objects that define a set of vocabulary words. This function can be used to perform your own noun-phrase parsing, and is documented fully in Chapter Five.


parserGetMe

Call: parserGetMe( )

Added: TADS 2.2.4

The parser provides a way to change the player character dynamically during game play. The parser in earlier versions of TADS used the object named “Me” to represent the player character; there was no way to change this. This made it difficult to write a game with different personas for the player character.

TADS now lets you specify other player characters, and you can use the built-in function parserGetMe() to get the parser’s current player character object. This function takes no arguments.

Note that adv.t and std.t no longer refer to the “Me” object directly in code related to the player character’s status (such as inventory or room location descriptions). Instead, adv.t and std.t use the new parserGetMe() function to get the player character object.

If you use parserSetMe() (see below) in your game, you should be careful not to refer to the “Me” object directly in contexts where you really want the current player character object. Use parserGetMe() instead to get the correct object from the parser. Note that existing games should not be affected by this change; if you don’t call parserSetMe(), then parserGetMe() will always return the “Me” object, so you can safely use a fixed “Me” object.


parserGetObj

Call: parserGetObj(argument)

Added: TADS 2.4.0

This function lets the game program learn the objects involved in the current command. The function takes a single argument, which indicates which object you want, using the following constants (defined in adv.t):

PO_ACTOR - the current command’s actor
PO_VERB - the deepverb object for the command’s verb
PO_DOBJ - the direct object
PO_PREP - the preposition object introducing the indirect object
PO_IOBJ - the indirect object
PO_IT - the current antecedent for "it"
PO_HIM - the current antecedent for "him"
PO_HER - the current antecedent for "her"
PO_THEM - the current antecedent for "them" (returned as a list)

The return value is an object, or nil if there is no such object for the current command. For example, a command with no indirect object will return nil for PO_PREP and PO_IOBJ.

The return value for PO_THEM is a list of objects, rather than a single object as the other forms return. This is because "them" can naturally refer to more than one object. The result for PO_THEM is always a list, even when there's only one object for "them", and even when there are no objects at all - in this last case the result is simply an empty list.

Other aspects of this function are documented in detail in Chapter 5.


parserGetTokTypes

Call: parserGetTokTypes(tokenList)

Added: TADS 2.4.0

This function lets you obtain a list of token types given a list of tokens. You can use this function to get the types of the tokens in a list returned by parserTokenize(), for example. It’s documented fully in Chapter Five.


parserSetMe

Call: parserSetMe(newMe)

Added: TADS 2.2.4

As noted above, you can switch the player character when you desire. The parser still uses “Me” as the initial player character, but you can change to a different object at any time using the new built-in function parserSetMe(newMe). The argument to parserSetMe() is the new object to use to represent the player character.


parserResolveObjects

Call: parserResolveObjects(actor, verb, prep, otherobj, usageType, verprop, tokenList, objList, silent)

Added: TADS 2.4.0

This function lets you access the parser’s object resolution and disambiguation subsystem programmatically. You can use the object resolver in conjunction with parseNounList() or with your own noun-list parser to implement your own command parsing system. It’s documented in Chapter Five.


parserTokenize

Call: parserTokenize(commandString)

Added: TADS 2.4.0

This function lets the game invoke the parser’s tokenizer to obtain a token list for a string of text. The function is documented extensively in Chapter Five.


parseUnknownVerb

Call: parseUnknownVerb(actor, wordlist, typelist, errnum)

Added: TADS 2.4.0

This parser hook lets the game take control whenever the parser encounters an unknown verb, or unknown sentence syntax. ‘actor’ is the current actor object. The ‘wordlist’ parameter is a list with the strings of the words in the command, in the same format as the list that is passed to preparseCmd. The ‘errnum’ parameter is the parser error number for the condition that caused the call to parseUnknownVerb; this is the same error number that is passed to parseError (and related functions).

The ‘typelist’ argument is a list of the types of the words in the ‘wordlist’ parameter. Each element of ‘typelist’ gives the word type of the corresponding element of ‘wordlist’ (so typelist[3] gives the type of the word in wordlist[3], for example). Each type is a number, which can contain any number of the values below combined with the bitwise OR operator (“|”). To test for a particular type, use an expression like this: ((typelist[3] & PRSTYP_NOUN) != 0).

For more information consult the parseUnknownVerb section in Chapter Six.


postAction

Call: postAction(actor, verb, dobj, prep, iobj, status)

Added: TADS 2.5.0

This function lets you write code that the parser calls immediately after the “action” method, and before any fuses or daemons.

The parser calls postAction once for each object in a command with multiple direct objects, just after it calls the “action” method for the object. If the command is terminated early with exit, exitobj, or abort, the parser invoked postAction immediately after the exit, exitobj, or abort statement executes.

The first five parameters specify the current command; any of the objects except “actor” and “verb” can be nil. The “status” parameter has the same meaning as the return codes from the execCommand built-in function; it can be one of the following values, defined in adv.t:

EC_SUCCESS   The command executed successfully, which indicates that everything up through and including the command’s “action” method (verb.action, dobj.doAction, or iobj.ioAction, as appropriate).
EC_EXIT   An exit statement was executed.
EC_EXITOBJ   An exitobj statement was executed.
EC_ABORT   An exit statement was executed.

The postAction function returns no value.


preCommand

Call: preCommand(actor, verb, dobj_list, prep, iobj)

Added: TADS 2.5.0

At the start of the execution phase, before calling verbAction() for the first direct object in the command, the parser invokes this function.

The “actor”, “verb”, “prep”, and “iobj” parameters are objects giving the actor, verb, preposition, and indirect object in the command, respectively. The “dobj_list” parameter is a list of the direct objects in the command, in the same order as they appear in the command.

This function can use exit to skip to fuses and daemons, or it can use abort to cancel the command entirely, in which case the parser will skip directly to the endCommand function (see below). If this function simply returns, the parser continues processing the command as normal.


proptype

Call: proptype(object, propPointer)

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

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

1 - Number
2 - Object
3 - String
5 - nil
6 - Code (executable statements enclosed in braces)
7 - List
8 - true
9 - Double-quoted string
10 - Function Pointer
13 - Property Pointer

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

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

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


quit

Call: quit()

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


rand

Call: rand(upper-limit)

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


randomize

Call: randomize()

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


reGetGroup

Call: reGetGroup()

Added: TADS 2.3.0

Note: this function is dependent upon the reSearch() function. Please read the documentation on that first.

reGetGroup(), lets you retrieve the matching text for parenthesized groups within regular expressions. reGetGroup() returns information about the last call to reSearch(). The function takes one argument, which is a number giving the group number to retrieve: the first parenthesized expression is group 1, the second is group 2, and so on. (When groups are nested, the position of the open parenthesis is used to determine the group numbering. The leftmost open parenthesis is numbered as group 1.)

reGetGroup() returns nil if there is no such group in the most recent regular expression or if the last call to reSearch() did not match the expression. Otherwise, reGetGroup() returns a list with three elements identifying the group. The first element is a number giving the character position within the original search string of the start of the text that matched the group. The second element is the length of the text that matched the group. The third element is the actual text that matched the group.

Here’s a code example:

 ret := reSearch('d(.*)h', 'abcdefghi');
   if (ret != nil)
   {
  grp := reGetGroup(1);
  if (grp != nil)
    "Start = <<grp[1]>>, len = <<grp[2]>>, text = \"<<grp[3]>>\". ";
   }

This will display the following:

 Start = 5, len = 3, text = "efg".

You can use regular expression grouping to carry out complicated transformations on strings with relatively little code. Since you determine exactly where in a string a particular group in a regular expression occurs, you can take the group text out of the string and put it back into the string in a different order or with other changes.

For example, suppose you want to write a preparse() function that finds sentences of the form “tell <actor> to <command> ” and converts them to the normal TADS actor command format, “<actor>, <command> ”. You can use regular expression grouping to find this pattern of text and build the new command from pieces of the original:

 ret := reSearch('^ *tell%> *(.*)%<to%> *(.*)', cmd);
   if (ret != nil)
  cmd := reGetGroup(1)[3] + ', ' + reGetGroup(2)[3];

Or, suppose you have a telephone in your game, and you want to let the player dial numbers on the phone using normal North American telephone number notation, including an area code. The TADS parser won’t normally let you do this, since it would try to parse the number as several words. You could solve this problem using preparse: after the player enters a command, find anything that looks like a telephone number, and enclose it in quotation marks; this will make the parser treat the phone number as a quoted string, so you can write your “dial” verb so that it uses strObj as the direct object. Here’s how you could write the preparse routine:

 ret := reSearch('(%([0-9][0-9][0-9]%) *)?'
               + '[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]', cmd);
   if (ret != nil)
      cmd := substr(cmd, 1, ret[1] - 1) + ' "' + ret[3] + '" '
         + substr(cmd, ret[1] + ret[2], length(cmd));


remdaemon

Call: remdaemon(function, value)

Removes a daemon set previously with setdaemon(). The daemon will no longer be called.

Note that if a given function has been set as a daemon multiple times, a call to remdaemon() only removes the first such daemon; hence, you must call remdaemon() as many times as you called setdaemon() on a particular function if you wish to cancel all calls to it. Note also that the value given must match the value used in the original call to setdaemon(); this allows you to set a daemon several times, with different contexts (the context being given by the value), and selectively remove the daemons later. Also, removing a daemon that isn’t running does nothing.


remfuse

Call: remfuse(function, value)

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


reSearch

Call: reSearch(pattern, string_to_search)

Added: TADS 2.3.0

TADS has a built-in regular expression matching facility. Regular expressions provide a powerful and simple way to search for a complex pattern within a text string. This feature is particularly useful for writing preparse() and preparseCmd() functions, since it allows you to perform complex pattern matching and replacing with very little code.

The function reSearch() searches for the first occurrence of a regular expression pattern within a string. It returns nil if the pattern is not found. If the pattern is found, the function returns a list, the first element of which is a number giving the character position within the string of the start of the match (the first character is at position 1), the second element giving the number of characters in the match, and the third element a string giving the actual text of the match.

The pattern is specified using regular expression syntax similar to that used by “grep” and other similar utilities. Here are the basic building blocks of the regular expression syntax:

| Alternation: matches the expression on the left or the expression on the right. This operator affects as many characters as it can, out to the nearest parentheses.
( ) Groups an expression.
+ Indicates that the immediately preceding character or parenthesized expression repeats one or more times.
* Indicates that the immediately preceding character or parenthesized expression repeats zero or more times.
? Indicates that the immediately preceding character or parenthesized expression can occur zero or one time.
. (a period/full stop) Wildcard: matches any single character.
^ Matches the beginning of the string.
$ Matches the end of the string.
% Quotes the following character, removing the special meaning of these characters: | . ( ) * ? + ^ $ % [ Also introduces the special sequences listed later.
[ ] Indicates a character list or range expression. Matches any one of the listed characters. A range can be specified by following a character with ‘-’ and another character; this matches all of the characters between and including these two characters. For example, [a-z] matches any one lower-case letter, and [0-9] matches any one digit. Ranges and single characters can be combined; for example, [a-zA-Z] matches any letter, upper- or lower-case. To include the character ‘]’ in a list, make it the first character after the opening bracket; to include ‘-’, make it the next character after that. For example, []] matches just ‘]’, [-] matches just ‘-’, and []-] matches ‘-’ and ‘]’.
[^ ] Exclusionary character list or range. This matches any character except the ones listed. For example, [^0-9] matches anything single character except a digit.
%1 This matches the same text that matched the first parenthesized expression. For example, consider the pattern ‘(a*).*%1’. The string ‘aaabbbaaa’ will match, because the first three characters match the parenthesized ‘a*’ expression, which causes ‘%1’ to match the last three characters; the middle three characters are matched by the ‘.*’ expression.
%2 Matches the text matching the second parenthesized expression.
  And so on through...
%9 Matches the text matching the ninth parenthesized expression.
%< Matches at the beginning of a word. Words are considered to be contiguous groups of letters and numbers.
%> Matches at the end of a word. For example, ‘%<and%>’ matches the “and” in ‘ball and box’ and ‘and then’, but not in ‘rubber band’ or ‘the android’. Note that %< and %> do not actually contribute any characters to the match - they simply ensure that they fall on a word boundary. So, searching for ‘%<and%>’ in ‘ball and box’ matches the string ‘and’ - the spaces are not included in the match.
%w Matches any word character (a letter or a digit).
%W Matches any non-word character (anything but a letter or digit).
%b Matches at any word boundary (beginning or end of a word).
%B Matches except at a word boundary.

Any character other than those listed above simply matches the exact same character. For example, ‘a’ matches ‘a’.

Here are some examples of simple regular expressions, to help clarify the meanings of the basic building blocks:

abc|def either ‘abc’ or ‘def’
(abc) ‘abc’
abc+ ‘abc’, ‘abcc’, ‘abccc’, etc.
abc* ‘ab’, ‘abc’, ‘abcc’, ‘abccc’, etc.
abc? ‘ab’ or ‘abc’
. any single character
^abc ‘abc’, but only at the start of the string
abc$ ‘abc’, but only at the end of the string
%^abc literally ‘^abc’
[abcx-z] ‘a’, ‘b’, ‘c’, ‘x’, ‘y’, or ‘z’
[]-] ‘]’ or ‘-’
[^abcx-z] any character except ‘a’, ‘b’, ‘c’, ‘x’, ‘y’, or ‘z’
[^]-q] any character except ‘]’, ‘-’, or ‘q’

Here are some more complicated examples:

 (%([0-9][0-9][0-9]%) *)?[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]

This matches a North American-style telephone number, either with or without an area code in parentheses. If an area code is present, it can optionally be separated by spaces from the rest of the number: ‘(415)555-1212’, ‘555-1212’, ‘(415) 555-1212’.

 [-+]?([0-9]+%.?|([0-9]*)%.[0-9]+)([eE][-+]?[0-9]+)?

This matches a floating-point number in the notation used by C and some other programming languages: either a string of digits optionally ending with a decimal point, or zero or more digits followed by a decimal point followed by one or more digits; optionally followed by an exponent specified with the letter “E” (upper- or lower-case), an optional sign (‘+’ or ‘-’), and one or more digits; all of this can be preceded by an optional sign. This matches: ‘3e9’, ‘.5e+10’, ‘+100’, ‘-100.’, ‘100.0’, ‘-5e-9’, ‘-23.e+50’.

 ^ *tell%>(.*)%<to%>(.*)

This matches the word “tell” at the beginning of the string, preceded only by zero or more spaces, followed by any text, followed by the word “to”, followed by any more text. This matches ‘tell bob to go north’ and ‘tell teeterwaller to give me the mask’.

Here’s a code example:

   ret := reSearch('d.*h', 'abcdefghi');
   if (ret = nil)
      "No match.";
   else
     "Start = <<ret[1]>>, length = <<ret[2]>>, text = \"<<ret[3]>>\". ";

When run, this code will display the following:

 Start = 4, length = 5, text = "defgh".


resourceExists

Call: resourceExists(resource)

Added: TADS 2.5.1

This function allows you to determine whether a named resource (such as a JPEG image or an MP3 audio file) can be loaded. This function takes as its single argument a string giving the name of a resource to find; the function returns true if the resource can be loaded, nil if not. The function returns nil if the interpreter is simply not capable of loading resources at all (the text-only interpreters thus always return nil for this function), or if the resource can’t be found. The function returns true only if the interpreter is capable of loading resources at all, and the resource is available.

If you’re writing a multimedia game for the HTML interpreters, but you also want your game to work on text-only systems, you can use this function for finer control over the presentation on the text systems; you might, for example, want to provide additional text to make up for missing graphics or sounds. You can also use this function if you’re planning to distribute your game in different subset versions in order to provide players with different options for download and install sizes; if you make some of your graphics optional (by bundling some into a separate “.RSn” resource file, for example), you can use resourceExists() to detect at run-time which resources the player has chosen to install, and customize the presentation accordingly.

Here’s an example that checks to see if a JPEG image is available for display:

    if (!resourceExists('images/title.jpg'))
    {
       // the title graphic isn't available - display a text version
       ...
    }


restart

Call: restart()

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

Alternative Call: restart( funcptr, param )

This form of restart() allows you to specify a function to be called after restarting the game, but before the init() function is invoked. This new feature has been added because it would otherwise be impossible to pass any information across a restart operation: the restart() function does not return, and all game state is reset to its initial state by restart().

You can use this function if you want a restarted game to have different startup behavior than the game has when it’s first started. Note that adv.t passes a pointer to the initRestart function (defined in adv.t when it invokes restart() in response to a “restart” command; the adv.t implementation of initRestart() simply sets the flag global.restarting to true to indicate that the game is being restarted rather than first entered.

The param value is simply passed as the parameter to the function to be called, and this allows you to pass information through the reset. For example, if you start the game with a questionnaire asking the player’s name, sex, and age, you could pass a list containing the player’s responses to your restart function, and have the restart function store the information without making the player answer the questionnaire again. The call to restart() in adv.t uses global.initRestartParam as the parameter for the initRestart() function; so, if you provide your own version of initRestart() that makes use of the parameter information, you can simply store the necessary information in global.initRestartParam to ensure that it’s passed to your function at the appropriate time.


restore

Call: restore(filename)

Added: Return values altered 2.5.0

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

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

As of TADS 2.5.0 the return value in the event of an error is a number. Different values are returned for different error conditions, which makes it possible to provide better information to the player about the specific problem that caused the operation to fail. The return values, defined in adv.t, are:

RESTORE_SUCCESS   Success
RESTORE_FILE_NOT_FOUND   The file to be restored does not exist (or could not be opened for some other reason).
RESTORE_NOT_SAVE_FILE   The file is not a valid saved game.
RESTORE_BAD_FMT_VSN   The file was saved by an incompatible version of the TADS Interpreter
RESTORE_BAD_GAME_VSN   The file was saved by a different game, or by a different version of the same game.
RESTORE_READ_ERROR   An error occurred reading the file. This could indicate that the file was corrupted, or that the physical medium containing the file is damaged.
RESTORE_NO_PARAM_FILE   No parameter file has been specified. This is returned only when restore(nil) is called to attempt to load the file specified by a start-up parameter; it indicates that there is in fact no parameter file to load.

For compatibility with older versions of TADS, RESTORE_SUCCESS is defined as 0, and all of the other values are non-zero. In most cases, this should allow existing code (that assumes the nil/true return value) to continue working without changes, since if (restore(fname)) will continue to have the same effect with this change. Only old code that explicitly compared the return value to nil or true will need to be changed.

Alternative Call: restore( nil )

This form of the restore() function allows you to choose the time during startup that the player’s saved game is restored when the player started your game with a saved game already specified. When you call restore(nil), the system checks to see if a saved game was specified by the player at startup, and if so, immediately restores the game and returns nil. If no game was specified, the function returns true.

It is possible for a player to start a game in this manner only on windowed GUI TADS runtimes, such as those that run on the Macintosh and under Windows, but the new restore() functionality will work correctly on all platforms. On the Macintosh, the operating system allows an application to be started by opening one of the application’s documents from the desktop. When this happens the application is started and informed that the user wishes to open the specified file. Saved game files on the Macintosh are associated with the game executable that created them in such a way that the game is executed when a saved game is opened. This is simply a convenience feature on the Macintosh that allows a player to run a game and restore a saved position in a single operation.

You can use restore(nil) in your init function to choose the point at which the saved game is restored. If your game has extensive introductory text, you could call restore(nil) (and return if the function returns nil) prior to displaying the introductory text, since the player has presumably already seen it anyway.

The reason that the system doesn’t restore the saved game prior to calling your init function is that you may want parts of your init function to be invoked regardless of whether a game is going to be restored or not. For example, you may wish to display your copyright message, or ask a question for copy protection, every time the game starts, even when a saved game is going to be restored.

If you do not make a call to restore(nil) in your init function, the system will automatically restore the saved game specified by the player at startup immediately after your init function returns. Hence, omitting the call to restore(nil) doesn’t do any harm; this function is provided to give you greater control


rundaemons

Call: rundaemons()

Runs all of the daemons. The function runs daemons set both with setdaemon() and notify(). This function returns no value.


runfuses

Call: runfuses()

Runs all expired fuses, if any. Returns true if any fuses expired, nil otherwise. This function runs fuses set both with setfuse() and notify().


save

Call: save(filename)

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

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


say

Call: say(value)

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

There is no return value.


setdaemon

Call: setdaemon(function, value)

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

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

  test: function;

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

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

See also the notify() function.


setfuse

Call: setfuse(function, time, value)

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

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

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

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


setit

Call: setit(object)

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

  >go north
  The metal door is closed.

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

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

  >the key
  The padlock swings open.

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

There is no return value.

Alternative Call: setit( nil )

You can use nil as the argument to setit(). This prevents the player from using “it” in subsequent commands.

Alternative Call: setit( obj, num )

You can also specify which pronoun you want to set. The optional parameter num specifies the pronoun: 1 for “him” and 2 for “her.” When num is not specified, setit() sets “it” as usual. Note that nil can be used for the obj argument to clear “him” or “her.”

Alternative Call: setit( list )

Finally, you can set “them” directly, simply by passing a list of objects (rather than a single object) to setit(). Calling setit() with a list argument has the side effect of clearing “it.”


setscore

Call: setscore(score, turns)

Alternative Call: setscore(stringval)

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

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

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

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


setversion

Call: setversion(version)

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

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


skipturn

Call: skipturn(num)

This function is similar to incturn(num). This function skips the given number of turns, which must be at least 1. The difference between skipturn() and incturn() is that skipturn() doesn’t run any of the fuses that burn down during the turns being skipped - instead, it simply removes them without running them.

Fuses set with setfuse() and notify() are both affected by this routine. Note that this function has no effect on daemons.


substr

Call: substr(string, offset, length)

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


systemInfo

Call: systemInfo( __SYSINFO_xxx )

Added: TADS 2.2.4. __SYSINFO_HTML_MODE and __SYSINFO_MPEG_AUDIO were added with 2.3.0.

This built-in function allows your game to determine programmatically whether the TADS run-time that is currently executing the game has certain capabilities. This function is called like this:

result = systemInfo( __SYSINFO_xxx );

where __SYSINFO_xxx is one of a number of pre-defined constants, listed below, defined automatically by the compiler. The result tells you about the particular run-time that is executing your game, so you can customize the game, if you wish, for certain system capabilities. For example, you might want to change some text in your game slightly depending on whether sound effects can be played. It’s also useful if you need to know whether a given function (for example, regular expression parsing) is available.

Before calling systemInfo() with any of the constant codes, you must check to see if systemInfo() itself is supported. Versions of the run-time prior to 2.2.4 do not support this function, so the return codes are meaningless. Fortunately, you can determine if systemInfo() is itself supported using the following code fragment:

        if (systemInfo(__SYSINFO_SYSINFO) = true)
        {
            /*
             *  systemInfo IS supported by this run-time - other
             *  systemInfo codes will return meaningful results
             */
        }
        else
        {
            /*
             *  systemInfo is NOT supported by this run-time
             */
        }

Only one version of TADS (version 2.2.3) was ever released without systemInfo support, and this was the first public beta release. So, it should be fairly safe to assume that any system that doesn’t support systemInfo() doesn’t support any of the multimedia features.

The predefined __SYSINFO_xxx codes are:

__SYSINFO_VERSION Returns a string with the run-time version number. This will be a string such as ‘2.2.4’.
__SYSINFO_HTML

Returns 1 if HTML markup is supported, 0 if not. 0 indicates that this is a standard text-mode run-time system. If this returns 0, then JPEG, PNG, WAV, MIDI, WAV/MIDI overlap, WAV overlap, and the images, sounds, music, and links preference items can all be assumed to be unsupported, since these features are only provided by multimedia TADS.

Note that __SYSINFO_HTML returns 0 (HTML not supported) for the character-mode run-time, even for the newer versions of the run-time that do provide some limited HTML support, because this information code is intended to indicate whether the full HTML feature set is supported. The character-mode version only supports a limited subset of HTML features, so it indicates that HTML is not supported.

__SYSINFO_HTML_MODE Returns true if the interpreter is currently interpreting HTML markups, nil if not. Note that this new code has nothing to do with whether the interpreter is a full multimedia system (such as HTML TADS or HyperTADS) or a text-only system (such as the DOS “TR” interpreters); this code instead indicates only whether or not a “\H+” sequence is currently in effect.
__SYSINFO_OS_NAME Returns a string with the name of the operating system on which the run-time is currently executing. (The name is the same as the string that the compiler uses to pre-define the __TADS_SYSTEM_NAME preprocessor symbol, but this lets you determine what system is executing at run-time, rather than the system that was used to compile the game.)
__SYSINFO_JPEG Returns 1 if JPEG images are supported, 0 if not.
__SYSINFO_PNG Returns 1 if PNG images are supported, 0 if not.
__SYSINFO_PNG_TRANS Tests for PNG transparency support. Some newer versions of the HTML interpreter support PNG transparency, which allows parts of a PNG image to be designated as transparent so that the background shows through. systemInfo(__SYSINFO_PNG_TRANS) returns true if transparency is supported when displaying PNG images, nil if not. This will never return true under interpreters that don’t support PNG images at all. Note that this flag indicates only simple transparency support, which allows pixels to be designated as fully opaque or fully transparent, and does not imply support of full alpha blending; some interpreters (such as the Windows HTML interpreter 2.5.4) support only simple transparency but not alpha blending. This code was added with TADS 2.5.4 and is not supported with earlier versions.
__SYSINFO_PNG_ALPHA Tests for PNG “alpha blending” support, which allows individual pixels of a PNG image to be designated as partially transparent. Currently, none of the interpreters support alpha blending in PNG images; this flag has been added for possible future use. This code was added with TADS 2.5.4 and is not supported with earlier versions.
__SYSINFO_WAV Returns 1 if WAV sounds are supported, 0 if not.
__SYSINFO_MIDI Returns 1 if MIDI music is supported, 0 if not.
__SYSINFO_MIDI_WAV_OVL Returns 1 if MIDI and WAV sounds can be played back simultaneously (overlapped), 0 if not. If this returns 0, it means that WAV playback will suspend MIDI playback.
__SYSINFO_WAV_OVL Returns 1 if multiple WAV sounds can be played back simultaneously (overlapped), 0 if not. If this returns 0, it means that any WAV played back in a foreground layer will suspend a WAV being played in any background layer.
__SYSINFO_MPEG_AUDIO Returns 1 if MPEG 2.0 audio support of any kind is present, 0 if not. It is possible that some systems may support some types of MPEG audio, but not all three layers. This feature code indicates whether MPEG audio of any kind is supported; the specific layer codes below can be used to check for each individual layer.
__SYSINFO_MPEG_AUDIO_1 Returns 1 if MPEG 2.0 layer I is supported
__SYSINFO_MPEG_AUDIO_2 Returns 1 if MPEG 2.0 layer II is supported
__SYSINFO_MPEG_AUDIO_3 Returns 1 if MPEG 2.0 layer III is supported. This media type is commonly known as ‘MP3’.
__SYSINFO_PREF_IMAGES Returns 1 if the user preferences are set to allow images to be displayed, 0 if not. Note that, even if this preference is set so that images are not displayed, the preferences for JPEG and PNG images will still return 1 for each of those image types that are supported by the platform. The image format codes (__SYSINFO_PNG and __SYSINFO_JPEG) indicate whether the image formats are supported at all, whereas this preference code indicates whether images are currently allowed for display.
__SYSINFO_PREF_SOUNDS Returns 1 if the user preferences are set to allow digitized sound effects (WAV files) to play back, 0 if not.
__SYSINFO_PREF_MUSIC Returns 1 if the user preferences are set to allow music (MIDI files) to play back, 0 if not.
__SYSINFO_PREF_LINKS Returns 0 if the user preferences are set so that links are not highlighted at all (in which case they’ll be displayed as ordinary text; they won’t be highlighted as links and won’t be active for the mouse); returns 1 if links are highlighted and active; and returns 2 if links are set to a “hot key” mode, in which case links aren’t highlighted except when the user is holding down a special key to explicitly illuminate the links.
__SYSINFO_AUDIO_FADE Returns nil or 0 if the system doesn't support audio fades (via HTML TADS, using the <SOUND> tag), or a combination of format bit flags if so. The bit flags are __SYSINFO_AUDIOFADE_WAV, __SYSINFO_AUDIOFADE_MPEG, __SYSINFO_AUDIOFADE_OGG, and __SYSINFO_AUDIOFADE_MIDI. These indicate that audio fades are supported for the respective formats (WAV, MP3, Ogg Vorbis, and MIDI).
__SYSINFO_AUDIO_CROSSFADE Returns nil or 0 if the system doesn't support audio cross-fades (via HTML TADS, using the <SOUND> tag), or a combination of format bit flags if so. The bit flags are the same as for __SYSINFO_AUDIO_FADE, and indicate that cross-fades are supported for the respective formats.

Version 2.5.2 also introduced the ability to add live Internet links. The systemInfo() function accepts several capability codes that let you discover whether the system is capable of following URL-style <A HREF> hypertext links in HTML-formatted text. The codes are:

__SYSINFO_LINKS_HTTP Checks if the system can follow HTTP (Hypertext transfer protocol) links to display Web pages. HTTP URLs start with “http:”.
__SYSINFO_LINKS_FTP Checks if the system can follow FTP (file transfer protocol) links, which start with “ftp:”.
__SYSINFO_LINKS_NEWS Checks if the system can follow links to Usenet newsgroups. News URLs start with “news:”.
__SYSINFO_LINKS_MAILTO Checks if the system can follow links to send email, which start with “mailto:”.
__SYSINFO_LINKS_TELNET Checks if the system can follow telnet links, which start with “telnet:”.

For example, suppose you want to display a link to a web page you’ve created for your game, so that players can check for updates and hints on your web site. You can use __SYSINFO_LINKS_HTTP to check the user’s TADS interpreter for HTTP link capability. If the interpreter can follow HTTP links, you can show your link with a friendly display, and fall back on spelling out the URL when the interpreter can’t directly follow the link.

   "You can check for updates to this game at ";
   if (systemInfo(__SYSINFO_LINKS_HTTP))
       "<A HREF='http://www.mysite.com/mygame.htm'>my web site</A>";
   else
       "my web site (http://www.mysite.com/mygame.htm)"
   ". This site also has hints and some background 
   information about the game. ";


timeDelay

Call: timeDelay(time)

Added: TADS 2.3.0

This built-in function lets you pause the game for a specified time interval. You can use this function in the middle of displaying a long text passage, for example, to create a dramatic pause effect, without making the user press a key to continue. The function takes a single argument giving the number of milliseconds to pause the game. For example, to pause the game for five seconds, use this:

timeDelay( 5000 );


undo

Call: undo()

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

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

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

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


unnotify

Call: unnotify(object, &message)

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

See also the notify() function.


upper

Call: upper(string)

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


verbinfo

Call: verbinfo(deepverb)

Alternative Call: verbinfo(deepverb, prep)

This function lets you retrieve information on the doAction and ioAction definitions for the given deepverb object. This function returns a list giving the verification and actor property pointers for the appropriate verb template. Refer to the section on getting verb information for details on this function.


yorn

Call: yorn()

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

Note that some interpreters will accept a localized version of the responses, suitable for the system's local language settings, in addition to the English Yes/No responses. This is intended to be more intuitive for users, since some people might unconsciously use their own language for a Yes/No response.

If you're writing a game in a non-English language, you might prefer to use your own custom function (using input() to read a response, then parsing it yourself with the regular expression functions, for example) instead of yorn(). This will give you control over the parsing, so that you can ensure that responses in your game's own language are understood properly.


Dynamic Object Creation

TADS allows you to create and delete objects dynamically at run-time. This is done through two operators: new and delete. To create a new object, use this syntax:

  x := new bookItem;

This dynamically creates a new object whose superclass is bookItem. When this statement is executed, the runtime creates a new object, assigns its superclass to be bookItem, and executes the construct method in the new object; this method can perform any creation-time setup that you wish to do. The default construct method in the class thing in adv.t simply moves the new object into its location - this is necessary so that the contents list of the location is updated to include the new object.

An expression involving the operator new applied to a class can be used wherever an object expression can be used. When the expression is evaluated, a new object of the given class is created.

Note that you can create a new object that has no superclass by using the keyword object:

  x := new object;

If you’re familiar with C++ constructors, you should note an important difference between the construct method and C++ constructors. A C++ constructor in a derived class will automatically call the construct in the base class (except for a virtual base class). The TADS construct method does not automatically call superclass construct methods; instead, construct works exactly the same as any other TADS method. You can, of course, call the superclass construct method explicitly using the inherited mechanism, just as with any other method. The same is true of the destruct method (described below).

A new object inherits all of the vocabulary of its superclass.

To destroy an object create with new, use this syntax:

  delete x;

This first calls the destruct method of the object to notify it that it is about to be deleted, then destroys the object. Further references to the object are illegal, since its memory has been released (and thus may be given to an other object). The default destruct method in the class thing in adv.t moves the object into nil, which removes it from its container’s contents list - this is necessary so that the reference to the object in that list is removed.

Only objects created with new can be destroyed with delete. Objects that are defined statically in your game’s source file cannot be deleted at run-time.

Object creation and deletion works correctly with the UNDO mechanism. If the player uses UNDO after a move that created an object, the object will be destroyed; likewise, if a player uses UNDO after a turn that deletes an object, the object will be re-created with the same property values it had prior to deletion.

Similarly, dynamically-created objects are preserved across SAVE and RESTORE operations.

Note that TADS does not perform any garbage collection on dynamically-created objects. The system is not capable of determining whether an object is accessible or not. Hence, if you lose track of any objects you create with new, they will remain part of the game forever - they will even be saved along with saved games and restored when the games are restored. You must be careful to keep track of all objects you create to avoid filling all available memory (and the swap file) with unreachable objects. Even though TADS will eventually swap inaccessible objects out of memory, the objects will consume object identifiers, which are a limited resource: only 65535 objects can be created within a game, including both static and dynamically-created objects.


Indistinguishable Objects

The ability to create new objects at run-time leads to some interesting problems involving indistinguishable objects. Although you could use addword() to make your newly-created objects distinguishable from one another, this will not always be desirable. For example, if you create new gold pieces that serve as currency, you will probably not want them to be uniquely named.

To support indistinguishable objects, especially those created dynamically at run-time, the system has a property that you can set to indicate to the parser that an object does not need to be distinguished from others of the same class. The property is isEquivalent. When isEquivalent returns true for an object, all other objects with the same immediate superclass are considered interchangeable by the parser. When a player uses one of these objects in a command, the parser will simply pick one arbitrarily and use it, without asking the player which one.

If a player uses a noun that is ambiguous with multiple equivalent items and one or more other items, the parser will need to disambiguate the objects as usual. In such cases, the parser’s question will list the distinguishable items only once. For example, assume we have five gold coins that are all equivalent (in other words, they all have isEquivalent set to true, and they all are immediate subclasses of the same class). Assume further that a silver coin and a bronze coin are also present in the room.

  Treasure Room

  You see a bronze coin, five gold coins, and a silver coin here.

  >get coin

  Which coin do you mean, the bronze coin, a gold coin, or the silver coin?

Note that the objects which appear only once are listed with “the” (using the property thedesc), while the indistinguishable objects are listed only once, with “a” (using adesc).

The functions listcont and itemcnt in adv.t list equivalent objects intelligently. The functions isIndistinguishable and sayPrefixCount are used to count and list indistinguishable objects correctly. The contents of the Treasure Room are listed in the example above in the new format. Refer to these functions in adv.t for examples of recognizing objects as equivalent, counting equivalent objects, and treating a set of equivalent objects together as a set.

Note that listcont uses the property pluraldesc to display the name of an object when more than one equivalent object is present. In the example above, pluraldesc was used to list the gold coins. This property is in the class thing in adv.t, but you may need to override it for some objects - the default implementation simply displays the sdesc plus the letter “s.”


Preprocessor Directives

TADS lets you define preprocessor symbols. The #define directive creates a preprocessor symbol definition:

  #define TEST  "This is a test! "

This defines the symbol TEST, and specifies that this symbol is to be replaced by the text "This is a test!" whenever it appears (outside of strings or comments) in your source code. Note that the definition of a symbol need not be a string - the entire text of the rest of the line is assigned to the symbol.

You can use a preprocessor symbol that you’ve defined simply by putting the symbol in your code:

  sdesc =
  {
    TEST;
  }

When the compiler encounters the preprocessor symbol, it replaces the symbol with its definition, so the compiler treats this the same as:

  sdesc =
  {
    "This is a test! ";
  }

You can delete a preprocessor symbol that you’ve defined using the #undef directive:

  #undef TEST

The compiler automatically defines a number of preprocessor symbols:

__TADS_VERSION_MAJOR Defined as the major version number of the current compiler. (Note that this symbol starts with two underscore characters, as do most of the symbols that the compiler defines for you.) Currently, this is defined as 2.
__TADS_VERSION_MINOR The minor version number; currently 5.
__TADS_SYSTEM_NAME A single-quoted string giving the name of the current system. For DOS systems, this is 'MSDOS'; for Macintosh systems, this is 'Macintosh'. TADS also defines the value of this symbol as a symbol itself - on MSDOS systems, the compiler defines the symbol MSDOS, and on Macintosh systems, the compiler defines Macintosh. (The value of the system name symbol is simply 1; its purpose is to allow for conditional compilation, so the value isn’t important.)
__DEBUG Defined if the game is being compiled for debugging (with the -ds command line option). By testing this symbol with #ifdef, you can easily include parts of your game (such as special “magic” verbs that you use for debugging) only in testing versions of your game, without having to worry about removing them manually when producing the final version of the game for players. The value of this symbol is 1 if it’s defined; the purpose of this symbol is to allow for conditional compilation, so its value isn’t important.
__DATE__ Defined as a single-quoted string giving the date when the current compilation began, in the format 'Jan 01 1994'.
__TIME__ Defined as a single-quoted string giving the time when the current compilation began, in the 24-hour format '01:23:45'. The time isn’t updated during the course of a compilation - it’s always the time when compilation began.
__FILE__ Defined as a single-quoted string giving the name of the current source file being compiled.
__LINE__ Defined as a number giving the line number currently being compiled.

You can define symbols from the compiler command line. The -D option lets you specify a symbol to define, and optionally a value. Specify the symbol, then an equals sign, then the value; if you omit the equals sign, the default value is 1. For example, to define the symbol DEMO from the command line, you could use this command:

  tc -DDEMO mygame

You can also specifically delete preprocessor symbols that the compiler defines (other than __FILE__ or __LINE__). You can also undefine any symbols defined earlier on the command line with -D options, which may be useful if you’re using a configuration file that defines certain symbol. To undefine a symbol, use the -U option:

  tc -UDEMO mygame

If the symbol DEMO was defined earlier on the command line, the definition is deleted.

The Macintosh compiler has a new preprocessor options dialogue box that you can access from the “Options” menu. You can use this dialogue to enter symbols to define; in the text box, simply list the symbols (one per line) that you wish to define. If you want to assign values to these symbols, use an equals sign, followed by the value. Another text box in the same dialogue lets you list pre-defined symbols that you want to delete; simply list the symbols to be deleted, one per line.

You can test for the existence of a preprocessor symbol with #ifdef (“if defined”) and you can test to see if a preprocessor symbol is undefined with the #ifndef (“if not defined”) directive. These directives let you conditionally include code in your program, depending on whether or not a symbol is defined. For example:

  #ifdef TEST
  sdesc = { TEST; }
  #endif

The code between the #ifdef and the #endif is included only if the preprocessor symbol TEST is defined. There’s also a #else directive, which lets you include a block of lines if the most recent condition failed.

Conditional compilation is particularly useful with the symbols you define from the command line with the -D option, since it allows you to write your game in such a way that certain features are enabled when you use a particular set of -D options. This allows you to use a single set of source files, but produce a variety of different .GAM files. For example, if you want to be able to generate a subset of your game as a demo, you could use conditional compilation to include or discard sections of the game depending on whether you’re compiling the demo or the full game. Similarly, you could use conditional compilation to include certain features only when you’re compiling a debugging version of your game; note that the compiler makes this even easier, because it defines the symbol __DEBUG when you’re compiling with debugging symbols.

I’ve also added the #error directive. This directive lets you generate an error from within your game. Any text following the #error on the line is displayed to the user as the text of the error message. For example:

  #ifndef TEST
  #error TEST is not defined!
  #endif


File Operations

TADS has support for reading and writing files from within your game program. This feature is intended to let you save information independently of the game-saving mechanism, which only really allows you to transfer information between sessions of a game. The file functions can save files in a proprietary binary format, which can be used to store TADS data, or as plain text files, which can be opened and read by any text editor, word processor or Web browser application.

Operations on files are all performed through a file handle. This is a special value, generated by TADS, that you use to refer to a file. TADS creates a file handle when you open a file; once you’ve opened a file and obtained its file handle, you can read and write the file. Once you’re done with the file, you close the file, which deletes the file handle.

To open a file, use the fopen() function. This function takes two arguments: a (single-quoted) string giving the name of the file to open, using local file system conventions, and a “mode.” The mode argument is one or two of these single-quoted string values:

‘b’ - open as a binary file. If you don’t specify the file mode, binary is assumed as the default mode, for compatibility with past versions.

‘r’ - open file for reading; file must already exist.

‘r+’ - open file for reading and writing; the file is created if it doesn’t already exist. Not currently allowed in text mode.

‘t’ - open as a text file. You can use the text mode in conjunction with ‘r’ (read) and ‘w’ (write) modes - ‘t’ is not currently allowed with ‘r+’ or ‘w+’ modes. Note that text file functionality was added with version 2.2.4 of TADS.

‘w’ - create a new file for writing; the file is deleted if it already exists.

‘w+’ - create a new file for reading and writing; the file is deleted if it already exists. Not currently allowed in text mode.

For maximum portability you should avoid using volume names, directories, folders, or other path information in filenames, because any such information might be specific to your computer.

The return value of fopen() is a file handle - you must save the file handle so you can use it later to perform operations on the file. If the operation fails, and the file cannot be opened as requested, fopen() returns nil. Opening a file could fail for a number of reasons. For example, if you attempt to open a non-existent file with mode ‘r’, fopen() will return nil, because this mode requires that the file already exists.

This example opens a new file called TEST.OUT for writing:

  fnum := fopen( 'test.out', 'w' );

To close an open file, use fclose():

  fclose( fnum );

Once you’ve closed a file, the file handle is no longer valid. TADS may re-use the same file handle on a subsequent fopen() call. Note that the TADS runtime allows only a limited number of files (currently 10) to be open simultaneously, so you should close a file when you’re done with it.

To write to a file, use fwrite(). This function takes a file handle, and a value to write; the value can be a string, a number, or true. The value can’t be nil (this is because the fread() function returns nil to indicate failure; if you could write nil to a file, there would be no way to distinguish reading a valid nil from an error condition). fwrite() stores the value, along with information on its type.

The fwrite() function returns nil on success, true on failure. If the function returns true, it usually means that the disk is full.

  if ( fwrite( fnum, 'string value!' ) or fwrite( fnum, 123 ) )
   "Error writing file!";

If the file is open for reading, you can read from the file with the fread() function. This function takes a file handle, and it returns a value it reads from the file. The value returned is of the same type as the value originally written at this position in the file with fwrite(). If this function returns nil, it indicates that an error occurred; this usually means that no more information is in the file (which is the case once you’ve read to the end of the file).

  res := fread( fnum );
  say( res );

You can get the current byte position in the file with the ftell() function:

  "The current seek position is "; ftell( fnum ); ". ";

The ftell() function returns a number giving the byte position that will be read or written by the next file operation.

You can set the file position with fseek() and fseekeof(). The fseek() function moves the file position to a particular byte position, relative to the beginning of the file. For example, this seeks to the very beginning of a file:

  fseek( fnum, 0 );

The fseekeof() function positions the file at its end (EOF stands for “end of file”):

  fseekeof( fnum );

Note that you must be careful with fseek(). You should only seek to positions that you obtained with the ftell() function; other positions may be in the middle of a string or a number in the file, so seeking to an arbitrary location and writing could render the file unusable by partially overwriting existing data.

Of the two formats, TADS binary files were not designed with any provisions for creating or reading formatted files, or for exchanging information with programs other than TADS games. TADS’ binary format is portable across platforms - you can take a binary file written using DOS TADS and read it on the Macintosh with Mac TADS; however, the TADS format isn’t designed to be interchanged with other programs.

If you want portable files, save your files in the text format. These files are plain ASCII and can be used by any other application on your computer.

Some game players may be uncomfortable about the possibility that a malicious game author could harm their systems by writing a game that, for example, modifies their AUTOEXEC.BAT files. To address any concerns that players may have, TADS 2.2.4 and higher provide a “file safety level” setting. This is an optional setting that allows the player to control the amount of access that a game has to files on the system at run-time.

The file safety level is set through a command line option (note that HTML TADS and HyperTADS provide this setting through the “Preferences” dialogue). Use the new -s option to specify one of the possible safety levels:

-s0 - (default) minimum safety - read and write in any directory
-s1 - read in any directory, write in current directory
-s2 - read-only access in any directory
-s3 - read-only access in current directory only
-s4 - maximum safety - no file I/O allowed

If the game attempts a file operation that is not allowed by the current safety level, the fopen() function returns nil to indicate that the file open failed.

These options affect only explicit file I/O operations performed by the game. Operations handled by the system, such as saving and restoring games and logging a transcript to a file, are not affected by the file safety level setting.




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

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


Chapter Four Table of Contents Chapter Nine