1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/js/examples/jorendb.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,510 @@ 1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- 1.5 + * vim: set ts=8 sw=4 et tw=78: 1.6 + * 1.7 + * jorendb - A toy command-line debugger for shell-js programs. 1.8 + * 1.9 + * This Source Code Form is subject to the terms of the Mozilla Public 1.10 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.11 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.12 + */ 1.13 + 1.14 +/* 1.15 + * jorendb is a simple command-line debugger for shell-js programs. It is 1.16 + * intended as a demo of the Debugger object (as there are no shell js programs 1.17 + * to speak of). 1.18 + * 1.19 + * To run it: $JS -d path/to/this/file/jorendb.js 1.20 + * To run some JS code under it, try: 1.21 + * (jorendb) print load("my-script-to-debug.js") 1.22 + * Execution will stop at debugger statements and you'll get a jorendb prompt. 1.23 + */ 1.24 + 1.25 +// Debugger state. 1.26 +var focusedFrame = null; 1.27 +var topFrame = null; 1.28 +var debuggeeValues = {}; 1.29 +var nextDebuggeeValueIndex = 1; 1.30 +var lastExc = null; 1.31 + 1.32 +// Cleanup functions to run when we next re-enter the repl. 1.33 +var replCleanups = []; 1.34 + 1.35 +// Convert a debuggee value v to a string. 1.36 +function dvToString(v) { 1.37 + return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]"; 1.38 +} 1.39 + 1.40 +function showDebuggeeValue(dv) { 1.41 + var dvrepr = dvToString(dv); 1.42 + var i = nextDebuggeeValueIndex++; 1.43 + debuggeeValues["$" + i] = dv; 1.44 + print("$" + i + " = " + dvrepr); 1.45 +} 1.46 + 1.47 +Object.defineProperty(Debugger.Frame.prototype, "num", { 1.48 + configurable: true, 1.49 + enumerable: false, 1.50 + get: function () { 1.51 + var i = 0; 1.52 + for (var f = topFrame; f && f !== this; f = f.older) 1.53 + i++; 1.54 + return f === null ? undefined : i; 1.55 + } 1.56 + }); 1.57 + 1.58 +Debugger.Frame.prototype.frameDescription = function frameDescription() { 1.59 + if (this.type == "call") 1.60 + return ((this.callee.name || '<anonymous>') + 1.61 + "(" + this.arguments.map(dvToString).join(", ") + ")"); 1.62 + else 1.63 + return this.type + " code"; 1.64 +} 1.65 + 1.66 +Debugger.Frame.prototype.positionDescription = function positionDescription() { 1.67 + if (this.script) { 1.68 + var line = this.script.getOffsetLine(this.offset); 1.69 + if (this.script.url) 1.70 + return this.script.url + ":" + line; 1.71 + return "line " + line; 1.72 + } 1.73 + return null; 1.74 +} 1.75 + 1.76 +Debugger.Frame.prototype.fullDescription = function fullDescription() { 1.77 + var fr = this.frameDescription(); 1.78 + var pos = this.positionDescription(); 1.79 + if (pos) 1.80 + return fr + ", " + pos; 1.81 + return fr; 1.82 +} 1.83 + 1.84 +Object.defineProperty(Debugger.Frame.prototype, "line", { 1.85 + configurable: true, 1.86 + enumerable: false, 1.87 + get: function() { 1.88 + if (this.script) 1.89 + return this.script.getOffsetLine(this.offset); 1.90 + else 1.91 + return null; 1.92 + } 1.93 + }); 1.94 + 1.95 +function callDescription(f) { 1.96 + return ((f.callee.name || '<anonymous>') + 1.97 + "(" + f.arguments.map(dvToString).join(", ") + ")"); 1.98 +} 1.99 + 1.100 +function showFrame(f, n) { 1.101 + if (f === undefined || f === null) { 1.102 + f = focusedFrame; 1.103 + if (f === null) { 1.104 + print("No stack."); 1.105 + return; 1.106 + } 1.107 + } 1.108 + if (n === undefined) { 1.109 + n = f.num; 1.110 + if (n === undefined) 1.111 + throw new Error("Internal error: frame not on stack"); 1.112 + } 1.113 + 1.114 + print('#' + n + " " + f.fullDescription()); 1.115 +} 1.116 + 1.117 +function saveExcursion(fn) { 1.118 + var tf = topFrame, ff = focusedFrame; 1.119 + try { 1.120 + return fn(); 1.121 + } finally { 1.122 + topFrame = tf; 1.123 + focusedFrame = ff; 1.124 + } 1.125 +} 1.126 + 1.127 +// Evaluate an expression in the Debugger global 1.128 +function evalCommand(expr) { 1.129 + eval(expr); 1.130 +} 1.131 + 1.132 +function quitCommand() { 1.133 + dbg.enabled = false; 1.134 + quit(0); 1.135 +} 1.136 + 1.137 +function backtraceCommand() { 1.138 + if (topFrame === null) 1.139 + print("No stack."); 1.140 + for (var i = 0, f = topFrame; f; i++, f = f.older) 1.141 + showFrame(f, i); 1.142 +} 1.143 + 1.144 +function printCommand(rest) { 1.145 + // This is the real deal. 1.146 + var cv = saveExcursion( 1.147 + () => focusedFrame == null 1.148 + ? debuggeeGlobalWrapper.evalInGlobalWithBindings(rest, debuggeeValues) 1.149 + : focusedFrame.evalWithBindings(rest, debuggeeValues)); 1.150 + if (cv === null) { 1.151 + if (!dbg.enabled) 1.152 + return [cv]; 1.153 + print("Debuggee died."); 1.154 + } else if ('return' in cv) { 1.155 + if (!dbg.enabled) 1.156 + return [undefined]; 1.157 + showDebuggeeValue(cv.return); 1.158 + } else { 1.159 + if (!dbg.enabled) 1.160 + return [cv]; 1.161 + print("Exception caught. (To rethrow it, type 'throw'.)"); 1.162 + lastExc = cv.throw; 1.163 + showDebuggeeValue(lastExc); 1.164 + } 1.165 +} 1.166 + 1.167 +function detachCommand() { 1.168 + dbg.enabled = false; 1.169 + return [undefined]; 1.170 +} 1.171 + 1.172 +function continueCommand() { 1.173 + if (focusedFrame === null) { 1.174 + print("No stack."); 1.175 + return; 1.176 + } 1.177 + return [undefined]; 1.178 +} 1.179 + 1.180 +function throwCommand(rest) { 1.181 + var v; 1.182 + if (focusedFrame !== topFrame) { 1.183 + print("To throw, you must select the newest frame (use 'frame 0')."); 1.184 + return; 1.185 + } else if (focusedFrame === null) { 1.186 + print("No stack."); 1.187 + return; 1.188 + } else if (rest === '') { 1.189 + return [{throw: lastExc}]; 1.190 + } else { 1.191 + var cv = saveExcursion(function () { return focusedFrame.eval(rest); }); 1.192 + if (cv === null) { 1.193 + if (!dbg.enabled) 1.194 + return [cv]; 1.195 + print("Debuggee died while determining what to throw. Stopped."); 1.196 + } else if ('return' in cv) { 1.197 + return [{throw: cv.return}]; 1.198 + } else { 1.199 + if (!dbg.enabled) 1.200 + return [cv]; 1.201 + print("Exception determining what to throw. Stopped."); 1.202 + showDebuggeeValue(cv.throw); 1.203 + } 1.204 + return; 1.205 + } 1.206 +} 1.207 + 1.208 +function frameCommand(rest) { 1.209 + var n, f; 1.210 + if (rest.match(/[0-9]+/)) { 1.211 + n = +rest; 1.212 + f = topFrame; 1.213 + if (f === null) { 1.214 + print("No stack."); 1.215 + return; 1.216 + } 1.217 + for (var i = 0; i < n && f; i++) { 1.218 + if (!f.older) { 1.219 + print("There is no frame " + rest + "."); 1.220 + return; 1.221 + } 1.222 + f.older.younger = f; 1.223 + f = f.older; 1.224 + } 1.225 + focusedFrame = f; 1.226 + showFrame(f, n); 1.227 + } else if (rest !== '') { 1.228 + if (topFrame === null) 1.229 + print("No stack."); 1.230 + else 1.231 + showFrame(); 1.232 + } else { 1.233 + print("do what now?"); 1.234 + } 1.235 +} 1.236 + 1.237 +function upCommand() { 1.238 + if (focusedFrame === null) 1.239 + print("No stack."); 1.240 + else if (focusedFrame.older === null) 1.241 + print("Initial frame selected; you cannot go up."); 1.242 + else { 1.243 + focusedFrame.older.younger = focusedFrame; 1.244 + focusedFrame = focusedFrame.older; 1.245 + showFrame(); 1.246 + } 1.247 +} 1.248 + 1.249 +function downCommand() { 1.250 + if (focusedFrame === null) 1.251 + print("No stack."); 1.252 + else if (!focusedFrame.younger) 1.253 + print("Youngest frame selected; you cannot go down."); 1.254 + else { 1.255 + focusedFrame = focusedFrame.younger; 1.256 + showFrame(); 1.257 + } 1.258 +} 1.259 + 1.260 +function forcereturnCommand(rest) { 1.261 + var v; 1.262 + var f = focusedFrame; 1.263 + if (f !== topFrame) { 1.264 + print("To forcereturn, you must select the newest frame (use 'frame 0')."); 1.265 + } else if (f === null) { 1.266 + print("Nothing on the stack."); 1.267 + } else if (rest === '') { 1.268 + return [{return: undefined}]; 1.269 + } else { 1.270 + var cv = saveExcursion(function () { return f.eval(rest); }); 1.271 + if (cv === null) { 1.272 + if (!dbg.enabled) 1.273 + return [cv]; 1.274 + print("Debuggee died while determining what to forcereturn. Stopped."); 1.275 + } else if ('return' in cv) { 1.276 + return [{return: cv.return}]; 1.277 + } else { 1.278 + if (!dbg.enabled) 1.279 + return [cv]; 1.280 + print("Error determining what to forcereturn. Stopped."); 1.281 + showDebuggeeValue(cv.throw); 1.282 + } 1.283 + } 1.284 +} 1.285 + 1.286 +function printPop(f, c) { 1.287 + var fdesc = f.fullDescription(); 1.288 + if (c.return) { 1.289 + print("frame returning (still selected): " + fdesc); 1.290 + showDebuggeeValue(c.return); 1.291 + } else if (c.throw) { 1.292 + print("frame threw exception: " + fdesc); 1.293 + showDebuggeeValue(c.throw); 1.294 + print("(To rethrow it, type 'throw'.)"); 1.295 + lastExc = c.throw; 1.296 + } else { 1.297 + print("frame was terminated: " + fdesc); 1.298 + } 1.299 +} 1.300 + 1.301 +// Set |prop| on |obj| to |value|, but then restore its current value 1.302 +// when we next enter the repl. 1.303 +function setUntilRepl(obj, prop, value) { 1.304 + var saved = obj[prop]; 1.305 + obj[prop] = value; 1.306 + replCleanups.push(function () { obj[prop] = saved; }); 1.307 +} 1.308 + 1.309 +function doStepOrNext(kind) { 1.310 + var startFrame = topFrame; 1.311 + var startLine = startFrame.line; 1.312 + print("stepping in: " + startFrame.fullDescription()); 1.313 + print("starting line: " + uneval(startLine)); 1.314 + 1.315 + function stepPopped(completion) { 1.316 + // Note that we're popping this frame; we need to watch for 1.317 + // subsequent step events on its caller. 1.318 + this.reportedPop = true; 1.319 + printPop(this, completion); 1.320 + topFrame = focusedFrame = this; 1.321 + return repl(); 1.322 + } 1.323 + 1.324 + function stepEntered(newFrame) { 1.325 + print("entered frame: " + newFrame.fullDescription()); 1.326 + topFrame = focusedFrame = newFrame; 1.327 + return repl(); 1.328 + } 1.329 + 1.330 + function stepStepped() { 1.331 + print("stepStepped: " + this.fullDescription()); 1.332 + // If we've changed frame or line, then report that. 1.333 + if (this !== startFrame || this.line != startLine) { 1.334 + topFrame = focusedFrame = this; 1.335 + if (focusedFrame != startFrame) 1.336 + print(focusedFrame.fullDescription()); 1.337 + return repl(); 1.338 + } 1.339 + 1.340 + // Otherwise, let execution continue. 1.341 + return undefined; 1.342 + } 1.343 + 1.344 + if (kind.step) 1.345 + setUntilRepl(dbg, 'onEnterFrame', stepEntered); 1.346 + 1.347 + // If we're stepping after an onPop, watch for steps and pops in the 1.348 + // next-older frame; this one is done. 1.349 + var stepFrame = startFrame.reportedPop ? startFrame.older : startFrame; 1.350 + if (!stepFrame || !stepFrame.script) 1.351 + stepFrame = null; 1.352 + if (stepFrame) { 1.353 + setUntilRepl(stepFrame, 'onStep', stepStepped); 1.354 + setUntilRepl(stepFrame, 'onPop', stepPopped); 1.355 + } 1.356 + 1.357 + // Let the program continue! 1.358 + return [undefined]; 1.359 +} 1.360 + 1.361 +function stepCommand() { return doStepOrNext({step:true}); } 1.362 +function nextCommand() { return doStepOrNext({next:true}); } 1.363 + 1.364 +// Build the table of commands. 1.365 +var commands = {}; 1.366 +var commandArray = [ 1.367 + backtraceCommand, "bt", "where", 1.368 + continueCommand, "c", 1.369 + detachCommand, 1.370 + downCommand, "d", 1.371 + forcereturnCommand, 1.372 + frameCommand, "f", 1.373 + nextCommand, "n", 1.374 + printCommand, "p", 1.375 + quitCommand, "q", 1.376 + stepCommand, "s", 1.377 + throwCommand, "t", 1.378 + upCommand, "u", 1.379 + helpCommand, "h", 1.380 + evalCommand, "!", 1.381 + ]; 1.382 +var last = null; 1.383 +for (var i = 0; i < commandArray.length; i++) { 1.384 + var cmd = commandArray[i]; 1.385 + if (typeof cmd === "string") 1.386 + commands[cmd] = last; 1.387 + else 1.388 + last = commands[cmd.name.replace(/Command$/, '')] = cmd; 1.389 +} 1.390 + 1.391 +function helpCommand(rest) { 1.392 + print("Available commands:"); 1.393 + var printcmd = function(group) { 1.394 + print(" " + group.join(", ")); 1.395 + } 1.396 + 1.397 + var group = []; 1.398 + for (var cmd of commandArray) { 1.399 + if (typeof cmd === "string") { 1.400 + group.push(cmd); 1.401 + } else { 1.402 + if (group.length) printcmd(group); 1.403 + group = [ cmd.name.replace(/Command$/, '') ]; 1.404 + } 1.405 + } 1.406 + printcmd(group); 1.407 +} 1.408 + 1.409 +// Break cmd into two parts: its first word and everything else. If it begins 1.410 +// with punctuation, treat that as a separate word. 1.411 +function breakcmd(cmd) { 1.412 + cmd = cmd.trimLeft(); 1.413 + if ("!@#$%^&*_+=/?.,<>:;'\"".indexOf(cmd.substr(0, 1)) != -1) 1.414 + return [cmd.substr(0, 1), cmd.substr(1).trimLeft()]; 1.415 + var m = /\s/.exec(cmd); 1.416 + if (m === null) 1.417 + return [cmd, '']; 1.418 + return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()]; 1.419 +} 1.420 + 1.421 +function runcmd(cmd) { 1.422 + var pieces = breakcmd(cmd); 1.423 + if (pieces[0] === "") 1.424 + return undefined; 1.425 + 1.426 + var first = pieces[0], rest = pieces[1]; 1.427 + if (!commands.hasOwnProperty(first)) { 1.428 + print("unrecognized command '" + first + "'"); 1.429 + return undefined; 1.430 + } 1.431 + 1.432 + var cmd = commands[first]; 1.433 + if (cmd.length === 0 && rest !== '') { 1.434 + print("this command cannot take an argument"); 1.435 + return undefined; 1.436 + } 1.437 + 1.438 + return cmd(rest); 1.439 +} 1.440 + 1.441 +function repl() { 1.442 + while (replCleanups.length > 0) 1.443 + replCleanups.pop()(); 1.444 + 1.445 + var cmd; 1.446 + for (;;) { 1.447 + putstr("\n" + prompt); 1.448 + cmd = readline(); 1.449 + if (cmd === null) 1.450 + return null; 1.451 + 1.452 + try { 1.453 + var result = runcmd(cmd); 1.454 + if (result === undefined) 1.455 + ; // do nothing 1.456 + else if (Array.isArray(result)) 1.457 + return result[0]; 1.458 + else 1.459 + throw new Error("Internal error: result of runcmd wasn't array or undefined"); 1.460 + } catch (exc) { 1.461 + print("*** Internal error: exception in the debugger code."); 1.462 + print(" " + exc); 1.463 + print(exc.stack); 1.464 + } 1.465 + } 1.466 +} 1.467 + 1.468 +var dbg = new Debugger(); 1.469 +dbg.onDebuggerStatement = function (frame) { 1.470 + return saveExcursion(function () { 1.471 + topFrame = focusedFrame = frame; 1.472 + print("'debugger' statement hit."); 1.473 + showFrame(); 1.474 + return repl(); 1.475 + }); 1.476 +}; 1.477 +dbg.onThrow = function (frame, exc) { 1.478 + return saveExcursion(function () { 1.479 + topFrame = focusedFrame = frame; 1.480 + print("Unwinding due to exception. (Type 'c' to continue unwinding.)"); 1.481 + showFrame(); 1.482 + print("Exception value is:"); 1.483 + showDebuggeeValue(exc); 1.484 + return repl(); 1.485 + }); 1.486 +}; 1.487 + 1.488 +// The depth of jorendb nesting. 1.489 +var jorendbDepth; 1.490 +if (typeof jorendbDepth == 'undefined') jorendbDepth = 0; 1.491 + 1.492 +var debuggeeGlobal = newGlobal("new-compartment"); 1.493 +debuggeeGlobal.jorendbDepth = jorendbDepth + 1; 1.494 +var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal); 1.495 + 1.496 +print("jorendb version -0.0"); 1.497 +prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) '; 1.498 + 1.499 +var args = arguments; 1.500 +while(args.length > 0) { 1.501 + var arg = args.shift(); 1.502 + if (arg == '-f') { 1.503 + arg = args.shift(); 1.504 + debuggeeGlobal.evaluate(read(arg), { fileName: arg, lineNumber: 1 }); 1.505 + } else if (arg == '-e') { 1.506 + arg = args.shift(); 1.507 + debuggeeGlobal.eval(arg); 1.508 + } else { 1.509 + throw("jorendb does not implement command-line argument '" + arg + "'"); 1.510 + } 1.511 +} 1.512 + 1.513 +repl();