This file is part of the TADS 2 Author’s Manual.
Copyright © 1987 - 2002 by Michael J. Roberts. All rights reserved.
The manual was converted to HTML and edited by NK Guy, tela design.


Appendix D


TADS Debugger

TADS features a source-level debugger that can be helpful in making your game work properly. The debugger makes it possible to watch your game program’s execution line by line, and examine the state of variables and properties. This chapter describes the debugger, and how to use it to get your game working.

Note that if you’re using TADS under Win32, some details of this appendix has been superseded somewhat. The Win32 TADS debugging environment has been upgraded to include a full integrated development environment and is now called the TADS Workbench. The basic principles are still applicable, however.


What is a Debugger?

The term “debugger” isn’t exactly accurate for this type of program, because it doesn’t actually describe what the program is so much as what you normally use it for. Unfortunately, a debugger doesn’t actually find and remove problems from your program, as the name suggests; instead, it provides a set of tools that makes it easier for you, the programmer, to find and remove the bugs in your program. A better term would be “debugging tool”; you’re the real debugger.

What the debugger actually does is to let you examine your game program in detail while it executes. Using the debugger, you can step through your game line by line, which allows you to see exactly what your program does as it runs. You can also set “breakpoints,” which cause the game’s execution to be suspended at certain lines of code that you specify; you can use breakpoints to determine when and why certain lines of code are executed (or not executed). You can also examine the state of your game, by displaying the value of a variable or property. You can even change the value of a variable or property.

If you’re just learning TADS, you may find that the debugger can help you understand how the system works. Using TDB, you can step through your game line by line, so you can see how the parser calls routines in your program, how inheritance works, how the functions and objects in adv.t work, and other details that can be confusing at first.


Using the Debugger

The rest of this chapter describes how to use the debugger. Note that some of the information in this chapter depends on the type of computer you are using, because the user interface to the debugger is different on each computer. So, the mechanism for invoking a debugger feature is different on some computers than described here; for example, a breakpoint may be set by typing a command on some computers, by pressing a function key on other machines, and by selecting an item from a menu on other systems.


Getting Ready for Debugging

Before you start using the debugger, you must first compile your game program with the special option that enables the generation of source-level debugging information in the binary (.GAM) file that the compiler produces. On most systems, this means that you must include the -ds option on the compiler command line, along with any other options that you normally use.

Other than including the -ds option on the compiler command line, compiling with debugging information is exactly the same as compiling without the extra information. The compiler still produces a .GAM file as usual, and you can still use this .GAM file with the TADS run-time to play the game. However, the .GAM file will be somewhat larger when you use the -ds option, because the compiler includes extra information that the debugger uses to identify the source code and variables within your game.

The fact that the .GAM file is larger when you include debugging information is the main reason not to use the -ds option at all times. When you’re ready to give your game to other people, you probably will want to recompile without the -ds option so that the .GAM file is smaller. Note that the other reason is that compiling with debugging information means that certain text strings within the game are left as plain ASCII. That would enable a player to open the game file using an editor and extract puzzle-spoiling text.


Starting the Debugger

On most systems, you start the debugger by typing the tdb command. This command is very similar to the tr command that you use to run your game normally. In fact, most of the same options can be used with the debugger, in addition to a couple of special debugger options. With the exception of the -i, -o, and -l options, which are all used for the run-time’s script processing feature, the debugger accepts all run-time options.

In addition, the debugger accepts the -mp size option, which has the same meaning as with the compiler: set the parse node pool to size bytes. The reason that this option is used by the debugger as well as the compiler is that the debugger uses the same subsystem as the compiler to parse expressions. This means that you can use the same types of expressions that you use in your source code when typing certain commands to the debugger.

