1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=78:
4 * jorendb - A toy command-line debugger for shell-js programs.
6 * ***** BEGIN LICENSE BLOCK *****
7 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
9 * The contents of this file are subject to the Mozilla Public License Version
10 * 1.1 (the "License"); you may not use this file except in compliance with
11 * the License. You may obtain a copy of the License at
12 * http://www.mozilla.org/MPL/
14 * Software distributed under the License is distributed on an "AS IS" basis,
15 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
16 * for the specific language governing rights and limitations under the
19 * The Original Code is jorendb toy JS debugger.
21 * The Initial Developer of the Original Code is
23 * Portions created by the Initial Developer are Copyright (C) 2011
24 * the Initial Developer. All Rights Reserved.
27 * Jason Orendorff <jorendorff@mozilla.com>
29 * Alternatively, the contents of this file may be used under the terms of
30 * either the GNU General Public License Version 2 or later (the "GPL"), or
31 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
32 * in which case the provisions of the GPL or the LGPL are applicable instead
33 * of those above. If you wish to allow use of your version of this file only
34 * under the terms of either the GPL or the LGPL, and not to allow others to
35 * use your version of this file under the terms of the MPL, indicate your
36 * decision by deleting the provisions above and replace them with the notice
37 * and other provisions required by the GPL or the LGPL. If you do not delete
38 * the provisions above, a recipient may use your version of this file under
39 * the terms of any one of the MPL, the GPL or the LGPL.
41 * ***** END LICENSE BLOCK ***** */
44 * jorendb is a simple command-line debugger for shell-js programs. It is
45 * intended as a demo of the Debugger object (as there are no shell js programs to
48 * To run it: $JS -d path/to/this/file/jorendb.js
49 * To run some JS code under it, try:
50 * (jorendb) print load("my-script-to-debug.js")
51 * Execution will stop at debugger statements and you'll get a jorendb prompt.
55 var debuggerSource = "(" + function () {
57 var focusedFrame = null;
59 var debuggeeValues = [null];
62 // Convert a debuggee value v to a string.
63 function dvToString(v) {
64 return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
67 function showDebuggeeValue(dv) {
68 var dvrepr = dvToString(dv);
69 var i = debuggeeValues.length;
70 debuggeeValues[i] = dv;
71 print("$" + i + " = " + dvrepr);
74 Object.defineProperty(Debugger.Frame.prototype, "num", {
79 for (var f = topFrame; f && f !== this; f = f.older)
81 return f === null ? undefined : i;
85 function framePosition(f) {
87 return f.type + " code";
88 return (f.script.url || f.type + " code") + ":" + f.script.getOffsetLine(f.offset);
91 function callDescription(f) {
92 return ((f.callee.name || '<anonymous>') +
93 "(" + f.arguments.map(dvToString).join(", ") + ")");
96 function showFrame(f, n) {
97 if (f === undefined || f === null) {
104 if (n === undefined) {
107 throw new Error("Internal error: frame not on stack");
111 if (f.type === "call")
112 me += ' ' + callDescription(f);
113 me += ' ' + framePosition(f);
117 function saveExcursion(fn) {
118 var tf = topFrame, ff = focusedFrame;
127 function quitCommand() {
132 function backtraceCommand() {
133 if (topFrame === null)
135 for (var i = 0, f = topFrame; f; i++, f = f.older)
139 function printCommand(rest) {
140 if (focusedFrame === null) {
141 // This is super bogus, need a way to create an env wrapping the debuggeeGlobal
142 // and eval against that.
145 nonwrappedValue = debuggeeGlobal.eval(rest);
147 print("Exception caught.");
148 nonwrappedValue = exc;
150 if (typeof nonwrappedValue !== "object" || nonwrappedValue === null) {
151 // primitive value, no sweat
152 print(" " + uneval(nonwrappedValue));
155 print(" " + Object.prototype.toString.call(nonwrappedValue));
158 // This is the real deal.
159 var cv = saveExcursion(function () {
160 return focusedFrame.eval(rest);
165 print("Debuggee died.");
166 } else if ('return' in cv) {
169 showDebuggeeValue(cv.return);
173 print("Exception caught. (To rethrow it, type 'throw'.)");
175 showDebuggeeValue(lastExc);
180 function detachCommand() {
185 function continueCommand() {
186 if (focusedFrame === null) {
193 function throwCommand(rest) {
195 if (focusedFrame !== topFrame) {
196 print("To throw, you must select the newest frame (use 'frame 0').");
198 } else if (focusedFrame === null) {
201 } else if (rest === '') {
202 return [{throw: lastExc}];
204 var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
208 print("Debuggee died while determining what to throw. Stopped.");
209 } else if ('return' in cv) {
210 return [{throw: cv.return}];
214 print("Exception determining what to throw. Stopped.");
215 showDebuggeeValue(cv.throw);
221 function frameCommand(rest) {
223 if (rest.match(/[0-9]+/)) {
230 for (var i = 0; i < n && f; i++) {
232 print("There is no frame " + rest + ".");
240 } else if (rest !== '') {
241 if (topFrame === null)
246 print("do what now?");
250 function debugmeCommand() {
251 var meta = newGlobal("new-compartment");
252 meta.debuggeeGlobal = this;
253 meta.debuggerSource = debuggerSource;
254 meta.prompt = prompt.replace('(', '(meta-');
255 meta.eval(debuggerSource);
258 function upCommand() {
259 if (focusedFrame === null)
261 else if (focusedFrame.older === null)
262 print("Initial frame selected; you cannot go up.");
264 focusedFrame.older.younger = focusedFrame;
265 focusedFrame = focusedFrame.older;
270 function downCommand() {
271 if (focusedFrame === null)
273 else if (!focusedFrame.younger)
274 print("Youngest frame selected; you cannot go down.");
276 focusedFrame = focusedFrame.younger;
281 function forcereturnCommand(rest) {
283 var f = focusedFrame;
284 if (f !== topFrame) {
285 print("To forcereturn, you must select the newest frame (use 'frame 0').");
286 } else if (f === null) {
287 print("Nothing on the stack.");
288 } else if (rest === '') {
289 return [{return: undefined}];
291 var cv = saveExcursion(function () { return f.eval(rest); });
295 print("Debuggee died while determining what to forcereturn. Stopped.");
296 } else if ('return' in cv) {
297 return [{return: cv.return}];
301 print("Error determining what to forcereturn. Stopped.");
302 showDebuggeeValue(cv.throw);
307 // Build the table of commands.
310 backtraceCommand, "bt", "where",
311 continueCommand, "c",
323 for (var i = 0; i < commandArray.length; i++) {
324 var cmd = commandArray[i];
325 if (typeof cmd === "string")
326 commands[cmd] = last;
328 last = commands[cmd.name.replace(/Command$/, '')] = cmd;
331 // Break cmd into two parts: its first word and everything else.
332 function breakcmd(cmd) {
333 cmd = cmd.trimLeft();
334 var m = /\s/.exec(cmd);
337 return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()];
340 function runcmd(cmd) {
341 var pieces = breakcmd(cmd);
342 if (pieces[0] === "")
345 var first = pieces[0], rest = pieces[1];
346 if (!commands.hasOwnProperty(first)) {
347 print("unrecognized command '" + first + "'");
351 var cmd = commands[first];
352 if (cmd.length === 0 && rest !== '') {
353 print("this command cannot take an argument");
363 print("\n" + prompt);
369 var result = runcmd(cmd);
370 if (result === undefined)
372 else if (Array.isArray(result))
375 throw new Error("Internal error: result of runcmd wasn't array or undefined");
377 print("*** Internal error: exception in the debugger code.");
379 var me = prompt.replace(/^\((.*)\)$/, function (a, b) { return b; });
380 print("Debug " + me + "? (y/n)");
381 if (readline().match(/^\s*y/i) !== null)
384 print("ok, ignoring error");
389 var dbg = new Debugger(debuggeeGlobal);
390 dbg.onDebuggerStatement = function (frame) {
391 return saveExcursion(function () {
392 topFrame = focusedFrame = frame;
393 print("'debugger' statement hit.");
398 dbg.onThrow = function (frame, exc) {
399 return saveExcursion(function () {
400 topFrame = focusedFrame = frame;
401 print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
403 print("Exception value is:");
404 showDebuggeeValue(exc);
411 print("jorendb version -0.0");
412 var g = newGlobal("new-compartment");
413 g.debuggeeGlobal = this;
414 g.prompt = '(jorendb)';
415 g.debuggerSource = debuggerSource;
416 g.eval(debuggerSource);