Backed out changeset 9d8b4c0b99ed (bug 1945683) for causing btime failures. CLOSED...
[gecko.git] / js / examples / jorendb.js
blob33d6c27316b30e0ed7006711e34aef53176342b1
1 /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
2 * vim: set ts=8 sw=4 et tw=78:
4 * jorendb - A toy command-line debugger for shell-js programs.
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).
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.
22 // Debugger state.
23 var focusedFrame = null;
24 var topFrame = null;
25 var debuggeeValues = {};
26 var nextDebuggeeValueIndex = 1;
27 var lastExc = null;
28 var todo = [];
29 var activeTask;
30 var options = { 'pretty': true,
31 'emacs': !!os.getenv('INSIDE_EMACS') };
32 var rerun = true;
34 // Cleanup functions to run when we next re-enter the repl.
35 var replCleanups = [];
37 // Redirect debugger printing functions to go to the original output
38 // destination, unaffected by any redirects done by the debugged script.
39 var initialOut = os.file.redirect();
40 var initialErr = os.file.redirectErr();
42 function wrap(global, name) {
43 var orig = global[name];
44 global[name] = function(...args) {
46 var oldOut = os.file.redirect(initialOut);
47 var oldErr = os.file.redirectErr(initialErr);
48 try {
49 return orig.apply(global, args);
50 } finally {
51 os.file.redirect(oldOut);
52 os.file.redirectErr(oldErr);
56 wrap(this, 'print');
57 wrap(this, 'printErr');
58 wrap(this, 'putstr');
60 // Convert a debuggee value v to a string.
61 function dvToString(v) {
62 if (typeof(v) === 'object' && v !== null) {
63 return `[object ${v.class}]`;
65 const s = uneval(v);
66 if (s.length > 400) {
67 return s.substr(0, 400) + "...<" + (s.length - 400) + " more bytes>...";
69 return s;
72 function summaryObject(dv) {
73 var obj = {};
74 for (var name of dv.getOwnPropertyNames()) {
75 var v = dv.getOwnPropertyDescriptor(name).value;
76 if (v instanceof Debugger.Object) {
77 v = "(...)";
79 obj[name] = v;
81 return obj;
84 function debuggeeValueToString(dv, style) {
85 var dvrepr = dvToString(dv);
86 if (!style.pretty || (typeof dv !== 'object') || (dv === null))
87 return [dvrepr, undefined];
89 const exec = debuggeeGlobalWrapper.executeInGlobalWithBindings.bind(debuggeeGlobalWrapper);
91 if (dv.class == "Error") {
92 let errval = exec("$$.toString()", debuggeeValues);
93 return [dvrepr, errval.return];
96 if (style.brief)
97 return [dvrepr, JSON.stringify(summaryObject(dv), null, 4)];
99 let str = exec("JSON.stringify(v, null, 4)", {v: dv});
100 if ('throw' in str) {
101 if (style.noerror)
102 return [dvrepr, undefined];
104 let substyle = {};
105 Object.assign(substyle, style);
106 substyle.noerror = true;
107 return [dvrepr, debuggeeValueToString(str.throw, substyle)];
110 return [dvrepr, str.return];
113 // Problem! Used to do [object Object] followed by details. Now just details?
115 function showDebuggeeValue(dv, style={pretty: options.pretty}) {
116 var i = nextDebuggeeValueIndex++;
117 debuggeeValues["$" + i] = dv;
118 debuggeeValues["$$"] = dv;
119 let [brief, full] = debuggeeValueToString(dv, style);
120 print("$" + i + " = " + brief);
121 if (full !== undefined)
122 print(full);
125 Object.defineProperty(Debugger.Frame.prototype, "num", {
126 configurable: true,
127 enumerable: false,
128 get: function () {
129 var i = 0;
130 for (var f = topFrame; f && f !== this; f = f.older)
131 i++;
132 return f === null ? undefined : i;
136 Debugger.Frame.prototype.frameDescription = function frameDescription() {
137 if (this.type == "call")
138 return ((this.callee.name || '<anonymous>') +
139 "(" + this.arguments.map(dvToString).join(", ") + ")");
140 else
141 return this.type + " code";
144 Debugger.Frame.prototype.positionDescription = function positionDescription() {
145 if (this.script) {
146 var line = this.script.getOffsetLocation(this.offset).lineNumber;
147 if (this.script.url)
148 return this.script.url + ":" + line;
149 return "line " + line;
151 return null;
154 Debugger.Frame.prototype.location = function () {
155 if (this.script) {
156 var { lineNumber, columnNumber, isEntryPoint } = this.script.getOffsetLocation(this.offset);
157 if (this.script.url)
158 return this.script.url + ":" + lineNumber;
159 return null;
161 return null;
164 Debugger.Frame.prototype.fullDescription = function fullDescription() {
165 var fr = this.frameDescription();
166 var pos = this.positionDescription();
167 if (pos)
168 return fr + ", " + pos;
169 return fr;
172 Object.defineProperty(Debugger.Frame.prototype, "line", {
173 configurable: true,
174 enumerable: false,
175 get: function() {
176 if (this.script)
177 return this.script.getOffsetLocation(this.offset).lineNumber;
178 else
179 return null;
183 function callDescription(f) {
184 return ((f.callee.name || '<anonymous>') +
185 "(" + f.arguments.map(dvToString).join(", ") + ")");
188 function showFrame(f, n) {
189 if (f === undefined || f === null) {
190 f = focusedFrame;
191 if (f === null) {
192 print("No stack.");
193 return;
196 if (n === undefined) {
197 n = f.num;
198 if (n === undefined)
199 throw new Error("Internal error: frame not on stack");
202 print('#' + n + " " + f.fullDescription());
205 function saveExcursion(fn) {
206 var tf = topFrame, ff = focusedFrame;
207 try {
208 return fn();
209 } finally {
210 topFrame = tf;
211 focusedFrame = ff;
215 function parseArgs(str) {
216 return str.split(" ");
219 function describedRv(r, desc) {
220 desc = "[" + desc + "] ";
221 if (r === undefined) {
222 print(desc + "Returning undefined");
223 } else if (r === null) {
224 print(desc + "Returning null");
225 } else if (r.length === undefined) {
226 print(desc + "Returning object " + JSON.stringify(r));
227 } else {
228 print(desc + "Returning length-" + r.length + " list");
229 if (r.length > 0) {
230 print(" " + r[0]);
233 return r;
236 // Rerun the program (reloading it from the file)
237 function runCommand(args) {
238 print(`Restarting program (${args})`);
239 if (args)
240 activeTask.scriptArgs = parseArgs(args);
241 else
242 activeTask.scriptArgs = [...actualScriptArgs];
243 rerun = true;
244 for (var f = topFrame; f; f = f.older) {
245 if (f.older) {
246 f.onPop = () => null;
247 } else {
248 f.onPop = () => ({ 'return': 0 });
251 //return describedRv([{ 'return': 0 }], "runCommand");
252 return null;
255 // Evaluate an expression in the Debugger global
256 function evalCommand(expr) {
257 eval(expr);
260 function quitCommand() {
261 dbg.removeAllDebuggees();
262 quit(0);
265 function backtraceCommand() {
266 if (topFrame === null)
267 print("No stack.");
268 for (var i = 0, f = topFrame; f; i++, f = f.older)
269 showFrame(f, i);
272 function setCommand(rest) {
273 var space = rest.indexOf(' ');
274 if (space == -1) {
275 print("Invalid set <option> <value> command");
276 } else {
277 var name = rest.substr(0, space);
278 var value = rest.substr(space + 1);
280 if (name == 'args') {
281 activeTask.scriptArgs = parseArgs(value);
282 } else {
283 var yes = ["1", "yes", "true", "on"];
284 var no = ["0", "no", "false", "off"];
286 if (yes.includes(value))
287 options[name] = true;
288 else if (no.includes(value))
289 options[name] = false;
290 else
291 options[name] = value;
296 function split_print_options(s, style) {
297 var m = /^\/(\w+)/.exec(s);
298 if (!m)
299 return [ s, style ];
300 if (m[1].includes("p"))
301 style.pretty = true;
302 if (m[1].includes("b"))
303 style.brief = true;
304 return [ s.substr(m[0].length).trimLeft(), style ];
307 function doPrint(expr, style) {
308 // This is the real deal.
309 var cv = saveExcursion(
310 () => focusedFrame == null
311 ? debuggeeGlobalWrapper.executeInGlobalWithBindings(expr, debuggeeValues)
312 : focusedFrame.evalWithBindings(expr, debuggeeValues));
313 if (cv === null) {
314 print("Debuggee died.");
315 } else if ('return' in cv) {
316 showDebuggeeValue(cv.return, style);
317 } else {
318 print("Exception caught. (To rethrow it, type 'throw'.)");
319 lastExc = cv.throw;
320 showDebuggeeValue(lastExc, style);
324 function printCommand(rest) {
325 var [expr, style] = split_print_options(rest, {pretty: options.pretty});
326 return doPrint(expr, style);
329 function keysCommand(rest) { return doPrint("Object.keys(" + rest + ")"); }
331 function detachCommand() {
332 dbg.removeAllDebuggees();
333 return [undefined];
336 function continueCommand(rest) {
337 if (focusedFrame === null) {
338 print("No stack.");
339 return;
342 var match = rest.match(/^(\d+)$/);
343 if (match) {
344 return doStepOrNext({upto:true, stopLine:match[1]});
347 return [undefined];
350 function throwCommand(rest) {
351 var v;
352 if (focusedFrame !== topFrame) {
353 print("To throw, you must select the newest frame (use 'frame 0').");
354 return;
355 } else if (focusedFrame === null) {
356 print("No stack.");
357 return;
358 } else if (rest === '') {
359 return [{throw: lastExc}];
360 } else {
361 var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
362 if (cv === null) {
363 print("Debuggee died while determining what to throw. Stopped.");
364 } else if ('return' in cv) {
365 return [{throw: cv.return}];
366 } else {
367 print("Exception determining what to throw. Stopped.");
368 showDebuggeeValue(cv.throw);
370 return;
374 function frameCommand(rest) {
375 var n, f;
376 if (rest.match(/[0-9]+/)) {
377 n = +rest;
378 f = topFrame;
379 if (f === null) {
380 print("No stack.");
381 return;
383 for (var i = 0; i < n && f; i++) {
384 if (!f.older) {
385 print("There is no frame " + rest + ".");
386 return;
388 f.older.younger = f;
389 f = f.older;
391 focusedFrame = f;
392 updateLocation(focusedFrame);
393 showFrame(f, n);
394 } else if (rest === '') {
395 if (topFrame === null) {
396 print("No stack.");
397 } else {
398 updateLocation(focusedFrame);
399 showFrame();
401 } else {
402 print("do what now?");
406 function upCommand() {
407 if (focusedFrame === null)
408 print("No stack.");
409 else if (focusedFrame.older === null)
410 print("Initial frame selected; you cannot go up.");
411 else {
412 focusedFrame.older.younger = focusedFrame;
413 focusedFrame = focusedFrame.older;
414 updateLocation(focusedFrame);
415 showFrame();
419 function downCommand() {
420 if (focusedFrame === null)
421 print("No stack.");
422 else if (!focusedFrame.younger)
423 print("Youngest frame selected; you cannot go down.");
424 else {
425 focusedFrame = focusedFrame.younger;
426 updateLocation(focusedFrame);
427 showFrame();
431 function forcereturnCommand(rest) {
432 var v;
433 var f = focusedFrame;
434 if (f !== topFrame) {
435 print("To forcereturn, you must select the newest frame (use 'frame 0').");
436 } else if (f === null) {
437 print("Nothing on the stack.");
438 } else if (rest === '') {
439 return [{return: undefined}];
440 } else {
441 var cv = saveExcursion(function () { return f.eval(rest); });
442 if (cv === null) {
443 print("Debuggee died while determining what to forcereturn. Stopped.");
444 } else if ('return' in cv) {
445 return [{return: cv.return}];
446 } else {
447 print("Error determining what to forcereturn. Stopped.");
448 showDebuggeeValue(cv.throw);
453 function printPop(f, c) {
454 var fdesc = f.fullDescription();
455 if (c.return) {
456 print("frame returning (still selected): " + fdesc);
457 showDebuggeeValue(c.return, {brief: true});
458 } else if (c.throw) {
459 print("frame threw exception: " + fdesc);
460 showDebuggeeValue(c.throw);
461 print("(To rethrow it, type 'throw'.)");
462 lastExc = c.throw;
463 } else {
464 print("frame was terminated: " + fdesc);
468 // Set |prop| on |obj| to |value|, but then restore its current value
469 // when we next enter the repl.
470 function setUntilRepl(obj, prop, value) {
471 var saved = obj[prop];
472 obj[prop] = value;
473 replCleanups.push(function () { obj[prop] = saved; });
476 function updateLocation(frame) {
477 if (options.emacs) {
478 var loc = frame.location();
479 if (loc)
480 print("\032\032" + loc + ":1");
484 function doStepOrNext(kind) {
485 var startFrame = topFrame;
486 var startLine = startFrame.line;
487 // print("stepping in: " + startFrame.fullDescription());
488 // print("starting line: " + uneval(startLine));
490 function stepPopped(completion) {
491 // Note that we're popping this frame; we need to watch for
492 // subsequent step events on its caller.
493 this.reportedPop = true;
494 printPop(this, completion);
495 topFrame = focusedFrame = this;
496 if (kind.finish) {
497 // We want to continue, but this frame is going to be invalid as
498 // soon as this function returns, which will make the replCleanups
499 // assert when it tries to access the dead frame's 'onPop'
500 // property. So clear it out now while the frame is still valid,
501 // and trade it for an 'onStep' callback on the frame we're popping to.
502 preReplCleanups();
503 setUntilRepl(this.older, 'onStep', stepStepped);
504 return undefined;
506 updateLocation(this);
507 return repl();
510 function stepEntered(newFrame) {
511 print("entered frame: " + newFrame.fullDescription());
512 updateLocation(newFrame);
513 topFrame = focusedFrame = newFrame;
514 return repl();
517 function stepStepped() {
518 // print("stepStepped: " + this.fullDescription());
519 updateLocation(this);
520 var stop = false;
522 if (kind.finish) {
523 // 'finish' set a one-time onStep for stopping at the frame it
524 // wants to return to
525 stop = true;
526 } else if (kind.upto) {
527 // running until a given line is reached
528 if (this.line == kind.stopLine)
529 stop = true;
530 } else {
531 // regular step; stop whenever the line number changes
532 if ((this.line != startLine) || (this != startFrame))
533 stop = true;
536 if (stop) {
537 topFrame = focusedFrame = this;
538 if (focusedFrame != startFrame)
539 print(focusedFrame.fullDescription());
540 return repl();
543 // Otherwise, let execution continue.
544 return undefined;
547 if (kind.step)
548 setUntilRepl(dbg, 'onEnterFrame', stepEntered);
550 // If we're stepping after an onPop, watch for steps and pops in the
551 // next-older frame; this one is done.
552 var stepFrame = startFrame.reportedPop ? startFrame.older : startFrame;
553 if (!stepFrame || !stepFrame.script)
554 stepFrame = null;
555 if (stepFrame) {
556 if (!kind.finish)
557 setUntilRepl(stepFrame, 'onStep', stepStepped);
558 setUntilRepl(stepFrame, 'onPop', stepPopped);
561 // Let the program continue!
562 return [undefined];
565 function stepCommand() { return doStepOrNext({step:true}); }
566 function nextCommand() { return doStepOrNext({next:true}); }
567 function finishCommand() { return doStepOrNext({finish:true}); }
569 // FIXME: DOES NOT WORK YET
570 function breakpointCommand(where) {
571 print("Sorry, breakpoints don't work yet.");
572 var script = focusedFrame.script;
573 var offsets = script.getLineOffsets(Number(where));
574 if (offsets.length == 0) {
575 print("Unable to break at line " + where);
576 return;
578 for (var offset of offsets) {
579 script.setBreakpoint(offset, { hit: handleBreakpoint });
581 print("Set breakpoint in " + script.url + ":" + script.startLine + " at line " + where + ", " + offsets.length);
584 // Build the table of commands.
585 var commands = {};
586 var commandArray = [
587 backtraceCommand, "bt", "where",
588 breakpointCommand, "b", "break",
589 continueCommand, "c",
590 detachCommand,
591 downCommand, "d",
592 evalCommand, "!",
593 forcereturnCommand,
594 frameCommand, "f",
595 finishCommand, "fin",
596 nextCommand, "n",
597 printCommand, "p",
598 keysCommand, "k",
599 quitCommand, "q",
600 runCommand, "run",
601 stepCommand, "s",
602 setCommand,
603 throwCommand, "t",
604 upCommand, "u",
605 helpCommand, "h",
607 var currentCmd = null;
608 for (var i = 0; i < commandArray.length; i++) {
609 var cmd = commandArray[i];
610 if (typeof cmd === "string")
611 commands[cmd] = currentCmd;
612 else
613 currentCmd = commands[cmd.name.replace(/Command$/, '')] = cmd;
616 function helpCommand(rest) {
617 print("Available commands:");
618 var printcmd = function(group) {
619 print(" " + group.join(", "));
622 var group = [];
623 for (var cmd of commandArray) {
624 if (typeof cmd === "string") {
625 group.push(cmd);
626 } else {
627 if (group.length) printcmd(group);
628 group = [ cmd.name.replace(/Command$/, '') ];
631 printcmd(group);
634 // Break cmd into two parts: its first word and everything else. If it begins
635 // with punctuation, treat that as a separate word. The first word is
636 // terminated with whitespace or the '/' character. So:
638 // print x => ['print', 'x']
639 // print => ['print', '']
640 // !print x => ['!', 'print x']
641 // ?!wtf!? => ['?', '!wtf!?']
642 // print/b x => ['print', '/b x']
644 function breakcmd(cmd) {
645 cmd = cmd.trimLeft();
646 if ("!@#$%^&*_+=/?.,<>:;'\"".includes(cmd.substr(0, 1)))
647 return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
648 var m = /\s+|(?=\/)/.exec(cmd);
649 if (m === null)
650 return [cmd, ''];
651 return [cmd.slice(0, m.index), cmd.slice(m.index + m[0].length)];
654 function runcmd(cmd) {
655 var pieces = breakcmd(cmd);
656 if (pieces[0] === "")
657 return undefined;
659 var first = pieces[0], rest = pieces[1];
660 if (!commands.hasOwnProperty(first)) {
661 print("unrecognized command '" + first + "'");
662 return undefined;
665 var cmd = commands[first];
666 if (cmd.length === 0 && rest !== '') {
667 print("this command cannot take an argument");
668 return undefined;
671 return cmd(rest);
674 function preReplCleanups() {
675 while (replCleanups.length > 0)
676 replCleanups.pop()();
679 var prevcmd = undefined;
680 function repl() {
681 preReplCleanups();
683 var cmd;
684 for (;;) {
685 putstr("\n" + prompt);
686 cmd = readline();
687 if (cmd === null)
688 return null;
689 else if (cmd === "")
690 cmd = prevcmd;
692 try {
693 prevcmd = cmd;
694 var result = runcmd(cmd);
695 if (result === undefined)
696 ; // do nothing, return to prompt
697 else if (Array.isArray(result))
698 return result[0];
699 else if (result === null)
700 return null;
701 else
702 throw new Error("Internal error: result of runcmd wasn't array or undefined: " + result);
703 } catch (exc) {
704 print("*** Internal error: exception in the debugger code.");
705 print(" " + exc);
706 print(exc.stack);
711 var dbg = new Debugger();
712 dbg.onDebuggerStatement = function (frame) {
713 return saveExcursion(function () {
714 topFrame = focusedFrame = frame;
715 print("'debugger' statement hit.");
716 showFrame();
717 updateLocation(focusedFrame);
718 backtrace();
719 return describedRv(repl(), "debugger.saveExc");
722 dbg.onThrow = function (frame, exc) {
723 return saveExcursion(function () {
724 topFrame = focusedFrame = frame;
725 print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
726 showFrame();
727 print("Exception value is:");
728 showDebuggeeValue(exc);
729 return repl();
733 function handleBreakpoint (frame) {
734 print("Breakpoint hit!");
735 return saveExcursion(() => {
736 topFrame = focusedFrame = frame;
737 print("breakpoint hit.");
738 showFrame();
739 updateLocation(focusedFrame);
740 return repl();
744 // The depth of jorendb nesting.
745 var jorendbDepth;
746 if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
748 var debuggeeGlobal = newGlobal({newCompartment: true});
749 debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
750 var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
752 print("jorendb version -0.0");
753 prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
755 var args = scriptArgs.slice(0);
756 print("INITIAL ARGS: " + args);
758 // Find the script to run and its arguments. The script may have been given as
759 // a plain script name, in which case all remaining arguments belong to the
760 // script. Or there may have been any number of arguments to the JS shell,
761 // followed by -f scriptName, followed by additional arguments to the JS shell,
762 // followed by the script arguments. There may be multiple -e or -f options in
763 // the JS shell arguments, and we want to treat each one as a debuggable
764 // script.
766 // The difficulty is that the JS shell has a mixture of
768 // --boolean
770 // and
772 // --value VAL
774 // parameters, and there's no way to know whether --option takes an argument or
775 // not. We will assume that VAL will never end in .js, or rather that the first
776 // argument that does not start with "-" but does end in ".js" is the name of
777 // the script.
779 // If you need to pass other options and not have them given to the script,
780 // pass them before the -f jorendb.js argument. Thus, the safe ways to pass
781 // arguments are:
783 // js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)+ -- [script args]
784 // js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)* script.js [script args]
786 // Additionally, if you want to run a script that is *NOT* debugged, put it in
787 // as part of the leading [JS shell options].
790 // Compute actualScriptArgs by finding the script to be run and grabbing every
791 // non-script argument. The script may be given by -f scriptname or just plain
792 // scriptname. In the latter case, it will be in the global variable
793 // 'scriptPath' (and NOT in scriptArgs.)
794 var actualScriptArgs = [];
795 var scriptSeen;
797 if (scriptPath !== undefined) {
798 todo.push({
799 'action': 'load',
800 'script': scriptPath,
802 scriptSeen = true;
805 while(args.length > 0) {
806 var arg = args.shift();
807 print("arg: " + arg);
808 if (arg == '-e') {
809 print(" eval");
810 todo.push({
811 'action': 'eval',
812 'code': args.shift()
814 } else if (arg == '-f') {
815 var script = args.shift();
816 print(" load -f " + script);
817 scriptSeen = true;
818 todo.push({
819 'action': 'load',
820 'script': script,
822 } else if (arg.indexOf("-") == 0) {
823 if (arg == '--') {
824 print(" pass remaining args to script");
825 actualScriptArgs.push(...args);
826 break;
827 } else if ((args.length > 0) && (args[0].indexOf(".js") + 3 == args[0].length)) {
828 // Ends with .js, assume we are looking at --boolean script.js
829 print(" load script.js after --boolean");
830 todo.push({
831 'action': 'load',
832 'script': args.shift(),
834 scriptSeen = true;
835 } else {
836 // Does not end with .js, assume we are looking at JS shell arg
837 // --value VAL
838 print(" ignore");
839 args.shift();
841 } else {
842 if (!scriptSeen) {
843 print(" load general");
844 actualScriptArgs.push(...args);
845 todo.push({
846 'action': 'load',
847 'script': arg,
849 break;
850 } else {
851 print(" arg " + arg);
852 actualScriptArgs.push(arg);
856 print("jorendb: scriptPath = " + scriptPath);
857 print("jorendb: scriptArgs = " + scriptArgs);
858 print("jorendb: actualScriptArgs = " + actualScriptArgs);
860 for (var task of todo) {
861 task['scriptArgs'] = [...actualScriptArgs];
864 // Always drop into a repl at the end. Especially if the main script throws an
865 // exception.
866 todo.push({ 'action': 'repl' });
868 while (rerun) {
869 print("Top of run loop");
870 rerun = false;
871 for (var task of todo) {
872 activeTask = task;
873 if (task.action == 'eval') {
874 debuggeeGlobal.eval(task.code);
875 } else if (task.action == 'load') {
876 debuggeeGlobal['scriptArgs'] = task.scriptArgs;
877 debuggeeGlobal['scriptPath'] = task.script;
878 print("Loading JavaScript file " + task.script);
879 try {
880 debuggeeGlobal.evaluate(read(task.script), { 'fileName': task.script, 'lineNumber': 1 });
881 } catch (exc) {
882 print("Caught exception " + exc);
883 print(exc.stack);
884 break;
886 } else if (task.action == 'repl') {
887 repl();
889 if (rerun)
890 break;
894 quit(0);