/** * Debugger support for skulpt module */ var Sk = Sk || {}; //jshint ignore:line function hasOwnProperty(obj, prop) { var proto = obj.constructor.prototype; return (prop in obj) && (!(prop in proto) || proto[prop] !== obj[prop]); } Sk.Breakpoint = function(filename, lineno, colno) { this.filename = filename; this.lineno = lineno; this.colno = colno; this.enabled = true; this.ignore_count = 0; }; Sk.Debugger = function(filename, output_callback) { this.dbg_breakpoints = {}; this.tmp_breakpoints = {}; this.suspension_stack = []; this.current_suspension = -1; this.eval_callback = null; this.suspension = null; this.output_callback = output_callback; this.step_mode = false; this.filename = filename; }; Sk.Debugger.prototype.print = function(txt) { if (this.output_callback != null) { this.output_callback.print(txt); } }; Sk.Debugger.prototype.get_source_line = function(lineno) { if (this.output_callback != null) { return this.output_callback.get_source_line(lineno); } return ""; }; Sk.Debugger.prototype.move_up_the_stack = function() { this.current_suspension = Math.min(this.current_suspension + 1, this.suspension_stack.length - 1); }; Sk.Debugger.prototype.move_down_the_stack = function() { this.current_suspension = Math.max(this.current_suspension - 1, 0); }; Sk.Debugger.prototype.enable_step_mode = function() { this.step_mode = true; }; Sk.Debugger.prototype.disable_step_mode = function() { this.step_mode = false; }; Sk.Debugger.prototype.get_suspension_stack = function() { return this.suspension_stack; }; Sk.Debugger.prototype.get_active_suspension = function() { if (this.suspension_stack.length === 0) { return null; } return this.suspension_stack[this.current_suspension]; }; Sk.Debugger.prototype.generate_breakpoint_key = function(filename, lineno, colno) { var key = filename + "-" + lineno; return key; }; Sk.Debugger.prototype.check_breakpoints = function(filename, lineno, colno, globals, locals) { // If Step mode is enabled then ignore breakpoints since we will just break // at every line. if (this.step_mode === true) { return true; } var key = this.generate_breakpoint_key(filename, lineno, colno); if (hasOwnProperty(this.dbg_breakpoints, key) && this.dbg_breakpoints[key].enabled === true) { var bp = null; if (hasOwnProperty(this.tmp_breakpoints, key)) { delete this.dbg_breakpoints[key]; delete this.tmp_breakpoints[key]; return true; } this.dbg_breakpoints[key].ignore_count -= 1; this.dbg_breakpoints[key].ignore_count = Math.max(0, this.dbg_breakpoints[key].ignore_count); bp = this.dbg_breakpoints[key]; if (bp.ignore_count === 0) { return true; } else { return false; } } return false; }; Sk.Debugger.prototype.get_breakpoints_list = function() { return this.dbg_breakpoints; }; Sk.Debugger.prototype.disable_breakpoint = function(filename, lineno, colno) { var key = this.generate_breakpoint_key(filename, lineno, colno); if (hasOwnProperty(this.dbg_breakpoints, key)) { this.dbg_breakpoints[key].enabled = false; } }; Sk.Debugger.prototype.enable_breakpoint = function(filename, lineno, colno) { var key = this.generate_breakpoint_key(filename, lineno, colno); if (hasOwnProperty(this.dbg_breakpoints, key)) { this.dbg_breakpoints[key].enabled = true; } }; Sk.Debugger.prototype.clear_breakpoint = function(filename, lineno, colno) { var key = this.generate_breakpoint_key(filename, lineno, colno); if (hasOwnProperty(this.dbg_breakpoints, key)) { delete this.dbg_breakpoints[key]; return null; } else { return "Invalid breakpoint specified: " + filename + " line: " + lineno; } }; Sk.Debugger.prototype.clear_all_breakpoints = function() { this.dbg_breakpoints = {}; this.tmp_breakpoints = {}; }; Sk.Debugger.prototype.set_ignore_count = function(filename, lineno, colno, count) { var key = this.generate_breakpoint_key(filename, lineno, colno); if (hasOwnProperty(this.dbg_breakpoints, key)) { var bp = this.dbg_breakpoints[key]; bp.ignore_count = count; } }; Sk.Debugger.prototype.set_condition = function(filename, lineno, colno, lhs, cond, rhs) { var key = this.generate_breakpoint_key(filename, lineno, colno); var bp; if (hasOwnProperty(this.dbg_breakpoints, key)) { // Set a new condition bp = this.dbg_breakpoints[key]; } else { bp = new Sk.Breakpoint(filename, lineno, colno); } bp.condition = new Sk.Condition(lhs, cond, rhs); this.dbg_breakpoints[key] = bp; }; Sk.Debugger.prototype.print_suspension_info = function(suspension) { var filename = suspension.filename; var lineno = suspension.lineno; var colno = suspension.colno; this.print("Hit Breakpoint at <" + filename + "> at line: " + lineno + " column: " + colno + "\n"); this.print("----------------------------------------------------------------------------------\n"); this.print(" ==> " + this.get_source_line(lineno - 1) + "\n"); this.print("----------------------------------------------------------------------------------\n"); }; Sk.Debugger.prototype.set_suspension = function(suspension) { var parent = null; if (!hasOwnProperty(suspension, "filename") && suspension.child instanceof Sk.misceval.Suspension) { suspension = suspension.child; } // Pop the last suspension of the stack if there is more than 0 if (this.suspension_stack.length > 0) { this.suspension_stack.pop(); this.current_suspension -= 1; } // Unroll the stack to get each suspension. while (suspension instanceof Sk.misceval.Suspension) { parent = suspension; this.suspension_stack.push(parent); this.current_suspension += 1; suspension = suspension.child; } suspension = parent; this.print_suspension_info(suspension); }; Sk.Debugger.prototype.add_breakpoint = function(filename, lineno, colno, temporary) { var key = this.generate_breakpoint_key(filename, lineno, colno); this.dbg_breakpoints[key] = new Sk.Breakpoint(filename, lineno, colno); if (temporary) { this.tmp_breakpoints[key] = true; } }; Sk.Debugger.prototype.suspension_handler = function(susp) { return new Promise(function(resolve, reject) { try { resolve(susp.resume()); } catch(e) { reject(e); } }); }; Sk.Debugger.prototype.resume = function() { // Reset the suspension stack to the topmost this.current_suspension = this.suspension_stack.length - 1; if (this.suspension_stack.length === 0) { this.print("No running program"); } else { var promise = this.suspension_handler(this.get_active_suspension()); promise.then(this.success.bind(this), this.error.bind(this)); } }; Sk.Debugger.prototype.pop_suspension_stack = function() { this.suspension_stack.pop(); this.current_suspension -= 1; }; Sk.Debugger.prototype.success = function(r) { if (r instanceof Sk.misceval.Suspension) { this.set_suspension(r); } else { if (this.suspension_stack.length > 0) { // Current suspension needs to be popped of the stack this.pop_suspension_stack(); if (this.suspension_stack.length === 0) { this.print("Program execution complete"); return; } var parent_suspension = this.get_active_suspension(); // The child has completed the execution. So override the child's resume // so we can continue the execution. parent_suspension.child.resume = function() {}; this.resume(); } else { this.print("Program execution complete"); } } }; Sk.Debugger.prototype.error = function(e) { this.print("Traceback (most recent call last):"); for (var idx = 0; idx < e.traceback.length; ++idx) { this.print(" File \"" + e.traceback[idx].filename + "\", line " + e.traceback[idx].lineno + ", in "); var code = this.get_source_line(e.traceback[idx].lineno - 1); code = code.trim(); code = " " + code; this.print(code); } var err_ty = e.constructor.tp$name; for (idx = 0; idx < e.args.v.length; ++idx) { this.print(err_ty + ": " + e.args.v[idx].v); } }; Sk.Debugger.prototype.asyncToPromise = function(suspendablefn, suspHandlers, debugger_obj) { return new Promise(function(resolve, reject) { try { var r = suspendablefn(); (function handleResponse (r) { try { while (r instanceof Sk.misceval.Suspension) { debugger_obj.set_suspension(r); return; } resolve(r); } catch(e) { reject(e); } })(r); } catch (e) { reject(e); } }); }; Sk.Debugger.prototype.execute = function(suspendablefn, suspHandlers) { var r = suspendablefn(); if (r instanceof Sk.misceval.Suspension) { this.suspensions.concat(r); this.eval_callback(r); } }; goog.exportSymbol("Sk.Debugger", Sk.Debugger);