Bug 1856126 [wpt PR 42137] - LoAF: ensure scripts are added after microtask checkpoin...
[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:
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;
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);
53         }
54     };
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}]`;
64     }
65     const s = uneval(v);
66     if (s.length > 400) {
67         return s.substr(0, 400) + "...<" + (s.length - 400) + " more bytes>...";
68     }
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 = "(...)";
78         }
79         obj[name] = v;
80     }
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];
94     }
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)];
108     }
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;
133         }
134     });
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;
150     }
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;
160     }
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;
180         }
181     });
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;
194         }
195     }
196     if (n === undefined) {
197         n = f.num;
198         if (n === undefined)
199             throw new Error("Internal error: frame not on stack");
200     }
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;
212     }
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]);
231         }
232     }
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 });
249         }
250     }
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;
292         }
293     }
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);
321     }
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;
340     }
342     var match = rest.match(/^(\d+)$/);
343     if (match) {
344         return doStepOrNext({upto:true, stopLine:match[1]});
345     }
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);
369         }
370         return;
371     }
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;
382         }
383         for (var i = 0; i < n && f; i++) {
384             if (!f.older) {
385                 print("There is no frame " + rest + ".");
386                 return;
387             }
388             f.older.younger = f;
389             f = f.older;
390         }
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();
400         }
401     } else {
402         print("do what now?");
403     }
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();
416     }
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();
428     }
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);
449         }
450     }
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);
465     }
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");
481     }
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;
505         }
506         updateLocation(this);
507         return repl();
508     }
510     function stepEntered(newFrame) {
511         print("entered frame: " + newFrame.fullDescription());
512         updateLocation(newFrame);
513         topFrame = focusedFrame = newFrame;
514         return repl();
515     }
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;
534         }
536         if (stop) {
537             topFrame = focusedFrame = this;
538             if (focusedFrame != startFrame)
539                 print(focusedFrame.fullDescription());
540             return repl();
541         }
543         // Otherwise, let execution continue.
544         return undefined;
545     }
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);
559     }
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;
577     }
578     for (var offset of offsets) {
579         script.setBreakpoint(offset, { hit: handleBreakpoint });
580     }
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(", "));
620     }
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$/, '') ];
629         }
630     }
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;
663     }
665     var cmd = commands[first];
666     if (cmd.length === 0 && rest !== '') {
667         print("this command cannot take an argument");
668         return undefined;
669     }
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);
707         }
708     }
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");
720         });
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();
730         });
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();
741     });
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,
801     });
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()
813         });
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,
821         });
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(),
833             });
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();
840         }
841     } else {
842         if (!scriptSeen) {
843             print("  load general");
844             actualScriptArgs.push(...args);
845             todo.push({
846                 'action': 'load',
847                 'script': arg,
848             });
849             break;
850         } else {
851             print("  arg " + arg);
852             actualScriptArgs.push(arg);
853         }
854     }
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;
885             }
886         } else if (task.action == 'repl') {
887             repl();
888         }
889         if (rerun)
890             break;
891     }
894 quit(0);