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);