Bug 1700051: part 32) Move `InvalidateWords` to `SoftText`. r=smaug
[gecko.git] / js / examples / jorendb.js
blob71bf0404de218623414cc4bc2eca2cfe5e3061ec
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     return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
65 function summaryObject(dv) {
66     var obj = {};
67     for (var name of dv.getOwnPropertyNames()) {
68         var v = dv.getOwnPropertyDescriptor(name).value;
69         if (v instanceof Debugger.Object) {
70             v = "(...)";
71         }
72         obj[name] = v;
73     }
74     return obj;
77 function debuggeeValueToString(dv, style) {
78     var dvrepr = dvToString(dv);
79     if (!style.pretty || (typeof dv !== 'object'))
80         return [dvrepr, undefined];
82     if (dv.class == "Error") {
83         let errval = debuggeeGlobalWrapper.executeInGlobalWithBindings("$$.toString()", debuggeeValues);
84         return [dvrepr, errval.return];
85     }
87     if (style.brief)
88         return [dvrepr, JSON.stringify(summaryObject(dv), null, 4)];
90     let str = debuggeeGlobalWrapper.executeInGlobalWithBindings("JSON.stringify(v, null, 4)", {v: dv});
91     if ('throw' in str) {
92         if (style.noerror)
93             return [dvrepr, undefined];
95         let substyle = {};
96         Object.assign(substyle, style);
97         substyle.noerror = true;
98         return [dvrepr, debuggeeValueToString(str.throw, substyle)];
99     }
101     return [dvrepr, str.return];
104 // Problem! Used to do [object Object] followed by details. Now just details?
106 function showDebuggeeValue(dv, style={pretty: options.pretty}) {
107     var i = nextDebuggeeValueIndex++;
108     debuggeeValues["$" + i] = dv;
109     debuggeeValues["$$"] = dv;
110     let [brief, full] = debuggeeValueToString(dv, style);
111     print("$" + i + " = " + brief);
112     if (full !== undefined)
113         print(full);
116 Object.defineProperty(Debugger.Frame.prototype, "num", {
117     configurable: true,
118     enumerable: false,
119     get: function () {
120             var i = 0;
121             for (var f = topFrame; f && f !== this; f = f.older)
122                 i++;
123             return f === null ? undefined : i;
124         }
125     });
127 Debugger.Frame.prototype.frameDescription = function frameDescription() {
128     if (this.type == "call")
129         return ((this.callee.name || '<anonymous>') +
130                 "(" + this.arguments.map(dvToString).join(", ") + ")");
131     else
132         return this.type + " code";
135 Debugger.Frame.prototype.positionDescription = function positionDescription() {
136     if (this.script) {
137         var line = this.script.getOffsetLocation(this.offset).lineNumber;
138         if (this.script.url)
139             return this.script.url + ":" + line;
140         return "line " + line;
141     }
142     return null;
145 Debugger.Frame.prototype.location = function () {
146     if (this.script) {
147         var { lineNumber, columnNumber, isEntryPoint } = this.script.getOffsetLocation(this.offset);
148         if (this.script.url)
149             return this.script.url + ":" + lineNumber;
150         return null;
151     }
152     return null;
155 Debugger.Frame.prototype.fullDescription = function fullDescription() {
156     var fr = this.frameDescription();
157     var pos = this.positionDescription();
158     if (pos)
159         return fr + ", " + pos;
160     return fr;
163 Object.defineProperty(Debugger.Frame.prototype, "line", {
164         configurable: true,
165         enumerable: false,
166         get: function() {
167             if (this.script)
168                 return this.script.getOffsetLocation(this.offset).lineNumber;
169             else
170                 return null;
171         }
172     });
174 function callDescription(f) {
175     return ((f.callee.name || '<anonymous>') +
176             "(" + f.arguments.map(dvToString).join(", ") + ")");
179 function showFrame(f, n) {
180     if (f === undefined || f === null) {
181         f = focusedFrame;
182         if (f === null) {
183             print("No stack.");
184             return;
185         }
186     }
187     if (n === undefined) {
188         n = f.num;
189         if (n === undefined)
190             throw new Error("Internal error: frame not on stack");
191     }
193     print('#' + n + " " + f.fullDescription());
196 function saveExcursion(fn) {
197     var tf = topFrame, ff = focusedFrame;
198     try {
199         return fn();
200     } finally {
201         topFrame = tf;
202         focusedFrame = ff;
203     }
206 function parseArgs(str) {
207     return str.split(" ");
210 function describedRv(r, desc) {
211     desc = "[" + desc + "] ";
212     if (r === undefined) {
213         print(desc + "Returning undefined");
214     } else if (r === null) {
215         print(desc + "Returning null");
216     } else if (r.length === undefined) {
217         print(desc + "Returning object " + JSON.stringify(r));
218     } else {
219         print(desc + "Returning length-" + r.length + " list");
220         if (r.length > 0) {
221             print("  " + r[0]);
222         }
223     }
224     return r;
227 // Rerun the program (reloading it from the file)
228 function runCommand(args) {
229     print("Restarting program");
230     if (args)
231         activeTask.scriptArgs = parseArgs(args);
232     rerun = true;
233     for (var f = topFrame; f; f = f.older) {
234         if (f.older) {
235             f.onPop = () => null;
236         } else {
237             f.onPop = () => ({ 'return': 0 });
238         }
239     }
240     //return describedRv([{ 'return': 0 }], "runCommand");
241     return null;
244 // Evaluate an expression in the Debugger global
245 function evalCommand(expr) {
246     eval(expr);
249 function quitCommand() {
250     dbg.removeAllDebuggees();
251     quit(0);
254 function backtraceCommand() {
255     if (topFrame === null)
256         print("No stack.");
257     for (var i = 0, f = topFrame; f; i++, f = f.older)
258         showFrame(f, i);
261 function setCommand(rest) {
262     var space = rest.indexOf(' ');
263     if (space == -1) {
264         print("Invalid set <option> <value> command");
265     } else {
266         var name = rest.substr(0, space);
267         var value = rest.substr(space + 1);
269         if (name == 'args') {
270             activeTask.scriptArgs = parseArgs(value);
271         } else {
272             var yes = ["1", "yes", "true", "on"];
273             var no = ["0", "no", "false", "off"];
275             if (yes.includes(value))
276                 options[name] = true;
277             else if (no.includes(value))
278                 options[name] = false;
279             else
280                 options[name] = value;
281         }
282     }
285 function split_print_options(s, style) {
286     var m = /^\/(\w+)/.exec(s);
287     if (!m)
288         return [ s, style ];
289     if (m[1].includes("p"))
290         style.pretty = true;
291     if (m[1].includes("b"))
292         style.brief = true;
293     return [ s.substr(m[0].length).trimLeft(), style ];
296 function doPrint(expr, style) {
297     // This is the real deal.
298     var cv = saveExcursion(
299         () => focusedFrame == null
300               ? debuggeeGlobalWrapper.executeInGlobalWithBindings(expr, debuggeeValues)
301               : focusedFrame.evalWithBindings(expr, debuggeeValues));
302     if (cv === null) {
303         print("Debuggee died.");
304     } else if ('return' in cv) {
305         showDebuggeeValue(cv.return, style);
306     } else {
307         print("Exception caught. (To rethrow it, type 'throw'.)");
308         lastExc = cv.throw;
309         showDebuggeeValue(lastExc, style);
310     }
313 function printCommand(rest) {
314     var [expr, style] = split_print_options(rest, {pretty: options.pretty});
315     return doPrint(expr, style);
318 function keysCommand(rest) { return doPrint("Object.keys(" + rest + ")"); }
320 function detachCommand() {
321     dbg.removeAllDebuggees();
322     return [undefined];
325 function continueCommand(rest) {
326     if (focusedFrame === null) {
327         print("No stack.");
328         return;
329     }
331     var match = rest.match(/^(\d+)$/);
332     if (match) {
333         return doStepOrNext({upto:true, stopLine:match[1]});
334     }
336     return [undefined];
339 function throwCommand(rest) {
340     var v;
341     if (focusedFrame !== topFrame) {
342         print("To throw, you must select the newest frame (use 'frame 0').");
343         return;
344     } else if (focusedFrame === null) {
345         print("No stack.");
346         return;
347     } else if (rest === '') {
348         return [{throw: lastExc}];
349     } else {
350         var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
351         if (cv === null) {
352             print("Debuggee died while determining what to throw. Stopped.");
353         } else if ('return' in cv) {
354             return [{throw: cv.return}];
355         } else {
356             print("Exception determining what to throw. Stopped.");
357             showDebuggeeValue(cv.throw);
358         }
359         return;
360     }
363 function frameCommand(rest) {
364     var n, f;
365     if (rest.match(/[0-9]+/)) {
366         n = +rest;
367         f = topFrame;
368         if (f === null) {
369             print("No stack.");
370             return;
371         }
372         for (var i = 0; i < n && f; i++) {
373             if (!f.older) {
374                 print("There is no frame " + rest + ".");
375                 return;
376             }
377             f.older.younger = f;
378             f = f.older;
379         }
380         focusedFrame = f;
381         updateLocation(focusedFrame);
382         showFrame(f, n);
383     } else if (rest === '') {
384         if (topFrame === null) {
385             print("No stack.");
386         } else {
387             updateLocation(focusedFrame);
388             showFrame();
389         }
390     } else {
391         print("do what now?");
392     }
395 function upCommand() {
396     if (focusedFrame === null)
397         print("No stack.");
398     else if (focusedFrame.older === null)
399         print("Initial frame selected; you cannot go up.");
400     else {
401         focusedFrame.older.younger = focusedFrame;
402         focusedFrame = focusedFrame.older;
403         updateLocation(focusedFrame);
404         showFrame();
405     }
408 function downCommand() {
409     if (focusedFrame === null)
410         print("No stack.");
411     else if (!focusedFrame.younger)
412         print("Youngest frame selected; you cannot go down.");
413     else {
414         focusedFrame = focusedFrame.younger;
415         updateLocation(focusedFrame);
416         showFrame();
417     }
420 function forcereturnCommand(rest) {
421     var v;
422     var f = focusedFrame;
423     if (f !== topFrame) {
424         print("To forcereturn, you must select the newest frame (use 'frame 0').");
425     } else if (f === null) {
426         print("Nothing on the stack.");
427     } else if (rest === '') {
428         return [{return: undefined}];
429     } else {
430         var cv = saveExcursion(function () { return f.eval(rest); });
431         if (cv === null) {
432             print("Debuggee died while determining what to forcereturn. Stopped.");
433         } else if ('return' in cv) {
434             return [{return: cv.return}];
435         } else {
436             print("Error determining what to forcereturn. Stopped.");
437             showDebuggeeValue(cv.throw);
438         }
439     }
442 function printPop(f, c) {
443     var fdesc = f.fullDescription();
444     if (c.return) {
445         print("frame returning (still selected): " + fdesc);
446         showDebuggeeValue(c.return, {brief: true});
447     } else if (c.throw) {
448         print("frame threw exception: " + fdesc);
449         showDebuggeeValue(c.throw);
450         print("(To rethrow it, type 'throw'.)");
451         lastExc = c.throw;
452     } else {
453         print("frame was terminated: " + fdesc);
454     }
457 // Set |prop| on |obj| to |value|, but then restore its current value
458 // when we next enter the repl.
459 function setUntilRepl(obj, prop, value) {
460     var saved = obj[prop];
461     obj[prop] = value;
462     replCleanups.push(function () { obj[prop] = saved; });
465 function updateLocation(frame) {
466     if (options.emacs) {
467         var loc = frame.location();
468         if (loc)
469             print("\032\032" + loc + ":1");
470     }
473 function doStepOrNext(kind) {
474     var startFrame = topFrame;
475     var startLine = startFrame.line;
476     // print("stepping in:   " + startFrame.fullDescription());
477     // print("starting line: " + uneval(startLine));
479     function stepPopped(completion) {
480         // Note that we're popping this frame; we need to watch for
481         // subsequent step events on its caller.
482         this.reportedPop = true;
483         printPop(this, completion);
484         topFrame = focusedFrame = this;
485         if (kind.finish) {
486             // We want to continue, but this frame is going to be invalid as
487             // soon as this function returns, which will make the replCleanups
488             // assert when it tries to access the dead frame's 'onPop'
489             // property. So clear it out now while the frame is still valid,
490             // and trade it for an 'onStep' callback on the frame we're popping to.
491             preReplCleanups();
492             setUntilRepl(this.older, 'onStep', stepStepped);
493             return undefined;
494         }
495         updateLocation(this);
496         return repl();
497     }
499     function stepEntered(newFrame) {
500         print("entered frame: " + newFrame.fullDescription());
501         updateLocation(newFrame);
502         topFrame = focusedFrame = newFrame;
503         return repl();
504     }
506     function stepStepped() {
507         // print("stepStepped: " + this.fullDescription());
508         updateLocation(this);
509         var stop = false;
511         if (kind.finish) {
512             // 'finish' set a one-time onStep for stopping at the frame it
513             // wants to return to
514             stop = true;
515         } else if (kind.upto) {
516             // running until a given line is reached
517             if (this.line == kind.stopLine)
518                 stop = true;
519         } else {
520             // regular step; stop whenever the line number changes
521             if ((this.line != startLine) || (this != startFrame))
522                 stop = true;
523         }
525         if (stop) {
526             topFrame = focusedFrame = this;
527             if (focusedFrame != startFrame)
528                 print(focusedFrame.fullDescription());
529             return repl();
530         }
532         // Otherwise, let execution continue.
533         return undefined;
534     }
536     if (kind.step)
537         setUntilRepl(dbg, 'onEnterFrame', stepEntered);
539     // If we're stepping after an onPop, watch for steps and pops in the
540     // next-older frame; this one is done.
541     var stepFrame = startFrame.reportedPop ? startFrame.older : startFrame;
542     if (!stepFrame || !stepFrame.script)
543         stepFrame = null;
544     if (stepFrame) {
545         if (!kind.finish)
546             setUntilRepl(stepFrame, 'onStep', stepStepped);
547         setUntilRepl(stepFrame, 'onPop',  stepPopped);
548     }
550     // Let the program continue!
551     return [undefined];
554 function stepCommand() { return doStepOrNext({step:true}); }
555 function nextCommand() { return doStepOrNext({next:true}); }
556 function finishCommand() { return doStepOrNext({finish:true}); }
558 // FIXME: DOES NOT WORK YET
559 function breakpointCommand(where) {
560     print("Sorry, breakpoints don't work yet.");
561     var script = focusedFrame.script;
562     var offsets = script.getLineOffsets(Number(where));
563     if (offsets.length == 0) {
564         print("Unable to break at line " + where);
565         return;
566     }
567     for (var offset of offsets) {
568         script.setBreakpoint(offset, { hit: handleBreakpoint });
569     }
570     print("Set breakpoint in " + script.url + ":" + script.startLine + " at line " + where + ", " + offsets.length);
573 // Build the table of commands.
574 var commands = {};
575 var commandArray = [
576     backtraceCommand, "bt", "where",
577     breakpointCommand, "b", "break",
578     continueCommand, "c",
579     detachCommand,
580     downCommand, "d",
581     evalCommand, "!",
582     forcereturnCommand,
583     frameCommand, "f",
584     finishCommand, "fin",
585     nextCommand, "n",
586     printCommand, "p",
587     keysCommand, "k",
588     quitCommand, "q",
589     runCommand, "run",
590     stepCommand, "s",
591     setCommand,
592     throwCommand, "t",
593     upCommand, "u",
594     helpCommand, "h",
596 var currentCmd = null;
597 for (var i = 0; i < commandArray.length; i++) {
598     var cmd = commandArray[i];
599     if (typeof cmd === "string")
600         commands[cmd] = currentCmd;
601     else
602         currentCmd = commands[cmd.name.replace(/Command$/, '')] = cmd;
605 function helpCommand(rest) {
606     print("Available commands:");
607     var printcmd = function(group) {
608         print("  " + group.join(", "));
609     }
611     var group = [];
612     for (var cmd of commandArray) {
613         if (typeof cmd === "string") {
614             group.push(cmd);
615         } else {
616             if (group.length) printcmd(group);
617             group = [ cmd.name.replace(/Command$/, '') ];
618         }
619     }
620     printcmd(group);
623 // Break cmd into two parts: its first word and everything else. If it begins
624 // with punctuation, treat that as a separate word. The first word is
625 // terminated with whitespace or the '/' character. So:
627 //   print x         => ['print', 'x']
628 //   print           => ['print', '']
629 //   !print x        => ['!', 'print x']
630 //   ?!wtf!?         => ['?', '!wtf!?']
631 //   print/b x       => ['print', '/b x']
633 function breakcmd(cmd) {
634     cmd = cmd.trimLeft();
635     if ("!@#$%^&*_+=/?.,<>:;'\"".includes(cmd.substr(0, 1)))
636         return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
637     var m = /\s+|(?=\/)/.exec(cmd);
638     if (m === null)
639         return [cmd, ''];
640     return [cmd.slice(0, m.index), cmd.slice(m.index + m[0].length)];
643 function runcmd(cmd) {
644     var pieces = breakcmd(cmd);
645     if (pieces[0] === "")
646         return undefined;
648     var first = pieces[0], rest = pieces[1];
649     if (!commands.hasOwnProperty(first)) {
650         print("unrecognized command '" + first + "'");
651         return undefined;
652     }
654     var cmd = commands[first];
655     if (cmd.length === 0 && rest !== '') {
656         print("this command cannot take an argument");
657         return undefined;
658     }
660     return cmd(rest);
663 function preReplCleanups() {
664     while (replCleanups.length > 0)
665         replCleanups.pop()();
668 var prevcmd = undefined;
669 function repl() {
670     preReplCleanups();
672     var cmd;
673     for (;;) {
674         putstr("\n" + prompt);
675         cmd = readline();
676         if (cmd === null)
677             return null;
678         else if (cmd === "")
679             cmd = prevcmd;
681         try {
682             prevcmd = cmd;
683             var result = runcmd(cmd);
684             if (result === undefined)
685                 ; // do nothing, return to prompt
686             else if (Array.isArray(result))
687                 return result[0];
688             else if (result === null)
689                 return null;
690             else
691                 throw new Error("Internal error: result of runcmd wasn't array or undefined: " + result);
692         } catch (exc) {
693             print("*** Internal error: exception in the debugger code.");
694             print("    " + exc);
695             print(exc.stack);
696         }
697     }
700 var dbg = new Debugger();
701 dbg.onDebuggerStatement = function (frame) {
702     return saveExcursion(function () {
703             topFrame = focusedFrame = frame;
704             print("'debugger' statement hit.");
705             showFrame();
706             updateLocation(focusedFrame);
707             backtrace();
708             return describedRv(repl(), "debugger.saveExc");
709         });
711 dbg.onThrow = function (frame, exc) {
712     return saveExcursion(function () {
713             topFrame = focusedFrame = frame;
714             print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
715             showFrame();
716             print("Exception value is:");
717             showDebuggeeValue(exc);
718             return repl();
719         });
722 function handleBreakpoint (frame) {
723     print("Breakpoint hit!");
724     return saveExcursion(() => {
725         topFrame = focusedFrame = frame;
726         print("breakpoint hit.");
727         showFrame();
728         updateLocation(focusedFrame);
729         return repl();
730     });
733 // The depth of jorendb nesting.
734 var jorendbDepth;
735 if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
737 var debuggeeGlobal = newGlobal({newCompartment: true});
738 debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
739 var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
741 print("jorendb version -0.0");
742 prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
744 var args = scriptArgs.slice(0);
745 print("INITIAL ARGS: " + args);
747 // Find the script to run and its arguments. The script may have been given as
748 // a plain script name, in which case all remaining arguments belong to the
749 // script. Or there may have been any number of arguments to the JS shell,
750 // followed by -f scriptName, followed by additional arguments to the JS shell,
751 // followed by the script arguments. There may be multiple -e or -f options in
752 // the JS shell arguments, and we want to treat each one as a debuggable
753 // script.
755 // The difficulty is that the JS shell has a mixture of
757 //   --boolean
759 // and
761 //   --value VAL
763 // parameters, and there's no way to know whether --option takes an argument or
764 // not. We will assume that VAL will never end in .js, or rather that the first
765 // argument that does not start with "-" but does end in ".js" is the name of
766 // the script.
768 // If you need to pass other options and not have them given to the script,
769 // pass them before the -f jorendb.js argument. Thus, the safe ways to pass
770 // arguments are:
772 //   js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)+ -- [script args]
773 //   js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)* script.js [script args]
775 // Additionally, if you want to run a script that is *NOT* debugged, put it in
776 // as part of the leading [JS shell options].
779 // Compute actualScriptArgs by finding the script to be run and grabbing every
780 // non-script argument. The script may be given by -f scriptname or just plain
781 // scriptname. In the latter case, it will be in the global variable
782 // 'scriptPath' (and NOT in scriptArgs.)
783 var actualScriptArgs = [];
784 var scriptSeen;
786 if (scriptPath !== undefined) {
787     todo.push({
788         'action': 'load',
789         'script': scriptPath,
790     });
791     scriptSeen = true;
794 while(args.length > 0) {
795     var arg = args.shift();
796     print("arg: " + arg);
797     if (arg == '-e') {
798         print("  eval");
799         todo.push({
800             'action': 'eval',
801             'code': args.shift()
802         });
803     } else if (arg == '-f') {
804         var script = args.shift();
805         print("  load -f " + script);
806         scriptSeen = true;
807         todo.push({
808             'action': 'load',
809             'script': script,
810         });
811     } else if (arg.indexOf("-") == 0) {
812         if (arg == '--') {
813             print("  pass remaining args to script");
814             actualScriptArgs.push(...args);
815             break;
816         } else if ((args.length > 0) && (args[0].indexOf(".js") + 3 == args[0].length)) {
817             // Ends with .js, assume we are looking at --boolean script.js
818             print("  load script.js after --boolean");
819             todo.push({
820                 'action': 'load',
821                 'script': args.shift(),
822             });
823             scriptSeen = true;
824         } else {
825             // Does not end with .js, assume we are looking at JS shell arg
826             // --value VAL
827             print("  ignore");
828             args.shift();
829         }
830     } else {
831         if (!scriptSeen) {
832             print("  load general");
833             actualScriptArgs.push(...args);
834             todo.push({
835                 'action': 'load',
836                 'script': arg,
837             });
838             break;
839         } else {
840             print("  arg " + arg);
841             actualScriptArgs.push(arg);
842         }
843     }
845 print("jorendb: scriptPath = " + scriptPath);
846 print("jorendb: scriptArgs = " + scriptArgs);
847 print("jorendb: actualScriptArgs = " + actualScriptArgs);
849 for (var task of todo) {
850     task['scriptArgs'] = actualScriptArgs;
853 // Always drop into a repl at the end. Especially if the main script throws an
854 // exception.
855 todo.push({ 'action': 'repl' });
857 while (rerun) {
858     print("Top of run loop");
859     rerun = false;
860     for (var task of todo) {
861         activeTask = task;
862         if (task.action == 'eval') {
863             debuggeeGlobal.eval(task.code);
864         } else if (task.action == 'load') {
865             debuggeeGlobal['scriptArgs'] = task.scriptArgs;
866             debuggeeGlobal['scriptPath'] = task.script;
867             print("Loading JavaScript file " + task.script);
868             try {
869                 debuggeeGlobal.evaluate(read(task.script), { 'fileName': task.script, 'lineNumber': 1 });
870             } catch (exc) {
871                 print("Caught exception " + exc);
872                 print(exc.stack);
873             }
874         } else if (task.action == 'repl') {
875             repl();
876         }
877         if (rerun)
878             break;
879     }
882 quit(0);