| author | wenzelm | 
| Tue, 09 Jan 2018 15:40:12 +0100 | |
| changeset 67386 | 998e01d6f8fd | 
| parent 67381 | 146757999c8d | 
| child 68816 | 5a53724fe247 | 
| 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 | |
| 61556 | 25 | (Markup.debugger_output (Standard_Thread.the_name ())) | 
| 60864 | 26 | [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: 
60887diff
changeset | 38 | (* thread input *) | 
| 60765 | 39 | |
| 60888 
35d85fd89fc1
eliminated cancel operation: disrupts normal evaluation of thread;
 wenzelm parents: 
60887diff
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: 
60931diff
changeset | 67 | (* global break *) | 
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 68 | |
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 69 | local | 
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
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: 
60931diff
changeset | 71 | in | 
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 72 | |
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
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: 
60931diff
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: 
60931diff
changeset | 75 | |
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 76 | end; | 
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 77 | |
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
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 | |
| 134 | fun evaluate {SML, verbose} =
 | |
| 135 | ML_Context.eval | |
| 62902 
3c0f53eae166
more conventional theory syntax for ML bootstrap, with 'ML_file' instead of 'use';
 wenzelm parents: 
62891diff
changeset | 136 |     {SML = SML, exchange = false, redirect = false, verbose = verbose,
 | 
| 60956 | 137 | debug = SOME false, writeln = writeln_message, warning = warning_message} | 
| 60862 | 138 | Position.none; | 
| 139 | ||
| 60935 | 140 | fun eval_setup thread_name index SML context = | 
| 141 | context | |
| 142 | |> Context_Position.set_visible_generic false | |
| 143 |   |> ML_Env.add_name_space {SML = SML}
 | |
| 62937 | 144 | (PolyML.DebuggerInterface.debugNameSpace (the_debug_state thread_name index)); | 
| 60935 | 145 | |
| 60897 | 146 | fun eval_context thread_name index SML toks = | 
| 60858 | 147 | let | 
| 62876 | 148 | val context = Context.the_generic_context (); | 
| 60897 | 149 | val context1 = | 
| 150 | if SML orelse forall (fn Antiquote.Text tok => ML_Lex.is_improper tok | _ => false) toks | |
| 60935 | 151 | then context | 
| 60862 | 152 | else | 
| 153 | let | |
| 60935 | 154 | val context' = context | 
| 155 | |> eval_setup thread_name index SML | |
| 60897 | 156 | |> ML_Context.exec (fn () => | 
| 157 |                 evaluate {SML = SML, verbose = true} (ML_Lex.read "val ML_context = " @ toks));
 | |
| 60862 | 158 | fun try_exec toks = | 
| 159 |             try (ML_Context.exec (fn () => evaluate {SML = false, verbose = false} toks)) context';
 | |
| 160 | in | |
| 161 | (case get_first try_exec context_attempts of | |
| 162 | SOME context2 => context2 | |
| 163 | | NONE => error "Malformed context: expected type theory, Proof.context, Context.generic") | |
| 164 | end; | |
| 60935 | 165 | in context1 |> eval_setup thread_name index SML end; | 
| 60897 | 166 | |
| 167 | in | |
| 168 | ||
| 169 | fun eval thread_name index SML txt1 txt2 = | |
| 170 | let | |
| 171 | val (toks1, toks2) = apply2 (ML_Lex.read_source SML o Input.string) (txt1, txt2); | |
| 172 | val context = eval_context thread_name index SML toks1; | |
| 62889 | 173 |   in Context.setmp_generic_context (SOME context) (evaluate {SML = SML, verbose = true}) toks2 end;
 | 
| 60897 | 174 | |
| 175 | fun print_vals thread_name index SML txt = | |
| 176 | let | |
| 60935 | 177 | val toks = ML_Lex.read_source SML (Input.string txt) | 
| 178 | val context = eval_context thread_name index SML toks; | |
| 62937 | 179 | val space = PolyML.DebuggerInterface.debugNameSpace (the_debug_state thread_name index); | 
| 60897 | 180 | |
| 181 | fun print x = | |
| 62663 | 182 | Pretty.from_polyml | 
| 62934 | 183 | (PolyML.NameSpace.Values.printWithType | 
| 184 | (x, FixedInt.fromInt (ML_Print_Depth.get_print_depth ()), SOME space)); | |
| 60897 | 185 | fun print_all () = | 
| 62937 | 186 | #allVal (PolyML.DebuggerInterface.debugLocalNameSpace (the_debug_state thread_name index)) () | 
| 60924 
610794dff23c
tuned signature, in accordance to sortBy in Scala;
 wenzelm parents: 
60904diff
changeset | 187 | |> sort_by #1 |> map (Pretty.item o single o print o #2) | 
| 60897 | 188 | |> Pretty.chunks |> Pretty.string_of |> writeln_message; | 
| 62889 | 189 | in Context.setmp_generic_context (SOME context) print_all () end; | 
| 60862 | 190 | |
| 191 | end; | |
| 60749 | 192 | |
| 60765 | 193 | |
| 60869 | 194 | |
| 60891 | 195 | (** debugger loop **) | 
| 60857 | 196 | |
| 197 | local | |
| 60765 | 198 | |
| 60842 | 199 | fun debugger_state thread_name = | 
| 200 | Output.protocol_message (Markup.debugger_state thread_name) | |
| 60856 | 201 | [get_debugging () | 
| 60842 | 202 | |> map (fn st => | 
| 62821 | 203 | (Position.properties_of | 
| 62937 | 204 | (Exn_Properties.position_of_polyml_location (PolyML.DebuggerInterface.debugLocation st)), | 
| 205 | PolyML.DebuggerInterface.debugFunction st)) | |
| 60842 | 206 | |> let open XML.Encode in list (pair properties string) end | 
| 207 | |> YXML.string_of_body]; | |
| 208 | ||
| 60857 | 209 | fun debugger_command thread_name = | 
| 210 | (case get_input thread_name of | |
| 60931 | 211 | [] => (continue (); false) | 
| 212 | | ["continue"] => (continue (); false) | |
| 213 | | ["step"] => (step (); false) | |
| 214 | | ["step_over"] => (step_over (); false) | |
| 215 | | ["step_out"] => (step_out (); false) | |
| 60857 | 216 | | ["eval", index, SML, txt1, txt2] => | 
| 217 | (error_wrapper (fn () => | |
| 63806 | 218 | eval thread_name (Value.parse_int index) (Value.parse_bool SML) txt1 txt2); true) | 
| 60897 | 219 | | ["print_vals", index, SML, txt] => | 
| 220 | (error_wrapper (fn () => | |
| 63806 | 221 | print_vals thread_name (Value.parse_int index) (Value.parse_bool SML) txt); true) | 
| 60857 | 222 | | bad => | 
| 223 | (Output.system_message | |
| 224 |         ("Debugger: bad input " ^ ML_Syntax.print_list ML_Syntax.print_string bad); true));
 | |
| 225 | ||
| 60889 | 226 | in | 
| 227 | ||
| 60842 | 228 | fun debugger_loop thread_name = | 
| 62923 | 229 | Thread_Attributes.uninterruptible (fn restore_attributes => fn () => | 
| 60885 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 230 | let | 
| 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 231 | fun loop () = | 
| 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 232 | (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: 
60884diff
changeset | 233 | val result = Exn.capture (restore_attributes with_debugging) loop; | 
| 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 234 | val _ = debugger_state thread_name; | 
| 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 235 | in Exn.release result end) (); | 
| 60765 | 236 | |
| 60857 | 237 | end; | 
| 238 | ||
| 60765 | 239 | |
| 60869 | 240 | |
| 241 | (** protocol commands **) | |
| 60765 | 242 | |
| 243 | val _ = | |
| 244 | Isabelle_Process.protocol_command "Debugger.init" | |
| 60889 | 245 | (fn [] => | 
| 60891 | 246 | (init_input (); | 
| 62937 | 247 | PolyML.DebuggerInterface.setOnBreakPoint | 
| 60889 | 248 | (SOME (fn (_, break) => | 
| 60932 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 249 | 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: 
60931diff
changeset | 250 | then | 
| 61556 | 251 | (case Standard_Thread.get_name () of | 
| 60889 | 252 | SOME thread_name => debugger_loop thread_name | 
| 253 | | NONE => ()) | |
| 60891 | 254 | else ())))); | 
| 60889 | 255 | |
| 256 | val _ = | |
| 257 | Isabelle_Process.protocol_command "Debugger.exit" | |
| 62937 | 258 | (fn [] => (PolyML.DebuggerInterface.setOnBreakPoint NONE; exit_input ())); | 
| 60765 | 259 | |
| 260 | val _ = | |
| 60932 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 261 | Isabelle_Process.protocol_command "Debugger.break" | 
| 63806 | 262 | (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: 
60931diff
changeset | 263 | |
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 264 | val _ = | 
| 60878 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 wenzelm parents: 
60871diff
changeset | 265 | Isabelle_Process.protocol_command "Debugger.breakpoint" | 
| 60880 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 266 | (fn [node_name, id0, breakpoint0, breakpoint_state0] => | 
| 60878 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 wenzelm parents: 
60871diff
changeset | 267 | let | 
| 63806 | 268 | val id = Value.parse_int id0; | 
| 269 | val breakpoint = Value.parse_int breakpoint0; | |
| 270 | 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: 
60878diff
changeset | 271 | |
| 63806 | 272 |         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: 
60878diff
changeset | 273 | in | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 274 | (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: 
60878diff
changeset | 275 | SOME (eval, _) => | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 276 | if Command.eval_finished eval then | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 277 | let | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 278 | val st = Command.eval_result_state eval; | 
| 67381 | 279 | 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: 
60878diff
changeset | 280 | handle Toplevel.UNDEF => err (); | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 281 | in | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 282 | (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: 
60878diff
changeset | 283 | SOME (b, _) => b := breakpoint_state | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 284 | | NONE => err ()) | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 285 | end | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 286 | else err () | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 287 | | NONE => err ()) | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 288 | end); | 
| 60878 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 wenzelm parents: 
60871diff
changeset | 289 | |
| 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 wenzelm parents: 
60871diff
changeset | 290 | val _ = | 
| 60765 | 291 | Isabelle_Process.protocol_command "Debugger.input" | 
| 60842 | 292 | (fn thread_name :: msg => input thread_name msg); | 
| 60765 | 293 | |
| 60749 | 294 | end; |