Bug 944164 - Update jorendb to current function names, and much else:
authorSteve Fink <sfink@mozilla.com>
Fri, 25 Mar 2016 10:52:43 -0700
changeset 290506 bddfb1f3b2f2d8e159e01a01ba183feb126ad434
parent 290505 2d171d75b7464583358fcfb7524617ad5dce6a6f
child 290507 34b0a503329ad8383f7ef499410fa7457f2be968
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs944164
milestone48.0a1
Bug 944164 - Update jorendb to current function names, and much else: Change how I/O redirection works Add brief output format Implement p/flags Many other changes r=NPOTB so DONTBUILD, and I am the only user MozReview-Commit-ID: 2sAxYcYHs72
js/examples/jorendb.js
--- a/js/examples/jorendb.js
+++ b/js/examples/jorendb.js
@@ -20,30 +20,101 @@
  */
 
 // 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('EMACS') == 't') };
+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) {
     return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
 }
 
-function showDebuggeeValue(dv) {
+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'))
+        return [dvrepr, undefined];
+
+    if (dv.class == "Error") {
+        let errval = debuggeeGlobalWrapper.executeInGlobalWithBindings("$" + i + ".toString()", debuggeeValues);
+        return [dvrepr, errval.return];
+    }
+
+    if (style.brief)
+        return [dvrepr, JSON.stringify(summaryObject(dv), null, 4)];
+
+    let str = debuggeeGlobalWrapper.executeInGlobalWithBindings("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;
-    print("$" + i + " = " + dvrepr);
+    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)
@@ -65,16 +136,26 @@ Debugger.Frame.prototype.positionDescrip
         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;
 }
 
@@ -116,16 +197,60 @@ function saveExcursion(fn) {
     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");
+    if (args)
+        activeTask.scriptArgs = parseArgs(args);
+    rerun = true;
+    for (var f = topFrame; f; f = f.older) {
+        print(f.script.url + ":" + f.script.getOffsetLine(f.offset) +" was " + f.onPop);
+        if (f.older) {
+            f.onPop = function() {
+                print("Resumifying " + this.script.url + ":" + this.script.getOffsetLine(this.offset));
+                return null;
+            };
+        } else {
+            f.onPop = function() {
+                return { 'return': 0 };
+            };
+        }
+    }
+    //return describedRv([{ 'return': 0 }], "runCommand");
+    return null;
+}
+
 // Evaluate an expression in the Debugger global
 function evalCommand(expr) {
     eval(expr);
 }
 
 function quitCommand() {
     dbg.enabled = false;
     quit(0);
@@ -133,49 +258,97 @@ function quitCommand() {
 
 function backtraceCommand() {
     if (topFrame === null)
         print("No stack.");
     for (var i = 0, f = topFrame; f; i++, f = f.older)
         showFrame(f, i);
 }
 
-function printCommand(rest) {
+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.indexOf(value) !== -1)
+                options[name] = true;
+            else if (no.indexOf(value) !== -1)
+                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].indexOf("p") != -1)
+        style.pretty = true;
+    if (m[1].indexOf("b") != -1)
+        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(rest, debuggeeValues)
-              : focusedFrame.evalWithBindings(rest, debuggeeValues));
+              ? debuggeeGlobalWrapper.executeInGlobalWithBindings(expr, debuggeeValues)
+              : focusedFrame.evalWithBindings(expr, debuggeeValues));
     if (cv === null) {
         if (!dbg.enabled)
             return [cv];
         print("Debuggee died.");
     } else if ('return' in cv) {
         if (!dbg.enabled)
             return [undefined];
-        showDebuggeeValue(cv.return);
+        showDebuggeeValue(cv.return, style);
     } else {
         if (!dbg.enabled)
             return [cv];
         print("Exception caught. (To rethrow it, type 'throw'.)");
         lastExc = cv.throw;
-        showDebuggeeValue(lastExc);
+        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.enabled = false;
     return [undefined];
 }
 
-function continueCommand() {
+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;
@@ -215,46 +388,51 @@ function frameCommand(rest) {
             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)
+    } else if (rest === '') {
+        if (topFrame === null) {
             print("No stack.");
-        else
+        } 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) {
@@ -279,17 +457,17 @@ function forcereturnCommand(rest) {
         }
     }
 }
 
 function printPop(f, c) {
     var fdesc = f.fullDescription();
     if (c.return) {
         print("frame returning (still selected): " + fdesc);
-        showDebuggeeValue(c.return);
+        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);
     }
@@ -298,41 +476,77 @@ function printPop(f, c) {
 // 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));
+    // 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());
-        // If we've changed frame or line, then report that.
-        if (this !== startFrame || this.line != startLine) {
+        // 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;
@@ -342,52 +556,74 @@ function doStepOrNext(kind) {
         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) {
-        setUntilRepl(stepFrame, 'onStep', stepStepped);
+        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",
-    evalCommand, "!",
-    ];
-var last = null;
+];
+var currentCmd = null;
 for (var i = 0; i < commandArray.length; i++) {
     var cmd = commandArray[i];
     if (typeof cmd === "string")
-        commands[cmd] = last;
+        commands[cmd] = currentCmd;
     else
-        last = commands[cmd.name.replace(/Command$/, '')] = cmd;
+        currentCmd = commands[cmd.name.replace(/Command$/, '')] = cmd;
 }
 
 function helpCommand(rest) {
     print("Available commands:");
     var printcmd = function(group) {
         print("  " + group.join(", "));
     }
 
@@ -399,25 +635,33 @@ function helpCommand(rest) {
             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.
+// 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 ("!@#$%^&*_+=/?.,<>:;'\"".indexOf(cmd.substr(0, 1)) != -1)
         return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
-    var m = /\s/.exec(cmd);
+    var m = /\s+|(?=\/)/.exec(cmd);
     if (m === null)
         return [cmd, ''];
-    return [cmd.slice(0, m.index), cmd.slice(m.index).trimLeft()];
+    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];
@@ -430,81 +674,223 @@ function runcmd(cmd) {
     if (cmd.length === 0 && rest !== '') {
         print("this command cannot take an argument");
         return undefined;
     }
 
     return cmd(rest);
 }
 
-function repl() {
+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
+                ; // 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");
+                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();
-            return repl();
+            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("new-compartment");
 debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
 var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
 
 print("jorendb version -0.0");
 prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
 
-var args = arguments;
+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();
-    if (arg == '-f') {
-        arg = args.shift();
-        debuggeeGlobal.evaluate(read(arg), { fileName: arg, lineNumber: 1 });
-    } else if (arg == '-e') {
-        arg = args.shift();
-        debuggeeGlobal.eval(arg);
+    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 {
-        throw("jorendb does not implement command-line argument '" + arg + "'");
+        if (!scriptSeen) {
+            print("  load general");
+            scriptSeen = true;
+            todo.push({
+                'action': 'load',
+                'script': arg,
+            });
+        } 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;
+}
+
+// If nothing to run, just drop into a repl
+if (todo.length == 0) {
+    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);
+            debuggeeGlobal.evaluate(read(task.script), { 'fileName': task.script, 'lineNumber': 1 });
+        } else if (task.action == 'repl') {
+            repl();
+        }
+        if (rerun)
+            break;
     }
 }
 
-repl();
+quit(0);