jorendb.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. /* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
  2. *
  3. * jorendb - A toy command-line debugger for shell-js programs.
  4. *
  5. * This Source Code Form is subject to the terms of the Mozilla Public
  6. * License, v. 2.0. If a copy of the MPL was not distributed with this
  7. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  8. */
  9. /*
  10. * jorendb is a simple command-line debugger for shell-js programs. It is
  11. * intended as a demo of the Debugger object (as there are no shell js programs
  12. * to speak of).
  13. *
  14. * To run it: $JS -d path/to/this/file/jorendb.js
  15. * To run some JS code under it, try:
  16. * (jorendb) print load("my-script-to-debug.js")
  17. * Execution will stop at debugger statements and you'll get a jorendb prompt.
  18. */
  19. // Debugger state.
  20. var focusedFrame = null;
  21. var topFrame = null;
  22. var debuggeeValues = {};
  23. var nextDebuggeeValueIndex = 1;
  24. var lastExc = null;
  25. var todo = [];
  26. var activeTask;
  27. var options = { 'pretty': true,
  28. 'emacs': (os.getenv('EMACS') == 't') };
  29. var rerun = true;
  30. // Cleanup functions to run when we next re-enter the repl.
  31. var replCleanups = [];
  32. // Redirect debugger printing functions to go to the original output
  33. // destination, unaffected by any redirects done by the debugged script.
  34. var initialOut = os.file.redirect();
  35. var initialErr = os.file.redirectErr();
  36. function wrap(global, name) {
  37. var orig = global[name];
  38. global[name] = function(...args) {
  39. var oldOut = os.file.redirect(initialOut);
  40. var oldErr = os.file.redirectErr(initialErr);
  41. try {
  42. return orig.apply(global, args);
  43. } finally {
  44. os.file.redirect(oldOut);
  45. os.file.redirectErr(oldErr);
  46. }
  47. };
  48. }
  49. wrap(this, 'print');
  50. wrap(this, 'printErr');
  51. wrap(this, 'putstr');
  52. // Convert a debuggee value v to a string.
  53. function dvToString(v) {
  54. return (typeof v !== 'object' || v === null) ? uneval(v) : "[object " + v.class + "]";
  55. }
  56. function summaryObject(dv) {
  57. var obj = {};
  58. for (var name of dv.getOwnPropertyNames()) {
  59. var v = dv.getOwnPropertyDescriptor(name).value;
  60. if (v instanceof Debugger.Object) {
  61. v = "(...)";
  62. }
  63. obj[name] = v;
  64. }
  65. return obj;
  66. }
  67. function debuggeeValueToString(dv, style) {
  68. var dvrepr = dvToString(dv);
  69. if (!style.pretty || (typeof dv !== 'object'))
  70. return [dvrepr, undefined];
  71. if (dv.class == "Error") {
  72. let errval = debuggeeGlobalWrapper.executeInGlobalWithBindings("$" + i + ".toString()", debuggeeValues);
  73. return [dvrepr, errval.return];
  74. }
  75. if (style.brief)
  76. return [dvrepr, JSON.stringify(summaryObject(dv), null, 4)];
  77. let str = debuggeeGlobalWrapper.executeInGlobalWithBindings("JSON.stringify(v, null, 4)", {v: dv});
  78. if ('throw' in str) {
  79. if (style.noerror)
  80. return [dvrepr, undefined];
  81. let substyle = {};
  82. Object.assign(substyle, style);
  83. substyle.noerror = true;
  84. return [dvrepr, debuggeeValueToString(str.throw, substyle)];
  85. }
  86. return [dvrepr, str.return];
  87. }
  88. // Problem! Used to do [object Object] followed by details. Now just details?
  89. function showDebuggeeValue(dv, style={pretty: options.pretty}) {
  90. var i = nextDebuggeeValueIndex++;
  91. debuggeeValues["$" + i] = dv;
  92. let [brief, full] = debuggeeValueToString(dv, style);
  93. print("$" + i + " = " + brief);
  94. if (full !== undefined)
  95. print(full);
  96. }
  97. Object.defineProperty(Debugger.Frame.prototype, "num", {
  98. configurable: true,
  99. enumerable: false,
  100. get: function () {
  101. var i = 0;
  102. for (var f = topFrame; f && f !== this; f = f.older)
  103. i++;
  104. return f === null ? undefined : i;
  105. }
  106. });
  107. Debugger.Frame.prototype.frameDescription = function frameDescription() {
  108. if (this.type == "call")
  109. return ((this.callee.name || '<anonymous>') +
  110. "(" + this.arguments.map(dvToString).join(", ") + ")");
  111. else
  112. return this.type + " code";
  113. }
  114. Debugger.Frame.prototype.positionDescription = function positionDescription() {
  115. if (this.script) {
  116. var line = this.script.getOffsetLocation(this.offset).lineNumber;
  117. if (this.script.url)
  118. return this.script.url + ":" + line;
  119. return "line " + line;
  120. }
  121. return null;
  122. }
  123. Debugger.Frame.prototype.location = function () {
  124. if (this.script) {
  125. var { lineNumber, columnNumber, isEntryPoint } = this.script.getOffsetLocation(this.offset);
  126. if (this.script.url)
  127. return this.script.url + ":" + lineNumber;
  128. return null;
  129. }
  130. return null;
  131. }
  132. Debugger.Frame.prototype.fullDescription = function fullDescription() {
  133. var fr = this.frameDescription();
  134. var pos = this.positionDescription();
  135. if (pos)
  136. return fr + ", " + pos;
  137. return fr;
  138. }
  139. Object.defineProperty(Debugger.Frame.prototype, "line", {
  140. configurable: true,
  141. enumerable: false,
  142. get: function() {
  143. if (this.script)
  144. return this.script.getOffsetLocation(this.offset).lineNumber;
  145. else
  146. return null;
  147. }
  148. });
  149. function callDescription(f) {
  150. return ((f.callee.name || '<anonymous>') +
  151. "(" + f.arguments.map(dvToString).join(", ") + ")");
  152. }
  153. function showFrame(f, n) {
  154. if (f === undefined || f === null) {
  155. f = focusedFrame;
  156. if (f === null) {
  157. print("No stack.");
  158. return;
  159. }
  160. }
  161. if (n === undefined) {
  162. n = f.num;
  163. if (n === undefined)
  164. throw new Error("Internal error: frame not on stack");
  165. }
  166. print('#' + n + " " + f.fullDescription());
  167. }
  168. function saveExcursion(fn) {
  169. var tf = topFrame, ff = focusedFrame;
  170. try {
  171. return fn();
  172. } finally {
  173. topFrame = tf;
  174. focusedFrame = ff;
  175. }
  176. }
  177. function parseArgs(str) {
  178. return str.split(" ");
  179. }
  180. function describedRv(r, desc) {
  181. desc = "[" + desc + "] ";
  182. if (r === undefined) {
  183. print(desc + "Returning undefined");
  184. } else if (r === null) {
  185. print(desc + "Returning null");
  186. } else if (r.length === undefined) {
  187. print(desc + "Returning object " + JSON.stringify(r));
  188. } else {
  189. print(desc + "Returning length-" + r.length + " list");
  190. if (r.length > 0) {
  191. print(" " + r[0]);
  192. }
  193. }
  194. return r;
  195. }
  196. // Rerun the program (reloading it from the file)
  197. function runCommand(args) {
  198. print("Restarting program");
  199. if (args)
  200. activeTask.scriptArgs = parseArgs(args);
  201. rerun = true;
  202. for (var f = topFrame; f; f = f.older) {
  203. if (f.older) {
  204. f.onPop = () => null;
  205. } else {
  206. f.onPop = () => ({ 'return': 0 });
  207. }
  208. }
  209. //return describedRv([{ 'return': 0 }], "runCommand");
  210. return null;
  211. }
  212. // Evaluate an expression in the Debugger global
  213. function evalCommand(expr) {
  214. eval(expr);
  215. }
  216. function quitCommand() {
  217. dbg.enabled = false;
  218. quit(0);
  219. }
  220. function backtraceCommand() {
  221. if (topFrame === null)
  222. print("No stack.");
  223. for (var i = 0, f = topFrame; f; i++, f = f.older)
  224. showFrame(f, i);
  225. }
  226. function setCommand(rest) {
  227. var space = rest.indexOf(' ');
  228. if (space == -1) {
  229. print("Invalid set <option> <value> command");
  230. } else {
  231. var name = rest.substr(0, space);
  232. var value = rest.substr(space + 1);
  233. if (name == 'args') {
  234. activeTask.scriptArgs = parseArgs(value);
  235. } else {
  236. var yes = ["1", "yes", "true", "on"];
  237. var no = ["0", "no", "false", "off"];
  238. if (yes.indexOf(value) !== -1)
  239. options[name] = true;
  240. else if (no.indexOf(value) !== -1)
  241. options[name] = false;
  242. else
  243. options[name] = value;
  244. }
  245. }
  246. }
  247. function split_print_options(s, style) {
  248. var m = /^\/(\w+)/.exec(s);
  249. if (!m)
  250. return [ s, style ];
  251. if (m[1].indexOf("p") != -1)
  252. style.pretty = true;
  253. if (m[1].indexOf("b") != -1)
  254. style.brief = true;
  255. return [ s.substr(m[0].length).trimLeft(), style ];
  256. }
  257. function doPrint(expr, style) {
  258. // This is the real deal.
  259. var cv = saveExcursion(
  260. () => focusedFrame == null
  261. ? debuggeeGlobalWrapper.executeInGlobalWithBindings(expr, debuggeeValues)
  262. : focusedFrame.evalWithBindings(expr, debuggeeValues));
  263. if (cv === null) {
  264. if (!dbg.enabled)
  265. return [cv];
  266. print("Debuggee died.");
  267. } else if ('return' in cv) {
  268. if (!dbg.enabled)
  269. return [undefined];
  270. showDebuggeeValue(cv.return, style);
  271. } else {
  272. if (!dbg.enabled)
  273. return [cv];
  274. print("Exception caught. (To rethrow it, type 'throw'.)");
  275. lastExc = cv.throw;
  276. showDebuggeeValue(lastExc, style);
  277. }
  278. }
  279. function printCommand(rest) {
  280. var [expr, style] = split_print_options(rest, {pretty: options.pretty});
  281. return doPrint(expr, style);
  282. }
  283. function keysCommand(rest) { return doPrint("Object.keys(" + rest + ")"); }
  284. function detachCommand() {
  285. dbg.enabled = false;
  286. return [undefined];
  287. }
  288. function continueCommand(rest) {
  289. if (focusedFrame === null) {
  290. print("No stack.");
  291. return;
  292. }
  293. var match = rest.match(/^(\d+)$/);
  294. if (match) {
  295. return doStepOrNext({upto:true, stopLine:match[1]});
  296. }
  297. return [undefined];
  298. }
  299. function throwCommand(rest) {
  300. var v;
  301. if (focusedFrame !== topFrame) {
  302. print("To throw, you must select the newest frame (use 'frame 0').");
  303. return;
  304. } else if (focusedFrame === null) {
  305. print("No stack.");
  306. return;
  307. } else if (rest === '') {
  308. return [{throw: lastExc}];
  309. } else {
  310. var cv = saveExcursion(function () { return focusedFrame.eval(rest); });
  311. if (cv === null) {
  312. if (!dbg.enabled)
  313. return [cv];
  314. print("Debuggee died while determining what to throw. Stopped.");
  315. } else if ('return' in cv) {
  316. return [{throw: cv.return}];
  317. } else {
  318. if (!dbg.enabled)
  319. return [cv];
  320. print("Exception determining what to throw. Stopped.");
  321. showDebuggeeValue(cv.throw);
  322. }
  323. return;
  324. }
  325. }
  326. function frameCommand(rest) {
  327. var n, f;
  328. if (rest.match(/[0-9]+/)) {
  329. n = +rest;
  330. f = topFrame;
  331. if (f === null) {
  332. print("No stack.");
  333. return;
  334. }
  335. for (var i = 0; i < n && f; i++) {
  336. if (!f.older) {
  337. print("There is no frame " + rest + ".");
  338. return;
  339. }
  340. f.older.younger = f;
  341. f = f.older;
  342. }
  343. focusedFrame = f;
  344. updateLocation(focusedFrame);
  345. showFrame(f, n);
  346. } else if (rest === '') {
  347. if (topFrame === null) {
  348. print("No stack.");
  349. } else {
  350. updateLocation(focusedFrame);
  351. showFrame();
  352. }
  353. } else {
  354. print("do what now?");
  355. }
  356. }
  357. function upCommand() {
  358. if (focusedFrame === null)
  359. print("No stack.");
  360. else if (focusedFrame.older === null)
  361. print("Initial frame selected; you cannot go up.");
  362. else {
  363. focusedFrame.older.younger = focusedFrame;
  364. focusedFrame = focusedFrame.older;
  365. updateLocation(focusedFrame);
  366. showFrame();
  367. }
  368. }
  369. function downCommand() {
  370. if (focusedFrame === null)
  371. print("No stack.");
  372. else if (!focusedFrame.younger)
  373. print("Youngest frame selected; you cannot go down.");
  374. else {
  375. focusedFrame = focusedFrame.younger;
  376. updateLocation(focusedFrame);
  377. showFrame();
  378. }
  379. }
  380. function forcereturnCommand(rest) {
  381. var v;
  382. var f = focusedFrame;
  383. if (f !== topFrame) {
  384. print("To forcereturn, you must select the newest frame (use 'frame 0').");
  385. } else if (f === null) {
  386. print("Nothing on the stack.");
  387. } else if (rest === '') {
  388. return [{return: undefined}];
  389. } else {
  390. var cv = saveExcursion(function () { return f.eval(rest); });
  391. if (cv === null) {
  392. if (!dbg.enabled)
  393. return [cv];
  394. print("Debuggee died while determining what to forcereturn. Stopped.");
  395. } else if ('return' in cv) {
  396. return [{return: cv.return}];
  397. } else {
  398. if (!dbg.enabled)
  399. return [cv];
  400. print("Error determining what to forcereturn. Stopped.");
  401. showDebuggeeValue(cv.throw);
  402. }
  403. }
  404. }
  405. function printPop(f, c) {
  406. var fdesc = f.fullDescription();
  407. if (c.return) {
  408. print("frame returning (still selected): " + fdesc);
  409. showDebuggeeValue(c.return, {brief: true});
  410. } else if (c.throw) {
  411. print("frame threw exception: " + fdesc);
  412. showDebuggeeValue(c.throw);
  413. print("(To rethrow it, type 'throw'.)");
  414. lastExc = c.throw;
  415. } else {
  416. print("frame was terminated: " + fdesc);
  417. }
  418. }
  419. // Set |prop| on |obj| to |value|, but then restore its current value
  420. // when we next enter the repl.
  421. function setUntilRepl(obj, prop, value) {
  422. var saved = obj[prop];
  423. obj[prop] = value;
  424. replCleanups.push(function () { obj[prop] = saved; });
  425. }
  426. function updateLocation(frame) {
  427. if (options.emacs) {
  428. var loc = frame.location();
  429. if (loc)
  430. print("\032\032" + loc + ":1");
  431. }
  432. }
  433. function doStepOrNext(kind) {
  434. var startFrame = topFrame;
  435. var startLine = startFrame.line;
  436. // print("stepping in: " + startFrame.fullDescription());
  437. // print("starting line: " + uneval(startLine));
  438. function stepPopped(completion) {
  439. // Note that we're popping this frame; we need to watch for
  440. // subsequent step events on its caller.
  441. this.reportedPop = true;
  442. printPop(this, completion);
  443. topFrame = focusedFrame = this;
  444. if (kind.finish) {
  445. // We want to continue, but this frame is going to be invalid as
  446. // soon as this function returns, which will make the replCleanups
  447. // assert when it tries to access the dead frame's 'onPop'
  448. // property. So clear it out now while the frame is still valid,
  449. // and trade it for an 'onStep' callback on the frame we're popping to.
  450. preReplCleanups();
  451. setUntilRepl(this.older, 'onStep', stepStepped);
  452. return undefined;
  453. }
  454. updateLocation(this);
  455. return repl();
  456. }
  457. function stepEntered(newFrame) {
  458. print("entered frame: " + newFrame.fullDescription());
  459. updateLocation(newFrame);
  460. topFrame = focusedFrame = newFrame;
  461. return repl();
  462. }
  463. function stepStepped() {
  464. // print("stepStepped: " + this.fullDescription());
  465. updateLocation(this);
  466. var stop = false;
  467. if (kind.finish) {
  468. // 'finish' set a one-time onStep for stopping at the frame it
  469. // wants to return to
  470. stop = true;
  471. } else if (kind.upto) {
  472. // running until a given line is reached
  473. if (this.line == kind.stopLine)
  474. stop = true;
  475. } else {
  476. // regular step; stop whenever the line number changes
  477. if ((this.line != startLine) || (this != startFrame))
  478. stop = true;
  479. }
  480. if (stop) {
  481. topFrame = focusedFrame = this;
  482. if (focusedFrame != startFrame)
  483. print(focusedFrame.fullDescription());
  484. return repl();
  485. }
  486. // Otherwise, let execution continue.
  487. return undefined;
  488. }
  489. if (kind.step)
  490. setUntilRepl(dbg, 'onEnterFrame', stepEntered);
  491. // If we're stepping after an onPop, watch for steps and pops in the
  492. // next-older frame; this one is done.
  493. var stepFrame = startFrame.reportedPop ? startFrame.older : startFrame;
  494. if (!stepFrame || !stepFrame.script)
  495. stepFrame = null;
  496. if (stepFrame) {
  497. if (!kind.finish)
  498. setUntilRepl(stepFrame, 'onStep', stepStepped);
  499. setUntilRepl(stepFrame, 'onPop', stepPopped);
  500. }
  501. // Let the program continue!
  502. return [undefined];
  503. }
  504. function stepCommand() { return doStepOrNext({step:true}); }
  505. function nextCommand() { return doStepOrNext({next:true}); }
  506. function finishCommand() { return doStepOrNext({finish:true}); }
  507. // FIXME: DOES NOT WORK YET
  508. function breakpointCommand(where) {
  509. print("Sorry, breakpoints don't work yet.");
  510. var script = focusedFrame.script;
  511. var offsets = script.getLineOffsets(Number(where));
  512. if (offsets.length == 0) {
  513. print("Unable to break at line " + where);
  514. return;
  515. }
  516. for (var offset of offsets) {
  517. script.setBreakpoint(offset, { hit: handleBreakpoint });
  518. }
  519. print("Set breakpoint in " + script.url + ":" + script.startLine + " at line " + where + ", " + offsets.length);
  520. }
  521. // Build the table of commands.
  522. var commands = {};
  523. var commandArray = [
  524. backtraceCommand, "bt", "where",
  525. breakpointCommand, "b", "break",
  526. continueCommand, "c",
  527. detachCommand,
  528. downCommand, "d",
  529. evalCommand, "!",
  530. forcereturnCommand,
  531. frameCommand, "f",
  532. finishCommand, "fin",
  533. nextCommand, "n",
  534. printCommand, "p",
  535. keysCommand, "k",
  536. quitCommand, "q",
  537. runCommand, "run",
  538. stepCommand, "s",
  539. setCommand,
  540. throwCommand, "t",
  541. upCommand, "u",
  542. helpCommand, "h",
  543. ];
  544. var currentCmd = null;
  545. for (var i = 0; i < commandArray.length; i++) {
  546. var cmd = commandArray[i];
  547. if (typeof cmd === "string")
  548. commands[cmd] = currentCmd;
  549. else
  550. currentCmd = commands[cmd.name.replace(/Command$/, '')] = cmd;
  551. }
  552. function helpCommand(rest) {
  553. print("Available commands:");
  554. var printcmd = function(group) {
  555. print(" " + group.join(", "));
  556. }
  557. var group = [];
  558. for (var cmd of commandArray) {
  559. if (typeof cmd === "string") {
  560. group.push(cmd);
  561. } else {
  562. if (group.length) printcmd(group);
  563. group = [ cmd.name.replace(/Command$/, '') ];
  564. }
  565. }
  566. printcmd(group);
  567. }
  568. // Break cmd into two parts: its first word and everything else. If it begins
  569. // with punctuation, treat that as a separate word. The first word is
  570. // terminated with whitespace or the '/' character. So:
  571. //
  572. // print x => ['print', 'x']
  573. // print => ['print', '']
  574. // !print x => ['!', 'print x']
  575. // ?!wtf!? => ['?', '!wtf!?']
  576. // print/b x => ['print', '/b x']
  577. //
  578. function breakcmd(cmd) {
  579. cmd = cmd.trimLeft();
  580. if ("!@#$%^&*_+=/?.,<>:;'\"".indexOf(cmd.substr(0, 1)) != -1)
  581. return [cmd.substr(0, 1), cmd.substr(1).trimLeft()];
  582. var m = /\s+|(?=\/)/.exec(cmd);
  583. if (m === null)
  584. return [cmd, ''];
  585. return [cmd.slice(0, m.index), cmd.slice(m.index + m[0].length)];
  586. }
  587. function runcmd(cmd) {
  588. var pieces = breakcmd(cmd);
  589. if (pieces[0] === "")
  590. return undefined;
  591. var first = pieces[0], rest = pieces[1];
  592. if (!commands.hasOwnProperty(first)) {
  593. print("unrecognized command '" + first + "'");
  594. return undefined;
  595. }
  596. var cmd = commands[first];
  597. if (cmd.length === 0 && rest !== '') {
  598. print("this command cannot take an argument");
  599. return undefined;
  600. }
  601. return cmd(rest);
  602. }
  603. function preReplCleanups() {
  604. while (replCleanups.length > 0)
  605. replCleanups.pop()();
  606. }
  607. var prevcmd = undefined;
  608. function repl() {
  609. preReplCleanups();
  610. var cmd;
  611. for (;;) {
  612. putstr("\n" + prompt);
  613. cmd = readline();
  614. if (cmd === null)
  615. return null;
  616. else if (cmd === "")
  617. cmd = prevcmd;
  618. try {
  619. prevcmd = cmd;
  620. var result = runcmd(cmd);
  621. if (result === undefined)
  622. ; // do nothing, return to prompt
  623. else if (Array.isArray(result))
  624. return result[0];
  625. else if (result === null)
  626. return null;
  627. else
  628. throw new Error("Internal error: result of runcmd wasn't array or undefined: " + result);
  629. } catch (exc) {
  630. print("*** Internal error: exception in the debugger code.");
  631. print(" " + exc);
  632. print(exc.stack);
  633. }
  634. }
  635. }
  636. var dbg = new Debugger();
  637. dbg.onDebuggerStatement = function (frame) {
  638. return saveExcursion(function () {
  639. topFrame = focusedFrame = frame;
  640. print("'debugger' statement hit.");
  641. showFrame();
  642. updateLocation(focusedFrame);
  643. backtrace();
  644. return describedRv(repl(), "debugger.saveExc");
  645. });
  646. };
  647. dbg.onThrow = function (frame, exc) {
  648. return saveExcursion(function () {
  649. topFrame = focusedFrame = frame;
  650. print("Unwinding due to exception. (Type 'c' to continue unwinding.)");
  651. showFrame();
  652. print("Exception value is:");
  653. showDebuggeeValue(exc);
  654. return repl();
  655. });
  656. };
  657. function handleBreakpoint (frame) {
  658. print("Breakpoint hit!");
  659. return saveExcursion(() => {
  660. topFrame = focusedFrame = frame;
  661. print("breakpoint hit.");
  662. showFrame();
  663. updateLocation(focusedFrame);
  664. return repl();
  665. });
  666. };
  667. // The depth of jorendb nesting.
  668. var jorendbDepth;
  669. if (typeof jorendbDepth == 'undefined') jorendbDepth = 0;
  670. var debuggeeGlobal = newGlobal("new-compartment");
  671. debuggeeGlobal.jorendbDepth = jorendbDepth + 1;
  672. var debuggeeGlobalWrapper = dbg.addDebuggee(debuggeeGlobal);
  673. print("jorendb version -0.0");
  674. prompt = '(' + Array(jorendbDepth+1).join('meta-') + 'jorendb) ';
  675. var args = scriptArgs.slice(0);
  676. print("INITIAL ARGS: " + args);
  677. // Find the script to run and its arguments. The script may have been given as
  678. // a plain script name, in which case all remaining arguments belong to the
  679. // script. Or there may have been any number of arguments to the JS shell,
  680. // followed by -f scriptName, followed by additional arguments to the JS shell,
  681. // followed by the script arguments. There may be multiple -e or -f options in
  682. // the JS shell arguments, and we want to treat each one as a debuggable
  683. // script.
  684. //
  685. // The difficulty is that the JS shell has a mixture of
  686. //
  687. // --boolean
  688. //
  689. // and
  690. //
  691. // --value VAL
  692. //
  693. // parameters, and there's no way to know whether --option takes an argument or
  694. // not. We will assume that VAL will never end in .js, or rather that the first
  695. // argument that does not start with "-" but does end in ".js" is the name of
  696. // the script.
  697. //
  698. // If you need to pass other options and not have them given to the script,
  699. // pass them before the -f jorendb.js argument. Thus, the safe ways to pass
  700. // arguments are:
  701. //
  702. // js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)+ -- [script args]
  703. // js [JS shell options] -f jorendb.js (-e SCRIPT | -f FILE)* script.js [script args]
  704. //
  705. // Additionally, if you want to run a script that is *NOT* debugged, put it in
  706. // as part of the leading [JS shell options].
  707. // Compute actualScriptArgs by finding the script to be run and grabbing every
  708. // non-script argument. The script may be given by -f scriptname or just plain
  709. // scriptname. In the latter case, it will be in the global variable
  710. // 'scriptPath' (and NOT in scriptArgs.)
  711. var actualScriptArgs = [];
  712. var scriptSeen;
  713. if (scriptPath !== undefined) {
  714. todo.push({
  715. 'action': 'load',
  716. 'script': scriptPath,
  717. });
  718. scriptSeen = true;
  719. }
  720. while(args.length > 0) {
  721. var arg = args.shift();
  722. print("arg: " + arg);
  723. if (arg == '-e') {
  724. print(" eval");
  725. todo.push({
  726. 'action': 'eval',
  727. 'code': args.shift()
  728. });
  729. } else if (arg == '-f') {
  730. var script = args.shift();
  731. print(" load -f " + script);
  732. scriptSeen = true;
  733. todo.push({
  734. 'action': 'load',
  735. 'script': script,
  736. });
  737. } else if (arg.indexOf("-") == 0) {
  738. if (arg == '--') {
  739. print(" pass remaining args to script");
  740. actualScriptArgs.push(...args);
  741. break;
  742. } else if ((args.length > 0) && (args[0].indexOf(".js") + 3 == args[0].length)) {
  743. // Ends with .js, assume we are looking at --boolean script.js
  744. print(" load script.js after --boolean");
  745. todo.push({
  746. 'action': 'load',
  747. 'script': args.shift(),
  748. });
  749. scriptSeen = true;
  750. } else {
  751. // Does not end with .js, assume we are looking at JS shell arg
  752. // --value VAL
  753. print(" ignore");
  754. args.shift();
  755. }
  756. } else {
  757. if (!scriptSeen) {
  758. print(" load general");
  759. actualScriptArgs.push(...args);
  760. todo.push({
  761. 'action': 'load',
  762. 'script': arg,
  763. });
  764. break;
  765. } else {
  766. print(" arg " + arg);
  767. actualScriptArgs.push(arg);
  768. }
  769. }
  770. }
  771. print("jorendb: scriptPath = " + scriptPath);
  772. print("jorendb: scriptArgs = " + scriptArgs);
  773. print("jorendb: actualScriptArgs = " + actualScriptArgs);
  774. for (var task of todo) {
  775. task['scriptArgs'] = actualScriptArgs;
  776. }
  777. // If nothing to run, just drop into a repl
  778. if (todo.length == 0) {
  779. todo.push({ 'action': 'repl' });
  780. }
  781. while (rerun) {
  782. print("Top of run loop");
  783. rerun = false;
  784. for (var task of todo) {
  785. activeTask = task;
  786. if (task.action == 'eval') {
  787. debuggeeGlobal.eval(task.code);
  788. } else if (task.action == 'load') {
  789. debuggeeGlobal['scriptArgs'] = task.scriptArgs;
  790. debuggeeGlobal['scriptPath'] = task.script;
  791. print("Loading JavaScript file " + task.script);
  792. debuggeeGlobal.evaluate(read(task.script), { 'fileName': task.script, 'lineNumber': 1 });
  793. } else if (task.action == 'repl') {
  794. repl();
  795. }
  796. if (rerun)
  797. break;
  798. }
  799. }
  800. quit(0);