| author | wenzelm | 
| Tue, 22 Oct 2024 12:45:38 +0200 | |
| changeset 81229 | e18600daa904 | 
| parent 80809 | 4a64fc4d1cde | 
| 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 | |
| 78648 | 25 | (Markup.debugger_output (Isabelle_Thread.print (Isabelle_Thread.self ()))) | 
| 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 | |
| 78725 
3c02ad5a1586
clarified treatment of exceptions: avoid catch-all handlers;
 wenzelm parents: 
78720diff
changeset | 32 | fun error_wrapper e = | 
| 
3c02ad5a1586
clarified treatment of exceptions: avoid catch-all handlers;
 wenzelm parents: 
78720diff
changeset | 33 | (case Exn.result e () of | 
| 
3c02ad5a1586
clarified treatment of exceptions: avoid catch-all handlers;
 wenzelm parents: 
78720diff
changeset | 34 | Exn.Res res => res | 
| 
3c02ad5a1586
clarified treatment of exceptions: avoid catch-all handlers;
 wenzelm parents: 
78720diff
changeset | 35 | | Exn.Exn exn => error_message (Runtime.exn_message exn)); | 
| 60856 | 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 = | 
| 78650 
47d0c333d155
clarified signature: retain original Poly/ML names Thread.Thread, Thread.Mutex, Thread.ConditionVar and de-emphasize them for Isabelle/ML;
 wenzelm parents: 
78648diff
changeset | 90 | Thread_Data.setmp debugging_var | 
| 
47d0c333d155
clarified signature: retain original Poly/ML names Thread.Thread, Thread.Mutex, Thread.ConditionVar and de-emphasize them for Isabelle/ML;
 wenzelm parents: 
78648diff
changeset | 91 | (SOME (PolyML.DebuggerInterface.debugState (Thread.Thread.self ()))) e (); | 
| 60749 | 92 | |
| 60858 | 93 | fun the_debug_state thread_name index = | 
| 94 | (case get_debugging () of | |
| 95 |     [] => error ("Missing debugger information for thread " ^ quote thread_name)
 | |
| 96 | | states => | |
| 97 | if index < 0 orelse index >= length states then | |
| 98 |         error ("Bad debugger stack index " ^ signed_string_of_int index ^ " for thread " ^
 | |
| 99 | quote thread_name) | |
| 100 | else nth states index); | |
| 101 | ||
| 102 | ||
| 60869 | 103 | (* flags for single-stepping *) | 
| 60765 | 104 | |
| 60869 | 105 | datatype stepping = Stepping of bool * int; (*stepping enabled, stack depth limit*) | 
| 106 | ||
| 62889 | 107 | val stepping_var = Thread_Data.var () : stepping Thread_Data.var; | 
| 60869 | 108 | |
| 62889 | 109 | fun get_stepping () = the_default (Stepping (false, ~1)) (Thread_Data.get stepping_var); | 
| 110 | fun put_stepping stepping = Thread_Data.put stepping_var (SOME (Stepping stepping)); | |
| 60869 | 111 | |
| 112 | fun is_stepping () = | |
| 113 | let | |
| 78650 
47d0c333d155
clarified signature: retain original Poly/ML names Thread.Thread, Thread.Mutex, Thread.ConditionVar and de-emphasize them for Isabelle/ML;
 wenzelm parents: 
78648diff
changeset | 114 | val stack = PolyML.DebuggerInterface.debugState (Thread.Thread.self ()); | 
| 60869 | 115 | val Stepping (stepping, depth) = get_stepping (); | 
| 116 | in stepping andalso (depth < 0 orelse length stack <= depth) end; | |
| 117 | ||
| 60931 | 118 | fun continue () = put_stepping (false, ~1); | 
| 119 | fun step () = put_stepping (true, ~1); | |
| 120 | fun step_over () = put_stepping (true, length (get_debugging ())); | |
| 121 | fun step_out () = put_stepping (true, length (get_debugging ()) - 1); | |
| 60869 | 122 | |
| 123 | ||
| 60935 | 124 | |
| 60869 | 125 | (** eval ML **) | 
| 60765 | 126 | |
| 60862 | 127 | local | 
| 128 | ||
| 129 | val context_attempts = | |
| 130 | map ML_Lex.read | |
| 62889 | 131 | ["Context.put_generic_context (SOME (Context.Theory ML_context))", | 
| 132 | "Context.put_generic_context (SOME (Context.Proof ML_context))", | |
| 133 | "Context.put_generic_context (SOME ML_context)"]; | |
| 60862 | 134 | |
| 68823 | 135 | fun environment SML = if SML then ML_Env.SML else ML_Env.Isabelle; | 
| 136 | 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: 
68820diff
changeset | 137 | |
| 60862 | 138 | fun evaluate {SML, verbose} =
 | 
| 68820 
2e4df245754e
clarified environment: allow "read>write" specification;
 wenzelm parents: 
68816diff
changeset | 139 | ML_Context.eval | 
| 78728 
72631efa3821
explicitly reject 'handle' with catch-all patterns;
 wenzelm parents: 
