Chapter 8: More built-in predicates

Checking the type of a value

Dialog contains a set of built-in predicates for checking if a value is of a particular type. They are:

(number $X)

Succeeds if and only if $X is bound to a number.

(word $X)

Succeeds if and only if $X is bound to a dictionary word.

(empty $X)

Succeeds if and only if $X is bound to an empty list.

(non-empty $X)

Succeeds if and only if $X is bound to a non-empty list.

(list $X)

Succeeds if and only if $X is bound to a list (empty or non-empty).

(bound $X)

Succeeds if and only if $X is bound to anything. That is, the predicate only fails if $X is an unbound variable. It succeeds for numbers, dictionary words, and lists, including lists with one or more unbound variables inside.

The last one comes with an extra feature:

(object $X)

If $X is bound to an object, the query succeeds. If it is bound to anything else, the query fails. But if $X is unbound, *(object $X) backtracks over every object in the game.

Numbers and arithmetic

The Dialog language is designed for symbolic manipulation, predicate logic, and storytelling. Arithmetic is possible, but the syntax is rather clunky.

($A plus $B into $C)

A and B must be bound to numbers; C is unified with their sum. If the result is outside the valid range of numbers, the query fails.

($A minus $B into $C)

A and B must be bound to numbers; C is unified with their difference. If the result is outside the valid range of numbers, the query fails.

($A times $B into $C)

A and B must be bound to numbers; C is unified with their product. If the product is outside the valid range of numbers, the query succeeds, but the numeric result is unpredictable (i.e. it depends on the interpreter).

($A divided by $B into $C)

A and B must be bound to numbers; C is unified with the (integer) quotient after dividing A by B. The query fails if B is zero.

($A modulo $B into $C)

A and B must be bound to numbers; C is unified with the remainder after dividing A by B. The query fails if B is zero.

(random from $A to $B into $C)

A and B must be bound to numbers, such that B is greater than or equal to A. A random number in the range A to B (inclusive) is picked, and then unified with C.

($A < $B)

This predicate succeeds if and only if A is numerically less than B.

($A > $B)

This predicate succeeds if and only if A is numerically greater than B.

Common to all of the above predicates is that they fail if A or B is unbound, or bound to a non-number. C may be bound or unbound; it is unified with the result of the computation.

To check for numerical equality, use regular unification, i.e. ($ = $).

All numbers in Dialog are restricted to the range 0–16383 (inclusive). This range directly supports four-digit numbers such as years and PIN codes. Pocket money should be fairly straightforward to implement by counting in cents; story authors (or library developers) that require more sophisticated number crunching will have to get creative.

Debugging

The following built-in predicates are useful for debugging:

(breakpoint)

If the program is currently running inside the interactive debugger, suspend execution and print the current source code filename and line number. When execution resumes, this query succeeds.

If the program is running on the Z-machine, the query simply succeeds.

(trace on)

Enables tracing. Following this, debugging information will be printed when queries are made, and when rule bodies are entered. The interactive debugger will also report when solutions are found, and when dynamic predicates are updated.

(trace off)

Disables tracing.

If your program source code contains a query to (trace on) anywhere, the Z-machine backend will insert extra instructions all over the generated code, to deal with tracing. This is known as instrumenting the code, and it makes the program slower and larger. Thus, you'll only want to use these predicates temporarily, during debugging. The compiler prints a warning when it adds the extra instructions.

Please be aware that the Dialog compiler and debugger do optimize your program, and you will be tracing the optimized code, so certain queries and rules will be missing from the debug printouts. You will generally want to do all your tracing in the debugger, which mercifully turns off some of the more confusing optimizations. That being said, tracing the optimized Z-code is highly useful when trying to speed up a program.

System control

The following built-in predicates offer low-level control over the interpreter and the Dialog runtime. This is decidedly in the domain of library code, so story authors rarely need to worry about these predicates.

(quit)

Immediately terminates the program. This predicate neither fails nor succeeds.

(restart)

Resets the program to its initial state. The only part of the game state that may survive a restart is the state of the output machinery (including the current style and on-screen contents, and whether the transcript feature is on or off). If the operation succeeds, execution resumes from the start of the program. If there is an error, or the interpreter doesn't support restarting, execution continues normally, i.e. the query succeeds.

(save $ComingBack)

Attempts to save the current game state to a file. The interpreter takes care of asking the player for a filename. In the event of a save error, or if the operation was cancelled, the query fails. On success, the parameter is unified with 0 if we just saved the state, and with 1 if we just restored the state from a file saved by this query.

(restore)

Attempts to restore the current game state from a file. The interpreter takes care of asking the player for a filename. The only part of the game state that may survive a restore is the state of the output machinery (including the current style and on-screen contents, and whether the transcript feature is on or off). If the operation succeeds, execution resumes after the query from which the save file was written. Otherwise, in the event of a load error or if the operation was cancelled, execution continues normally, i.e. the query succeeds.

(save undo $ComingBack)

Works just like (save $), but stores the game state in a buffer in memory. This operation is typically invoked once per move.

(undo)

Works like (restore), but restores the game state from the undo buffer. If there is no saved undo state, the predicate fails. If there's some other problem—such as the interpreter imposing a limit on the number of undo states that are retained in memory—the predicate succeeds, and execution continues normally.

(interpreter supports quit)

Succeeds if and only if the current interpreter handles (quit) in a way that is meaningful to the player. For instance, it fails under the Å-machine web interpreter, because a web page cannot close itself.

(interpreter supports undo)

Succeeds if and only if the current interpreter declares that it supports undo functionality.

(transcript on)

Enables the transcript feature. The interpreter takes care of asking the player for a filename. If the operation succeeds, the query suceeds. In case of an error, or if the operation was cancelled, the query fails.

(transcript off)

Disables the transcript feature. This predicate always succeeds.

(display memory statistics)

Prints a line of information specific to the compiler backend, about the peak memory usage in the heap, auxiliary heap, and long-term heap areas. Currently, only the Z-machine backend does this. The size of these areas can be adjusted by passing commandline options to the compiler. During debugging and testing, you may wish to invoke this predicate just before quitting, as it will tell you how close you are to the limits.