The debugger accepts another special option: -i path. This option is very similar to the -i path option accepted by the compiler. With the debugger, this option indicates a directory that should be searched for source code. The only difference between the -i option of the debugger and that of the compiler is that the debugger will search each directory specified with a -i option for the main source file as well as header (#include) files. Normally, you will use the same set of -i options with the debugger that you use with the compiler.

Finally, the -D and -U options allow you to define and undefine preprocessor symbols from the command line. Consult the section in chapter 5 called “Preprocessor Directives”.

Here’s an example debugger command line. This command starts the debugger and loads the binary game file MYGAME.GAM. The debugger will search the directory c:\tads for source files that it doesn’t find in the current directory.

  tdb -i c:\tads mygame 

Note that the debugger looks for a configuration file named CONFIG.TDB, in the same way that the compiler uses CONFIG.TC. Refer to Appendix C, which describes the TADS Compiler, for information about using configuration files. The debugger uses the same method as the compiler to find and interpret the configuration file.

Once you’re running the debugger, you can get help at any time by typing “?” or “help”. On MS-DOS, you can also press the function key F1. The debugger will display a brief list of commands (and function keys, if appropriate for your operating system) in the source window.

Note that the debugger is not case-sensitive. For example, the command “BP” is equivalent to “bp.”

The debugger will take control when a run-time error occurs. The error message will be displayed, and execution will be suspended at the line where the error occurred. When you resume execution, the current command will be aborted (as though an abort statement had been executed).

The debugger will also stop at a breakpoint in a method inherited by an object. For example, if you set a breakpoint at room.lookAround, execution will stop any time a subclass of room executes the inherited lookAround method. Of course, if a subclass overrides the lookAround method, execution will not stop at room.lookAround.


Screen Layout

The screen layout used by the debugger varies by system, but each system shows roughly the same information. On some machines (such as MS-DOS PCs), the debugger actually uses two different “screens” to display information. These aren’t actually two physical monitors, but two virtual monitors that you can switch between; the debugger will normally display the active virtual monitor, and you can switch to the other screen by pressing a certain function key or typing a command. On other systems, such as the Macintosh or Win32 running the new Workbench software, the debugger uses windows to display the different areas.

On two-screen systems, the first screen is the normal run-time system screen, showing the status line, the output of your game, and the player’s commands. The second screen is the debugger screen, which shows source code, variable values, a debugger status line that indicates the current state of execution of your game (such as which source code line will be executed next), and the debugger command and display area. On windowing systems, these areas will be displayed in different windows all on one screen, and you can use your mouse as usual to bring different windows to the foreground.


Controlling Execution

The debugger gives you control over your game’s execution. When the debugger first starts, it will load your game and wait for a command. Nothing in your game will have been executed at this point, so the debugger source code area will show your preinit() function; the first line of this function will be highlighted, which indicates that it is the next line to be executed.

At this point, your game is “suspended.” This means that the debugger has control, not your game. Execution of your game can be suspended in a number of ways: after stepping by one line, after hitting a breakpoint, immediately after entering the debugger, or after your game calls the debugTrace() built-in function. When your game is suspended, you can type debugger commands, and you can return control to your game at any time.

To start your game running when it’s suspended, you can use the g (for “go”) command. This command resumes execution of your game. After typing g, your game will execute until something causes it to be suspended, such as a breakpoint or a call to the debugTrace() function.

You can also step through a single line of your game’s source code by using the t (for “trace”) command. This command simply executes the current line and then suspends execution again; if the current line calls another method or function, the debugger will step into that function, and suspend your game before executing any lines in the new function.

You can also step a single line using the p (for “procedure trace”) command. This command is similar to the t command, in that it executes a single line of code; however, unlike the t command, the p command steps over, rather than into, functions and methods that are called by the current line of code. That is, if a function or method is called, the debugger will execute the entire called function (and any functions and methods it calls) without stopping. After the p command, execution is suspended at the next line of source after the current line.


Breakpoints

You can arrange for execution to be suspended at a particular point in your program by setting a “breakpoint.” To set a breakpoint, use the bp (for “break point”) command: type bp followed by the name of a function, or object.property, where you want execution to be suspended.

You can set many breakpoints. Use the bl (“breakpoint list”) command to list the breakpoints that are currently active. Each breakpoint in the list is given a number; you use this number to refer to the breakpoints you have previously set.

To remove a breakpoint, use the bc (“breakpoint clear”) command. Type bc followed by the number (from the bl command’s output) of the breakpoint you wish to remove. You can also temporarily disable a breakpoint without removing it entirely by using the bd (“breakpoint disable”) command. When a breakpoint is disabled, the debugger remembers the breakpoint, but doesn’t do anything about it; that is, when the breakpoint is hit, execution continues anyway as though no breakpoint were set there. You can re-enable a disabled breakpoint with the be (“breakpoint enable”) command.

You can also set a breakpoint at a particular line of source code by pointing at the line of code and using a function key or menu selection. On MS-DOS PCs, use the arrow keys to position the cursor at the line where you wish to set a breakpoint, and press the F2 function key; this will highlight the line with a special color to indicate that a breakpoint is set there.

You can also set breakpoints that only suspend execution when a specific condition is met. You do this by adding the keyword when followed by an expression to a normal bp command; the breakpoint only stops execution when the particular line of source is executed and the expression is true. For example, to set a breakpoint when the sleepDaemon() function is called, but only when the player is in the room kitchen, you would specify this:

  bp sleepDaemon when Me.location = kitchen 

You can also set a “global breakpoint”; this is a breakpoint that stops execution when an expression becomes true, regardless of which line in your game is being executed. To set a global breakpoint, simply omit the location; for example, to stop execution when the player moves into the kitchen:

  bp when Me.location = kitchen 

Note that the debugger automatically disables a global breakpoint when it is hit. This is because, once the condition becomes true, it will generally remain true for a while; if the breakpoint remained enabled, g would be the same as t, since the breakpoint would be hit on every line as long as the condition is true. You can re-enable the breakpoint with the be command.


Where Am I?

When your game is suspended, you can determine what line in your game is about to be executed, and how you got there, using the k (“stacK”) command. This command displays the function or method that is currently executing, followed by the function or method that called the current function, followed by the function or method that called it, and so forth. The run-time system’s player command parser is always the last thing in this chain (although the debugger doesn’t display “player command parser” anywhere, since it’s not part of your game program, but part of the run-time system).


Examining Variables

Any time your game is suspended, you can examine the value of any variable or property in your program. Use the e (“evaluate”) command to examine a value: type e followed by an expression. The expression is exactly the same as you would use in a source program. For example, to check the player’s current location:

  e Me.location 

You can use a local variable in an expression, but only if that local variable is currently active. Naturally, you can’t use a local variable from a function that isn’t currently executing.

You can look at the value of a local variable from the current function, or even from a caller of the current function (see the k command above). Use the u (“up”) and d (“down”) commands to select the active function. Whenever your program is suspended, the debugger sets the active function to the current function, so you can look at variables in the current function. To look at a variable in the caller of the current function, use the u command to set the active function to the caller of the current function. Use the u command to set the active function to the caller of that function. Use the d command to go back down the stack chain.

Note that the k command displays an asterisk next to the active function. You can use the k command if you forget how many times you’ve typed u and d. Note also that the u and d commands don’t have any effect on your program’s execution; they simply tell the debugger which function’s variables you want to use in expressions.

If you want to change the value of a variable, you can simply evaluate an expression that involves an assignment. For example, to change the value of the local variable cnt to 3, you would type this:

  e cnt := 3 

You should use assignments with extreme care, because you could accidentally introduce an inconsistentency that confuses your game.

If you evaluate an expression that displays double-quoted text strings, or calls the say() built-in function, the text will be displayed in the game window. This may cause confusing displays, because the text will simply be added to whatever was already in the game window. Note that, on MS-DOS, you’ll have to use the F5 key (or the \ command) to switch to the game screen to see the results of any double-quoted strings displayed by the e command.

In addition, since the run-time system formats text displayed in the game window, you may sometimes find that double-quoted text isn’t immediately displayed. This is because the run-time system saves up text until it has enough to fill an entire line, then displays the text all at once. If this happens, you can force the debugger to display the text by evaluating a double-quoted string that contains a newline. For example:

  e "\n" 

This will force the debugger to display any text that it has been saving.


Watch Expressions

You will often want to see the value of a variable, property, or other expression as it changes while you’re stepping through code. You could repeatedly use the e command to evaluate the expression, but the debugger makes this more convenient by allowing you to enter “watch expressions.” Once you create a watch expression, the debugger will display the current value of the expression in a special area on the screen. The current value will be updated on the screen whenever the expression’s value changes. This means that you can monitor a set of expressions without having to type the e command repeatedly.

To set a watch expression, use the ws (“watch set”) command. Type ws followed by an expression. The debugger will add the expression to the watch area.

To delete a watch expression, use the wd (“watch delete”) command. Type wd followed by the watch expression number (which is displayed next to the watch expression in the watch window).

Note that watch expressions do not display any double-quoted string text, or any text displayed with the say() built-in function. If any double-quoted text is displayed in the course of evaluating a watch expression, the text is discarded. In general, it’s not a good idea to evaluate expressions with side effects (such as assignments) in watch expressions, because the expressions are evaluated repeatedly, and at times that you can’t directly control.

If you set a watch expression that involves a local variable, the expression will only be meaningful when the local variable is active - that is, when the block that contains the local variable is being executed. When execution moves outside of the block that contains the local variable, TDB will not be able to evaluate the expression, and will simply display an error message in place of a value for that watch expression. When execution returns to the block that contains the local variable, TDB will once again be able to display a value for the expression.


Source Window Commands

The debugger provides a couple of commands for displaying other source files in the source window, and for repositioning the source window display.

To list the source files that make up your game, use the fl (“file list”) command. This command displays a list of the source files in your game, giving each file a number. You can use this number as a shortcut with the fv command.

To display a different file in the source window, use the fv (“file view”) command. Type fv followed by either a filename or a file number (this number comes from the output of the fl command). The source window will show the newly selected file.

You can search for text within the current source file using the / command. Type / followed immediately (without any spaces) by the string you want to find. The debugger will position the cursor at the first occurrence of the text within the file, if it was found, or at the end of the file if not. To search again for the same text, type / without anything following it. Note that the cursor will be moved to the source window after searching for text.


Logging Commands

The debugger has a mechanism that lets you capture a log of all method and function calls, and then inspect the log. This feature can help you determine the exact sequence of calls that TADS itself makes into your game, and also lets you see how your game and lower-level classes (such as those from adv.t) interact. Four commands support this feature. These are also accessible from the “Execution” menu on the Macintosh.

c+ Begins call logging. Any previous call log is cleared, and all subsequent method and function calls and returns will be added to the new call log.
c- Ends call logging.
cc Clears the current call log.
c Displays the current call log. All function and method calls after the most recent c+ (up until the current time or the most recent c-) are displayed.

The reason that commands are provided to turn call logging on and off is that the logging process can slow down your game’s execution substantially, because the system must do extra work every time a function or method is entered and exited. You should enable call logging at the point you’re about to execute a command that you want to trace through its execution, then turn it off when you’re finished with the command.

Note that the call log is limited in capacity. If the log becomes full, the oldest information is discarded to make room for the new information. If you leave call logging activated for an extended period of time, information toward the beginning of the log may be lost.

The lines of text displayed in the call log will be indented to show nesting. The functions and methods called directly by TADS will not be indented at all; anything called by these functions and methods will be indented one space, and so on. If a function or method has a return value, it will be indicated in the log (prefixed by =>) at the point when the function or method returns. Each call will show the object and method or function name involved, along with the arguments; the format is the same as in the stack trace.


Miscellaneous Commands

To quit the debugger, use the quit command.

You can get help with the debugger’s commands and function keys by using the ? command; the help command is equivalent. The debugger will display in the source window a file with a brief description of TDB’s commands and function keys.

It is possible to break out of an infinite loop in your game. Hit Ctrl-Break on a DOS machine, and command-period on a Macintosh, if your game goes into an infinite loop. Control will return to the debugger.


TDB on MS-DOS

On MS-DOS, the Debugger makes use of screen switching. This means that your monitor is completely filled with either your game’s output, or with the debugger screen. While your game is active, it takes up the entire screen; it looks exactly like it does when you run with the normal TADS run-time. When the debugger has control (after a breakpoint has been encountered, for example), the debugger takes up the entire screen.

You can switch the display back to the game screen by pressing F5, or by using the “\” command. This will display the game screen; press any key to return to the debugger screen. Note that you can’t type any commands into the game, since it’s suspended.

The debugger’s screen is separated into three windows. The top window is the source window; this window displays the current source file (although you can also display other source files from your game, or any other text file, in this window; see the fv command above). The bottom of the source window is a status line that shows you the function or method where execution is currently suspended, and the name of the file that contains that code.

The bottom window is the command window. You type commands to TDB and see the results in this window. When the debugger is ready for a command, it will show you this prompt:

  tdb> 

The cursor will be positioned after the prompt. You can now type a command to the debugger (terminated with the Return or Enter key), and the results will be displayed in the command window.

The middle window is the watch expression window. This window is only displayed when one or more watch expressions are active, so it is not shown when you first enter the debugger. The debugger automatically adjusts the size of this window to make room for the watch expressions you have entered. See the discussion of watch expressions earlier in this chapter for information on creating watch expressions.


Function Keys on MS-DOS

This section lists the special function keys used in the MS-DOS version of the TADS Debugger. Note that some of these keys can be used only in the source window; to move the cursor to the source window from the command window, press the Tab key. Note also that most of the functions performed by these keys can be obtained with a command typed in the command window; however, the function key equivalent is often more convenient.

F2: (Used only when the cursor is in the source window.) Sets or clears the breakpoint at the current line (the line at which the cursor is positioned). This is similar to the bp command, except that it allows you to set a break point at any executable line of code, rather than just at the start of a function or method.

Shift-F2: (Used only when the cursor is in the source window.) Sets a breakpoint at the current line, with a conditional expression. The debugger will prompt you for a conditional expression; type an expression and hit the Enter key. This is similar to the bp command using the when keyword, but allows you to set a conditional breakpoint at any executable line of code.

F5: Show the game screen. When the game is suspended, the game output is replaced on your monitor by the debugger screen; hitting F5 switches back to the game screen. Hit any key to return to the debugger screen. This is the same as the \ command.

F7: Same as t: trace a line of code, stepping into functions and method calls.

F8: Same as p: trace a line of code, stepping over function and method calls.

TAB: Switch windows. When the cursor is in the command area, it moves to the source window; when in the source window, it moves back into the command area.

Up and Down arrows: When the cursor is in the command area, these keys scroll the source display up and down one line. When in the source window, they move the cursor up and down one line.

PgUp and PgDn keys: Scrolls text in the source window up or down one page (i.e., the number of lines of text displayed in the window).

Ctrl-PgUp and ctrl-PgDn: Moves text in the source window to the top or bottom (respectively) of the source file.

Ctrl-Home: Shows current line of source in the source window. If you have switched to another file with the fv command, the source file containing the current line will replace the other file in the source window.


TDB on Macintosh

When you start the Macintosh version of TDB, a dialog will let you select the game you want to debug and any additional information you need to specify. The information that you specify with command line options on other operating systems is entered through this dialog on the Macintosh. Once you have entered all of the necessary information, press the “OK” button to start the debugging session.

The debugger has a window for your game’s output, and a separate window for entering debugger commands and viewing debugger output. An additional window displays the current source file (although you can use this window to view another file using the fv command, described earlier in this chapter). In addition, a separate window displays watch expressions.


Menu Commands

You can access most debugger commands directly from the menu bar. A command selected from the menu bar uses a dialog to ask you for any additional information required for the command. Note that many of the menu items have associated Command keys, so you can select these items with a single keystroke. For example, you can press Command-G to resume execution (the g command), or Command-T to step one line (the t command).


Source Window

You can set breakpoints using the mouse in the source window. To set a breakpoint at a particular line, simply move the mouse over that line in the source window, and click the mouse. A small diamond mark will appear in the left margin of that line, indicating that a breakpoint is set at that line. (Note that breakpoints that you set using the bp command will also show up in the source window with a diamond mark.) To remove the breakpoint, click on the line again, and the diamond mark will disappear.

If you hold down the Option key when you click the mouse on a line in the source window, the debugger will allow you to set a conditional breakpoint on that line. A dialog box will appear; type an expression in the dialog box and select the “OK” button to set a breakpoint with a condition.

Whenever the debugger gets control, it will update the source window to display the next line to be executed (which is referred to as the current line), with a small arrow to the left of the line. When your game is running, the debugger does not keep the current line display up-to-date; the current line arrow is only meaningful when the debugger has control.


Watch Window

The watch window is displayed whenever one or more watch expressions are active; it is removed from the screen when no watch expressions are set.

You can delete a watch expression directly from the watch window. Click the mouse on a watch expression; it will be highlighted to show that it has been selected. Now you can use either the delete key or the “Clear” item in the “Edit” menu to delete the watch expression.

Note that watch expressions are kept up-to-date while the game is running, even when the debugger doesn’t have control.


Who has control?

On MS-DOS, it’s always obvious whether the debugger or the game has control, because the entire screen displays one or the other. Under Macintosh or Windows, however, all of the windows are always on the screen, so it may sometimes not be entirely obvious whether your game is running or suspended. The way to tell is that you can only enter commands in either the game window or the debugger window. When your game is active, you can only enter commands into the game window; when your game is suspended (so the debugger has control), you can only enter commands in the debugger command window. You can also tell by looking at the debugger menus: all of the debugger menu items are disabled while your game is running, except for the “Stop” item under the “Execution” menu. While the debugger has control, everything except the “Stop” item is enabled.



Give us the tools, and we will finish the job.
WINSTON CHURCHILL, Radio broadcast (1941)


Appendix C Table of Contents Appendix E