tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

jorendb.js (26220B)


      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 */
     10 
     11 /*
     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 */
     21 
     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;
     33 
     34 // Cleanup functions to run when we next re-enter the repl.
     35 var replCleanups = [];
     36 
     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();
     41 
     42 function wrap(global, name) {
     43    var orig = global[name];
     44    global[name] = function(...args) {
     45 
     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    };
     55 }
     56 wrap(this, 'print');
     57 wrap(this, 'printErr');
     58 wrap(this, 'putstr');
     59 
     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;
     70 }
     71 
     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;
     82 }
     83 
     84 function debuggeeValueToString(dv, style) {
     85    var dvrepr = dvToString(dv);
     86    if (!style.pretty || (typeof dv !== 'object') || (dv === null))
     87        return [dvrepr, undefined];
     88 
     89    const exec = debuggeeGlobalWrapper.executeInGlobalWithBindings.bind(debuggeeGlobalWrapper);
     90 
     91    if (dv.class == "Error") {
     92        let errval = exec("$$.toString()", debuggeeValues);
     93        return [dvrepr, errval.return];
     94    }
     95 
     96    if (style.brief)
     97        return [dvrepr, JSON.stringify(summaryObject(dv), null, 4)];
     98 
     99    let str = exec("JSON.stringify(v, null, 4)", {v: dv});
    100    if ('throw' in str) {
    101        if (style.noerror)
    102            return [dvrepr, undefined];
    103 
    104        let substyle = {};
    105        Object.assign(substyle, style);
    106        substyle.noerror = true;
    107        return [dvrepr, debuggeeValueToString(str.throw, substyle)];
    108    }
    109 
    110    return [dvrepr, str.return];
    111 }
    112 
    113 // Problem! Used to do [object Object] followed by details. Now just details?
    114 
    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);
    123 }
    124 
    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    });
    135 
    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";
    142 }
    143 
    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;
    152 }
    153 
    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;
    162 }
    163 
    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;
    170 }
    171 
    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    });
    182 
    183 function callDescription(f) {
    184    return ((f.callee.name || '<anonymous>') +
    185            "(" + f.arguments.map(dvToString).join(", ") + ")");
    186 }
    187 
    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    }
    201 
    202    print('#' + n + " " + f.fullDescription());
    203 }
    204 
    205 function saveExcursion(fn) {
    206    var tf = topFrame, ff = focusedFrame;
    207    try {
    208        return fn();
    209    } finally {
    210        topFrame = tf;
    211        focusedFrame = ff;
    212    }
    213 }
    214 
    215 function parseArgs(str) {
    216    return str.split(" ");
    217 }
    218 
    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;
    234 }
    235 
    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;
    253 }
    254 
    255 // Evaluate an expression in the Debugger global
    256 function evalCommand(expr) {
    257    eval(expr);
    258 }
    259 
    260 function quitCommand() {
    261    dbg.removeAllDebuggees();
    262    quit(0);
    263 }
    264 
    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);
    270 }
    271 
    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);
    279 
    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"];
    285 
    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    }
    294 }
    295 
    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 ];
    305 }
    306 
    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    }
    322 }
    323 
    324 function printCommand(rest) {
    325    var [expr, style] = split_print_options(rest, {pretty: options.pretty});
    326    return doPrint(expr, style);
    327 }
    328 
    329 function keysCommand(rest) { return doPrint("Object.keys(" + rest + ")"); }
    330 
    331 function detachCommand() {
    332    dbg.removeAllDebuggees();
    333    return [undefined];
    334 }
    335 
    336 function continueCommand(rest) {
    337    if (focusedFrame === null) {
    338        print("No stack.");
    339        return;
    340    }
    341 
    342    var match = rest.match(/^(\d+)$/);
    343    if (match) {
    344        return doStepOrNext({upto:true, stopLine:match[1]});
    345    }
    346 
    347    return [undefined];
    348 }
    349 
    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    }
    372 }
    373 
    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    }
    404 }
    405 
    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    }
    417 }
    418 
    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    }
    429 }
    430 
    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    }
    451 }
    452 
    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    }
    466 }
    467 
    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; });
    474 }
    475 
    476 function updateLocation(frame) {
    477    if (options.emacs) {
    478        var loc = frame.location();
    479        if (loc)
    480            print("\032\032" + loc + ":1");
    481    }
    482 }
    483 
    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));
    489 
    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    }
    509 
    510    function stepEntered(newFrame) {
    511        print("entered frame: " + newFrame.fullDescription());
    512        updateLocation(newFrame);
    513        topFrame = focusedFrame = newFrame;
    514        return repl();
    515    }
    516 
    517    function stepStepped() {
    518        // print("stepStepped: " + this.fullDescription());
    519        updateLocation(this);
    520        var stop = false;
    521 
    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        }
    535 
    536        if (stop) {
    537            topFrame = focusedFrame = this;
    538            if (focusedFrame != startFrame)
    539                print(focusedFrame.fullDescription());
    540            return repl();
    541        }
    542 
    543        // Otherwise, let execution continue.
    544        return undefined;
    545    }
    546 
    547    if (kind.step)
    548        setUntilRepl(dbg, 'onEnterFrame', stepEntered);
    549 
    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    }
    560 
    561    // Let the program continue!
    562    return [undefined];
    563 }
    564 
    565 function stepCommand() { return doStepOrNext({step:true}); }
    566 function nextCommand() { return doStepOrNext({next:true}); }
    567 function finishCommand() { return doStepOrNext({finish:true}); }
    568 
    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);
    582 }
    583 
    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",
    606 ];
    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;
    614 }
    615 
    616 function helpCommand(rest) {
    617    print("Available commands:");
    618    var printcmd = function(group) {
    619        print("  " + group.join(", "));
    620    }
    621 
    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);
    632 }
    633 
    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:
    637 //
    638 //   print x         => ['print', 'x']
    639 //   print           => ['print', '']
    640 //   !print x        => ['!', 'print x']
    641 //   ?!wtf!?         => ['?', '!wtf!?']
    642 //   print/b x       => ['print', '/b x']
    643 //
    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)];
    652 }
    653 
    654 function runcmd(cmd) {
    655    var pieces = breakcmd(cmd);
    656    if (pieces[0] === "")
    657        return undefined;
    658 
    659    var first = pieces[0], rest = pieces[1];
    660    if (!commands.hasOwnProperty(first)) {
    661        print("unrecognized command '" + first + "'");
    662        return undefined;
    663    }
    664 
    665    var cmd = commands[first];
    666    if (cmd.length === 0 && rest !== '') {
    667        print("this command cannot take an argument");
    668        return undefined;
    669    }
    670 
    671    return cmd(rest);
    672 }
    673 
    674 function preReplCleanups() {
    675    while (replCleanups.length > 0)
    676        replCleanups.pop()();
    677 }
    678 
    679 var prevcmd = undefined;
    680 function repl() {
    681    preReplCleanups();
    682 
    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;
    691 
    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    }
    709 }
    710 
    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        });
    721 };
    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        });
    731 };
    732 
    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    });
    742 };
    743 
    744 // The depth of jorendb nesting.
    745 var jorendbDepth;
    746 if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
    747 
    748 var debuggeeGlobal = newGlobal({newCompartment: true});
    749 debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
    750 var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
    751 
    752 print("jorendb version -0.0");
    753 prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
    754 
    755 var args = scriptArgs.slice(0);
    756 print("INITIAL ARGS: " + args);
    757 
    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.
    765 //
    766 // The difficulty is that the JS shell has a mixture of
    767 //
    768 //   --boolean
    769 //
    770 // and
    771 //
    772 //   --value VAL
    773 //
    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.
    778 //
    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:
    782 //
    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]
    785 //
    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].
    788 
    789 
    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;
    796 
    797 if (scriptPath !== undefined) {
    798    todo.push({
    799        'action': 'load',
    800        'script': scriptPath,
    801    });
    802    scriptSeen = true;
    803 }
    804 
    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    }
    855 }
    856 print("jorendb: scriptPath = " + scriptPath);
    857 print("jorendb: scriptArgs = " + scriptArgs);
    858 print("jorendb: actualScriptArgs = " + actualScriptArgs);
    859 
    860 for (var task of todo) {
    861    task['scriptArgs'] = [...actualScriptArgs];
    862 }
    863 
    864 // Always drop into a repl at the end. Especially if the main script throws an
    865 // exception.
    866 todo.push({ 'action': 'repl' });
    867 
    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    }
    892 }
    893 
    894 quit(0);