Brief lull in my WIP, taking the opportunity to update my debug module. There is a lot of code here for some pretty modest (but useful!) functionality additions. Note this builds on the work discussed here. Specifically, because of an apparent bug in frobtads, there are more gymnastics here than usual to get dynamic code to work. Seems to be pretty cross-platform at this point, tested under Lectrote, Gargoyle, QTADS, Frobtads and Parchment.
Previous functionality:
QUIET mode, to disable/enable output, mostly used when processing transcript to advance gamestate to debug point
DEBUG mode, to report ‘spy list’ values every turn
Additional functionality (work independent of DEBUG mode)
Peek (DX) - display property and some method values
Poke(DS) - set property values, not tested for complicated methods
(supports both >ds obj.property
and >ds obj.property = val
syntax. Former case will prompt for val)
Spyadd (DA) - add property to ‘spy’ list of reported properties echoed every turn
Spydel (DD) - remove property from same list
Note Spy list can ALSO be seeded directly in code, as might be more convenient for a drawn out debug to play with a static list for a while. DA/DD will append to or prune list, including initial seed.
Included is a string preProcessor that allows you to omit quotes around the symbols in question.
#charset "us-ascii"
/* Super Basic Debug Manager...
*
* Include the main header for the standard TADS 3 adventure library.
* Also include the US English definitions, since this game is written
* in English.
*/
#include <adv3.h>
#include <en_us.h>
#include <dynfunc.h>
#include <tads.h>
// GENERAL MACRO DEFs, opportunistically for pasting to other files
#define gDebug (debugMgr.debugEnabled)
#define gQuiet (debugMgr.quietEnabled)
#define v2s(Val) reflectionServices.valToSymbol(Val)
/*
* Macro Defs for readability
*
* DEBUG EXAMINE: dx(property) : JZ workaround to frobs bug, unclear why needed!
*
* SPYLIST SHORTHAND: spy(property) : adds to lookup list, in way that facilitates frequent edits
*/
// thanks JZ!
#define dx(val) { \
local str = '\n'## #@val ## ' = <<reflectionServices.valToSymbol(val)>> \n'; \
if(gTranscript) \
extraReport(str); \
else say(str); }
#define spy(val) ''## #@val ##'' -> function() { return reflectionServices.valToSymbol(val); }
/*
* DEBUG VERBS
*
* QUIET: disables output, intended for use when importing transcript to FF gamestate to debug area
*
* SPYADD/SPYDEL: adds/deletes properties from spy list - list of signals reported every turn when DEBUG mode enabled
* [shortcuts are da/dd]
*
* PEEK: report current value of any property [shortcut dx]
*
* POKE: deposit value in any property [shortcut ds]
*
* DEBUG: enter debug mode, really just turn on SPYLIST reporting
*
* Most make use of DynFunc capabilities to compile string variables as executable code.
* Note Frobtads bug prevents use of Compiler.eval()
*/
// Adding output disable capability, specifically for debug FFW
//
modify mainOutputStream
outputOn = true
enable() { outputOn = true; }
disable() {outputOn = nil; }
writeFromStream(txt) {
/* if an input event was interrupted, cancel the event */
inputManager.inputEventEnd();
/* write the text to the console */
if (outputOn) aioSay(txt);
}
;
DefineSystemAction(Quiet)
execSystemAction() {
gQuiet = (gQuiet ? nil : true);
if (gQuiet) {
"##QUIET MODE ENABLED\n";
mainOutputStream.disable();
} else {
mainOutputStream.enable();
"##QUIET MODE DISABLED\n";
}
}
actionTime = 0
;
VerbRule(Quiet)
('quiet') : QuietAction
verbPhrase = 'quiet/quieting'
;
//======================= SPY ADD ========================
//
DefineLiteralAction(SpyAdd)
execAction() {
Compiler.compile('function() { debugMgr.spyProps[\'' + gLiteral + '\'] = new function() { return reflectionServices.valToSymbol(' + gLiteral + '); }; }')();
debugMgr.debugPrint();
}
// this is a meta-command, so don't consume any time
actionTime = 0
;
VerbRule(SpyAdd)
('spyadd' | 'da') singleLiteral : SpyAddAction
verbPhrase = 'spy add/adding (what)'
;
//======================= SPY DEL ========================
//
DefineLiteralAction(SpyDel)
execAction() {
Compiler.compile('function() { debugMgr.spyProps.removeElement(\'' + gLiteral + '\'); }')();
debugMgr.debugPrint();
}
// this is a meta-command, so don't consume any time
actionTime = 0
;
VerbRule(SpyDel)
('spydel' | 'dd') singleLiteral : SpyDelAction
verbPhrase = 'spy delete/deleting (what)'
;
//======================= PEEK ========================
//
DefineLiteralAction(Peek)
// via JZ...
execAction() {
Compiler.compile('function() { dx(' + gLiteral + '); }')();
}
// this is a meta-command, so don't consume any time
actionTime = 0
;
VerbRule(Peek)
('peek' | 'dx') singleLiteral : PeekAction
verbPhrase = 'peek/peeking (into what)'
;
//======================= POKE ========================
//
DefineLiteralAction(Poke)
execAction() {
// local syntaxOk = true;
if (debugMgr.pokeVal == nil) {
"DEBUG: enter value >";
debugMgr.pokeVal = inputManager.getInputLine(nil, nil);
}
Compiler.compile('function() { ' + gLiteral + ' = ' + debugMgr.pokeVal + '; }')();
nestedAction(Peek, gLiteral);
// Abandoned syntax checking attempt
//
// local setFn = Compiler.compile('function() { ' + gLiteral + ' = ' + val + '; }');
/* try { setFn(); }
catch(Exception err) { "DEBUG: <<err.exceptionMessage>>\n"; syntaxOk = nil; }
finally {
if (syntaxOk) {
setFn();
"<<gLiteral>> set to <<val>>";
}
} */
}
// this is a meta-command, so don't consume any time
actionTime = 0
;
VerbRule(Poke)
('poke' | 'ds' ) singleLiteral : PokeAction
verbPhrase = 'poke/poking (into what)'
;
//======================= DEBUG ========================
//
modify DebugAction
execAction() {
gDebug = (gDebug ? nil : true);
"##DEBUG MESSAGES <<gDebug ? 'ENABLED' : 'DISABLED'>>";
if (gDebug) new Daemon(debugMgr, &debugPrint, 1);
else eventManager.removeMatchingEvents(debugMgr, &debugPrint);
/* ORIG: if the debugger is present, break into it
if (t3DebugTrace(T3DebugCheck))
t3DebugTrace(T3DebugBreak);
else
"Debugger not present. "; */
}
;
/*
* DEBUG MGR
* manages quiet/debug states and debugPrint method reports SPYLIST every turn
* can either pre-define with spy(prop) macros, or add/del at runtime
* also stores set value for poke command, as needed
*
* Note Spylist is a lookup table where entries are of format 'property-as-string'-> anonFn() { return v2s(property); }
* this was needed to ensure current values always reported, not just value at time of adding property to list
* spy(prop) macro just makes it easier to add properties offline
*
*/
debugMgr : object
debugEnabled = nil
quietEnabled = nil
pokeVal = nil // value to set property to for ds command
// compile-time spy list, da adds to this, dd removes
spyProps = [spy(me.location)]
debugPrint() {
if (spyProps.keysToList().length() < 1)
"DEBUG_ERR: NO PROPERTIES SPIED! Add with \'>spyadd [prop]\'";
else {
"<.p>";
spyProps.forEachAssoc(function(key, val) {
"DEBUG: <<key>> = <<spyProps[key]()>>\n";
});
}
}
;
/*
* PREPARSE command string to allow Literal input without
* surrounding quotes. Prevents parser tripping over periods
*/
StringPreParser
doParsing(str, which) {
local pokeRegex = R'^(<^Space|=>+)<Space>*=<Space>*(.*)$';
local dbgRegex = R'<NoCase>^<Space>*(peek|dx|spyadd|da|poke|ds|spydel|dd)<Space>+(.*)$';
local trailSpaceRegex = R'(.*<^Space>)<Space>+$';
local quoteLeadRegex = R'^(\'|\")';
local quoteTrailRegex = R'(.*)(\'|\")<Space>*$';
local leadQuote = '\'';
local propStr = '';
local cmd = '';
if (rexMatch(dbgRegex, str)) {
propStr = rexGroup(2)[3];
cmd = rexGroup(1)[3];
debugMgr.pokeVal = nil;
if ((cmd is in ('ds', 'poke')) && (rexMatch(pokeRegex, propStr))) {
propStr = rexGroup(1)[3];
local pokeVal = rexGroup(2)[3];
if (rexMatch(quoteTrailRegex, pokeVal)) pokeVal = rexGroup(1)[3]; // trim trailing quote
if (rexMatch(trailSpaceRegex, pokeVal)) pokeVal = rexGroup(1)[3]; // trim trailing spaces
debugMgr.pokeVal = pokeVal; // save set value in Mgr
}
if (rexMatch(trailSpaceRegex, propStr)) propStr = rexGroup(1)[3]; // trim trailing spaces
if (!rexMatch(quoteLeadRegex, propStr)) propStr = leadQuote + propStr; // add leading quote if missing
else leadQuote = rexGroup(1)[3]; // in case lead with \"
if (!rexMatch(quoteTrailRegex, propStr)) propStr = propStr + leadQuote; // add trail quote if missing
str = cmd + ' ' + propStr;
}
return str;
}
;
This lets you do the following:
>dx me.age18plus
me.age18plus = nil
>ds me.age18plus
DEBUG: enter value >true
me.age18plus = true
>ds me.age18plus = nil
me.age18plus = nil
>da me.age18plus
DEBUG: me.location = startRoom
DEBUG: me.age18plus = nil
>dd me.location
DEBUG: me.age18plus = nil
QUIET and DEBUG work as before. I use these capabilities A LOT.