78725diff
changeset | 140 |     {environment = environment SML, redirect = false, verbose = verbose, catch_all = SML,
 | 
| 68820 
2e4df245754e
clarified environment: allow "read>write" specification;
 wenzelm parents: 
68816diff
changeset | 141 | debug = SOME false, writeln = writeln_message, warning = warning_message} | 
| 
2e4df245754e
clarified environment: allow "read>write" specification;
 wenzelm parents: 
68816diff
changeset | 142 | Position.none; | 
| 60862 | 143 | |
| 60935 | 144 | fun eval_setup thread_name index SML context = | 
| 145 | context | |
| 146 | |> Context_Position.set_visible_generic false | |
| 68823 | 147 | |> ML_Env.add_name_space (environment SML) | 
| 62937 | 148 | (PolyML.DebuggerInterface.debugNameSpace (the_debug_state thread_name index)); | 
| 60935 | 149 | |
| 60897 | 150 | fun eval_context thread_name index SML toks = | 
| 60858 | 151 | let | 
| 62876 | 152 | val context = Context.the_generic_context (); | 
| 60897 | 153 | val context1 = | 
| 154 | if SML orelse forall (fn Antiquote.Text tok => ML_Lex.is_improper tok | _ => false) toks | |
| 60935 | 155 | then context | 
| 60862 | 156 | else | 
| 157 | let | |
| 60935 | 158 | val context' = context | 
| 159 | |> eval_setup thread_name index SML | |
| 60897 | 160 | |> ML_Context.exec (fn () => | 
| 161 |                 evaluate {SML = SML, verbose = true} (ML_Lex.read "val ML_context = " @ toks));
 | |
| 60862 | 162 | fun try_exec toks = | 
| 163 |             try (ML_Context.exec (fn () => evaluate {SML = false, verbose = false} toks)) context';
 | |
| 164 | in | |
| 165 | (case get_first try_exec context_attempts of | |
| 166 | SOME context2 => context2 | |
| 167 | | NONE => error "Malformed context: expected type theory, Proof.context, Context.generic") | |
| 168 | end; | |
| 60935 | 169 | in context1 |> eval_setup thread_name index SML end; | 
| 60897 | 170 | |
| 171 | in | |
| 172 | ||
| 173 | fun eval thread_name index SML txt1 txt2 = | |
| 174 | let | |
| 68823 | 175 | val (toks1, toks2) = apply2 (#read_source (operations SML) o Input.string) (txt1, txt2); | 
| 60897 | 176 | val context = eval_context thread_name index SML toks1; | 
| 62889 | 177 |   in Context.setmp_generic_context (SOME context) (evaluate {SML = SML, verbose = true}) toks2 end;
 | 
| 60897 | 178 | |
| 179 | fun print_vals thread_name index SML txt = | |
| 180 | let | |
| 68823 | 181 | val toks = #read_source (operations SML) (Input.string txt) | 
| 60935 | 182 | val context = eval_context thread_name index SML toks; | 
| 62937 | 183 | val space = PolyML.DebuggerInterface.debugNameSpace (the_debug_state thread_name index); | 
| 60897 | 184 | |
| 185 | fun print x = | |
| 80809 
4a64fc4d1cde
clarified signature: type ML_Pretty.pretty coincides with PolyML.pretty;
 wenzelm parents: 
78728diff
changeset | 186 | Pretty.from_ML | 
| 62934 | 187 | (PolyML.NameSpace.Values.printWithType | 
| 188 | (x, FixedInt.fromInt (ML_Print_Depth.get_print_depth ()), SOME space)); | |
| 60897 | 189 | fun print_all () = | 
| 62937 | 190 | #allVal (PolyML.DebuggerInterface.debugLocalNameSpace (the_debug_state thread_name index)) () | 
| 60924 
610794dff23c
tuned signature, in accordance to sortBy in Scala;
 wenzelm parents: 
60904diff
changeset | 191 | |> sort_by #1 |> map (Pretty.item o single o print o #2) | 
| 60897 | 192 | |> Pretty.chunks |> Pretty.string_of |> writeln_message; | 
| 62889 | 193 | in Context.setmp_generic_context (SOME context) print_all () end; | 
| 60862 | 194 | |
| 195 | end; | |
| 60749 | 196 | |
| 60765 | 197 | |
| 60869 | 198 | |
| 60891 | 199 | (** debugger loop **) | 
| 60857 | 200 | |
| 201 | local | |
| 60765 | 202 | |
| 60842 | 203 | fun debugger_state thread_name = | 
| 204 | Output.protocol_message (Markup.debugger_state thread_name) | |
| 73559 | 205 | [get_debugging () | 
| 60842 | 206 | |> map (fn st => | 
| 62821 | 207 | (Position.properties_of | 
| 62937 | 208 | (Exn_Properties.position_of_polyml_location (PolyML.DebuggerInterface.debugLocation st)), | 
| 209 | PolyML.DebuggerInterface.debugFunction st)) | |
| 73559 | 210 | |> let open XML.Encode in list (pair properties string) end]; | 
| 60842 | 211 | |
| 60857 | 212 | fun debugger_command thread_name = | 
| 213 | (case get_input thread_name of | |
| 60931 | 214 | [] => (continue (); false) | 
| 215 | | ["continue"] => (continue (); false) | |
| 216 | | ["step"] => (step (); false) | |
| 217 | | ["step_over"] => (step_over (); false) | |
| 218 | | ["step_out"] => (step_out (); false) | |
| 60857 | 219 | | ["eval", index, SML, txt1, txt2] => | 
| 220 | (error_wrapper (fn () => | |
| 63806 | 221 | eval thread_name (Value.parse_int index) (Value.parse_bool SML) txt1 txt2); true) | 
| 60897 | 222 | | ["print_vals", index, SML, txt] => | 
| 223 | (error_wrapper (fn () => | |
| 63806 | 224 | print_vals thread_name (Value.parse_int index) (Value.parse_bool SML) txt); true) | 
| 60857 | 225 | | bad => | 
| 226 | (Output.system_message | |
| 227 |         ("Debugger: bad input " ^ ML_Syntax.print_list ML_Syntax.print_string bad); true));
 | |
| 228 | ||
| 60889 | 229 | in | 
| 230 | ||
| 60842 | 231 | fun debugger_loop thread_name = | 
| 78720 | 232 | Thread_Attributes.uninterruptible_body (fn run => | 
| 60885 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 233 | let | 
| 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 234 | fun loop () = | 
| 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 235 | (debugger_state thread_name; if debugger_command thread_name then loop () else ()); | 
| 78716 | 236 | val result = Exn.capture (run with_debugging) loop; | 
| 60885 
d8d51f956f05
report final debugger_state more robustly, e.g. after interrupt;
 wenzelm parents: 
60884diff
changeset | 237 | val _ = debugger_state thread_name; | 
| 78720 | 238 | in Exn.release result end); | 
| 60765 | 239 | |
| 60857 | 240 | end; | 
| 241 | ||
| 60765 | 242 | |
| 60869 | 243 | |
| 244 | (** protocol commands **) | |
| 60765 | 245 | |
| 246 | val _ = | |
| 73225 
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
 wenzelm parents: 
71694diff
changeset | 247 | Protocol_Command.define "Debugger.init" | 
| 60889 | 248 | (fn [] => | 
| 60891 | 249 | (init_input (); | 
| 62937 | 250 | PolyML.DebuggerInterface.setOnBreakPoint | 
| 60889 | 251 | (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 | 252 | 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 | 253 | then | 
| 78648 | 254 | (case try (Isabelle_Thread.print o Isabelle_Thread.self) () of | 
| 60889 | 255 | SOME thread_name => debugger_loop thread_name | 
| 256 | | NONE => ()) | |
| 60891 | 257 | else ())))); | 
| 60889 | 258 | |
| 259 | val _ = | |
| 73225 
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
 wenzelm parents: 
