author | wenzelm |
Mon, 24 Jan 2022 21:29:37 +0100 | |
changeset 74995 | 68ffcf5cc94b |
parent 73559 | 22b5ecb53dd9 |
child 78648 | 852ec09aef13 |
permissions | -rw-r--r-- |
60749 | 1 |
(* Title: Pure/Tools/debugger.ML |
2 |
Author: Makarius |
|
3 |
||
4 |
Interactive debugger for Isabelle/ML. |
|
5 |
*) |
|
6 |
||
60830 | 7 |
signature DEBUGGER = |
8 |
sig |
|
60834 | 9 |
val writeln_message: string -> unit |
10 |
val warning_message: string -> unit |
|
11 |
val error_message: string -> unit |
|
60830 | 12 |
end; |
13 |
||
14 |
structure Debugger: DEBUGGER = |
|
60765 | 15 |
struct |
16 |
||
60869 | 17 |
(** global state **) |
18 |
||
19 |
(* output messages *) |
|
60830 | 20 |
|
60834 | 21 |
fun output_message kind msg = |
60864 | 22 |
if msg = "" then () |
23 |
else |
|
24 |
Output.protocol_message |
|
71694 | 25 |
(Markup.debugger_output (Isabelle_Thread.get_name ())) |
73559 | 26 |
[[XML.Text (Markup.markup (kind, Markup.serial_properties (serial ())) msg)]]; |
60834 | 27 |
|
28 |
val writeln_message = output_message Markup.writelnN; |
|
29 |
val warning_message = output_message Markup.warningN; |
|
30 |
val error_message = output_message Markup.errorN; |
|
60830 | 31 |
|
60856 | 32 |
fun error_wrapper e = e () |
33 |
handle exn => |
|
62505 | 34 |
if Exn.is_interrupt exn then Exn.reraise exn |
60856 | 35 |
else error_message (Runtime.exn_message exn); |
36 |
||
60830 | 37 |
|
60888
35d85fd89fc1
eliminated cancel operation: disrupts normal evaluation of thread;
wenzelm
parents:
60887
diff
changeset
|
38 |
(* thread input *) |
60765 | 39 |
|
60888
35d85fd89fc1
eliminated cancel operation: disrupts normal evaluation of thread;
wenzelm
parents:
60887
diff
changeset
|
40 |
val thread_input = |
60891 | 41 |
Synchronized.var "Debugger.state" (NONE: string list Queue.T Symtab.table option); |
42 |
||
43 |
fun init_input () = Synchronized.change thread_input (K (SOME Symtab.empty)); |
|
44 |
fun exit_input () = Synchronized.change thread_input (K NONE); |
|
60765 | 45 |
|
60842 | 46 |
fun input thread_name msg = |
60891 | 47 |
if null msg then error "Empty input" |
48 |
else |
|
49 |
Synchronized.change thread_input |
|
50 |
(Option.map (Symtab.map_default (thread_name, Queue.empty) (Queue.enqueue msg))); |
|
60887 | 51 |
|
60842 | 52 |
fun get_input thread_name = |
60891 | 53 |
Synchronized.guarded_access thread_input |
54 |
(fn NONE => SOME ([], NONE) |
|
55 |
| SOME input => |
|
56 |
(case Symtab.lookup input thread_name of |
|
57 |
NONE => NONE |
|
58 |
| SOME queue => |
|
59 |
let |
|
60 |
val (msg, queue') = Queue.dequeue queue; |
|
61 |
val input' = |
|
62 |
if Queue.is_empty queue' then Symtab.delete_safe thread_name input |
|
63 |
else Symtab.update (thread_name, queue') input; |
|
64 |
in SOME (msg, SOME input') end)); |
|
60765 | 65 |
|
66 |
||
60932
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
67 |
(* global break *) |
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
68 |
|
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
69 |
local |
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
70 |
val break = Synchronized.var "Debugger.break" false; |
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
71 |
in |
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
72 |
|
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
73 |
fun is_break () = Synchronized.value break; |
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
74 |
fun set_break b = Synchronized.change break (K b); |
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
75 |
|
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
76 |
end; |
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
77 |
|
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
78 |
|
60869 | 79 |
|
80 |
(** thread state **) |
|
81 |
||
82 |
(* stack frame during debugging *) |
|
60856 | 83 |
|
62937 | 84 |
val debugging_var = Thread_Data.var () : PolyML.DebuggerInterface.debugState list Thread_Data.var; |
60765 | 85 |
|
62889 | 86 |
fun get_debugging () = the_default [] (Thread_Data.get debugging_var); |
60856 | 87 |
val is_debugging = not o null o get_debugging; |
60765 | 88 |
|
60856 | 89 |
fun with_debugging e = |
62937 | 90 |
Thread_Data.setmp debugging_var (SOME (PolyML.DebuggerInterface.debugState (Thread.self ()))) e (); |
60749 | 91 |
|
60858 | 92 |
fun the_debug_state thread_name index = |
93 |
(case get_debugging () of |
|
94 |
[] => error ("Missing debugger information for thread " ^ quote thread_name) |
|
95 |
| states => |
|
96 |
if index < 0 orelse index >= length states then |
|
97 |
error ("Bad debugger stack index " ^ signed_string_of_int index ^ " for thread " ^ |
|
98 |
quote thread_name) |
|
99 |
else nth states index); |
|
100 |
||
101 |
||
60869 | 102 |
(* flags for single-stepping *) |
60765 | 103 |
|
60869 | 104 |
datatype stepping = Stepping of bool * int; (*stepping enabled, stack depth limit*) |
105 |
||
62889 | 106 |
val stepping_var = Thread_Data.var () : stepping Thread_Data.var; |
60869 | 107 |
|
62889 | 108 |
fun get_stepping () = the_default (Stepping (false, ~1)) (Thread_Data.get stepping_var); |
109 |
fun put_stepping stepping = Thread_Data.put stepping_var (SOME (Stepping stepping)); |
|
60869 | 110 |
|
111 |
fun is_stepping () = |
|
112 |
let |
|
62937 | 113 |
val stack = PolyML.DebuggerInterface.debugState (Thread.self ()); |
60869 | 114 |
val Stepping (stepping, depth) = get_stepping (); |
115 |
in stepping andalso (depth < 0 orelse length stack <= depth) end; |
|
116 |
||
60931 | 117 |
fun continue () = put_stepping (false, ~1); |
118 |
fun step () = put_stepping (true, ~1); |
|
119 |
fun step_over () = put_stepping (true, length (get_debugging ())); |
|
120 |
fun step_out () = put_stepping (true, length (get_debugging ()) - 1); |
|
60869 | 121 |
|
122 |
||
60935 | 123 |
|
60869 | 124 |
(** eval ML **) |
60765 | 125 |
|
60862 | 126 |
local |
127 |
||
128 |
val context_attempts = |
|
129 |
map ML_Lex.read |
|
62889 | 130 |
["Context.put_generic_context (SOME (Context.Theory ML_context))", |
131 |
"Context.put_generic_context (SOME (Context.Proof ML_context))", |
|
132 |
"Context.put_generic_context (SOME ML_context)"]; |
|
60862 | 133 |
|
68823 | 134 |
fun environment SML = if SML then ML_Env.SML else ML_Env.Isabelle; |
135 |
fun operations SML = if SML then ML_Env.SML_operations else ML_Env.Isabelle_operations; |
|
68821
877534be1930
explicit setup of operations: avoid hardwired stuff;
wenzelm
parents:
68820
diff
changeset
|
136 |
|
60862 | 137 |
fun evaluate {SML, verbose} = |
68820
2e4df245754e
clarified environment: allow "read>write" specification;
wenzelm
parents:
68816
diff
changeset
|
138 |
ML_Context.eval |
68823 | 139 |
{environment = environment SML, redirect = false, verbose = verbose, |
68820
2e4df245754e
clarified environment: allow "read>write" specification;
wenzelm
parents:
68816
diff
changeset
|
140 |
debug = SOME false, writeln = writeln_message, warning = warning_message} |
2e4df245754e
clarified environment: allow "read>write" specification;
wenzelm
parents:
68816
diff
changeset
|
141 |
Position.none; |
60862 | 142 |
|
60935 | 143 |
fun eval_setup thread_name index SML context = |
144 |
context |
|
145 |
|> Context_Position.set_visible_generic false |
|
68823 | 146 |
|> ML_Env.add_name_space (environment SML) |
62937 | 147 |
(PolyML.DebuggerInterface.debugNameSpace (the_debug_state thread_name index)); |
60935 | 148 |
|
60897 | 149 |
fun eval_context thread_name index SML toks = |
60858 | 150 |
let |
62876 | 151 |
val context = Context.the_generic_context (); |
60897 | 152 |
val context1 = |
153 |
if SML orelse forall (fn Antiquote.Text tok => ML_Lex.is_improper tok | _ => false) toks |
|
60935 | 154 |
then context |
60862 | 155 |
else |
156 |
let |
|
60935 | 157 |
val context' = context |
158 |
|> eval_setup thread_name index SML |
|
60897 | 159 |
|> ML_Context.exec (fn () => |
160 |
evaluate {SML = SML, verbose = true} (ML_Lex.read "val ML_context = " @ toks)); |
|
60862 | 161 |
fun try_exec toks = |
162 |
try (ML_Context.exec (fn () => evaluate {SML = false, verbose = false} toks)) context'; |
|
163 |
in |
|
164 |
(case get_first try_exec context_attempts of |
|
165 |
SOME context2 => context2 |
|
166 |
| NONE => error "Malformed context: expected type theory, Proof.context, Context.generic") |
|
167 |
end; |
|
60935 | 168 |
in context1 |> eval_setup thread_name index SML end; |
60897 | 169 |
|
170 |
in |
|
171 |
||
172 |
fun eval thread_name index SML txt1 txt2 = |
|
173 |
let |
|
68823 | 174 |
val (toks1, toks2) = apply2 (#read_source (operations SML) o Input.string) (txt1, txt2); |
60897 | 175 |
val context = eval_context thread_name index SML toks1; |
62889 | 176 |
in Context.setmp_generic_context (SOME context) (evaluate {SML = SML, verbose = true}) toks2 end; |
60897 | 177 |
|
178 |
fun print_vals thread_name index SML txt = |
|
179 |
let |
|
68823 | 180 |
val toks = #read_source (operations SML) (Input.string txt) |
60935 | 181 |
val context = eval_context thread_name index SML toks; |
62937 | 182 |
val space = PolyML.DebuggerInterface.debugNameSpace (the_debug_state thread_name index); |
60897 | 183 |
|
184 |
fun print x = |
|
62663 | 185 |
Pretty.from_polyml |
62934 | 186 |
(PolyML.NameSpace.Values.printWithType |
187 |
(x, FixedInt.fromInt (ML_Print_Depth.get_print_depth ()), SOME space)); |
|
60897 | 188 |
fun print_all () = |
62937 | 189 |
#allVal (PolyML.DebuggerInterface.debugLocalNameSpace (the_debug_state thread_name index)) () |
60924
610794dff23c
tuned signature, in accordance to sortBy in Scala;
wenzelm
parents:
60904
diff
changeset
|
190 |
|> sort_by #1 |> map (Pretty.item o single o print o #2) |
60897 | 191 |
|> Pretty.chunks |> Pretty.string_of |> writeln_message; |
62889 | 192 |
in Context.setmp_generic_context (SOME context) print_all () end; |
60862 | 193 |
|
194 |
end; |
|
60749 | 195 |
|
60765 | 196 |
|
60869 | 197 |
|
60891 | 198 |
(** debugger loop **) |
60857 | 199 |
|
200 |
local |
|
60765 | 201 |
|
60842 | 202 |
fun debugger_state thread_name = |
203 |
Output.protocol_message (Markup.debugger_state thread_name) |
|
73559 | 204 |
[get_debugging () |
60842 | 205 |
|> map (fn st => |
62821 | 206 |
(Position.properties_of |
62937 | 207 |
(Exn_Properties.position_of_polyml_location (PolyML.DebuggerInterface.debugLocation st)), |
208 |
PolyML.DebuggerInterface.debugFunction st)) |
|
73559 | 209 |
|> let open XML.Encode in list (pair properties string) end]; |
60842 | 210 |
|
60857 | 211 |
fun debugger_command thread_name = |
212 |
(case get_input thread_name of |
|
60931 | 213 |
[] => (continue (); false) |
214 |
| ["continue"] => (continue (); false) |
|
215 |
| ["step"] => (step (); false) |
|
216 |
| ["step_over"] => (step_over (); false) |
|
217 |
| ["step_out"] => (step_out (); false) |
|
60857 | 218 |
| ["eval", index, SML, txt1, txt2] => |
219 |
(error_wrapper (fn () => |
|
63806 | 220 |
eval thread_name (Value.parse_int index) (Value.parse_bool SML) txt1 txt2); true) |
60897 | 221 |
| ["print_vals", index, SML, txt] => |
222 |
(error_wrapper (fn () => |
|
63806 | 223 |
print_vals thread_name (Value.parse_int index) (Value.parse_bool SML) txt); true) |
60857 | 224 |
| bad => |
225 |
(Output.system_message |
|
226 |
("Debugger: bad input " ^ ML_Syntax.print_list ML_Syntax.print_string bad); true)); |
|
227 |
||
60889 | 228 |
in |
229 |
||
60842 | 230 |
fun debugger_loop thread_name = |
62923 | 231 |
Thread_Attributes.uninterruptible (fn restore_attributes => fn () => |
60885
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
wenzelm
parents:
60884
diff
changeset
|
232 |
let |
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
wenzelm
parents:
60884
diff
changeset
|
233 |
fun loop () = |
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
wenzelm
parents:
60884
diff
changeset
|
234 |
(debugger_state thread_name; if debugger_command thread_name then loop () else ()); |
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
wenzelm
parents:
60884
diff
changeset
|
235 |
val result = Exn.capture (restore_attributes with_debugging) loop; |
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
wenzelm
parents:
60884
diff
changeset
|
236 |
val _ = debugger_state thread_name; |
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
wenzelm
parents:
60884
diff
changeset
|
237 |
in Exn.release result end) (); |
60765 | 238 |
|
60857 | 239 |
end; |
240 |
||
60765 | 241 |
|
60869 | 242 |
|
243 |
(** protocol commands **) |
|
60765 | 244 |
|
245 |
val _ = |
|
73225
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
wenzelm
parents:
71694
diff
changeset
|
246 |
Protocol_Command.define "Debugger.init" |
60889 | 247 |
(fn [] => |
60891 | 248 |
(init_input (); |
62937 | 249 |
PolyML.DebuggerInterface.setOnBreakPoint |
60889 | 250 |
(SOME (fn (_, break) => |
60932
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
251 |
if not (is_debugging ()) andalso (! break orelse is_break () orelse is_stepping ()) |
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
252 |
then |
71694 | 253 |
(case try Isabelle_Thread.get_name () of |
60889 | 254 |
SOME thread_name => debugger_loop thread_name |
255 |
| NONE => ()) |
|
60891 | 256 |
else ())))); |
60889 | 257 |
|
258 |
val _ = |
|
73225
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
wenzelm
parents:
71694
diff
changeset
|
259 |
Protocol_Command.define "Debugger.exit" |
62937 | 260 |
(fn [] => (PolyML.DebuggerInterface.setOnBreakPoint NONE; exit_input ())); |
60765 | 261 |
|
262 |
val _ = |
|
73225
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
wenzelm
parents:
71694
diff
changeset
|
263 |
Protocol_Command.define "Debugger.break" |
63806 | 264 |
(fn [b] => set_break (Value.parse_bool b)); |
60932
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
265 |
|
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
wenzelm
parents:
60931
diff
changeset
|
266 |
val _ = |
73225
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
wenzelm
parents:
71694
diff
changeset
|
267 |
Protocol_Command.define "Debugger.breakpoint" |
60880
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
268 |
(fn [node_name, id0, breakpoint0, breakpoint_state0] => |
60878
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
wenzelm
parents:
60871
diff
changeset
|
269 |
let |
63806 | 270 |
val id = Value.parse_int id0; |
271 |
val breakpoint = Value.parse_int breakpoint0; |
|
272 |
val breakpoint_state = Value.parse_bool breakpoint_state0; |
|
60880
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
273 |
|
63806 | 274 |
fun err () = error ("Bad exec for command " ^ Value.print_int id); |
60880
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
275 |
in |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
276 |
(case Document.command_exec (Document.state ()) node_name id of |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
277 |
SOME (eval, _) => |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
278 |
if Command.eval_finished eval then |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
279 |
let |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
280 |
val st = Command.eval_result_state eval; |
69892 | 281 |
val ctxt = Toplevel.presentation_context st; |
60880
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
282 |
in |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
283 |
(case ML_Env.get_breakpoint (Context.Proof ctxt) breakpoint of |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
284 |
SOME (b, _) => b := breakpoint_state |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
285 |
| NONE => err ()) |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
286 |
end |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
287 |
else err () |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
288 |
| NONE => err ()) |
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
wenzelm
parents:
60878
diff
changeset
|
289 |
end); |
60878
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
wenzelm
parents:
60871
diff
changeset
|
290 |
|
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
wenzelm
parents:
60871
diff
changeset
|
291 |
val _ = |
73225
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
wenzelm
parents:
71694
diff
changeset
|
292 |
Protocol_Command.define "Debugger.input" |
60842 | 293 |
(fn thread_name :: msg => input thread_name msg); |
60765 | 294 |
|
60749 | 295 |
end; |