debugger.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. /**
  2. * Debugger support for skulpt module
  3. */
  4. var Sk = Sk || {}; //jshint ignore:line
  5. function hasOwnProperty(obj, prop) {
  6. var proto = obj.constructor.prototype;
  7. return (prop in obj) &&
  8. (!(prop in proto) || proto[prop] !== obj[prop]);
  9. }
  10. Sk.Breakpoint = function(filename, lineno, colno) {
  11. this.filename = filename;
  12. this.lineno = lineno;
  13. this.colno = colno;
  14. this.enabled = true;
  15. this.ignore_count = 0;
  16. };
  17. Sk.Debugger = function(filename, output_callback) {
  18. this.dbg_breakpoints = {};
  19. this.tmp_breakpoints = {};
  20. this.suspension_stack = [];
  21. this.current_suspension = -1;
  22. this.eval_callback = null;
  23. this.suspension = null;
  24. this.output_callback = output_callback;
  25. this.step_mode = false;
  26. this.filename = filename;
  27. };
  28. Sk.Debugger.prototype.print = function(txt) {
  29. if (this.output_callback != null) {
  30. this.output_callback.print(txt);
  31. }
  32. };
  33. Sk.Debugger.prototype.get_source_line = function(lineno) {
  34. if (this.output_callback != null) {
  35. return this.output_callback.get_source_line(lineno);
  36. }
  37. return "";
  38. };
  39. Sk.Debugger.prototype.move_up_the_stack = function() {
  40. this.current_suspension = Math.min(this.current_suspension + 1, this.suspension_stack.length - 1);
  41. };
  42. Sk.Debugger.prototype.move_down_the_stack = function() {
  43. this.current_suspension = Math.max(this.current_suspension - 1, 0);
  44. };
  45. Sk.Debugger.prototype.enable_step_mode = function() {
  46. this.step_mode = true;
  47. };
  48. Sk.Debugger.prototype.disable_step_mode = function() {
  49. this.step_mode = false;
  50. };
  51. Sk.Debugger.prototype.get_suspension_stack = function() {
  52. return this.suspension_stack;
  53. };
  54. Sk.Debugger.prototype.get_active_suspension = function() {
  55. if (this.suspension_stack.length === 0) {
  56. return null;
  57. }
  58. return this.suspension_stack[this.current_suspension];
  59. };
  60. Sk.Debugger.prototype.generate_breakpoint_key = function(filename, lineno, colno) {
  61. var key = filename + "-" + lineno;
  62. return key;
  63. };
  64. Sk.Debugger.prototype.check_breakpoints = function(filename, lineno, colno, globals, locals) {
  65. // If Step mode is enabled then ignore breakpoints since we will just break
  66. // at every line.
  67. if (this.step_mode === true) {
  68. return true;
  69. }
  70. var key = this.generate_breakpoint_key(filename, lineno, colno);
  71. if (hasOwnProperty(this.dbg_breakpoints, key) &&
  72. this.dbg_breakpoints[key].enabled === true) {
  73. var bp = null;
  74. if (hasOwnProperty(this.tmp_breakpoints, key)) {
  75. delete this.dbg_breakpoints[key];
  76. delete this.tmp_breakpoints[key];
  77. return true;
  78. }
  79. this.dbg_breakpoints[key].ignore_count -= 1;
  80. this.dbg_breakpoints[key].ignore_count = Math.max(0, this.dbg_breakpoints[key].ignore_count);
  81. bp = this.dbg_breakpoints[key];
  82. if (bp.ignore_count === 0) {
  83. return true;
  84. } else {
  85. return false;
  86. }
  87. }
  88. return false;
  89. };
  90. Sk.Debugger.prototype.get_breakpoints_list = function() {
  91. return this.dbg_breakpoints;
  92. };
  93. Sk.Debugger.prototype.disable_breakpoint = function(filename, lineno, colno) {
  94. var key = this.generate_breakpoint_key(filename, lineno, colno);
  95. if (hasOwnProperty(this.dbg_breakpoints, key)) {
  96. this.dbg_breakpoints[key].enabled = false;
  97. }
  98. };
  99. Sk.Debugger.prototype.enable_breakpoint = function(filename, lineno, colno) {
  100. var key = this.generate_breakpoint_key(filename, lineno, colno);
  101. if (hasOwnProperty(this.dbg_breakpoints, key)) {
  102. this.dbg_breakpoints[key].enabled = true;
  103. }
  104. };
  105. Sk.Debugger.prototype.clear_breakpoint = function(filename, lineno, colno) {
  106. var key = this.generate_breakpoint_key(filename, lineno, colno);
  107. if (hasOwnProperty(this.dbg_breakpoints, key)) {
  108. delete this.dbg_breakpoints[key];
  109. return null;
  110. } else {
  111. return "Invalid breakpoint specified: " + filename + " line: " + lineno;
  112. }
  113. };
  114. Sk.Debugger.prototype.clear_all_breakpoints = function() {
  115. this.dbg_breakpoints = {};
  116. this.tmp_breakpoints = {};
  117. };
  118. Sk.Debugger.prototype.set_ignore_count = function(filename, lineno, colno, count) {
  119. var key = this.generate_breakpoint_key(filename, lineno, colno);
  120. if (hasOwnProperty(this.dbg_breakpoints, key)) {
  121. var bp = this.dbg_breakpoints[key];
  122. bp.ignore_count = count;
  123. }
  124. };
  125. Sk.Debugger.prototype.set_condition = function(filename, lineno, colno, lhs, cond, rhs) {
  126. var key = this.generate_breakpoint_key(filename, lineno, colno);
  127. var bp;
  128. if (hasOwnProperty(this.dbg_breakpoints, key)) {
  129. // Set a new condition
  130. bp = this.dbg_breakpoints[key];
  131. } else {
  132. bp = new Sk.Breakpoint(filename, lineno, colno);
  133. }
  134. bp.condition = new Sk.Condition(lhs, cond, rhs);
  135. this.dbg_breakpoints[key] = bp;
  136. };
  137. Sk.Debugger.prototype.print_suspension_info = function(suspension) {
  138. var filename = suspension.filename;
  139. var lineno = suspension.lineno;
  140. var colno = suspension.colno;
  141. this.print("Hit Breakpoint at <" + filename + "> at line: " + lineno + " column: " + colno + "\n");
  142. this.print("----------------------------------------------------------------------------------\n");
  143. this.print(" ==> " + this.get_source_line(lineno - 1) + "\n");
  144. this.print("----------------------------------------------------------------------------------\n");
  145. };
  146. Sk.Debugger.prototype.set_suspension = function(suspension) {
  147. var parent = null;
  148. if (!hasOwnProperty(suspension, "filename") && suspension.child instanceof Sk.misceval.Suspension) {
  149. suspension = suspension.child;
  150. }
  151. // Pop the last suspension of the stack if there is more than 0
  152. if (this.suspension_stack.length > 0) {
  153. this.suspension_stack.pop();
  154. this.current_suspension -= 1;
  155. }
  156. // Unroll the stack to get each suspension.
  157. while (suspension instanceof Sk.misceval.Suspension) {
  158. parent = suspension;
  159. this.suspension_stack.push(parent);
  160. this.current_suspension += 1;
  161. suspension = suspension.child;
  162. }
  163. suspension = parent;
  164. this.print_suspension_info(suspension);
  165. };
  166. Sk.Debugger.prototype.add_breakpoint = function(filename, lineno, colno, temporary) {
  167. var key = this.generate_breakpoint_key(filename, lineno, colno);
  168. this.dbg_breakpoints[key] = new Sk.Breakpoint(filename, lineno, colno);
  169. if (temporary) {
  170. this.tmp_breakpoints[key] = true;
  171. }
  172. };
  173. Sk.Debugger.prototype.suspension_handler = function(susp) {
  174. return new Promise(function(resolve, reject) {
  175. try {
  176. resolve(susp.resume());
  177. } catch(e) {
  178. reject(e);
  179. }
  180. });
  181. };
  182. Sk.Debugger.prototype.resume = function() {
  183. // Reset the suspension stack to the topmost
  184. this.current_suspension = this.suspension_stack.length - 1;
  185. if (this.suspension_stack.length === 0) {
  186. this.print("No running program");
  187. } else {
  188. var promise = this.suspension_handler(this.get_active_suspension());
  189. promise.then(this.success.bind(this), this.error.bind(this));
  190. }
  191. };
  192. Sk.Debugger.prototype.pop_suspension_stack = function() {
  193. this.suspension_stack.pop();
  194. this.current_suspension -= 1;
  195. };
  196. Sk.Debugger.prototype.success = function(r) {
  197. if (r instanceof Sk.misceval.Suspension) {
  198. this.set_suspension(r);
  199. } else {
  200. if (this.suspension_stack.length > 0) {
  201. // Current suspension needs to be popped of the stack
  202. this.pop_suspension_stack();
  203. if (this.suspension_stack.length === 0) {
  204. this.print("Program execution complete");
  205. return;
  206. }
  207. var parent_suspension = this.get_active_suspension();
  208. // The child has completed the execution. So override the child's resume
  209. // so we can continue the execution.
  210. parent_suspension.child.resume = function() {};
  211. this.resume();
  212. } else {
  213. this.print("Program execution complete");
  214. }
  215. }
  216. };
  217. Sk.Debugger.prototype.error = function(e) {
  218. this.print("Traceback (most recent call last):");
  219. for (var idx = 0; idx < e.traceback.length; ++idx) {
  220. this.print(" File \"" + e.traceback[idx].filename + "\", line " + e.traceback[idx].lineno + ", in <module>");
  221. var code = this.get_source_line(e.traceback[idx].lineno - 1);
  222. code = code.trim();
  223. code = " " + code;
  224. this.print(code);
  225. }
  226. var err_ty = e.constructor.tp$name;
  227. for (idx = 0; idx < e.args.v.length; ++idx) {
  228. this.print(err_ty + ": " + e.args.v[idx].v);
  229. }
  230. };
  231. Sk.Debugger.prototype.asyncToPromise = function(suspendablefn, suspHandlers, debugger_obj) {
  232. return new Promise(function(resolve, reject) {
  233. try {
  234. var r = suspendablefn();
  235. (function handleResponse (r) {
  236. try {
  237. while (r instanceof Sk.misceval.Suspension) {
  238. debugger_obj.set_suspension(r);
  239. return;
  240. }
  241. resolve(r);
  242. } catch(e) {
  243. reject(e);
  244. }
  245. })(r);
  246. } catch (e) {
  247. reject(e);
  248. }
  249. });
  250. };
  251. Sk.Debugger.prototype.execute = function(suspendablefn, suspHandlers) {
  252. var r = suspendablefn();
  253. if (r instanceof Sk.misceval.Suspension) {
  254. this.suspensions.concat(r);
  255. this.eval_callback(r);
  256. }
  257. };
  258. goog.exportSymbol("Sk.Debugger", Sk.Debugger);