1 /* Title: Pure/System/session.scala
3 Options: :folding=explicit:collapseFolds=1:
5 Isabelle session, potentially with running prover.
10 import java.lang.System
12 import scala.actors.TIMEOUT
13 import scala.actors.Actor
14 import scala.actors.Actor._
19 /* abstract file store */
21 abstract class File_Store
23 def read(path: Path): String
29 case object Global_Settings
30 case object Perspective
31 case object Assignment
32 case class Commands_Changed(set: Set[Command])
34 sealed abstract class Phase
35 case object Inactive extends Phase
36 case object Startup extends Phase // transient
37 case object Failed extends Phase
38 case object Ready extends Phase
39 case object Shutdown extends Phase // transient
43 class Session(val system: Isabelle_System, val file_store: Session.File_Store)
45 /* real time parameters */ // FIXME properties or settings (!?)
47 // user input (e.g. text edits, cursor movement)
48 val input_delay = Time.seconds(0.3)
50 // prover output (markup, common messages)
51 val output_delay = Time.seconds(0.1)
54 val update_delay = Time.seconds(0.5)
57 /* pervasive event buses */
59 val phase_changed = new Event_Bus[Session.Phase]
60 val global_settings = new Event_Bus[Session.Global_Settings.type]
61 val raw_messages = new Event_Bus[Isabelle_Process.Result]
62 val commands_changed = new Event_Bus[Session.Commands_Changed]
63 val perspective = new Event_Bus[Session.Perspective.type]
64 val assignments = new Event_Bus[Session.Assignment.type]
69 private var id_count: Document.ID = 0
70 def new_id(): Document.ID = synchronized {
71 require(id_count > java.lang.Long.MIN_VALUE)
78 /** buffered command changes (delay_first discipline) **/
80 private case object Stop
82 private val (_, command_change_buffer) =
83 Simple_Thread.actor("command_change_buffer", daemon = true)
86 var changed: Set[Command] = Set()
87 var flush_time: Option[Long] = None
89 def flush_timeout: Long =
92 case Some(time) => (time - System.currentTimeMillis()) max 0
97 if (!changed.isEmpty) commands_changed.event(Session.Commands_Changed(changed))
104 val now = System.currentTimeMillis()
106 case None => flush_time = Some(now + output_delay.ms)
107 case Some(time) => if (now >= time) flush()
113 receiveWithin(flush_timeout) {
114 case command: Command => changed += command; invoke()
115 case TIMEOUT => flush()
116 case Stop => finished = true; reply(())
117 case bad => System.err.println("command_change_buffer: ignoring bad message " + bad)
125 /** main protocol actor **/
127 val thy_header = new Thy_Header(system.symbols)
129 @volatile private var syntax = new Outer_Syntax(system.symbols)
130 def current_syntax(): Outer_Syntax = syntax
132 @volatile private var reverse_syslog = List[XML.Elem]()
133 def syslog(): String = reverse_syslog.reverse.map(msg => XML.content(msg).mkString).mkString("\n")
135 @volatile private var _phase: Session.Phase = Session.Inactive
137 private def phase_=(new_phase: Session.Phase)
140 phase_changed.event(new_phase)
142 def is_ready: Boolean = phase == Session.Ready
144 private val global_state = new Volatile(Document.State.init)
145 def current_state(): Document.State = global_state.peek()
147 private case object Interrupt_Prover
148 private case class Edit_Node(thy_name: String,
149 header: Exn.Result[Thy_Header.Header], edits: List[Text.Edit])
150 private case class Init_Node(thy_name: String,
151 header: Exn.Result[Thy_Header.Header], text: String)
152 private case class Start(timeout: Time, args: List[String])
154 var verbose: Boolean = false
156 private val (_, session_actor) = Simple_Thread.actor("session_actor", daemon = true)
158 val this_actor = self
159 var prover: Option[Isabelle_Process with Isar_Document] = None
162 /* document changes */
164 def handle_change(change: Document.Change)
167 val previous = change.previous.get_finished
168 val (node_edits, version) = change.result.get_finished
170 var former_assignment = global_state.peek().the_assignment(previous).get_finished
172 (name, Some(cmd_edits)) <- node_edits
173 (prev, None) <- cmd_edits
174 removed <- previous.nodes(name).commands.get_after(prev)
175 } former_assignment -= removed
179 case (name, None) => (name, None)
180 case (name, Some(cmd_edits)) =>
184 val id1 = c1.map(_.id)
188 case Some(command) =>
189 if (global_state.peek().lookup_command(command.id).isEmpty) {
190 global_state.change(_.define_command(command))
192 define_command(command.id, system.symbols.encode(command.source))
200 global_state.change(_.define_version(version, former_assignment))
201 prover.get.edit_version(previous.id, version.id, id_edits)
208 def bad_result(result: Isabelle_Process.Result)
211 System.err.println("Ignoring prover result: " + result.message.toString)
214 def handle_result(result: Isabelle_Process.Result)
217 result.properties match {
218 case Position.Id(state_id) =>
220 val st = global_state.change_yield(_.accumulate(state_id, result.message))
221 command_change_buffer ! st.command
223 catch { case _: Document.State.Fail => bad_result(result) }
225 if (result.is_syslog) {
226 reverse_syslog ::= result.message
227 if (result.is_ready) {
228 // FIXME move to ML side
229 syntax += ("hence", Keyword.PRF_ASM_GOAL, "then have")
230 syntax += ("thus", Keyword.PRF_ASM_GOAL, "then show")
231 phase = Session.Ready
233 else if (result.is_exit && phase == Session.Startup) phase = Session.Failed
234 else if (result.is_exit) phase = Session.Inactive
236 else if (result.is_stdout) { }
237 else if (result.is_status) {
239 case List(Isar_Document.Assign(id, edits)) =>
241 val cmds: List[Command] = global_state.change_yield(_.assign(id, edits))
242 for (cmd <- cmds) command_change_buffer ! cmd
243 assignments.event(Session.Assignment)
245 catch { case _: Document.State.Fail => bad_result(result) }
246 case List(Keyword.Command_Decl(name, kind)) => syntax += (name, kind)
247 case List(Keyword.Keyword_Decl(name)) => syntax += name
248 case _ => bad_result(result)
251 else bad_result(result)
254 raw_messages.event(result)
259 def edit_version(edits: List[Document.Edit_Text])
262 val previous = global_state.peek().history.tip.version
263 val syntax = current_syntax()
264 val result = Future.fork { Thy_Syntax.text_edits(syntax, new_id _, previous.join, edits) }
265 val change = global_state.change_yield(_.extend_history(previous, edits, result))
267 change.version.map(_ => {
268 assignments.await { global_state.peek().is_assigned(previous.get_finished) }
269 this_actor ! change })
279 case Interrupt_Prover =>
280 prover.map(_.interrupt)
282 case Edit_Node(thy_name, header, text_edits) if prover.isDefined =>
283 edit_version(List((thy_name, Some(text_edits))))
286 case Init_Node(thy_name, header, text) if prover.isDefined =>
287 // FIXME compare with existing node
290 (thy_name, Some(List(Text.Edit.insert(0, text))))))
293 case change: Document.Change if prover.isDefined =>
294 handle_change(change)
296 case result: Isabelle_Process.Result => handle_result(result)
298 case Start(timeout, args) if prover.isEmpty =>
299 if (phase == Session.Inactive || phase == Session.Failed) {
300 phase = Session.Startup
302 Some(new Isabelle_Process(system, timeout, this_actor, args:_*) with Isar_Document)
306 if (phase == Session.Ready) {
307 global_state.change(_ => Document.State.init) // FIXME event bus!?
308 phase = Session.Shutdown
310 phase = Session.Inactive
315 case bad if prover.isDefined =>
316 System.err.println("session_actor: ignoring bad message " + bad)
325 def start(timeout: Time, args: List[String]) { session_actor ! Start(timeout, args) }
327 def stop() { command_change_buffer !? Stop; session_actor !? Stop }
329 def interrupt() { session_actor ! Interrupt_Prover }
331 def edit_node(thy_name: String, header: Exn.Result[Thy_Header.Header], edits: List[Text.Edit])
333 session_actor !? Edit_Node(thy_name, header, edits)
336 def init_node(thy_name: String, header: Exn.Result[Thy_Header.Header], text: String)
338 session_actor !? Init_Node(thy_name, header, text)
341 def snapshot(name: String, pending_edits: List[Text.Edit]): Document.Snapshot =
342 global_state.peek().snapshot(name, pending_edits)