Rudimentary interactive(-ish) debugger implemented in T3/adv3

I don’t use the workbench and there’s no (afaik) standalone T3 debugger, so I’ve accumulated a bunch of little kludges that I’ve been using to debug T3 code. After mentioning this in another thread, I decided to clean up the code (as much as is possible given it’s inherently kludge-y nature) and put it up as a git repo.

It’s now available here.

It’s all implemented in T3, so there are a number of limitations. But I find it marginally more useful that debug-by-printf, which is more or less all you’re left with without a real debugger.

A couple of examples to illustrate. First, a simple “game” containing the player and a pebble. The pebble has a property, foozle.

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

versionInfo:    GameID;

startRoom: Room 'Void'
        "This is a featureless void."
;
+me: Person;
+pebble: Thing 'small round pebble' 'pebble'
        "A small, round pebble, marked <q>foozle = <<toString(foozle)>></q>. "

        foozle = 0
;

gameMain: GameMainDef initialPlayerChar = me;

Compiled with the debugger module, you can then drop into the debugger at any point by using the >BREAKPOINT command, and from there use the debugger as an expression evaluator to twiddle the value of pebble.foozle:

Void
This is a featureless void.

You see a pebble here.

>x pebble  
A small, round pebble, marked "foozle = 0".

>breakpoint
===breakpoint in {obj:predicate(DebugToolBreakpoint)}.execSystemAction()
../debugToolActions.t, line 23===
===type HELP or ? for information on the interactive debugger===
>>> pebble.foozle=69105
69105
>>> exit
Exiting debugger.

>x pebble
A small, round pebble, marked "foozle = 69105".

>

You can also set a compile-time flag to have the game drop into the debugger on any runtime error or “generic” unhandled exception (the parser uses exceptions to control command processing and those exceptions are all ignored by the debugger). So tweaking our example code to:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

versionInfo: GameID;

startRoom: Room 'Void'
        "This is a featureless void."
;
+me: Person;
+pebble: Thing 'small round pebble' 'pebble'
        "A small, round pebble. "

        foozle = nil

        dobjFor(Take) {
                // This since foozle is nil, this will throw a runtime
                // error, dropping us into the debugger.
                action() {
                        foozle += 1;
                        inherited();
                }
        }
;

gameMain: GameMainDef initialPlayerChar = me;

Here we’ve defined pebble.foozle = nil, and then in pebble.dobjFor(Take) we try to pebble.foozle += 1, which will throw a runtime error. If we compile the game with the debugger and the -D DEBUG_TOOL_CATCH_ALL flag, we get (I’ve trimmed the actual exception output for readability here, it’s printed in full in actual usage):

Void
This is a featureless void.

You see a pebble here.

>take pebble
[Runtime error: invalid datatypes for addition operator
   ...
===breakpoint in pebble.actionDobjTake() src/catch.t, line 65===
===type HELP or ? for information on the interactive debugger===
>>> 

From there you can look up and down the stack, and look at the contents of each frame, including (for example) examining the self object defined in each frame. So for example, for the exception thrown by pebble.dobjFor(Take), you can

===breakpoint in pebble.actionDobjTake() src/catch.t, line 65===
===type HELP or ? for information on the interactive debugger===
>>> self
   self:
       pebble
           adjective = ['small', 'round']
           explicitVisualSenseInfo = nil
           foozle = nil
           holdingIndex = 2
           location = startRoom
           name = 'pebble'
           noun = ['pebble']
           seen = true
           sentinelDobjTake = TakeAction
           sourceTextOrder = 4
           tmpAmbientFill_ = nil
           tmpAmbientWithin_ = 0
           tmpAmbient_ = 0
           tmpFillMedium_ = nil
           tmpObstructorWithin_ = nil
           tmpObstructor_ = nil
           tmpPathIsIn_ = true
           tmpTransWithin_ = opaque
           tmpTrans_ = transparent
           vocabWords = 'small round pebble'
>>> 

There’s also a very rudimentary source viewer (which may require some jiggling of the handle to get the path names right, and which will probably require running your interpreter with lower safety settings than normal in order to let it view files outside of the directory the game file was loaded from) and a few other features.

But it is not really a full interactive debugger. You can’t step through execution, for example, and you can’t directly tweak local variables in arbitrary stack frames. So it’s not really a substitute for a real standalone debugger.

But at least for my purposes, lacking a real debugger it’s better than nothing.

9 Likes

Minor update: following discussion in another thread, I’ve made a minor fork of the debugger module to work with adv3lite. Adding a note here because future folks reading this are likely to have come here via search.

The new version is called debugToolLite, and the repo is here.

The behavior is (or at least should be) identical to that of the “base” version. I’m not a regular user of adv3lite, so if anyone runs into any adv3lite-specific problems, let me know.

3 Likes