| author | wenzelm | 
| Thu, 05 Jan 2017 22:28:22 +0100 | |
| changeset 64807 | 7d556bb6046b | 
| parent 63805 | c272680df665 | 
| child 65213 | 51c0f094dc02 | 
| permissions | -rw-r--r-- | 
| 60749 | 1  | 
/* Title: Pure/Tools/debugger.scala  | 
2  | 
Author: Makarius  | 
|
3  | 
||
4  | 
Interactive debugger for Isabelle/ML.  | 
|
5  | 
*/  | 
|
6  | 
||
7  | 
package isabelle  | 
|
8  | 
||
9  | 
||
| 61016 | 10  | 
import scala.collection.immutable.SortedMap  | 
11  | 
||
12  | 
||
| 60749 | 13  | 
object Debugger  | 
14  | 
{
 | 
|
| 61010 | 15  | 
/* context */  | 
| 60834 | 16  | 
|
| 60842 | 17  | 
sealed case class Debug_State(  | 
18  | 
pos: Position.T,  | 
|
19  | 
function: String)  | 
|
20  | 
||
| 61010 | 21  | 
sealed case class Context(thread_name: String, debug_states: List[Debug_State], index: Int = 0)  | 
22  | 
  {
 | 
|
23  | 
def size: Int = debug_states.length + 1  | 
|
24  | 
def reset: Context = copy(index = 0)  | 
|
25  | 
def select(i: Int) = copy(index = i + 1)  | 
|
26  | 
||
27  | 
def thread_state: Option[Debug_State] = debug_states.headOption  | 
|
28  | 
||
29  | 
def stack_state: Option[Debug_State] =  | 
|
30  | 
if (1 <= index && index <= debug_states.length)  | 
|
31  | 
Some(debug_states(index - 1))  | 
|
32  | 
else None  | 
|
33  | 
||
| 61014 | 34  | 
def debug_index: Option[Int] =  | 
| 61010 | 35  | 
if (stack_state.isDefined) Some(index - 1)  | 
36  | 
else if (debug_states.nonEmpty) Some(0)  | 
|
37  | 
else None  | 
|
38  | 
||
39  | 
def debug_state: Option[Debug_State] = stack_state orElse thread_state  | 
|
| 61014 | 40  | 
def debug_position: Option[Position.T] = debug_state.map(_.pos)  | 
| 61010 | 41  | 
|
42  | 
override def toString: String =  | 
|
43  | 
      stack_state match {
 | 
|
44  | 
case None => thread_name  | 
|
45  | 
case Some(d) => d.function  | 
|
46  | 
}  | 
|
47  | 
}  | 
|
48  | 
||
49  | 
||
50  | 
/* global state */  | 
|
51  | 
||
| 61016 | 52  | 
type Threads = SortedMap[String, List[Debug_State]]  | 
53  | 
||
| 60835 | 54  | 
sealed case class State(  | 
| 
60932
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
55  | 
session: Session = new Session(Resources.empty), // implicit session  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
56  | 
active: Int = 0, // active views  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
57  | 
break: Boolean = false, // break at next possible breakpoint  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
58  | 
active_breakpoints: Set[Long] = Set.empty, // explicit breakpoint state  | 
| 61016 | 59  | 
threads: Threads = SortedMap.empty, // thread name ~> stack of debug states  | 
| 61014 | 60  | 
focus: Map[String, Context] = Map.empty, // thread name ~> focus  | 
| 60834 | 61  | 
output: Map[String, Command.Results] = Map.empty) // thread name ~> output messages  | 
62  | 
  {
 | 
|
| 60842 | 63  | 
def set_session(new_session: Session): State =  | 
| 62051 | 64  | 
    {
 | 
65  | 
session.stop()  | 
|
| 60842 | 66  | 
copy(session = new_session)  | 
| 62051 | 67  | 
}  | 
| 60842 | 68  | 
|
| 
60932
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
69  | 
def set_break(b: Boolean): State = copy(break = b)  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
70  | 
|
| 60910 | 71  | 
def is_active: Boolean = active > 0 && session.is_ready  | 
| 60898 | 72  | 
def inc_active: State = copy(active = active + 1)  | 
73  | 
def dec_active: State = copy(active = active - 1)  | 
|
| 60876 | 74  | 
|
| 
60880
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
75  | 
def toggle_breakpoint(breakpoint: Long): (Boolean, State) =  | 
| 60876 | 76  | 
    {
 | 
77  | 
val active_breakpoints1 =  | 
|
| 
60880
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
78  | 
if (active_breakpoints(breakpoint)) active_breakpoints - breakpoint  | 
| 
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
79  | 
else active_breakpoints + breakpoint  | 
| 
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
80  | 
(active_breakpoints1(breakpoint), copy(active_breakpoints = active_breakpoints1))  | 
| 60876 | 81  | 
}  | 
82  | 
||
| 60842 | 83  | 
def get_thread(thread_name: String): List[Debug_State] =  | 
84  | 
threads.getOrElse(thread_name, Nil)  | 
|
85  | 
||
86  | 
def update_thread(thread_name: String, debug_states: List[Debug_State]): State =  | 
|
| 
61019
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
87  | 
    {
 | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
88  | 
val threads1 =  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
89  | 
if (debug_states.nonEmpty) threads + (thread_name -> debug_states)  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
90  | 
else threads - thread_name  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
91  | 
val focus1 =  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
92  | 
        focus.get(thread_name) match {
 | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
93  | 
case Some(c) if debug_states.nonEmpty =>  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
94  | 
focus + (thread_name -> Context(thread_name, debug_states))  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
95  | 
case _ => focus - thread_name  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
96  | 
}  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
97  | 
copy(threads = threads1, focus = focus1)  | 
| 
 
7ce030f14aa9
reset focus after thread update (with new debug_states);
 
wenzelm 
parents: 
61018 
diff
changeset
 | 
98  | 
}  | 
| 60842 | 99  | 
|
| 61014 | 100  | 
def set_focus(c: Context): State = copy(focus = focus + (c.thread_name -> c))  | 
101  | 
||
| 60842 | 102  | 
def get_output(thread_name: String): Command.Results =  | 
103  | 
output.getOrElse(thread_name, Command.Results.empty)  | 
|
104  | 
||
105  | 
def add_output(thread_name: String, entry: Command.Results.Entry): State =  | 
|
106  | 
copy(output = output + (thread_name -> (get_output(thread_name) + entry)))  | 
|
107  | 
||
108  | 
def clear_output(thread_name: String): State =  | 
|
109  | 
copy(output = output - thread_name)  | 
|
| 60834 | 110  | 
}  | 
111  | 
||
112  | 
private val global_state = Synchronized(State())  | 
|
113  | 
||
114  | 
||
| 60902 | 115  | 
/* update events */  | 
| 60749 | 116  | 
|
| 60900 | 117  | 
case object Update  | 
| 60749 | 118  | 
|
| 60902 | 119  | 
private val delay_update =  | 
| 61556 | 120  | 
    Standard_Thread.delay_first(global_state.value.session.output_delay) {
 | 
| 60902 | 121  | 
global_state.value.session.debugger_updates.post(Update)  | 
122  | 
}  | 
|
123  | 
||
124  | 
||
125  | 
/* protocol handler */  | 
|
126  | 
||
| 60749 | 127  | 
class Handler extends Session.Protocol_Handler  | 
128  | 
  {
 | 
|
| 60842 | 129  | 
private def debugger_state(prover: Prover, msg: Prover.Protocol_Output): Boolean =  | 
130  | 
    {
 | 
|
131  | 
      msg.properties match {
 | 
|
132  | 
case Markup.Debugger_State(thread_name) =>  | 
|
| 60863 | 133  | 
val msg_body =  | 
134  | 
YXML.parse_body_failsafe(Symbol.decode(UTF8.decode_permissive(msg.bytes)))  | 
|
| 60842 | 135  | 
val debug_states =  | 
136  | 
          {
 | 
|
137  | 
import XML.Decode._  | 
|
138  | 
            list(pair(properties, Symbol.decode_string))(msg_body).map({
 | 
|
139  | 
case (pos, function) => Debug_State(pos, function)  | 
|
140  | 
})  | 
|
141  | 
}  | 
|
142  | 
global_state.change(_.update_thread(thread_name, debug_states))  | 
|
| 60900 | 143  | 
delay_update.invoke()  | 
| 60842 | 144  | 
true  | 
145  | 
case _ => false  | 
|
146  | 
}  | 
|
147  | 
}  | 
|
148  | 
||
| 60830 | 149  | 
private def debugger_output(prover: Prover, msg: Prover.Protocol_Output): Boolean =  | 
150  | 
    {
 | 
|
151  | 
      msg.properties match {
 | 
|
| 60835 | 152  | 
case Markup.Debugger_Output(thread_name) =>  | 
| 60834 | 153  | 
val msg_body =  | 
154  | 
prover.xml_cache.body(  | 
|
| 60863 | 155  | 
YXML.parse_body_failsafe(Symbol.decode(UTF8.decode_permissive(msg.bytes))))  | 
| 60834 | 156  | 
          msg_body match {
 | 
157  | 
case List(XML.Elem(Markup(name, props @ Markup.Serial(i)), body)) =>  | 
|
158  | 
val message = XML.Elem(Markup(Markup.message(name), props), body)  | 
|
| 60835 | 159  | 
global_state.change(_.add_output(thread_name, i -> message))  | 
| 60900 | 160  | 
delay_update.invoke()  | 
| 60834 | 161  | 
true  | 
162  | 
case _ => false  | 
|
163  | 
}  | 
|
| 60830 | 164  | 
case _ => false  | 
165  | 
}  | 
|
166  | 
}  | 
|
167  | 
||
168  | 
val functions =  | 
|
| 60842 | 169  | 
Map(  | 
170  | 
Markup.DEBUGGER_STATE -> debugger_state _,  | 
|
171  | 
Markup.DEBUGGER_OUTPUT -> debugger_output _)  | 
|
| 60749 | 172  | 
}  | 
| 60765 | 173  | 
|
174  | 
||
175  | 
/* protocol commands */  | 
|
176  | 
||
| 60898 | 177  | 
def is_active(): Boolean = global_state.value.is_active  | 
| 60889 | 178  | 
|
| 60910 | 179  | 
def init_session(session: Session)  | 
180  | 
  {
 | 
|
181  | 
global_state.change(state =>  | 
|
182  | 
    {
 | 
|
183  | 
val state1 = state.set_session(session)  | 
|
184  | 
if (!state.session.is_ready && state1.session.is_ready && state1.is_active)  | 
|
185  | 
        state1.session.protocol_command("Debugger.init")
 | 
|
186  | 
state1  | 
|
187  | 
})  | 
|
188  | 
}  | 
|
189  | 
||
190  | 
def init(): Unit =  | 
|
| 60889 | 191  | 
global_state.change(state =>  | 
192  | 
    {
 | 
|
| 60898 | 193  | 
val state1 = state.inc_active  | 
194  | 
if (!state.is_active && state1.is_active)  | 
|
195  | 
        state1.session.protocol_command("Debugger.init")
 | 
|
| 60889 | 196  | 
state1  | 
197  | 
})  | 
|
198  | 
||
| 60910 | 199  | 
def exit(): Unit =  | 
| 60889 | 200  | 
global_state.change(state =>  | 
201  | 
    {
 | 
|
| 60898 | 202  | 
val state1 = state.dec_active  | 
203  | 
if (state.is_active && !state1.is_active)  | 
|
204  | 
        state1.session.protocol_command("Debugger.exit")
 | 
|
| 60889 | 205  | 
state1  | 
206  | 
})  | 
|
| 60876 | 207  | 
|
| 
60932
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
208  | 
def is_break(): Boolean = global_state.value.break  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
209  | 
def set_break(b: Boolean)  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
210  | 
  {
 | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
211  | 
    global_state.change(state => {
 | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
212  | 
val state1 = state.set_break(b)  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
213  | 
      state1.session.protocol_command("Debugger.break", b.toString)
 | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
214  | 
state1  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
215  | 
})  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
216  | 
delay_update.invoke()  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
217  | 
}  | 
| 
 
13ee73f57c85
allow to break running threads at next possible breakpoint (simplified version of former option, see f3039309702e);
 
wenzelm 
parents: 
60912 
diff
changeset
 | 
218  | 
|
| 60882 | 219  | 
def active_breakpoint_state(breakpoint: Long): Option[Boolean] =  | 
| 60876 | 220  | 
  {
 | 
| 60898 | 221  | 
val state = global_state.value  | 
| 60912 | 222  | 
if (state.is_active) Some(state.active_breakpoints(breakpoint)) else None  | 
| 60876 | 223  | 
}  | 
224  | 
||
| 60882 | 225  | 
def breakpoint_state(breakpoint: Long): Boolean =  | 
| 60898 | 226  | 
global_state.value.active_breakpoints(breakpoint)  | 
| 60882 | 227  | 
|
| 
60880
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
228  | 
def toggle_breakpoint(command: Command, breakpoint: Long)  | 
| 
60878
 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 
wenzelm 
parents: 
60876 
diff
changeset
 | 
229  | 
  {
 | 
| 
 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 
wenzelm 
parents: 
60876 
diff
changeset
 | 
230  | 
global_state.change(state =>  | 
| 
 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 
wenzelm 
parents: 
60876 
diff
changeset
 | 
231  | 
    {
 | 
| 
60880
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
232  | 
val (breakpoint_state, state1) = state.toggle_breakpoint(breakpoint)  | 
| 
60878
 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 
wenzelm 
parents: 
60876 
diff
changeset
 | 
233  | 
state1.session.protocol_command(  | 
| 
60880
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
234  | 
"Debugger.breakpoint",  | 
| 
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
235  | 
Symbol.encode(command.node_name.node),  | 
| 
 
fa958e24ff24
set breakpoint state on ML side, relying on stable situation within the PIDE editing queue;
 
wenzelm 
parents: 
60878 
diff
changeset
 | 
236  | 
Document_ID(command.id),  | 
| 63805 | 237  | 
Value.Long(breakpoint),  | 
238  | 
Value.Boolean(breakpoint_state))  | 
|
| 
60878
 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 
wenzelm 
parents: 
60876 
diff
changeset
 | 
239  | 
state1  | 
| 
 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 
wenzelm 
parents: 
60876 
diff
changeset
 | 
240  | 
})  | 
| 
 
1f0d2bbcf38b
added action to toggle breakpoints (on editor side);
 
wenzelm 
parents: 
60876 
diff
changeset
 | 
241  | 
}  | 
| 60876 | 242  | 
|
| 61018 | 243  | 
def status(focus: Option[Context]): (Threads, List[XML.Tree]) =  | 
244  | 
  {
 | 
|
245  | 
val state = global_state.value  | 
|
246  | 
val output =  | 
|
247  | 
      focus match {
 | 
|
248  | 
case None => Nil  | 
|
249  | 
case Some(c) =>  | 
|
250  | 
          (for {
 | 
|
251  | 
(thread_name, results) <- state.output  | 
|
252  | 
if thread_name == c.thread_name  | 
|
253  | 
(_, tree) <- results.iterator  | 
|
254  | 
} yield tree).toList  | 
|
255  | 
}  | 
|
256  | 
(state.threads, output)  | 
|
257  | 
}  | 
|
| 61011 | 258  | 
|
| 61014 | 259  | 
def focus(): List[Context] = global_state.value.focus.toList.map(_._2)  | 
260  | 
def set_focus(c: Context)  | 
|
| 61008 | 261  | 
  {
 | 
| 61014 | 262  | 
global_state.change(_.set_focus(c))  | 
| 61008 | 263  | 
delay_update.invoke()  | 
264  | 
}  | 
|
| 60875 | 265  | 
|
| 60854 | 266  | 
def input(thread_name: String, msg: String*): Unit =  | 
| 60898 | 267  | 
    global_state.value.session.protocol_command("Debugger.input", (thread_name :: msg.toList):_*)
 | 
| 60854 | 268  | 
|
| 60899 | 269  | 
def continue(thread_name: String): Unit = input(thread_name, "continue")  | 
| 60869 | 270  | 
def step(thread_name: String): Unit = input(thread_name, "step")  | 
271  | 
def step_over(thread_name: String): Unit = input(thread_name, "step_over")  | 
|
272  | 
def step_out(thread_name: String): Unit = input(thread_name, "step_out")  | 
|
| 60856 | 273  | 
|
| 60901 | 274  | 
def clear_output(thread_name: String)  | 
275  | 
  {
 | 
|
276  | 
global_state.change(_.clear_output(thread_name))  | 
|
| 60902 | 277  | 
delay_update.invoke()  | 
| 60901 | 278  | 
}  | 
279  | 
||
| 61010 | 280  | 
def eval(c: Context, SML: Boolean, context: String, expression: String)  | 
| 60856 | 281  | 
  {
 | 
| 60896 | 282  | 
    global_state.change(state => {
 | 
| 61014 | 283  | 
input(c.thread_name, "eval", c.debug_index.getOrElse(0).toString,  | 
| 61010 | 284  | 
SML.toString, Symbol.encode(context), Symbol.encode(expression))  | 
285  | 
state.clear_output(c.thread_name)  | 
|
| 60896 | 286  | 
})  | 
| 60902 | 287  | 
delay_update.invoke()  | 
| 60856 | 288  | 
}  | 
| 60897 | 289  | 
|
| 61010 | 290  | 
def print_vals(c: Context, SML: Boolean, context: String)  | 
| 60897 | 291  | 
  {
 | 
| 61014 | 292  | 
require(c.debug_index.isDefined)  | 
| 61010 | 293  | 
|
| 60897 | 294  | 
    global_state.change(state => {
 | 
| 61014 | 295  | 
input(c.thread_name, "print_vals", c.debug_index.getOrElse(0).toString,  | 
| 61010 | 296  | 
SML.toString, Symbol.encode(context))  | 
297  | 
state.clear_output(c.thread_name)  | 
|
| 60897 | 298  | 
})  | 
| 60902 | 299  | 
delay_update.invoke()  | 
| 60897 | 300  | 
}  | 
| 60749 | 301  | 
}  |