71694diff
changeset | 260 | Protocol_Command.define "Debugger.exit" | 
| 62937 | 261 | (fn [] => (PolyML.DebuggerInterface.setOnBreakPoint NONE; exit_input ())); | 
| 60765 | 262 | |
| 263 | val _ = | |
| 73225 
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
 wenzelm parents: 
71694diff
changeset | 264 | Protocol_Command.define "Debugger.break" | 
| 63806 | 265 | (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 | 266 | |
| 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 wenzelm parents: 
60931diff
changeset | 267 | val _ = | 
| 73225 
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
 wenzelm parents: 
71694diff
changeset | 268 | Protocol_Command.define "Debugger.breakpoint" | 
| 60880 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 269 | (fn [node_name, id0, breakpoint0, breakpoint_state0] => | 
| 60878 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 wenzelm parents: 
60871diff
changeset | 270 | let | 
| 63806 | 271 | val id = Value.parse_int id0; | 
| 272 | val breakpoint = Value.parse_int breakpoint0; | |
| 273 | 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 | 274 | |
| 63806 | 275 |         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 | 276 | in | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 277 | (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 | 278 | SOME (eval, _) => | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 279 | 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 | 280 | let | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 281 | val st = Command.eval_result_state eval; | 
| 69892 | 282 | 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 | 283 | in | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 284 | (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 | 285 | 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 | 286 | | NONE => err ()) | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 287 | end | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 288 | else err () | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 289 | | NONE => err ()) | 
| 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 wenzelm parents: 
60878diff
changeset | 290 | end); | 
| 60878 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 wenzelm parents: 
60871diff
changeset | 291 | |
| 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 wenzelm parents: 
60871diff
changeset | 292 | val _ = | 
| 73225 
3ab0cedaccad
clarified modules: allow early definition of protocol commands;
 wenzelm parents: 
71694diff
changeset | 293 | Protocol_Command.define "Debugger.input" | 
| 60842 | 294 | (fn thread_name :: msg => input thread_name msg); | 
| 60765 | 295 | |
| 60749 | 296 | end; |