Bumping manifests a=b2g-bump
[gecko.git] / js / examples / jorendb.js
blobd9dc52ffd8bd5eca0fbd6445bf914a20cd666f86
1 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
2  * vim: set ts=8 sw=4 et tw=78:
3  *
4  * jorendb - A toy command-line debugger for shell-js programs.
5  *
6  * This Source Code Form is subject to the terms of the Mozilla Public
7  * License, v. 2.0. If a copy of the MPL was not distributed with this
8  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9  */
12  * jorendb is a simple command-line debugger for shell-js programs. It is
13  * intended as a demo of the Debugger object (as there are no shell js programs
14  * to speak of).
15  *
16  * To run it: $JS -d path/to/this/file/jorendb.js
17  * To run some JS code under it, try:
18  *    (jorendb) print load("my-script-to-debug.js")
19  * Execution will stop at debugger statements and you'll get a jorendb prompt.
20  */
22 // Debugger state.
23 var focusedFrame = null;
24 var topFrame = null;
25 var debuggeeValues = {};
26 var nextDebuggeeValueIndex = 1;
27 var lastExc = null;
29 // Cleanup functions to run when we next re-enter the repl.
30 var replCleanups = [];
32 // Convert a debuggee value v to a string.
33 function dvToString(v) {
34     return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
37 function showDebuggeeValue(dv) {
38     var dvrepr = dvToString(dv);
39     var i = nextDebuggeeValueIndex++;
40     debuggeeValues["$" + i] = dv;
41     print("$" + i + " = " + dvrepr);
44 Object.defineProperty(Debugger.Frame.prototype, "num", {
45     configurable: true,
46     enumerable: false,
47     get: function () {
48             var i = 0;
49             for (var f = topFrame; f && f !== this; f = f.older)
50                 i++;
51             return f === null ? undefined : i;
52         }
53     });
55 Debugger.Frame.prototype.frameDescription = function frameDescription() {
56     if (this.type == "call")
57         return ((this.callee.name || '<anonymous>') +
58                 "(" + this.arguments.map(dvToString).join(", ") + ")");
59     else
60         return this.type + " code";
63 Debugger.Frame.prototype.positionDescription = function positionDescription() {
64     if (this.script) {
65         var line = this.script.getOffsetLine(this.offset);
66         if (this.script.url)
67             return this.script.url + ":" + line;
68         return "line " + line;
69     }
70     return null;
73 Debugger.Frame.prototype.fullDescription = function fullDescription() {
74     var fr = this.frameDescription();
75     var pos = this.positionDescription();
76     if (pos)
77         return fr + ", " + pos;
78     return fr;
81 Object.defineProperty(Debugger.Frame.prototype, "line", {
82         configurable: true,
83         enumerable: false,
84         get: function() {
85             if (this.script)
86                 return this.script.getOffsetLine(this.offset);
87             else
88                 return null;
89         }
90     });
92 function callDescription(f) {
93     return ((f.callee.name || '<anonymous>') +
94             "(" + f.arguments.map(dvToString).join(", ") + ")");
97 function showFrame(f, n) {
98     if (f === undefined || f === null) {
99         f = focusedFrame;
100         if (f === null) {
101             print("No stack.");
102             return;
103         }
104     }
105     if (n === undefined) {
106         n = f.num;
107         if (n === undefined)
108             throw new Error("Internal error: frame not on stack");
109     }
111     print('#' + n + " " + f.fullDescription());
114 function saveExcursion(fn) {
115     var tf = topFrame, ff = focusedFrame;
116     try {
117         return fn();
118     } finally {
119         topFrame = tf;
120         focusedFrame = ff;
121     }
124 // Evaluate an expression in the Debugger global
125 function evalCommand(expr) {
126     eval(expr);
129 function quitCommand() {
130     dbg.enabled = false;
131     quit(0);
134 function backtraceCommand() {
135     if (topFrame === null)
136         print("No stack.");
137     for (var i = 0, f = topFrame; f; i++, f = f.older)
138         showFrame(f, i);
141 function printCommand(rest) {
142     // This is the real deal.
143     var cv = saveExcursion(
144         () => focusedFrame == null
145               ? debuggeeGlobalWrapper.evalInGlobalWithBindings(rest, debuggeeValues)
146               : focusedFrame.evalWithBindings(rest, debuggeeValues));
147     if (cv === null) {
148         if (!dbg.enabled)
149             return [cv];
150         print("Debuggee died.");
151     } else if ('return' in cv) {
152         if (!dbg.enabled)
153             return [undefined];
154         showDebuggeeValue(cv.return);
155     } else {
156         if (!dbg.enabled)
157             return [cv];
158         print("Exception caught. (To rethrow it, type 'throw'.)");
159         lastExc = cv.throw;
160         showDebuggeeValue(lastExc);
161     }
164 function detachCommand() {
165     dbg.enabled = false;
166     return [undefined];
169 function continueCommand() {
170     if (focusedFrame === null) {
171         print("No stack.");
172         return;
173     }
174     return [undefined];
177 function throwCommand(rest) {
178     var v;
179     if (focusedFrame !== topFrame) {
180         print("To throw, you must select the newest frame (use 'frame 0').");
181         return;
182     } else if (focusedFrame === null) {
183         print("No stack.");
184         return;
185     } else if (rest === '') {
186         return [{throw: lastExc}];
187     } else {
188         var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
189         if (cv === null) {
190             if (!dbg.enabled)
191                 return [cv];
192             print("Debuggee died while determining what to throw. Stopped.");
193         } else if ('return' in cv) {
194             return [{throw: cv.return}];
195         } else {
196             if (!dbg.enabled)
197                 return [cv];
198             print("Exception determining what to throw. Stopped.");
199             showDebuggeeValue(cv.throw);
200         }
201         return;
202     }
205 function frameCommand(rest) {
206     var n, f;
207     if (rest.match(/[0-9]+/)) {
208         n = +rest;
209         f = topFrame;
210         if (f === null) {
211             print("No stack.");
212             return;
213         }
214         for (var i = 0; i < n && f; i++) {
215             if (!f.older) {
216                 print("There is no frame " + rest + ".");
217                 return;
218             }
219             f.older.younger = f;
220             f = f.older;
221         }
222         focusedFrame = f;
223         showFrame(f, n);
224     } else if (rest !== '') {
225         if (topFrame === null)
226             print("No stack.");
227         else
228             showFrame();
229     } else {
230         print("do what now?");
231     }
234 function upCommand() {
235     if (focusedFrame === null)
236         print("No stack.");
237     else if (focusedFrame.older === null)
238         print("Initial frame selected; you cannot go up.");
239     else {
240         focusedFrame.older.younger = focusedFrame;
241         focusedFrame = focusedFrame.older;
242         showFrame();
243     }
246 function downCommand() {
247     if (focusedFrame === null)
248         print("No stack.");
249     else if (!focusedFrame.younger)
250         print("Youngest frame selected; you cannot go down.");
251     else {
252         focusedFrame = focusedFrame.younger;
253         showFrame();
254     }
257 function forcereturnCommand(rest) {
258     var v;
259     var f = focusedFrame;
260     if (f !== topFrame) {
261         print("To forcereturn, you must select the newest frame (use 'frame 0').");
262     } else if (f === null) {
263         print("Nothing on the stack.");
264     } else if (rest === '') {
265         return [{return: undefined}];
266     } else {
267         var cv = saveExcursion(function () { return f.eval(rest); });
268         if (cv === null) {
269             if (!dbg.enabled)
270                 return [cv];
271             print("Debuggee died while determining what to forcereturn. Stopped.");
272         } else if ('return' in cv) {
273             return [{return: cv.return}];
274         } else {
275             if (!dbg.enabled)
276                 return [cv];
277             print("Error determining what to forcereturn. Stopped.");
278             showDebuggeeValue(cv.throw);
279         }
280     }
283 function printPop(f, c) {
284     var fdesc = f.fullDescription();
285     if (c.return) {
286         print("frame returning (still selected): " + fdesc);
287         showDebuggeeValue(c.return);
288     } else if (c.throw) {
289         print("frame threw exception: " + fdesc);
290         showDebuggeeValue(c.throw);
291         print("(To rethrow it, type 'throw'.)");
292         lastExc = c.throw;
293     } else {
294         print("frame was terminated: " + fdesc);
295     }
298 // Set |prop| on |obj| to |value|, but then restore its current value
299 // when we next enter the repl.
300 function setUntilRepl(obj, prop, value) {
301     var saved = obj[prop];
302     obj[prop] = value;
303     replCleanups.push(function () { obj[prop] = saved; });
306 function doStepOrNext(kind) {
307     var startFrame = topFrame;
308     var startLine = startFrame.line;
309     print("stepping in:   " + startFrame.fullDescription());
310     print("starting line: " + uneval(startLine));
312     function stepPopped(completion) {
313         // Note that we're popping this frame; we need to watch for
314         // subsequent step events on its caller.
315         this.reportedPop = true;
316         printPop(this, completion);
317         topFrame = focusedFrame = this;
318         return repl();
319     }
321     function stepEntered(newFrame) {
322         print("entered frame: " + newFrame.fullDescription());
323         topFrame = focusedFrame = newFrame;
324         return repl();
325     }
327     function stepStepped() {
328         print("stepStepped: " + this.fullDescription());
329         // If we've changed frame or line, then report that.
330         if (this !== startFrame || this.line != startLine) {
331             topFrame = focusedFrame = this;
332             if (focusedFrame != startFrame)
333                 print(focusedFrame.fullDescription());
334             return repl();
335         }
337         // Otherwise, let execution continue.
338         return undefined;
339     }
341     if (kind.step)
342         setUntilRepl(dbg, 'onEnterFrame', stepEntered);
344     // If we're stepping after an onPop, watch for steps and pops in the
345     // next-older frame; this one is done.
346     var stepFrame = startFrame.reportedPop ? startFrame.older : startFrame;
347     if (!stepFrame || !stepFrame.script)
348         stepFrame = null;
349     if (stepFrame) {
350         setUntilRepl(stepFrame, 'onStep', stepStepped);
351         setUntilRepl(stepFrame, 'onPop',  stepPopped);
352     }
354     // Let the program continue!
355     return [undefined];
358 function stepCommand() { return doStepOrNext({step:true}); }
359 function nextCommand() { return doStepOrNext({next:true}); }
361 // Build the table of commands.
362 var commands = {};
363 var commandArray = [
364     backtraceCommand, "bt", "where",
365     continueCommand, "c",
366     detachCommand,
367     downCommand, "d",
368     forcereturnCommand,
369     frameCommand, "f",
370     nextCommand, "n",
371     printCommand, "p",
372     quitCommand, "q",
373     stepCommand, "s",
374     throwCommand, "t",
375     upCommand, "u",
376     helpCommand, "h",
377     evalCommand, "!",
378     ];
379 var last = null;
380 for (var i = 0; i < commandArray.length; i++) {
381     var cmd = commandArray[i];
382     if (typeof cmd === "string")
383         commands[cmd] = last;
384     else
385         last = commands[cmd.name.replace(/Command$/, '')] = cmd;
388 function helpCommand(rest) {
389     print("Available commands:");
390     var printcmd = function(group) {
391         print("  " + group.join(", "));
392     }
394     var group = [];
395     for (var cmd of commandArray) {
396         if (typeof cmd === "string") {
397             group.push(cmd);
398         } else {
399             if (group.length) printcmd(group);
400             group = [ cmd.name.replace(/Command$/, '') ];
401         }
402     }
403     printcmd(group);
406 // Break cmd into two parts: its first word and everything else. If it begins
407 // with punctuation, treat that as a separate word.
408 function breakcmd(cmd) {
409     cmd = cmd.trimLeft();
410     if ("!@#$%^&*_+=/?.,<>:;'\"".indexOf(cmd.substr(0, 1)) != -1)
411         return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
412     var m = /\s/.exec(cmd);
413     if (m === null)
414         return [cmd, ''];
415     return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()];
418 function runcmd(cmd) {
419     var pieces = breakcmd(cmd);
420     if (pieces[0] === "")
421         return undefined;
423     var first = pieces[0], rest = pieces[1];
424     if (!commands.hasOwnProperty(first)) {
425         print("unrecognized command '" + first + "'");
426         return undefined;
427     }
429     var cmd = commands[first];
430     if (cmd.length === 0 && rest !== '') {
431         print("this command cannot take an argument");
432         return undefined;
433     }
435     return cmd(rest);
438 function repl() {
439     while (replCleanups.length > 0)
440         replCleanups.pop()();
442     var cmd;
443     for (;;) {
444         putstr("\n" + prompt);
445         cmd = readline();
446         if (cmd === null)
447             return null;
449         try {
450             var result = runcmd(cmd);
451             if (result === undefined)
452                 ; // do nothing
453             else if (Array.isArray(result))
454                 return result[0];
455             else
456                 throw new Error("Internal error: result of runcmd wasn't array or undefined");
457         } catch (exc) {
458             print("*** Internal error: exception in the debugger code.");
459             print("    " + exc);
460             print(exc.stack);
461         }
462     }
465 var dbg = new Debugger();
466 dbg.onDebuggerStatement = function (frame) {
467     return saveExcursion(function () {
468             topFrame = focusedFrame = frame;
469             print("'debugger' statement hit.");
470             showFrame();
471             return repl();
472         });
474 dbg.onThrow = function (frame, exc) {
475     return saveExcursion(function () {
476             topFrame = focusedFrame = frame;
477             print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
478             showFrame();
479             print("Exception value is:");
480             showDebuggeeValue(exc);
481             return repl();
482         });
485 // The depth of jorendb nesting.
486 var jorendbDepth;
487 if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
489 var debuggeeGlobal = newGlobal("new-compartment");
490 debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
491 var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
493 print("jorendb version -0.0");
494 prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
496 var args = arguments;
497 while(args.length > 0) {
498     var arg = args.shift();
499     if (arg == '-f') {
500         arg = args.shift();
501         debuggeeGlobal.evaluate(read(arg), { fileName: arg, lineNumber: 1 });
502     } else if (arg == '-e') {
503         arg = args.shift();
504         debuggeeGlobal.eval(arg);
505     } else {
506         throw("jorendb does not implement command-line argument '" + arg + "'");
507     }
510 repl();