axmol/scripting/javascript/bindings/js/jsb_debugger.js

629 lines
18 KiB
JavaScript

dbg = {};
dbg.log = log;
var textCommandProcessor = {};
textCommandProcessor.break = function (str) {
var md = str.match(/^b(reak)?\s+([^:]+):(\d+)/);
if (!md) {
return ({commandname : "break",
success : false,
stringResult : "command could not be parsed"});
}
var scripts = dbg.scripts[md[2]],
tmpScript = null;
if (scripts) {
var breakLine = parseInt(md[3], 10),
off = -1;
for (var n=0; n < scripts.length; n++) {
offsets = scripts[n].getLineOffsets(breakLine);
if (offsets.length > 0) {
off = offsets[0];
tmpScript = scripts[n];
break;
}
}
if (off >= 0) {
tmpScript.setBreakpoint(off, breakpointHandler);
return ({commandname : "break",
success : true,
jsfilename : md[2],
breakpointlinenumber : breakLine});
} else {
return ({commandname : "break",
success : false,
stringResult : "no valid offsets at that line"});
}
} else {
return ({commandname : "break",
success : false,
jsfilename : md[2],
stringResult : "Invalid script name"});
}
}
textCommandProcessor.info = function (str) {
var report = "";
var md = str.match(/^info\s+(\S+)/);
if (md) {
report += "info - NYI";
report += "\nmd[0] = " + md[0];
report += "\nmd[1] = " + md[1];
return ({commandname : "info",
success : true,
stringResult : report});
} else {
return ({commandname : "info",
success : false,
stringResult : report});
}
}
textCommandProcessor.clear = function (str) {
var report = "";
report += "clearing all breakpoints";
dbg.dbg.clearAllBreakpoints();
return ({commandname : "clear",
success : true,
stringResult : report});
}
textCommandProcessor.scripts = function (str) {
var report = "List of available scripts\n";
report += Object.keys(dbg.scripts).join("\n");
return ({commandname : "scripts",
success : true,
stringResult : report});
}
textCommandProcessor.step = function (str, frame, script) {
if (frame) {
dbg.breakLine = script.getOffsetLine(frame.offset) + 1;
frame.onStep = function () {
stepFunction(frame, frame.script);
return undefined;
};
stop = true;
_unlockVM();
return ({commandname : "step",
success : true,
stringResult : ""});
} else {
return ({commandname : "step",
success : false,
stringResult : ""});
}
}
textCommandProcessor.continue = function (str, frame, script) {
if (frame) {
frame.onStep = undefined;
dbg.breakLine = 0;
}
stop = true;
_unlockVM();
return ({commandname : "continue",
success : true,
stringResult : ""});
}
textCommandProcessor.deval = function (str, frame, script) {
// debugger eval
var md = str.match(/^deval\s+(.+)/);
if (md[1]) {
try {
var devalReturn = eval(md[1]);
if (devalReturn) {
var stringreport = debugObject(devalReturn, true);
return ({commandname : "deval",
success : true,
stringResult : stringreport});
}
} catch (e) {
return ({commandname : "deval",
success : false,
stringResult : "exception:\n" + e.message});
}
} else {
return ({commandname : "deval",
success : false,
stringResult : "could not parse script to evaluate"});
}
}
textCommandProcessor.eval = function (str, frame, script) {
if (!frame) {
return ({commandname : "eval",
success : false,
stringResult : "no frame to eval in"});
}
var stringToEval = str.substring(4);
if (stringToEval) {
try {
var evalResult = frame['eval']("JSON.stringify(eval(" + stringToEval + "));");
if (evalResult && evalResult['return']) {
var stringreport = evalResult['return'];
// var stringreport = debugObject(evalResult['return']);
return ({commandname : "eval",
success : true,
stringResult : stringreport});
} else if (evalResult && evalResult['throw']) {
return ({commandname : "eval",
success : false,
stringResult : "got exception: " + evalResult['throw'].message});
} else {
return ({commandname : "eval",
success : false,
stringResult : "invalid return from eval"});
}
} catch (e) {
dbg.log("exception = " + e);
return ({commandname : "eval",
success : false,
stringResult : "Exception : " + e});
}
}
}
textCommandProcessor.line = function (str, frame, script) {
if (frame) {
try {
return ({commandname : "line",
success : true,
stringResult : script.getOffsetLine(frame.offset)});
} catch (e) {
return ({commandname : "line",
success : false,
stringResult : "exception " + e});
}
}
return ({commandname : "line",
success : false,
// probably entering script
stringResult : "NOLINE"});
}
textCommandProcessor.backtrace = function (str, frame, script) {
if (!frame) {
return ({commandname : "backtrace",
success : false,
stringResult : "no valid frame"});
}
var result = "";
var cur = frame,
stack = [cur.script.url + ":" + cur.script.getOffsetLine(cur.offset)];
while ((cur = cur.older)) {
stack.push(cur.script.url + ":" + cur.script.getOffsetLine(cur.offset));
}
result += stack.join("\n");
return ({commandname : "backtrace",
success : true,
stringResult : result});
}
textCommandProcessor.uiresponse = function (str) {
var subcommandstring = (str.substring("uiresponse".length)).replace(/\s+/g, '');
var response = "";
switch (subcommandstring) {
case "json":
dbg.responder = jsonResponder;
response += "DEBUGGER UI : responding with json messages";
break;
case "plaintext":
dbg.responder = textResponder;
response += "DEBUGGER UI : responding with plaintext messages";
break;
}
// note : we return an empty string
// dbg.log(response);
return ({commandname : "uiresponse",
success : true,
stringResult : ""});
}
textCommandProcessor.help = function () {
_printHelp();
return ({commandname : "help",
success : true,
stringResult : ""});
}
textCommandProcessor.getCommandProcessor = function (str) {
// break
var md = str.match(/[a-z]*/);
if (!md) {
return null;
}
switch (md[0]) {
case "b" :
case "break" :
return textCommandProcessor.break;
case "info" :
return textCommandProcessor.info;
case "clear" :
return textCommandProcessor.clear;
case "scripts" :
return textCommandProcessor.scripts;
case "s" :
case "step" :
return textCommandProcessor.step;
case "c" :
case "continue" :
return textCommandProcessor.continue;
case "deval" :
return textCommandProcessor.deval;
case "eval" :
return textCommandProcessor.eval;
case "line" :
return textCommandProcessor.line;
case "bt" :
return textCommandProcessor.backtrace;
case "uiresponse" :
return textCommandProcessor.uiresponse;
case "help" :
return textCommandProcessor.help;
default :
return null;
}
}
// JSON output
var jsonResponder = {};
jsonResponder.write = function (str) {
_bufferWrite(str);
_bufferWrite("\n");
_bufferWrite(String.fromCharCode(23));
}
jsonResponder.onBreakpoint = function (filename, linenumber) {
var response = {"from" : "server",
"why" : "onBreakpoint",
"data" : {"jsfilename" : filename,
"linenumber" : linenumber}};
this.write(JSON.stringify(response));
}
jsonResponder.onStep = function (filename, linenumber) {
var response = {"from" : "server",
"why" : "onStep",
"data" : {"jsfilename" : filename,
"linenumber" : linenumber}};
this.write(JSON.stringify(response));
}
jsonResponder.commandResponse = function (commandresult) {
var response = {"from" : "server",
"why" : "commandresponse",
"data" : commandresult};
this.write(JSON.stringify(response));
}
jsonResponder.commandNotFound = function () {
// do nothing
}
// Plain Old Text output
var textResponder = {};
textResponder.write = function (str) {
_bufferWrite(str);
_bufferWrite("\n");
_bufferWrite(String.fromCharCode(23));
}
textResponder.onBreakpoint = function (filename, linenumber) {
var shortFilename = filename.substring(filename.lastIndexOf("/") + 1);
var response = "Breakpoint hit at " + shortFilename + " line number : " + linenumber;
this.write(response);
}
textResponder.onStep = function (filename, linenumber) {
var shortFilename = filename.substring(filename.lastIndexOf("/") + 1);
var response = "Stopped at " + shortFilename + " line number : " + linenumber;
this.write(response);
}
textResponder.commandResponse = function (commandresult) {
var response = "";
try {
switch (commandresult.commandname) {
case "break" :
if (!commandresult.success) {
response += "ERROR : " + commandresult.stringResult;
}
break;
case "info" :
if (!commandresult.success) {
response += "ERROR : " + commandresult.stringResult;
}
break;
case "clear" :
break;
case "scripts" :
if (true === commandresult.success) {
response += commandresult.stringResult;
}
break;
case "step" :
if (!commandresult.success) {
response += "ERROR : step failed " + commandresult.stringResult;
}
break;
case "continue" :
if (!commandresult.success) {
response += "ERROR : continue failed " + commandresult.stringResult;
}
break;
case "deval" :
if (true === commandresult.success) {
response += commandresult.stringResult;
} else {
response += "ERROR : deval failed " + commandresult.stringResult;
}
break;
case "eval" :
if (true === commandresult.success) {
response += commandresult.stringResult;
} else {
response += "ERROR : eval failed " + commandresult.stringResult;
}
break;
case "line" :
if (true === commandresult.success) {
response += commandresult.stringResult;
} else {
response += "ERROR : " + commandresult.stringResult;
}
break;
case "backtrace" :
if (true === commandresult.success) {
response += commandresult.stringResult;
} else {
response += "ERROR : " + commandresult.stringResult;
}
break;
case "help" :
break;
}
} catch (e) {
response += "\nException logging response " + e;
}
this.write(response);
}
textResponder.commandNotFound = function () {
_printCommandNotFound();
}
var breakpointHandler = {
hit: function (frame) {
try {
dbg.responder.onBreakpoint(frame.script.url, frame.script.getOffsetLine(frame.offset));
} catch (e) {
dbg.log("exception " + e);
}
var script = frame.script;
_lockVM(frame, frame.script);
}
};
var stepFunction = function (frame, script) {
if (dbg.breakLine > 0) {
var curLine = script.getOffsetLine(frame.offset);
if (curLine < dbg.breakLine) {
return;
} else {
try {
dbg.responder.onStep(frame.script.url, frame.script.getOffsetLine(frame.offset));
} catch (e) {
dbg.log("exception " + e);
}
_lockVM(frame, script);
// dbg.breakLine = 0;
// frame.onStep = undefined;
}
} else {
dbg.log("invalid state onStep");
}
};
var debugObject = function (r, isNormal) {
var stringres = "";
try {
stringres += "* " + (typeof r) + "\n";
if (typeof r != "object") {
stringres += "~> " + r + "\n";
} else {
var props;
if (isNormal) {
props = Object.keys(r);
} else {
props = r.getOwnPropertyNames();
}
for (k in props) {
var desc = r.getOwnPropertyDescriptor(props[k]);
stringres += "~> " + props[k] + " = ";
if (desc.value) {
stringres += "" + desc.value;
} else if (desc.get) {
stringres += "" + desc.get();
} else {
stringres += "undefined (no value or getter)";
}
stringres += "\n";
}
}
return stringres;
} catch (e) {
return ("Exception when accessing object properties = " + e);
}
}
dbg.breakLine = 0;
this.processInput = function (inputstr, frame, script) {
var command_func;
var command_return;
var commands_array = [];
var _command;
var i;
if (!inputstr) {
return;
}
// remove Carriage Return's
inputstr = inputstr.replace(/\r+/, "");
// split into an array using Line Feed as the delimiter
commands_array = inputstr.split("\n");
// trace the commands received
// dbg.log("received " + commands_array.length + " commands:");
// for (i = 0; i < commands_array.length; i++) {
// if (i in commands_array) {
// dbg.log("~~~ commandstring =" + commands_array[i]);
// dbg.log(" commandstring.length = " + commands_array[i].length);
// }
// }
for (i = 0; i < commands_array.length; i++) {
if (i in commands_array) {
_command = commands_array[i];
if (_command === "") {
// dbg.log("Empty input. Ignoring.");
} else {
// dbg.log(_command);
command_func = dbg.getCommandProcessor(_command);
if (!command_func) {
dbg.log("did not find a command processor!");
dbg.responder.commandNotFound();
} else {
try {
command_return = command_func(_command, frame, script);
if (true === command_return.success) {
dbg.responder.commandResponse(command_return);
} else {
dbg.log("command failed. return value = " + command_return.stringResult);
dbg.responder.commandResponse(command_return);
}
} catch (e) {
dbg.log("Exception in command processing. e =\n" + e + "\n");
var _output = {success : false,
commandname : command_func.name,
stringResult : e};
dbg.responder.commandResponse(_output);
}
}
}
}
}
};
_printCommandNotFound = function() {
var str = "ERROR : command not found!\n";
_bufferWrite(str);
};
_printHelp = function() {
var help = "break filename:numer\tAdds a breakpoint at a given filename and line number\n" +
"clear\tClear all breakpoints\n" +
"c / continue\tContinues the execution\n" +
"s / step\tStep\n" +
"bt\tBacktrace\n" +
"scripts\tShow the scripts\n" +
"line\tShows current line\n" +
"eval js_command\tEvaluates JS code\n" +
"deval js_command\tEvaluates JS Debugger command\n" +
"uiresponse [json|plaintext] Switch between JSON and plaintext output from the debugger\n";
_bufferWrite(help);
};
dbg.scripts = [];
dbg.onNewScript = function (script) {
// skip if the url is this script
var last = script.url.split("/").pop();
var children = script.getChildScripts(),
arr = [script].concat(children);
/**
* just dumping all the offsets from the scripts
for (var i in arr) {
dbg.log("script: " + arr[i].url);
for (var start=arr[i].startLine, j=start; j < start+arr[i].lineCount; j++) {
var offsets = arr[i].getLineOffsets(j);
dbg.log(" off: " + offsets.join(",") + "; line: " + j);
}
}
*/
dbg.scripts[last] = arr;
};
dbg.onError = function (frame, report) {
if (dbg.socket && report) {
_socketWrite(dbg.socket, "!! exception @ " + report.file + ":" + report.line);
}
dbg.log("!! exception");
};
this._prepareDebugger = function (global) {
var tmp = new Debugger(global);
tmp.onNewScript = dbg.onNewScript;
tmp.onDebuggerStatement = dbg.onDebuggerStatement;
tmp.onError = dbg.onError;
dbg.dbg = tmp;
// use the text command processor at startup
dbg.getCommandProcessor = textCommandProcessor.getCommandProcessor;
// use the text responder at startup
dbg.responder = textResponder;
};
this._startDebugger = function (global, files, startFunc) {
// dbg.log("[DBG] starting debug session");
for (var i in files) {
try {
global['eval']("require('" + files[i] + "');");
} catch (e) {
dbg.log("[DBG] error evaluating file: " + files[i]);
}
}
// dbg.log("[DBG] all files required");
if (startFunc) {
// dbg.log("executing start func: " + startFunc);
global['eval'](startFunc);
}
// beginDebug();
}