diff options
author | Max Horn | 2006-09-16 16:58:27 +0000 |
---|---|---|
committer | Max Horn | 2006-09-16 16:58:27 +0000 |
commit | 919092e5fcd0389b4cbd862e9e8cceab202e6741 (patch) | |
tree | 7d9c8f643191812bb1eb24f2e7cc2e67586149d5 /gui | |
parent | 1add07becae0179e026a90924b419b74ffb1078f (diff) | |
download | scummvm-rg350-919092e5fcd0389b4cbd862e9e8cceab202e6741.tar.gz scummvm-rg350-919092e5fcd0389b4cbd862e9e8cceab202e6741.tar.bz2 scummvm-rg350-919092e5fcd0389b4cbd862e9e8cceab202e6741.zip |
Overhaul of the debugger code
* Moved Common::Debuggger to GUI::Debugger (mainly to satisfy linker
restrictions)
* Change the base Debugger class to *not* be a template class anymore;
instead, a thin (template based) wrapper class is used to hook up
debugger commands
* Removed duplicate Cmd_Exit and Cmd_Help methods in favor of a single
version of each in GUI::Debugger
* New Cmd_Help doesn't word wrap after 39/78 chars, but rather queries
the console to determine when to wrap
* Debugger::preEnter and postEnter aren't pure virtual anymore
svn-id: r23890
Diffstat (limited to 'gui')
-rw-r--r-- | gui/console.h | 2 | ||||
-rw-r--r-- | gui/debugger.cpp | 466 | ||||
-rw-r--r-- | gui/debugger.h | 158 | ||||
-rw-r--r-- | gui/module.mk | 1 |
4 files changed, 626 insertions, 1 deletions
diff --git a/gui/console.h b/gui/console.h index d6ac4e3350..8079e2620b 100644 --- a/gui/console.h +++ b/gui/console.h @@ -36,7 +36,6 @@ public: typedef bool (*InputCallbackProc)(ConsoleDialog *console, const char *input, void *refCon); typedef bool (*CompletionCallbackProc)(ConsoleDialog* console, const char *input, char*& completion, void *refCon); -protected: enum { kBufferSize = 32768, kCharsPerLine = 128, @@ -45,6 +44,7 @@ protected: kHistorySize = 20 }; +protected: const Graphics::Font *_font; char _buffer[kBufferSize]; diff --git a/gui/debugger.cpp b/gui/debugger.cpp new file mode 100644 index 0000000000..cde8b22a03 --- /dev/null +++ b/gui/debugger.cpp @@ -0,0 +1,466 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/stdafx.h" + +#include "common/system.h" + +#include "gui/debugger.h" +#if USE_CONSOLE + #include "gui/console.h" +#endif + +namespace GUI { + +Debugger::Debugger() { + _frame_countdown = 0; + _dvar_count = 0; + _dcmd_count = 0; + _detach_now = false; + _isAttached = false; + _errStr = NULL; + _firstTime = true; + _debuggerDialog = new GUI::ConsoleDialog(1.0, 0.67F); + _debuggerDialog->setInputCallback(debuggerInputCallback, this); + _debuggerDialog->setCompletionCallback(debuggerCompletionCallback, this); + + //DCmd_Register("continue", WRAP_METHOD(Debugger, Cmd_Exit)); + DCmd_Register("exit", WRAP_METHOD(Debugger, Cmd_Exit)); + DCmd_Register("quit", WRAP_METHOD(Debugger, Cmd_Exit)); + + DCmd_Register("help", WRAP_METHOD(Debugger, Cmd_Help)); + + DCmd_Register("debugflag_list", WRAP_METHOD(Debugger, Cmd_DebugFlagsList)); + DCmd_Register("debugflag_enable", WRAP_METHOD(Debugger, Cmd_DebugFlagEnable)); + DCmd_Register("debugflag_disable", WRAP_METHOD(Debugger, Cmd_DebugFlagDisable)); +} + +Debugger::~Debugger() { + for (int i = 0; i < _dcmd_count; i++) { + delete _dcmds[i].debuglet; + _dcmds[i].debuglet = 0; + } + delete _debuggerDialog; +} + + +// Initialisation Functions +int Debugger::DebugPrintf(const char *format, ...) { + va_list argptr; + + va_start(argptr, format); + int count; +#if USE_CONSOLE + count = _debuggerDialog->vprintf(format, argptr); +#else + count = ::vprintf(format, argptr); +#endif + va_end (argptr); + return count; +} + +#ifndef __SYMBIAN32__ // gcc/UIQ doesn't like the debugger code for some reason? Actually get a cc1plus core dump here :) +void Debugger::attach(const char *entry) { + + g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, true); + + if (entry) { + _errStr = strdup(entry); + } + + _frame_countdown = 1; + _detach_now = false; + _isAttached = true; +} + +void Debugger::detach() { + g_system->setFeatureState(OSystem::kFeatureVirtualKeyboard, false); + + _detach_now = false; + _isAttached = false; +} + +// Temporary execution handler +void Debugger::onFrame() { + if (_frame_countdown == 0) + return; + --_frame_countdown; + + if (!_frame_countdown) { + + preEnter(); + enter(); + postEnter(); + + // Detach if we're finished with the debugger + if (_detach_now) + detach(); + } +} +#endif // of ifndef __SYMBIAN32__ // gcc/UIQ doesn't like the debugger code for some reason? Actually get a cc1plus core dump here :) + +// Main Debugger Loop +void Debugger::enter() { +#if USE_CONSOLE + if (_firstTime) { + DebugPrintf("Debugger started, type 'exit' to return to the game.\n"); + DebugPrintf("Type 'help' to see a little list of commands and variables.\n"); + _firstTime = false; + } + + if (_errStr) { + DebugPrintf("ERROR: %s\n\n", _errStr); + free(_errStr); + _errStr = NULL; + } + + _debuggerDialog->runModal(); +#else + // TODO: compared to the console input, this here is very bare bone. + // For example, no support for tab completion and no history. At least + // we should re-add (optional) support for the readline library. + // Or maybe instead of choosing between a console dialog and stdio, + // we should move that choice into the ConsoleDialog class - that is, + // the console dialog code could be #ifdef'ed to not print to the dialog + // but rather to stdio. This way, we could also reuse the command history + // and tab completion of the console. It would still require a lot of + // work, but at least no dependency on a 3rd party library... + + printf("Debugger entered, please switch to this console for input.\n"); + + int i; + char buf[256]; + + do { + printf("debug> "); + if (!fgets(buf, sizeof(buf), stdin)) + return; + + i = strlen(buf); + while (i > 0 && buf[i - 1] == '\n') + buf[--i] = 0; + + if (i == 0) + continue; + } while (parseCommand(buf)); + +#endif +} + +bool Debugger::handleCommand(int argc, const char **argv, bool &result) { + for (int i = 0; i < _dcmd_count; ++i) { + if (!strcmp(_dcmds[i].name, argv[0])) { + Debuglet *debuglet = _dcmds[i].debuglet; + assert(debuglet); + result = (*debuglet)(argc, argv); + return true; + } + } + + return false; +} + +// Command execution loop +bool Debugger::parseCommand(const char *inputOrig) { + int i = 0, num_params = 0; + const char *param[256]; + char *input = strdup(inputOrig); // One of the rare occasions using strdup is OK (although avoiding strtok might be more elegant here). + + // Parse out any params + char *tok = strtok(input, " "); + if (tok) { + do { + param[num_params++] = tok; + } while ((tok = strtok(NULL, " ")) != NULL); + } else { + param[num_params++] = input; + } + + // Handle commands first + bool result; + if (handleCommand(num_params, param, result)) { + free(input); + return result; + } + + // It's not a command, so things get a little tricky for variables. Do fuzzy matching to ignore things like subscripts. + for (i = 0; i < _dvar_count; i++) { + if (!strncmp(_dvars[i].name, param[0], strlen(_dvars[i].name))) { + if (num_params > 1) { + // Alright, we need to check the TYPE of the variable to deref and stuff... the array stuff is a bit ugly :) + switch (_dvars[i].type) { + // Integer + case DVAR_BYTE: + *(byte *)_dvars[i].variable = atoi(param[1]); + DebugPrintf("byte%s = %d\n", param[0], *(byte *)_dvars[i].variable); + break; + case DVAR_INT: + *(int32 *)_dvars[i].variable = atoi(param[1]); + DebugPrintf("(int)%s = %d\n", param[0], *(int32 *)_dvars[i].variable); + break; + // Integer Array + case DVAR_INTARRAY: { + char *chr = (char *)strchr(param[0], '['); + if (!chr) { + DebugPrintf("You must access this array as %s[element]\n", param[0]); + } else { + int element = atoi(chr+1); + int32 *var = *(int32 **)_dvars[i].variable; + if (element >= _dvars[i].optional) { + DebugPrintf("%s is out of range (array is %d elements big)\n", param[0], _dvars[i].optional); + } else { + var[element] = atoi(param[1]); + DebugPrintf("(int)%s = %d\n", param[0], var[element]); + } + } + } + break; + default: + DebugPrintf("Failed to set variable %s to %s - unknown type\n", _dvars[i].name, param[1]); + break; + } + } else { + // And again, type-dependent prints/defrefs. The array one is still ugly. + switch (_dvars[i].type) { + // Integer + case DVAR_BYTE: + DebugPrintf("(byte)%s = %d\n", param[0], *(const byte *)_dvars[i].variable); + break; + case DVAR_INT: + DebugPrintf("(int)%s = %d\n", param[0], *(const int32 *)_dvars[i].variable); + break; + // Integer array + case DVAR_INTARRAY: { + const char *chr = strchr(param[0], '['); + if (!chr) { + DebugPrintf("You must access this array as %s[element]\n", param[0]); + } else { + int element = atoi(chr+1); + const int32 *var = *(const int32 **)_dvars[i].variable; + if (element >= _dvars[i].optional) { + DebugPrintf("%s is out of range (array is %d elements big)\n", param[0], _dvars[i].optional); + } else { + DebugPrintf("(int)%s = %d\n", param[0], var[element]); + } + } + } + break; + // String + case DVAR_STRING: + DebugPrintf("(string)%s = %s\n", param[0], ((Common::String *)_dvars[i].variable)->c_str()); + break; + default: + DebugPrintf("%s = (unknown type)\n", param[0]); + break; + } + } + + free(input); + return true; + } + } + + DebugPrintf("Unknown command or variable\n"); + free(input); + return true; +} + +// returns true if something has been completed +// completion has to be delete[]-ed then +bool Debugger::tabComplete(const char *input, char*& completion) { + // very basic tab completion + // for now it just supports command completions + + // adding completions of command parameters would be nice (but hard) :-) + // maybe also give a list of possible command completions? + // (but this will require changes to console) + + if (strchr(input, ' ')) + return false; // already finished the first word + + unsigned int inputlen = strlen(input); + + unsigned int matchlen = 0; + char match[30]; // the max. command name is 30 chars + + for (int i = 0; i < _dcmd_count; i++) { + if (!strncmp(_dcmds[i].name, input, inputlen)) { + unsigned int commandlen = strlen(_dcmds[i].name); + if (commandlen == inputlen) { // perfect match + return false; + } + if (commandlen > inputlen) { // possible match + // no previous match + if (matchlen == 0) { + strcpy(match, _dcmds[i].name + inputlen); + matchlen = commandlen - inputlen; + } else { + // take common prefix of previous match and this command + unsigned int j; + for (j = 0; j < matchlen; j++) { + if (match[j] != _dcmds[i].name[inputlen + j]) break; + } + matchlen = j; + } + if (matchlen == 0) + return false; + } + } + } + if (matchlen == 0) + return false; + + completion = new char[matchlen + 1]; + memcpy(completion, match, matchlen); + completion[matchlen] = 0; + return true; +} + +// Variable registration function +void Debugger::DVar_Register(const char *varname, void *pointer, int type, int optional) { + assert(_dvar_count < ARRAYSIZE(_dvars)); + strcpy(_dvars[_dvar_count].name, varname); + _dvars[_dvar_count].type = type; + _dvars[_dvar_count].variable = pointer; + _dvars[_dvar_count].optional = optional; + + _dvar_count++; +} + +// Command registration function +void Debugger::DCmd_Register(const char *cmdname, Debuglet *debuglet) { + assert(_dcmd_count < ARRAYSIZE(_dcmds)); + strcpy(_dcmds[_dcmd_count].name, cmdname); + _dcmds[_dcmd_count].debuglet = debuglet; + + _dcmd_count++; +} + + +// Detach ("exit") the debugger +bool Debugger::Cmd_Exit(int argc, const char **argv) { + _detach_now = true; + return false; +} + +// Print a list of all registered commands (and variables, if any), +// nicely word-wrapped. +bool Debugger::Cmd_Help(int argc, const char **argv) { + + int width, size, i; + + DebugPrintf("Commands are:\n"); + width = 0; + for (i = 0; i < _dcmd_count; i++) { + size = strlen(_dcmds[i].name) + 1; + + if ((width + size) >= GUI::ConsoleDialog::kCharsPerLine) { + DebugPrintf("\n"); + width = size; + } else + width += size; + + DebugPrintf("%s ", _dcmds[i].name); + } + DebugPrintf("\n"); + + if (_dvar_count > 0) { + DebugPrintf("\n"); + DebugPrintf("Variables are:\n"); + width = 0; + for (i = 0; i < _dvar_count; i++) { + size = strlen(_dvars[i].name) + 1; + + if ((width + size) >= GUI::ConsoleDialog::kCharsPerLine) { + DebugPrintf("\n"); + width = size; + } else + width += size; + + DebugPrintf("%s ", _dvars[i].name); + } + DebugPrintf("\n"); + } + + return true; +} + +bool Debugger::Cmd_DebugFlagsList(int argc, const char **argv) { + const Common::Array<Common::EngineDebugLevel> &debugLevels = Common::listSpecialDebugLevels(); + + DebugPrintf("Engine debug levels:\n"); + DebugPrintf("--------------------\n"); + if (!debugLevels.size()) { + DebugPrintf("No engine debug levels\n"); + return true; + } + for (uint i = 0; i < debugLevels.size(); ++i) { + DebugPrintf("'%s' - Description: %s\n", debugLevels[i].option.c_str(), debugLevels[i].description.c_str()); + } + DebugPrintf("\n"); + return true; +} + +bool Debugger::Cmd_DebugFlagEnable(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("debugflag_enable <flag>\n"); + } else { + if (Common::enableSpecialDebugLevel(argv[1])) { + DebugPrintf("Enabled debug flag '%s'\n", argv[1]); + } else { + DebugPrintf("Failed to enable debug flag '%s'\n", argv[1]); + } + } + return true; +} + +bool Debugger::Cmd_DebugFlagDisable(int argc, const char **argv) { + if (argc < 2) { + DebugPrintf("debugflag_disable <flag>\n"); + } else { + if (Common::disableSpecialDebugLevel(argv[1])) { + DebugPrintf("Disabled debug flag '%s'\n", argv[1]); + } else { + DebugPrintf("Failed to disable debug flag '%s'\n", argv[1]); + } + } + return true; +} + +// Console handler +#if USE_CONSOLE +bool Debugger::debuggerInputCallback(GUI::ConsoleDialog *console, const char *input, void *refCon) { + Debugger *debugger = (Debugger *)refCon; + + return debugger->parseCommand(input); +} + + +bool Debugger::debuggerCompletionCallback(GUI::ConsoleDialog *console, const char *input, char*& completion, void *refCon) { + Debugger *debugger = (Debugger *)refCon; + + return debugger->tabComplete(input, completion); +} + +#endif + +} // End of namespace GUI diff --git a/gui/debugger.h b/gui/debugger.h new file mode 100644 index 0000000000..7b07b1bca3 --- /dev/null +++ b/gui/debugger.h @@ -0,0 +1,158 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2002-2006 The ScummVM project + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + */ + +#ifndef GUI_DEBUGGER_H +#define GUI_DEBUGGER_H + +namespace GUI { + +// Choose between text console or ScummConsole +#define USE_CONSOLE 1 + +#ifdef USE_CONSOLE +class ConsoleDialog; +#endif + +class Debugger { +public: + Debugger(); + virtual ~Debugger(); + + int DebugPrintf(const char *format, ...); + +#ifndef __SYMBIAN32__ // gcc/UIQ doesn't like the debugger code for some reason? Actually get a cc1plus core dump here :) + // FIXME: Fingolfin asks: This code *used* to be a problem when GUI::Debugger + // was a template class. But is it really still causing problems, or can + // this hack go away now? + virtual void onFrame(); + + virtual void attach(const char *entry = 0); +#else + void onFrame() {} + void attach(const char *entry = 0) {} +#endif + bool isAttached() const { return _isAttached; } + +protected: + class Debuglet { + public: + virtual ~Debuglet() {} + virtual bool operator()(int argc, const char **argv) = 0; + }; + + template <class T> + class DelegateDebuglet : public Debuglet { + typedef bool (T::*Method)(int argc, const char **argv); + + T *_delegate; + const Method _method; + public: + DelegateDebuglet(T *delegate, Method method) + : _delegate(delegate), _method(method) { + assert(delegate != 0); + } + virtual bool operator()(int argc, const char **argv) { + return (_delegate->*_method)(argc, argv); + }; + }; + + // Convenicence macro for registering a method of a debugger class + // as the current command. + #define WRAP_METHOD(cls, method) \ + new DelegateDebuglet<cls>(this, &cls::method) + + enum { + DVAR_BYTE, + DVAR_INT, + DVAR_BOOL, + DVAR_INTARRAY, + DVAR_STRING + }; + + struct DVar { + char name[30]; + void *variable; + int type, optional; + }; + + struct DCmd { + char name[30]; + Debuglet *debuglet; + }; + + int _frame_countdown; + bool _detach_now; + + // TODO: Consider replacing the following two arrays by a Hashmap + + int _dvar_count; + DVar _dvars[256]; + + int _dcmd_count; + DCmd _dcmds[256]; + +private: + bool _isAttached; + char *_errStr; + bool _firstTime; + GUI::ConsoleDialog *_debuggerDialog; + +protected: + // Hook for subclasses: Called just before enter() is run + virtual void preEnter() {} + + // Hook for subclasses: Called just after enter() was run + virtual void postEnter() {} + + // Hook for subclasses: Process the given command line. + // Should return true if and only if argv[0] is a known command and was + // handled, false otherwise. + virtual bool handleCommand(int argc, const char **argv, bool &keepRunning); + + +private: +//protected: + void detach(); + void enter(); + + bool parseCommand(const char *input); + bool tabComplete(const char *input, char*& completion); + +protected: + void DVar_Register(const char *varname, void *pointer, int type, int optional); + void DCmd_Register(const char *cmdname, Debuglet *debuglet); + + bool Cmd_Exit(int argc, const char **argv); + bool Cmd_Help(int argc, const char **argv); + bool Cmd_DebugFlagsList(int argc, const char **argv); + bool Cmd_DebugFlagEnable(int argc, const char **argv); + bool Cmd_DebugFlagDisable(int argc, const char **argv); + +#if USE_CONSOLE +private: + static bool debuggerInputCallback(GUI::ConsoleDialog *console, const char *input, void *refCon); + static bool debuggerCompletionCallback(GUI::ConsoleDialog *console, const char *input, char*& completion, void *refCon); +#endif +}; + +} // End of namespace GUI + +#endif diff --git a/gui/module.mk b/gui/module.mk index 4ec0140be8..bab4b31c94 100644 --- a/gui/module.mk +++ b/gui/module.mk @@ -5,6 +5,7 @@ MODULE_OBJS := \ browser.o \ chooser.o \ console.o \ + debugger.o \ dialog.o \ editable.o \ EditTextWidget.o \ |