1 /* Title: Pure/System/session.scala
3 Options: :folding=explicit:collapseFolds=1:
5 Main Isabelle/Scala session, potentially with running prover process.
10 import java.lang.System
12 import scala.actors.TIMEOUT
13 import scala.actors.Actor
14 import scala.actors.Actor._
22 case object Global_Settings
23 case object Perspective
24 case object Assignment
25 case class Commands_Changed(set: Set[Command])
27 sealed abstract class Phase
28 case object Inactive extends Phase
29 case object Startup extends Phase // transient
30 case object Failed extends Phase
31 case object Ready extends Phase
32 case object Shutdown extends Phase // transient
37 class Session(thy_load: Thy_Load)
39 /* real time parameters */ // FIXME properties or settings (!?)
41 val input_delay = Time.seconds(0.3) // user input (e.g. text edits, cursor movement)
42 val output_delay = Time.seconds(0.1) // prover output (markup, common messages)
43 val update_delay = Time.seconds(0.5) // GUI layout updates
44 val load_delay = Time.seconds(0.5) // file load operations (new buffers etc.)
47 /* pervasive event buses */
49 val global_settings = new Event_Bus[Session.Global_Settings.type]
50 val perspective = new Event_Bus[Session.Perspective.type]
51 val assignments = new Event_Bus[Session.Assignment.type]
52 val commands_changed = new Event_Bus[Session.Commands_Changed]
53 val phase_changed = new Event_Bus[Session.Phase]
54 val raw_messages = new Event_Bus[Isabelle_Process.Message]
58 /** buffered command changes (delay_first discipline) **/
61 private case object Stop
63 private val (_, command_change_buffer) =
64 Simple_Thread.actor("command_change_buffer", daemon = true)
66 var changed: Set[Command] = Set()
67 var flush_time: Option[Long] = None
69 def flush_timeout: Long =
72 case Some(time) => (time - System.currentTimeMillis()) max 0
77 if (!changed.isEmpty) commands_changed.event(Session.Commands_Changed(changed))
84 val now = System.currentTimeMillis()
86 case None => flush_time = Some(now + output_delay.ms)
87 case Some(time) => if (now >= time) flush()
93 receiveWithin(flush_timeout) {
94 case command: Command => changed += command; invoke()
95 case TIMEOUT => flush()
96 case Stop => finished = true; reply(())
97 case bad => System.err.println("command_change_buffer: ignoring bad message " + bad)
105 /** main protocol actor **/
109 @volatile var verbose: Boolean = false
111 @volatile private var syntax = new Outer_Syntax
112 def current_syntax(): Outer_Syntax = syntax
114 @volatile private var reverse_syslog = List[XML.Elem]()
115 def syslog(): String = reverse_syslog.reverse.map(msg => XML.content(msg).mkString).mkString("\n")
117 @volatile private var _phase: Session.Phase = Session.Inactive
118 private def phase_=(new_phase: Session.Phase)
121 phase_changed.event(new_phase)
124 def is_ready: Boolean = phase == Session.Ready
126 private val global_state = new Volatile(Document.State.init)
127 def current_state(): Document.State = global_state()
129 def snapshot(name: String, pending_edits: List[Text.Edit]): Document.Snapshot =
130 global_state().snapshot(name, pending_edits)
135 def header_edit(name: String, master_dir: String,
136 header: Document.Node_Header): Document.Edit_Text =
138 def norm_import(s: String): String =
140 val thy_name = Thy_Header.base_name(s)
141 if (thy_load.is_loaded(thy_name)) thy_name
142 else thy_load.append(master_dir, Thy_Header.thy_path(Path.explode(s)))
144 def norm_use(s: String): String =
145 thy_load.append(master_dir, Path.explode(s))
147 val header1: Document.Node_Header =
149 case Exn.Res(Thy_Header(thy_name, _, _))
150 if (thy_load.is_loaded(thy_name)) =>
151 Exn.Exn(ERROR("Attempt to update loaded theory " + quote(thy_name)))
152 case _ => Document.Node.norm_header(norm_import, norm_use, header)
154 (name, Document.Node.Header(header1))
160 private case class Start(timeout: Time, args: List[String])
161 private case object Interrupt
162 private case class Init_Node(name: String, master_dir: String,
163 header: Document.Node_Header, perspective: Text.Perspective, text: String)
164 private case class Edit_Node(name: String, master_dir: String,
165 header: Document.Node_Header, perspective: Text.Perspective, edits: List[Text.Edit])
166 private case class Change_Node(
168 doc_edits: List[Document.Edit_Command],
169 previous: Document.Version,
170 version: Document.Version)
172 private val (_, session_actor) = Simple_Thread.actor("session_actor", daemon = true)
174 val this_actor = self
175 var prover: Option[Isabelle_Process with Isar_Document] = None
180 def update_perspective(name: String, text_perspective: Text.Perspective)
182 val previous = global_state().tip_version
183 val (perspective, version) = Thy_Syntax.edit_perspective(previous, name, text_perspective)
185 val text_edits: List[Document.Edit_Text] =
186 List((name, Document.Node.Perspective(text_perspective)))
188 global_state.change_yield(
189 _.continue_history(Future.value(previous), text_edits, Future.value(version)))
191 val assignment = global_state().the_assignment(previous).check_finished
192 global_state.change(_.define_version(version, assignment))
193 global_state.change_yield(_.assign(version.id, Document.no_assign))
195 prover.get.update_perspective(previous.id, version.id, name, perspective)
201 def handle_edits(name: String, master_dir: String,
202 header: Document.Node_Header, edits: List[Document.Node.Edit[Text.Edit, Text.Perspective]])
205 val syntax = current_syntax()
206 val previous = global_state().history.tip.version
208 val text_edits = header_edit(name, master_dir, header) :: edits.map(edit => (name, edit))
209 val result = Future.fork { Thy_Syntax.text_edits(syntax, previous.join, text_edits) }
211 global_state.change_yield(_.continue_history(previous, text_edits, result.map(_._2)))
214 case (doc_edits, _) =>
215 assignments.await { global_state().is_assigned(previous.get_finished) }
216 this_actor ! Change_Node(name, doc_edits, previous.join, change.version.join)
222 /* exec state assignment */
224 def handle_assign(id: Document.Version_ID, assign: Document.Assign)
227 val cmds = global_state.change_yield(_.assign(id, assign))
228 for (cmd <- cmds) command_change_buffer ! cmd
229 assignments.event(Session.Assignment)
234 /* resulting changes */
236 def handle_change(change: Change_Node)
239 val previous = change.previous
240 val version = change.version
241 val name = change.name
242 val doc_edits = change.doc_edits
244 def id_command(command: Command)
246 if (!global_state().defined_command(command.id)) {
247 global_state.change(_.define_command(command))
248 prover.get.define_command(command.id, Symbol.encode(command.source))
253 edit foreach { case (c1, c2) => c1 foreach id_command; c2 foreach id_command }
256 val assignment = global_state().the_assignment(previous).check_finished
257 global_state.change(_.define_version(version, assignment))
258 prover.get.update(previous.id, version.id, doc_edits)
265 def handle_result(result: Isabelle_Process.Result)
268 def bad_result(result: Isabelle_Process.Result)
271 System.err.println("Ignoring prover result: " + result.message.toString)
274 result.properties match {
275 case Position.Id(state_id) if !result.is_raw =>
277 val st = global_state.change_yield(_.accumulate(state_id, result.message))
278 command_change_buffer ! st.command
281 case _: Document.State.Fail => bad_result(result)
283 case Markup.Invoke_Scala(name, id) if result.is_raw =>
285 val arg = XML.content(result.body).mkString
286 val (tag, res) = Invoke_Scala.method(name, arg)
287 prover.get.invoke_scala(id, tag, res)
289 case Markup.Cancel_Scala(id) =>
290 System.err.println("cancel_scala " + id) // FIXME cancel JVM task
292 if (result.is_syslog) {
293 reverse_syslog ::= result.message
294 if (result.is_ready) {
295 // FIXME move to ML side (!?)
296 syntax += ("hence", Keyword.PRF_ASM_GOAL, "then have")
297 syntax += ("thus", Keyword.PRF_ASM_GOAL, "then show")
298 phase = Session.Ready
300 else if (result.is_exit && phase == Session.Startup) phase = Session.Failed
301 else if (result.is_exit) phase = Session.Inactive
303 else if (result.is_stdout) { }
304 else if (result.is_status) {
306 case List(Isar_Document.Assign(id, assign)) =>
307 try { handle_assign(id, assign) }
308 catch { case _: Document.State.Fail => bad_result(result) }
309 case List(Keyword.Command_Decl(name, kind)) => syntax += (name, kind)
310 case List(Keyword.Keyword_Decl(name)) => syntax += name
311 case List(Thy_Info.Loaded_Theory(name)) => thy_load.register_thy(name)
312 case _ => bad_result(result)
315 else bad_result(result)
327 case Start(timeout, args) if prover.isEmpty =>
328 if (phase == Session.Inactive || phase == Session.Failed) {
329 phase = Session.Startup
330 prover = Some(new Isabelle_Process(timeout, this_actor, args:_*) with Isar_Document)
334 if (phase == Session.Ready) {
335 global_state.change(_ => Document.State.init) // FIXME event bus!?
336 phase = Session.Shutdown
339 phase = Session.Inactive
344 case Interrupt if prover.isDefined =>
347 case Init_Node(name, master_dir, header, perspective, text) if prover.isDefined =>
348 // FIXME compare with existing node
349 handle_edits(name, master_dir, header,
350 List(Document.Node.Clear(),
351 Document.Node.Edits(List(Text.Edit.insert(0, text))),
352 Document.Node.Perspective(perspective)))
355 case Edit_Node(name, master_dir, header, perspective, text_edits) if prover.isDefined =>
356 if (text_edits.isEmpty && global_state().tip_stable &&
357 perspective.range.stop <= global_state().last_exec_offset(name))
358 update_perspective(name, perspective)
360 handle_edits(name, master_dir, header,
361 List(Document.Node.Edits(text_edits), Document.Node.Perspective(perspective)))
364 case change: Change_Node if prover.isDefined =>
365 handle_change(change)
367 case input: Isabelle_Process.Input =>
368 raw_messages.event(input)
370 case result: Isabelle_Process.Result =>
371 handle_result(result)
372 raw_messages.event(result)
374 case bad => System.err.println("session_actor: ignoring bad message " + bad)
383 def start(timeout: Time, args: List[String]) { session_actor ! Start(timeout, args) }
385 def stop() { command_change_buffer !? Stop; session_actor !? Stop }
387 def interrupt() { session_actor ! Interrupt }
389 // FIXME simplify signature
390 def init_node(name: String, master_dir: String,
391 header: Document.Node_Header, perspective: Text.Perspective, text: String)
392 { session_actor !? Init_Node(name, master_dir, header, perspective, text) }
394 // FIXME simplify signature
395 def edit_node(name: String, master_dir: String, header: Document.Node_Header,
396 perspective: Text.Perspective, edits: List[Text.Edit])
397 { session_actor !? Edit_Node(name, master_dir, header, perspective, edits) }