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