1 /* Title: Pure/System/session.scala
3 Options: :folding=explicit:collapseFolds=1:
5 Isabelle session, potentially with running prover.
11 import scala.actors.TIMEOUT
12 import scala.actors.Actor
13 import scala.actors.Actor._
20 case object Global_Settings
21 case object Perspective
22 case object Assignment
23 case class Commands_Changed(set: Set[Command])
25 sealed abstract class Phase
26 case object Inactive extends Phase
27 case object Startup extends Phase // transient
28 case object Failed extends Phase
29 case object Ready extends Phase
30 case object Shutdown extends Phase // transient
34 class Session(system: Isabelle_System)
36 /* real time parameters */ // FIXME properties or settings (!?)
38 // user input (e.g. text edits, cursor movement)
41 // prover output (markup, common messages)
42 val output_delay = 100
45 val update_delay = 500
48 /* pervasive event buses */
50 val phase_changed = new Event_Bus[Session.Phase]
51 val global_settings = new Event_Bus[Session.Global_Settings.type]
52 val raw_messages = new Event_Bus[Isabelle_Process.Result]
53 val commands_changed = new Event_Bus[Session.Commands_Changed]
54 val perspective = new Event_Bus[Session.Perspective.type]
55 val assignments = new Event_Bus[Session.Assignment.type]
60 private var id_count: Document.ID = 0
61 def new_id(): Document.ID = synchronized {
62 require(id_count > java.lang.Long.MIN_VALUE)
69 /** buffered command changes (delay_first discipline) **/
71 private case object Stop
73 private val (_, command_change_buffer) =
74 Simple_Thread.actor("command_change_buffer", daemon = true)
77 import scala.compat.Platform.currentTime
79 var changed: Set[Command] = Set()
80 var flush_time: Option[Long] = None
82 def flush_timeout: Long =
85 case Some(time) => (time - currentTime) max 0
90 if (!changed.isEmpty) commands_changed.event(Session.Commands_Changed(changed))
99 case None => flush_time = Some(now + output_delay)
100 case Some(time) => if (now >= time) flush()
106 receiveWithin(flush_timeout) {
107 case command: Command => changed += command; invoke()
108 case TIMEOUT => flush()
109 case Stop => finished = true; reply(())
110 case bad => System.err.println("command_change_buffer: ignoring bad message " + bad)
118 /** main protocol actor **/
120 @volatile private var syntax = new Outer_Syntax(system.symbols)
121 def current_syntax(): Outer_Syntax = syntax
123 @volatile private var reverse_syslog = List[XML.Elem]()
124 def syslog(): String = reverse_syslog.reverse.map(msg => XML.content(msg).mkString).mkString("\n")
126 @volatile private var _phase: Session.Phase = Session.Inactive
128 private def phase_=(new_phase: Session.Phase)
131 phase_changed.event(new_phase)
134 private val global_state = new Volatile(Document.State.init)
135 def current_state(): Document.State = global_state.peek()
137 private case object Interrupt_Prover
138 private case class Edit_Version(edits: List[Document.Edit_Text])
139 private case class Start(timeout: Int, args: List[String])
141 private val (_, session_actor) = Simple_Thread.actor("session_actor", daemon = true)
143 var prover: Isabelle_Process with Isar_Document = null
146 /* document changes */
148 def handle_change(change: Document.Change)
151 val previous = change.previous.get_finished
152 val (node_edits, version) = change.result.get_finished
154 var former_assignment = global_state.peek().the_assignment(previous).get_finished
156 (name, Some(cmd_edits)) <- node_edits
157 (prev, None) <- cmd_edits
158 removed <- previous.nodes(name).commands.get_after(prev)
159 } former_assignment -= removed
163 case (name, None) => (name, None)
164 case (name, Some(cmd_edits)) =>
168 val id1 = c1.map(_.id)
172 case Some(command) =>
173 if (global_state.peek().lookup_command(command.id).isEmpty) {
174 global_state.change(_.define_command(command))
175 prover.define_command(command.id, system.symbols.encode(command.source))
183 global_state.change(_.define_version(version, former_assignment))
184 prover.edit_version(previous.id, version.id, id_edits)
191 def bad_result(result: Isabelle_Process.Result)
193 System.err.println("Ignoring prover result: " + result.message.toString)
196 def handle_result(result: Isabelle_Process.Result)
199 raw_messages.event(result)
201 result.properties match {
202 case Position.Id(state_id) =>
204 val st = global_state.change_yield(_.accumulate(state_id, result.message))
205 command_change_buffer ! st.command
207 catch { case _: Document.State.Fail => bad_result(result) }
209 if (result.is_syslog) {
210 reverse_syslog ::= result.message
211 if (result.is_ready) {
212 // FIXME move to ML side
213 syntax += ("hence", Keyword.PRF_ASM_GOAL, "then have")
214 syntax += ("thus", Keyword.PRF_ASM_GOAL, "then show")
215 phase = Session.Ready
217 else if (result.is_exit && phase == Session.Startup) phase = Session.Failed
218 else if (result.is_exit) phase = Session.Inactive
220 else if (result.is_stdout) { }
221 else if (result.is_status) {
223 case List(Isar_Document.Assign(id, edits)) =>
225 val cmds: List[Command] = global_state.change_yield(_.assign(id, edits))
226 for (cmd <- cmds) command_change_buffer ! cmd
227 assignments.event(Session.Assignment)
229 catch { case _: Document.State.Fail => bad_result(result) }
230 case List(Keyword.Command_Decl(name, kind)) => syntax += (name, kind)
231 case List(Keyword.Keyword_Decl(name)) => syntax += name
232 case _ => bad_result(result)
235 else bad_result(result)
246 case Interrupt_Prover =>
247 if (prover != null) prover.interrupt
249 case Edit_Version(edits) if prover != null =>
250 val previous = global_state.peek().history.tip.version
251 val result = Future.fork { Thy_Syntax.text_edits(Session.this, previous.join, edits) }
252 val change = global_state.change_yield(_.extend_history(previous, edits, result))
254 val this_actor = self
255 change.version.map(_ => {
256 assignments.await { global_state.peek().is_assigned(previous.get_finished) }
257 this_actor ! change })
261 case change: Document.Change if prover != null => handle_change(change)
263 case result: Isabelle_Process.Result => handle_result(result)
265 case Start(timeout, args) if prover == null =>
266 if (phase == Session.Inactive || phase == Session.Failed) {
267 phase = Session.Startup
268 prover = new Isabelle_Process(system, timeout, self, args:_*) with Isar_Document
272 if (phase == Session.Ready) {
273 global_state.change(_ => Document.State.init) // FIXME event bus!?
274 phase = Session.Shutdown
276 phase = Session.Inactive
281 case bad if prover != null =>
282 System.err.println("session_actor: ignoring bad message " + bad)
291 def start(timeout: Int, args: List[String]) { session_actor ! Start(timeout, args) }
293 def stop() { command_change_buffer !? Stop; session_actor !? Stop }
295 def interrupt() { session_actor ! Interrupt_Prover }
297 def edit_version(edits: List[Document.Edit_Text]) { session_actor !? Edit_Version(edits) }
299 def snapshot(name: String, pending_edits: List[Text.Edit]): Document.Snapshot =
300 global_state.peek().snapshot(name, pending_edits)