@@ -207,3 +207,123 @@ function next_line!(@nospecialize(recurse), frame::Frame, istoplevel::Bool=false
207
207
maybe_next_call! (recurse, frame, pc)
208
208
end
209
209
next_line! (frame:: Frame , istoplevel:: Bool = false ) = next_line! (finish_and_return!, frame, istoplevel)
210
+
211
+ """
212
+ cframe = maybe_step_through_wrapper!(recurse, frame)
213
+ cframe = maybe_step_through_wrapper!(frame)
214
+
215
+ Return the new frame of execution, potentially stepping through "wrapper" methods like those
216
+ that supply default positional arguments or handle keywords. `cframe` is the leaf frame from
217
+ which execution should start.
218
+ """
219
+ function maybe_step_through_wrapper! (@nospecialize (recurse), frame:: Frame )
220
+ code = frame. framecode
221
+ stmts, scope = code. src. code, code. scope:: Method
222
+ length (stmts) < 2 && return frame
223
+ last = stmts[end - 1 ]
224
+ isexpr (last, :(= )) && (last = last. args[2 ])
225
+ is_kw = isa (scope, Method) && startswith (String (Base. unwrap_unionall (scope. sig). parameters[1 ]. name. name), " #kw" )
226
+ if is_kw || isexpr (last, :call ) && any (x-> x== Core. SlotNumber (1 ), last. args)
227
+ # If the last expr calls #self# or passes it to an implementation method,
228
+ # this is a wrapper function that we might want to step through
229
+ while frame. pc != length (stmts)- 1
230
+ pc = next_call! (recurse, frame, false ) # since we're in a Method we're not at toplevel
231
+ end
232
+ ret = evaluate_call! (dummy_breakpoint, frame, last)
233
+ @assert isa (ret, BreakpointRef)
234
+ return maybe_step_through_wrapper! (recurse, callee (frame))
235
+ end
236
+ return frame
237
+ end
238
+ maybe_step_through_wrapper! (frame:: Frame ) = maybe_step_through_wrapper! (finish_and_return!, frame)
239
+
240
+ """
241
+ ret = maybe_reset_frame!(recurse, frame, pc, rootistoplevel)
242
+
243
+ Perform a return to the caller, or descend to the level of a breakpoint.
244
+ `pc` is the return state from the previous command (e.g., `next_call!` or similar).
245
+ `rootistoplevel` should be true if the root frame is top-level.
246
+
247
+ `ret` will be `nothing` if we have just completed a top-level frame. Otherwise,
248
+
249
+ cframe, cpc = ret
250
+
251
+ where `cframe` is the frame from which execution should continue and `cpc` is the state
252
+ of `cframe` (the program counter, a `BreakpointRef`, or `nothing`).
253
+ """
254
+ function maybe_reset_frame! (@nospecialize (recurse), frame:: Frame , @nospecialize (pc), rootistoplevel:: Bool )
255
+ isa (pc, BreakpointRef) && return leaf (frame), pc
256
+ if pc === nothing
257
+ val = get_return (frame)
258
+ recycle (frame)
259
+ frame = caller (frame)
260
+ frame === nothing && return nothing
261
+ frame. callee = nothing
262
+ maybe_assign! (frame, val)
263
+ frame. pc += 1
264
+ pc = maybe_next_call! (recurse, frame, rootistoplevel && frame. caller=== nothing )
265
+ return maybe_reset_frame! (recurse, frame, pc, rootistoplevel)
266
+ end
267
+ return frame, pc
268
+ end
269
+
270
+ """
271
+ ret = debug_command(recurse, frame, cmd, rootistoplevel=false)
272
+ ret = debug_command(frame, cmd, rootistoplevel=false)
273
+
274
+ Perform one "debugger" command. `cmd` should be one of:
275
+
276
+ - "n": advance to the next line
277
+ - "s": step into the next call
278
+ - "c": continue execution until termination or reaching a breakpoint
279
+ - "finish": finish the current frame and return to the parent
280
+
281
+ or one of the 'advanced' commands
282
+
283
+ - "nc": step forward to the next call
284
+ - "se": execute a single statement
285
+ - "si": execute a single statement, stepping in if it's a call
286
+ - "sg": step into the generator of a generated function
287
+
288
+ `rootistoplevel` and `ret` are as described for [`JuliaInterpreter.maybe_reset_frame!`](@ref).
289
+ Unlike other commands, the default setting for `recurse` is `Compiled()`.
290
+ """
291
+ function debug_command (@nospecialize (recurse), frame:: Frame , cmd:: AbstractString , rootistoplevel:: Bool = false )
292
+ # ::Union{Val{:nc},Val{:n},Val{:se}},
293
+ # Need try/catch
294
+ istoplevel = rootistoplevel && frame. caller === nothing
295
+ cmd == " nc" && return maybe_reset_frame! (recurse, frame, next_call! (recurse, frame, istoplevel), rootistoplevel)
296
+ cmd == " n" && return maybe_reset_frame! (recurse, frame, next_line! (recurse, frame, istoplevel), rootistoplevel)
297
+ if cmd == " si"
298
+ stmt = pc_expr (frame)
299
+ cmd = is_call (stmt) ? " s" : " se"
300
+ end
301
+ cmd == " se" && return maybe_reset_frame! (recurse, frame, step_expr! (recurse, frame, istoplevel), rootistoplevel)
302
+ enter_generated = false
303
+ if cmd == " sg"
304
+ enter_generated = true
305
+ cmd = " s"
306
+ end
307
+ if cmd == " s"
308
+ pc = maybe_next_call! (recurse, frame, istoplevel)
309
+ (isa (pc, BreakpointRef) || pc === nothing ) && return maybe_reset_frame! (recurse, frame, pc, rootistoplevel)
310
+ stmt0 = stmt = pc_expr (frame, pc)
311
+ if isexpr (stmt, :(= ))
312
+ stmt = stmt. args[2 ]
313
+ end
314
+ ret = evaluate_call! (dummy_breakpoint, frame, stmt; enter_generated= enter_generated)
315
+ isa (ret, BreakpointRef) && return maybe_reset_frame! (recurse, frame, ret, rootistoplevel)
316
+ maybe_assign! (frame, stmt0, ret)
317
+ frame. pc = ret + 1
318
+ return frame, frame. pc
319
+ end
320
+ if cmd == " c"
321
+ r = root (frame)
322
+ ret = finish_stack! (recurse, r, rootistoplevel)
323
+ return isa (ret, BreakpointRef) ? (leaf (r), ret) : nothing
324
+ end
325
+ cmd == " finish" && return maybe_reset_frame! (recurse, frame, finish! (recurse, frame, istoplevel), rootistoplevel)
326
+ throw (ArgumentError (" command $cmd not recognized" ))
327
+ end
328
+ debug_command (frame:: Frame , cmd:: AbstractString , rootistoplevel:: Bool = false ) =
329
+ debug_command (Compiled (), frame, cmd, rootistoplevel)
0 commit comments