Bug 732976 - SingleSourceFactory should generate checksums file. r=ted
[gecko.git] / js / examples / jorendb.js
blobcbf35bc1e8c1da5d48fcea90e487b2ca64cab8ec
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 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  * ***** BEGIN LICENSE BLOCK *****
7  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
8  *
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/
13  *
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
17  * License.
18  *
19  * The Original Code is jorendb toy JS debugger.
20  *
21  * The Initial Developer of the Original Code is
22  * Mozilla Foundation.
23  * Portions created by the Initial Developer are Copyright (C) 2011
24  * the Initial Developer. All Rights Reserved.
25  *
26  * Contributor(s):
27  *   Jason Orendorff <jorendorff@mozilla.com>
28  *
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.
40  *
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
46  * speak of).
47  *
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.
52  */
54 (function () {
55     var debuggerSource = "(" + function () {
56         // Debugger state.
57         var focusedFrame = null;
58         var topFrame = null;
59         var debuggeeValues = [null];
60         var lastExc = 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 + "]";
65         }
67         function showDebuggeeValue(dv) {
68             var dvrepr = dvToString(dv);
69             var i = debuggeeValues.length;
70             debuggeeValues[i] = dv;
71             print("$" + i + " = " + dvrepr);
72         }
74         Object.defineProperty(Debugger.Frame.prototype, "num", {
75             configurable: true,
76             enumerable: false,
77             get: function () {
78                     var i = 0;
79                     for (var f = topFrame; f && f !== this; f = f.older)
80                         i++;
81                     return f === null ? undefined : i;
82                 }
83             });
85         function framePosition(f) {
86             if (!f.script)
87                 return f.type + " code";
88             return (f.script.url || f.type + " code") + ":" + f.script.getOffsetLine(f.offset);
89         }
91         function callDescription(f) {
92             return ((f.callee.name || '<anonymous>') + 
93                     "(" + f.arguments.map(dvToString).join(", ") + ")");
94         }
96         function showFrame(f, n) {
97             if (f === undefined || f === null) {
98                 f = focusedFrame;
99                 if (f === null) {
100                     print("No stack.");
101                     return;
102                 }
103             }
104             if (n === undefined) {
105                 n = f.num;
106                 if (n === undefined)
107                     throw new Error("Internal error: frame not on stack");
108             }
110             var me = '#' + n;
111             if (f.type === "call")
112                 me += ' ' + callDescription(f);
113             me += ' ' + framePosition(f);
114             print(me);
115         }
117         function saveExcursion(fn) {
118             var tf = topFrame, ff = focusedFrame;
119             try {
120                 return fn();
121             } finally {
122                 topFrame = tf;
123                 focusedFrame = ff;
124             }
125         }
127         function quitCommand() {
128             dbg.enabled = false;
129             quit(0);
130         }
132         function backtraceCommand() {
133             if (topFrame === null)
134                 print("No stack.");
135             for (var i = 0, f = topFrame; f; i++, f = f.older)
136                 showFrame(f, i);
137         }
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.
143                 var nonwrappedValue;
144                 try {
145                     nonwrappedValue = debuggeeGlobal.eval(rest);
146                 } catch (exc) {
147                     print("Exception caught.");
148                     nonwrappedValue = exc;
149                 }
150                 if (typeof nonwrappedValue !== "object" || nonwrappedValue === null) {
151                     // primitive value, no sweat
152                     print("    " + uneval(nonwrappedValue));
153                 } else {
154                     // junk for now
155                     print("    " + Object.prototype.toString.call(nonwrappedValue));
156                 }
157             } else {
158                 // This is the real deal.
159                 var cv = saveExcursion(function () {
160                         return focusedFrame.eval(rest);
161                     });
162                 if (cv === null) {
163                     if (!dbg.enabled)
164                         return [cv];
165                     print("Debuggee died.");
166                 } else if ('return' in cv) {
167                     if (!dbg.enabled)
168                         return [undefined];
169                     showDebuggeeValue(cv.return);
170                 } else {
171                     if (!dbg.enabled)
172                         return [cv];
173                     print("Exception caught. (To rethrow it, type 'throw'.)");
174                     lastExc = cv.throw;
175                     showDebuggeeValue(lastExc);
176                 }
177             }
178         }
180         function detachCommand() {
181             dbg.enabled = false;
182             return [undefined];
183         }
185         function continueCommand() {
186             if (focusedFrame === null) {
187                 print("No stack.");
188                 return;
189             }
190             return [undefined];
191         }
193         function throwCommand(rest) {
194             var v;
195             if (focusedFrame !== topFrame) {
196                 print("To throw, you must select the newest frame (use 'frame 0').");
197                 return;
198             } else if (focusedFrame === null) {
199                 print("No stack.");
200                 return;
201             } else if (rest === '') {
202                 return [{throw: lastExc}];
203             } else {
204                 var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
205                 if (cv === null) {
206                     if (!dbg.enabled)
207                         return [cv];
208                     print("Debuggee died while determining what to throw. Stopped.");
209                 } else if ('return' in cv) {
210                     return [{throw: cv.return}];
211                 } else {
212                     if (!dbg.enabled)
213                         return [cv];
214                     print("Exception determining what to throw. Stopped.");
215                     showDebuggeeValue(cv.throw);
216                 }
217                 return;
218             }
219         }
221         function frameCommand(rest) {
222             var n, f;
223             if (rest.match(/[0-9]+/)) {
224                 n = +rest;
225                 f = topFrame;
226                 if (f === null) {
227                     print("No stack.");
228                     return;
229                 }
230                 for (var i = 0; i < n && f; i++) {
231                     if (!f.older) {
232                         print("There is no frame " + rest + ".");
233                         return;
234                     }
235                     f.older.younger = f;
236                     f = f.older;
237                 }
238                 focusedFrame = f;
239                 showFrame(f, n);
240             } else if (rest !== '') {
241                 if (topFrame === null)
242                     print("No stack.");
243                 else
244                     showFrame();
245             } else {
246                 print("do what now?");
247             }
248         }
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);
256         }
258         function upCommand() {
259             if (focusedFrame === null)
260                 print("No stack.");
261             else if (focusedFrame.older === null)
262                 print("Initial frame selected; you cannot go up.");
263             else {
264                 focusedFrame.older.younger = focusedFrame;
265                 focusedFrame = focusedFrame.older;
266                 showFrame();
267             }
268         }
270         function downCommand() {
271             if (focusedFrame === null)
272                 print("No stack.");
273             else if (!focusedFrame.younger)
274                 print("Youngest frame selected; you cannot go down.");
275             else {
276                 focusedFrame = focusedFrame.younger;
277                 showFrame();
278             }
279         }
281         function forcereturnCommand(rest) {
282             var v;
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}];
290             } else {
291                 var cv = saveExcursion(function () { return f.eval(rest); });
292                 if (cv === null) {
293                     if (!dbg.enabled)
294                         return [cv];
295                     print("Debuggee died while determining what to forcereturn. Stopped.");
296                 } else if ('return' in cv) {
297                     return [{return: cv.return}];
298                 } else {
299                     if (!dbg.enabled)
300                         return [cv];
301                     print("Error determining what to forcereturn. Stopped.");
302                     showDebuggeeValue(cv.throw);
303                 }
304             }
305         }
307         // Build the table of commands.
308         var commands = {};
309         var commandArray = [
310             backtraceCommand, "bt", "where",
311             continueCommand, "c",
312             detachCommand,
313             debugmeCommand,
314             downCommand, "d",
315             forcereturnCommand,
316             frameCommand, "f",
317             printCommand, "p",
318             quitCommand, "q",
319             throwCommand, "t",
320             upCommand, "u"
321             ];
322         var last = null;
323         for (var i = 0; i < commandArray.length; i++) {
324             var cmd = commandArray[i];
325             if (typeof cmd === "string")
326                 commands[cmd] = last;
327             else
328                 last = commands[cmd.name.replace(/Command$/, '')] = cmd;
329         }
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);
335             if (m === null)
336                 return [cmd, ''];
337             return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()];
338         }
340         function runcmd(cmd) {
341             var pieces = breakcmd(cmd);
342             if (pieces[0] === "")
343                 return undefined;
345             var first = pieces[0], rest = pieces[1];
346             if (!commands.hasOwnProperty(first)) {
347                 print("unrecognized command '" + first + "'");
348                 return undefined;
349             }
351             var cmd = commands[first];
352             if (cmd.length === 0 && rest !== '') {
353                 print("this command cannot take an argument");
354                 return undefined;
355             }
357             return cmd(rest);
358         }
360         function repl() {
361             var cmd;
362             for (;;) {
363                 print("\n" + prompt);
364                 cmd = readline();
365                 if (cmd === null)
366                     break;
368                 try {
369                     var result = runcmd(cmd);
370                     if (result === undefined)
371                         ; // do nothing
372                     else if (Array.isArray(result))
373                         return result[0];
374                     else
375                         throw new Error("Internal error: result of runcmd wasn't array or undefined");
376                 } catch (exc) {
377                     print("*** Internal error: exception in the debugger code.");
378                     print("    " + exc);
379                     var me = prompt.replace(/^\((.*)\)$/, function (a, b) { return b; });
380                     print("Debug " + me + "? (y/n)");
381                     if (readline().match(/^\s*y/i) !== null)
382                         debugMe();
383                     else
384                         print("ok, ignoring error");
385                 }
386             }
387         }
389         var dbg = new Debugger(debuggeeGlobal);
390         dbg.onDebuggerStatement = function (frame) {
391             return saveExcursion(function () {
392                     topFrame = focusedFrame = frame;
393                     print("'debugger' statement hit.");
394                     showFrame();
395                     return repl();
396                 });
397         };
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.)");
402                     showFrame();
403                     print("Exception value is:");
404                     showDebuggeeValue(exc);
405                     return repl();
406                 });
407         };
408         repl();
409     } + ")();"
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);
417 })();