summaryrefslogtreecommitdiffstats
path: root/js/examples/jorendb.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/examples/jorendb.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/examples/jorendb.js')
-rw-r--r--js/examples/jorendb.js894
1 files changed, 894 insertions, 0 deletions
diff --git a/js/examples/jorendb.js b/js/examples/jorendb.js
new file mode 100644
index 0000000000..33d6c27316
--- /dev/null
+++ b/js/examples/jorendb.js
@@ -0,0 +1,894 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
+ * vim: set ts=8 sw=4 et tw=78:
+ *
+ * jorendb - A toy command-line debugger for shell-js programs.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/*
+ * jorendb is a simple command-line debugger for shell-js programs. It is
+ * intended as a demo of the Debugger object (as there are no shell js programs
+ * to speak of).
+ *
+ * To run it: $JS -d path/to/this/file/jorendb.js
+ * To run some JS code under it, try:
+ * (jorendb) print load("my-script-to-debug.js")
+ * Execution will stop at debugger statements and you'll get a jorendb prompt.
+ */
+
+// Debugger state.
+var focusedFrame = null;
+var topFrame = null;
+var debuggeeValues = {};
+var nextDebuggeeValueIndex = 1;
+var lastExc = null;
+var todo = [];
+var activeTask;
+var options = { 'pretty': true,
+ 'emacs': !!os.getenv('INSIDE_EMACS') };
+var rerun = true;
+
+// Cleanup functions to run when we next re-enter the repl.
+var replCleanups = [];
+
+// Redirect debugger printing functions to go to the original output
+// destination, unaffected by any redirects done by the debugged script.
+var initialOut = os.file.redirect();
+var initialErr = os.file.redirectErr();
+
+function wrap(global, name) {
+ var orig = global[name];
+ global[name] = function(...args) {
+
+ var oldOut = os.file.redirect(initialOut);
+ var oldErr = os.file.redirectErr(initialErr);
+ try {
+ return orig.apply(global, args);
+ } finally {
+ os.file.redirect(oldOut);
+ os.file.redirectErr(oldErr);
+ }
+ };
+}
+wrap(this, 'print');
+wrap(this, 'printErr');
+wrap(this, 'putstr');
+
+// Convert a debuggee value v to a string.
+function dvToString(v) {
+ if (typeof(v) === 'object' && v !== null) {
+ return `[object ${v.class}]`;
+ }
+ const s = uneval(v);
+ if (s.length > 400) {
+ return s.substr(0, 400) + "...<" + (s.length - 400) + " more bytes>...";
+ }
+ return s;
+}
+
+function summaryObject(dv) {
+ var obj = {};
+ for (var name of dv.getOwnPropertyNames()) {
+ var v = dv.getOwnPropertyDescriptor(name).value;
+ if (v instanceof Debugger.Object) {
+ v = "(...)";
+ }
+ obj[name] = v;
+ }
+ return obj;
+}
+
+function debuggeeValueToString(dv, style) {
+ var dvrepr = dvToString(dv);
+ if (!style.pretty || (typeof dv !== 'object') || (dv === null))
+ return [dvrepr, undefined];
+
+ const exec = debuggeeGlobalWrapper.executeInGlobalWithBindings.bind(debuggeeGlobalWrapper);
+
+ if (dv.class == "Error") {
+ let errval = exec("$$.toString()", debuggeeValues);
+ return [dvrepr, errval.return];
+ }
+
+ if (style.brief)
+ return [dvrepr, JSON.stringify(summaryObject(dv), null, 4)];
+
+ let str = exec("JSON.stringify(v, null, 4)", {v: dv});
+ if ('throw' in str) {
+ if (style.noerror)
+ return [dvrepr, undefined];
+
+ let substyle = {};
+ Object.assign(substyle, style);
+ substyle.noerror = true;
+ return [dvrepr, debuggeeValueToString(str.throw, substyle)];
+ }
+
+ return [dvrepr, str.return];
+}
+
+// Problem! Used to do [object Object] followed by details. Now just details?
+
+function showDebuggeeValue(dv, style={pretty: options.pretty}) {
+ var i = nextDebuggeeValueIndex++;
+ debuggeeValues["$" + i] = dv;
+ debuggeeValues["$$"] = dv;
+ let [brief, full] = debuggeeValueToString(dv, style);
+ print("$" + i + " = " + brief);
+ if (full !== undefined)
+ print(full);
+}
+
+Object.defineProperty(Debugger.Frame.prototype, "num", {
+ configurable: true,
+ enumerable: false,
+ get: function () {
+ var i = 0;
+ for (var f = topFrame; f && f !== this; f = f.older)
+ i++;
+ return f === null ? undefined : i;
+ }
+ });
+
+Debugger.Frame.prototype.frameDescription = function frameDescription() {
+ if (this.type == "call")
+ return ((this.callee.name || '<anonymous>') +
+ "(" + this.arguments.map(dvToString).join(", ") + ")");
+ else
+ return this.type + " code";
+}
+
+Debugger.Frame.prototype.positionDescription = function positionDescription() {
+ if (this.script) {
+ var line = this.script.getOffsetLocation(this.offset).lineNumber;
+ if (this.script.url)
+ return this.script.url + ":" + line;
+ return "line " + line;
+ }
+ return null;
+}
+
+Debugger.Frame.prototype.location = function () {
+ if (this.script) {
+ var { lineNumber, columnNumber, isEntryPoint } = this.script.getOffsetLocation(this.offset);
+ if (this.script.url)
+ return this.script.url + ":" + lineNumber;
+ return null;
+ }
+ return null;
+}
+
+Debugger.Frame.prototype.fullDescription = function fullDescription() {
+ var fr = this.frameDescription();
+ var pos = this.positionDescription();
+ if (pos)
+ return fr + ", " + pos;
+ return fr;
+}
+
+Object.defineProperty(Debugger.Frame.prototype, "line", {
+ configurable: true,
+ enumerable: false,
+ get: function() {
+ if (this.script)
+ return this.script.getOffsetLocation(this.offset).lineNumber;
+ else
+ return null;
+ }
+ });
+
+function callDescription(f) {
+ return ((f.callee.name || '<anonymous>') +
+ "(" + f.arguments.map(dvToString).join(", ") + ")");
+}
+
+function showFrame(f, n) {
+ if (f === undefined || f === null) {
+ f = focusedFrame;
+ if (f === null) {
+ print("No stack.");
+ return;
+ }
+ }
+ if (n === undefined) {
+ n = f.num;
+ if (n === undefined)
+ throw new Error("Internal error: frame not on stack");
+ }
+
+ print('#' + n + " " + f.fullDescription());
+}
+
+function saveExcursion(fn) {
+ var tf = topFrame, ff = focusedFrame;
+ try {
+ return fn();
+ } finally {
+ topFrame = tf;
+ focusedFrame = ff;
+ }
+}
+
+function parseArgs(str) {
+ return str.split(" ");
+}
+
+function describedRv(r, desc) {
+ desc = "[" + desc + "] ";
+ if (r === undefined) {
+ print(desc + "Returning undefined");
+ } else if (r === null) {
+ print(desc + "Returning null");
+ } else if (r.length === undefined) {
+ print(desc + "Returning object " + JSON.stringify(r));
+ } else {
+ print(desc + "Returning length-" + r.length + " list");
+ if (r.length > 0) {
+ print(" " + r[0]);
+ }
+ }
+ return r;
+}
+
+// Rerun the program (reloading it from the file)
+function runCommand(args) {
+ print(`Restarting program (${args})`);
+ if (args)
+ activeTask.scriptArgs = parseArgs(args);
+ else
+ activeTask.scriptArgs = [...actualScriptArgs];
+ rerun = true;
+ for (var f = topFrame; f; f = f.older) {
+ if (f.older) {
+ f.onPop = () => null;
+ } else {
+ f.onPop = () => ({ 'return': 0 });
+ }
+ }
+ //return describedRv([{ 'return': 0 }], "runCommand");
+ return null;
+}
+
+// Evaluate an expression in the Debugger global
+function evalCommand(expr) {
+ eval(expr);
+}
+
+function quitCommand() {
+ dbg.removeAllDebuggees();
+ quit(0);
+}
+
+function backtraceCommand() {
+ if (topFrame === null)
+ print("No stack.");
+ for (var i = 0, f = topFrame; f; i++, f = f.older)
+ showFrame(f, i);
+}
+
+function setCommand(rest) {
+ var space = rest.indexOf(' ');
+ if (space == -1) {
+ print("Invalid set <option> <value> command");
+ } else {
+ var name = rest.substr(0, space);
+ var value = rest.substr(space + 1);
+
+ if (name == 'args') {
+ activeTask.scriptArgs = parseArgs(value);
+ } else {
+ var yes = ["1", "yes", "true", "on"];
+ var no = ["0", "no", "false", "off"];
+
+ if (yes.includes(value))
+ options[name] = true;
+ else if (no.includes(value))
+ options[name] = false;
+ else
+ options[name] = value;
+ }
+ }
+}
+
+function split_print_options(s, style) {
+ var m = /^\/(\w+)/.exec(s);
+ if (!m)
+ return [ s, style ];
+ if (m[1].includes("p"))
+ style.pretty = true;
+ if (m[1].includes("b"))
+ style.brief = true;
+ return [ s.substr(m[0].length).trimLeft(), style ];
+}
+
+function doPrint(expr, style) {
+ // This is the real deal.
+ var cv = saveExcursion(
+ () => focusedFrame == null
+ ? debuggeeGlobalWrapper.executeInGlobalWithBindings(expr, debuggeeValues)
+ : focusedFrame.evalWithBindings(expr, debuggeeValues));
+ if (cv === null) {
+ print("Debuggee died.");
+ } else if ('return' in cv) {
+ showDebuggeeValue(cv.return, style);
+ } else {
+ print("Exception caught. (To rethrow it, type 'throw'.)");
+ lastExc = cv.throw;
+ showDebuggeeValue(lastExc, style);
+ }
+}
+
+function printCommand(rest) {
+ var [expr, style] = split_print_options(rest, {pretty: options.pretty});
+ return doPrint(expr, style);
+}
+
+function keysCommand(rest) { return doPrint("Object.keys(" + rest + ")"); }
+
+function detachCommand() {
+ dbg.removeAllDebuggees();
+ return [undefined];
+}
+
+function continueCommand(rest) {
+ if (focusedFrame === null) {
+ print("No stack.");
+ return;
+ }
+
+ var match = rest.match(/^(\d+)$/);
+ if (match) {
+ return doStepOrNext({upto:true, stopLine:match[1]});
+ }
+
+ return [undefined];
+}
+
+function throwCommand(rest) {
+ var v;
+ if (focusedFrame !== topFrame) {
+ print("To throw, you must select the newest frame (use 'frame 0').");
+ return;
+ } else if (focusedFrame === null) {
+ print("No stack.");
+ return;
+ } else if (rest === '') {
+ return [{throw: lastExc}];
+ } else {
+ var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
+ if (cv === null) {
+ print("Debuggee died while determining what to throw. Stopped.");
+ } else if ('return' in cv) {
+ return [{throw: cv.return}];
+ } else {
+ print("Exception determining what to throw. Stopped.");
+ showDebuggeeValue(cv.throw);
+ }
+ return;
+ }
+}
+
+function frameCommand(rest) {
+ var n, f;
+ if (rest.match(/[0-9]+/)) {
+ n = +rest;
+ f = topFrame;
+ if (f === null) {
+ print("No stack.");
+ return;
+ }
+ for (var i = 0; i < n && f; i++) {
+ if (!f.older) {
+ print("There is no frame " + rest + ".");
+ return;
+ }
+ f.older.younger = f;
+ f = f.older;
+ }
+ focusedFrame = f;
+ updateLocation(focusedFrame);
+ showFrame(f, n);
+ } else if (rest === '') {
+ if (topFrame === null) {
+ print("No stack.");
+ } else {
+ updateLocation(focusedFrame);
+ showFrame();
+ }
+ } else {
+ print("do what now?");
+ }
+}
+
+function upCommand() {
+ if (focusedFrame === null)
+ print("No stack.");
+ else if (focusedFrame.older === null)
+ print("Initial frame selected; you cannot go up.");
+ else {
+ focusedFrame.older.younger = focusedFrame;
+ focusedFrame = focusedFrame.older;
+ updateLocation(focusedFrame);
+ showFrame();
+ }
+}
+
+function downCommand() {
+ if (focusedFrame === null)
+ print("No stack.");
+ else if (!focusedFrame.younger)
+ print("Youngest frame selected; you cannot go down.");
+ else {
+ focusedFrame = focusedFrame.younger;
+ updateLocation(focusedFrame);
+ showFrame();
+ }
+}
+
+function forcereturnCommand(rest) {
+ var v;
+ var f = focusedFrame;
+ if (f !== topFrame) {
+ print("To forcereturn, you must select the newest frame (use 'frame 0').");
+ } else if (f === null) {
+ print("Nothing on the stack.");
+ } else if (rest === '') {
+ return [{return: undefined}];
+ } else {
+ var cv = saveExcursion(function () { return f.eval(rest); });
+ if (cv === null) {
+ print("Debuggee died while determining what to forcereturn. Stopped.");
+ } else if ('return' in cv) {
+ return [{return: cv.return}];
+ } else {
+ print("Error determining what to forcereturn. Stopped.");
+ showDebuggeeValue(cv.throw);
+ }
+ }
+}
+
+function printPop(f, c) {
+ var fdesc = f.fullDescription();
+ if (c.return) {
+ print("frame returning (still selected): " + fdesc);
+ showDebuggeeValue(c.return, {brief: true});
+ } else if (c.throw) {
+ print("frame threw exception: " + fdesc);
+ showDebuggeeValue(c.throw);
+ print("(To rethrow it, type 'throw'.)");
+ lastExc = c.throw;
+ } else {
+ print("frame was terminated: " + fdesc);
+ }
+}
+
+// Set |prop| on |obj| to |value|, but then restore its current value
+// when we next enter the repl.
+function setUntilRepl(obj, prop, value) {
+ var saved = obj[prop];
+ obj[prop] = value;
+ replCleanups.push(function () { obj[prop] = saved; });
+}
+
+function updateLocation(frame) {
+ if (options.emacs) {
+ var loc = frame.location();
+ if (loc)
+ print("\032\032" + loc + ":1");
+ }
+}
+
+function doStepOrNext(kind) {
+ var startFrame = topFrame;
+ var startLine = startFrame.line;
+ // print("stepping in: " + startFrame.fullDescription());
+ // print("starting line: " + uneval(startLine));
+
+ function stepPopped(completion) {
+ // Note that we're popping this frame; we need to watch for
+ // subsequent step events on its caller.
+ this.reportedPop = true;
+ printPop(this, completion);
+ topFrame = focusedFrame = this;
+ if (kind.finish) {
+ // We want to continue, but this frame is going to be invalid as
+ // soon as this function returns, which will make the replCleanups
+ // assert when it tries to access the dead frame's 'onPop'
+ // property. So clear it out now while the frame is still valid,
+ // and trade it for an 'onStep' callback on the frame we're popping to.
+ preReplCleanups();
+ setUntilRepl(this.older, 'onStep', stepStepped);
+ return undefined;
+ }
+ updateLocation(this);
+ return repl();
+ }
+
+ function stepEntered(newFrame) {
+ print("entered frame: " + newFrame.fullDescription());
+ updateLocation(newFrame);
+ topFrame = focusedFrame = newFrame;
+ return repl();
+ }
+
+ function stepStepped() {
+ // print("stepStepped: " + this.fullDescription());
+ updateLocation(this);
+ var stop = false;
+
+ if (kind.finish) {
+ // 'finish' set a one-time onStep for stopping at the frame it
+ // wants to return to
+ stop = true;
+ } else if (kind.upto) {
+ // running until a given line is reached
+ if (this.line == kind.stopLine)
+ stop = true;
+ } else {
+ // regular step; stop whenever the line number changes
+ if ((this.line != startLine) || (this != startFrame))
+ stop = true;
+ }
+
+ if (stop) {
+ topFrame = focusedFrame = this;
+ if (focusedFrame != startFrame)
+ print(focusedFrame.fullDescription());
+ return repl();
+ }
+
+ // Otherwise, let execution continue.
+ return undefined;
+ }
+
+ if (kind.step)
+ setUntilRepl(dbg, 'onEnterFrame', stepEntered);
+
+ // If we're stepping after an onPop, watch for steps and pops in the
+ // next-older frame; this one is done.
+ var stepFrame = startFrame.reportedPop ? startFrame.older : startFrame;
+ if (!stepFrame || !stepFrame.script)
+ stepFrame = null;
+ if (stepFrame) {
+ if (!kind.finish)
+ setUntilRepl(stepFrame, 'onStep', stepStepped);
+ setUntilRepl(stepFrame, 'onPop', stepPopped);
+ }
+
+ // Let the program continue!
+ return [undefined];
+}
+
+function stepCommand() { return doStepOrNext({step:true}); }
+function nextCommand() { return doStepOrNext({next:true}); }
+function finishCommand() { return doStepOrNext({finish:true}); }
+
+// FIXME: DOES NOT WORK YET
+function breakpointCommand(where) {
+ print("Sorry, breakpoints don't work yet.");
+ var script = focusedFrame.script;
+ var offsets = script.getLineOffsets(Number(where));
+ if (offsets.length == 0) {
+ print("Unable to break at line " + where);
+ return;
+ }
+ for (var offset of offsets) {
+ script.setBreakpoint(offset, { hit: handleBreakpoint });
+ }
+ print("Set breakpoint in " + script.url + ":" + script.startLine + " at line " + where + ", " + offsets.length);
+}
+
+// Build the table of commands.
+var commands = {};
+var commandArray = [
+ backtraceCommand, "bt", "where",
+ breakpointCommand, "b", "break",
+ continueCommand, "c",
+ detachCommand,
+ downCommand, "d",
+ evalCommand, "!",
+ forcereturnCommand,
+ frameCommand, "f",
+ finishCommand, "fin",
+ nextCommand, "n",
+ printCommand, "p",
+ keysCommand, "k",
+ quitCommand, "q",
+ runCommand, "run",
+ stepCommand, "s",
+ setCommand,
+ throwCommand, "t",
+ upCommand, "u",
+ helpCommand, "h",
+];
+var currentCmd = null;
+for (var i = 0; i < commandArray.length; i++) {
+ var cmd = commandArray[i];
+ if (typeof cmd === "string")
+ commands[cmd] = currentCmd;
+ else
+ currentCmd = commands[cmd.name.replace(/Command$/, '')] = cmd;
+}
+
+function helpCommand(rest) {
+ print("Available commands:");
+ var printcmd = function(group) {
+ print(" " + group.join(", "));
+ }
+
+ var group = [];
+ for (var cmd of commandArray) {
+ if (typeof cmd === "string") {
+ group.push(cmd);
+ } else {
+ if (group.length) printcmd(group);
+ group = [ cmd.name.replace(/Command$/, '') ];
+ }
+ }
+ printcmd(group);
+}
+
+// Break cmd into two parts: its first word and everything else. If it begins
+// with punctuation, treat that as a separate word. The first word is
+// terminated with whitespace or the '/' character. So:
+//
+// print x => ['print', 'x']
+// print => ['print', '']
+// !print x => ['!', 'print x']
+// ?!wtf!? => ['?', '!wtf!?']
+// print/b x => ['print', '/b x']
+//
+function breakcmd(cmd) {
+ cmd = cmd.trimLeft();
+ if ("!@#$%^&*_+=/?.,<>:;'\"".includes(cmd.substr(0, 1)))
+ return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
+ var m = /\s+|(?=\/)/.exec(cmd);
+ if (m === null)
+ return [cmd, ''];
+ return [cmd.slice(0, m.index), cmd.slice(m.index + m[0].length)];
+}
+
+function runcmd(cmd) {
+ var pieces = breakcmd(cmd);
+ if (pieces[0] === "")
+ return undefined;
+
+ var first = pieces[0], rest = pieces[1];
+ if (!commands.hasOwnProperty(first)) {
+ print("unrecognized command '" + first + "'");
+ return undefined;
+ }
+
+ var cmd = commands[first];
+ if (cmd.length === 0 && rest !== '') {
+ print("this command cannot take an argument");
+ return undefined;
+ }
+
+ return cmd(rest);
+}
+
+function preReplCleanups() {
+ while (replCleanups.length > 0)
+ replCleanups.pop()();
+}
+
+var prevcmd = undefined;
+function repl() {
+ preReplCleanups();
+
+ var cmd;
+ for (;;) {
+ putstr("\n" + prompt);
+ cmd = readline();
+ if (cmd === null)
+ return null;
+ else if (cmd === "")
+ cmd = prevcmd;
+
+ try {
+ prevcmd = cmd;
+ var result = runcmd(cmd);
+ if (result === undefined)
+ ; // do nothing, return to prompt
+ else if (Array.isArray(result))
+ return result[0];
+ else if (result === null)
+ return null;
+ else
+ throw new Error("Internal error: result of runcmd wasn't array or undefined: " + result);
+ } catch (exc) {
+ print("*** Internal error: exception in the debugger code.");
+ print(" " + exc);
+ print(exc.stack);
+ }
+ }
+}
+
+var dbg = new Debugger();
+dbg.onDebuggerStatement = function (frame) {
+ return saveExcursion(function () {
+ topFrame = focusedFrame = frame;
+ print("'debugger' statement hit.");
+ showFrame();
+ updateLocation(focusedFrame);
+ backtrace();
+ return describedRv(repl(), "debugger.saveExc");
+ });
+};
+dbg.onThrow = function (frame, exc) {
+ return saveExcursion(function () {
+ topFrame = focusedFrame = frame;
+ print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
+ showFrame();
+ print("Exception value is:");
+ showDebuggeeValue(exc);
+ return repl();
+ });
+};
+
+function handleBreakpoint (frame) {
+ print("Breakpoint hit!");
+ return saveExcursion(() => {
+ topFrame = focusedFrame = frame;
+ print("breakpoint hit.");
+ showFrame();
+ updateLocation(focusedFrame);
+ return repl();
+ });
+};
+
+// The depth of jorendb nesting.
+var jorendbDepth;
+if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
+
+var debuggeeGlobal = newGlobal({newCompartment: true});
+debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
+var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
+
+print("jorendb version -0.0");
+prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
+
+var args = scriptArgs.slice(0);
+print("INITIAL ARGS: " + args);
+
+// Find the script to run and its arguments. The script may have been given as
+// a plain script name, in which case all remaining arguments belong to the
+// script. Or there may have been any number of arguments to the JS shell,
+// followed by -f scriptName, followed by additional arguments to the JS shell,
+// followed by the script arguments. There may be multiple -e or -f options in
+// the JS shell arguments, and we want to treat each one as a debuggable
+// script.
+//
+// The difficulty is that the JS shell has a mixture of
+//
+// --boolean
+//
+// and
+//
+// --value VAL
+//
+// parameters, and there's no way to know whether --option takes an argument or
+// not. We will assume that VAL will never end in .js, or rather that the first
+// argument that does not start with "-" but does end in ".js" is the name of
+// the script.
+//
+// If you need to pass other options and not have them given to the script,
+// pass them before the -f jorendb.js argument. Thus, the safe ways to pass
+// arguments are:
+//
+// js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)+ -- [script args]
+// js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)* script.js [script args]
+//
+// Additionally, if you want to run a script that is *NOT* debugged, put it in
+// as part of the leading [JS shell options].
+
+
+// Compute actualScriptArgs by finding the script to be run and grabbing every
+// non-script argument. The script may be given by -f scriptname or just plain
+// scriptname. In the latter case, it will be in the global variable
+// 'scriptPath' (and NOT in scriptArgs.)
+var actualScriptArgs = [];
+var scriptSeen;
+
+if (scriptPath !== undefined) {
+ todo.push({
+ 'action': 'load',
+ 'script': scriptPath,
+ });
+ scriptSeen = true;
+}
+
+while(args.length > 0) {
+ var arg = args.shift();
+ print("arg: " + arg);
+ if (arg == '-e') {
+ print(" eval");
+ todo.push({
+ 'action': 'eval',
+ 'code': args.shift()
+ });
+ } else if (arg == '-f') {
+ var script = args.shift();
+ print(" load -f " + script);
+ scriptSeen = true;
+ todo.push({
+ 'action': 'load',
+ 'script': script,
+ });
+ } else if (arg.indexOf("-") == 0) {
+ if (arg == '--') {
+ print(" pass remaining args to script");
+ actualScriptArgs.push(...args);
+ break;
+ } else if ((args.length > 0) && (args[0].indexOf(".js") + 3 == args[0].length)) {
+ // Ends with .js, assume we are looking at --boolean script.js
+ print(" load script.js after --boolean");
+ todo.push({
+ 'action': 'load',
+ 'script': args.shift(),
+ });
+ scriptSeen = true;
+ } else {
+ // Does not end with .js, assume we are looking at JS shell arg
+ // --value VAL
+ print(" ignore");
+ args.shift();
+ }
+ } else {
+ if (!scriptSeen) {
+ print(" load general");
+ actualScriptArgs.push(...args);
+ todo.push({
+ 'action': 'load',
+ 'script': arg,
+ });
+ break;
+ } else {
+ print(" arg " + arg);
+ actualScriptArgs.push(arg);
+ }
+ }
+}
+print("jorendb: scriptPath = " + scriptPath);
+print("jorendb: scriptArgs = " + scriptArgs);
+print("jorendb: actualScriptArgs = " + actualScriptArgs);
+
+for (var task of todo) {
+ task['scriptArgs'] = [...actualScriptArgs];
+}
+
+// Always drop into a repl at the end. Especially if the main script throws an
+// exception.
+todo.push({ 'action': 'repl' });
+
+while (rerun) {
+ print("Top of run loop");
+ rerun = false;
+ for (var task of todo) {
+ activeTask = task;
+ if (task.action == 'eval') {
+ debuggeeGlobal.eval(task.code);
+ } else if (task.action == 'load') {
+ debuggeeGlobal['scriptArgs'] = task.scriptArgs;
+ debuggeeGlobal['scriptPath'] = task.script;
+ print("Loading JavaScript file " + task.script);
+ try {
+ debuggeeGlobal.evaluate(read(task.script), { 'fileName': task.script, 'lineNumber': 1 });
+ } catch (exc) {
+ print("Caught exception " + exc);
+ print(exc.stack);
+ break;
+ }
+ } else if (task.action == 'repl') {
+ repl();
+ }
+ if (rerun)
+ break;
+ }
+}
+
+quit(0);