src/Pure/System/session.scala
author wenzelm
Tue, 30 Aug 2011 16:04:26 +0200
changeset 45469 479c07072992
parent 45446 24444588fddd
child 45499 76c2e3ddc183
permissions -rw-r--r--
tuned signature;
     1 /*  Title:      Pure/System/session.scala
     2     Author:     Makarius
     3     Options:    :folding=explicit:collapseFolds=1:
     4 
     5 Main Isabelle/Scala session, potentially with running prover process.
     6 */
     7 
     8 package isabelle
     9 
    10 import java.lang.System
    11 
    12 import scala.actors.TIMEOUT
    13 import scala.actors.Actor
    14 import scala.actors.Actor._
    15 
    16 
    17 object Session
    18 {
    19   /* events */
    20 
    21   //{{{
    22   case object Global_Settings
    23   case object Perspective
    24   case object Assignment
    25   case class Commands_Changed(set: Set[Command])
    26 
    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
    33   //}}}
    34 }
    35 
    36 
    37 class Session(thy_load: Thy_Load)
    38 {
    39   /* real time parameters */  // FIXME properties or settings (!?)
    40 
    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.)
    45 
    46 
    47   /* pervasive event buses */
    48 
    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]
    55 
    56 
    57 
    58   /** buffered command changes (delay_first discipline) **/
    59 
    60   //{{{
    61   private case object Stop
    62 
    63   private val (_, command_change_buffer) =
    64     Simple_Thread.actor("command_change_buffer", daemon = true)
    65   {
    66     var changed: Set[Command] = Set()
    67     var flush_time: Option[Long] = None
    68 
    69     def flush_timeout: Long =
    70       flush_time match {
    71         case None => 5000L
    72         case Some(time) => (time - System.currentTimeMillis()) max 0
    73       }
    74 
    75     def flush()
    76     {
    77       if (!changed.isEmpty) commands_changed.event(Session.Commands_Changed(changed))
    78       changed = Set()
    79       flush_time = None
    80     }
    81 
    82     def invoke()
    83     {
    84       val now = System.currentTimeMillis()
    85       flush_time match {
    86         case None => flush_time = Some(now + output_delay.ms)
    87         case Some(time) => if (now >= time) flush()
    88       }
    89     }
    90 
    91     var finished = false
    92     while (!finished) {
    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)
    98       }
    99     }
   100   }
   101   //}}}
   102 
   103 
   104 
   105   /** main protocol actor **/
   106 
   107   /* global state */
   108 
   109   @volatile var verbose: Boolean = false
   110 
   111   @volatile private var syntax = new Outer_Syntax
   112   def current_syntax(): Outer_Syntax = syntax
   113 
   114   @volatile private var reverse_syslog = List[XML.Elem]()
   115   def syslog(): String = reverse_syslog.reverse.map(msg => XML.content(msg).mkString).mkString("\n")
   116 
   117   @volatile private var _phase: Session.Phase = Session.Inactive
   118   private def phase_=(new_phase: Session.Phase)
   119   {
   120     _phase = new_phase
   121     phase_changed.event(new_phase)
   122   }
   123   def phase = _phase
   124   def is_ready: Boolean = phase == Session.Ready
   125 
   126   private val global_state = new Volatile(Document.State.init)
   127   def current_state(): Document.State = global_state()
   128 
   129   def snapshot(name: String, pending_edits: List[Text.Edit]): Document.Snapshot =
   130     global_state().snapshot(name, pending_edits)
   131 
   132 
   133   /* theory files */
   134 
   135   def header_edit(name: String, master_dir: String,
   136     header: Document.Node_Header): Document.Edit_Text =
   137   {
   138     def norm_import(s: String): String =
   139     {
   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)))
   143     }
   144     def norm_use(s: String): String =
   145       thy_load.append(master_dir, Path.explode(s))
   146 
   147     val header1: Document.Node_Header =
   148       header match {
   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)
   153       }
   154     (name, Document.Node.Header(header1))
   155   }
   156 
   157 
   158   /* actor messages */
   159 
   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(
   167     name: String,
   168     doc_edits: List[Document.Edit_Command],
   169     previous: Document.Version,
   170     version: Document.Version)
   171 
   172   private val (_, session_actor) = Simple_Thread.actor("session_actor", daemon = true)
   173   {
   174     val this_actor = self
   175     var prover: Option[Isabelle_Process with Isar_Document] = None
   176 
   177 
   178     /* perspective */
   179 
   180     def update_perspective(name: String, text_perspective: Text.Perspective)
   181     {
   182       val previous = global_state().tip_version
   183       val (perspective, version) = Thy_Syntax.edit_perspective(previous, name, text_perspective)
   184 
   185       val text_edits: List[Document.Edit_Text] =
   186         List((name, Document.Node.Perspective(text_perspective)))
   187       val change =
   188         global_state.change_yield(
   189           _.continue_history(Future.value(previous), text_edits, Future.value(version)))
   190 
   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))
   194 
   195       prover.get.update_perspective(previous.id, version.id, name, perspective)
   196     }
   197 
   198 
   199     /* incoming edits */
   200 
   201     def handle_edits(name: String, master_dir: String,
   202         header: Document.Node_Header, edits: List[Document.Node.Edit[Text.Edit, Text.Perspective]])
   203     //{{{
   204     {
   205       val syntax = current_syntax()
   206       val previous = global_state().history.tip.version
   207 
   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) }
   210       val change =
   211         global_state.change_yield(_.continue_history(previous, text_edits, result.map(_._2)))
   212 
   213       result.map {
   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)
   217       }
   218     }
   219     //}}}
   220 
   221 
   222     /* exec state assignment */
   223 
   224     def handle_assign(id: Document.Version_ID, assign: Document.Assign)
   225     //{{{
   226     {
   227       val cmds = global_state.change_yield(_.assign(id, assign))
   228       for (cmd <- cmds) command_change_buffer ! cmd
   229       assignments.event(Session.Assignment)
   230     }
   231     //}}}
   232 
   233 
   234     /* resulting changes */
   235 
   236     def handle_change(change: Change_Node)
   237     //{{{
   238     {
   239       val previous = change.previous
   240       val version = change.version
   241       val name = change.name
   242       val doc_edits = change.doc_edits
   243 
   244       def id_command(command: Command)
   245       {
   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))
   249         }
   250       }
   251       doc_edits foreach {
   252         case (_, edit) =>
   253           edit foreach { case (c1, c2) => c1 foreach id_command; c2 foreach id_command }
   254       }
   255 
   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)
   259     }
   260     //}}}
   261 
   262 
   263     /* prover results */
   264 
   265     def handle_result(result: Isabelle_Process.Result)
   266     //{{{
   267     {
   268       def bad_result(result: Isabelle_Process.Result)
   269       {
   270         if (verbose)
   271           System.err.println("Ignoring prover result: " + result.message.toString)
   272       }
   273 
   274       result.properties match {
   275         case Position.Id(state_id) if !result.is_raw =>
   276           try {
   277             val st = global_state.change_yield(_.accumulate(state_id, result.message))
   278             command_change_buffer ! st.command
   279           }
   280           catch {
   281             case _: Document.State.Fail => bad_result(result)
   282           }
   283         case Markup.Invoke_Scala(name, id) if result.is_raw =>
   284           Future.fork {
   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)
   288           }
   289         case Markup.Cancel_Scala(id) =>
   290           System.err.println("cancel_scala " + id)  // FIXME cancel JVM task
   291         case _ =>
   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
   299             }
   300             else if (result.is_exit && phase == Session.Startup) phase = Session.Failed
   301             else if (result.is_exit) phase = Session.Inactive
   302           }
   303           else if (result.is_stdout) { }
   304           else if (result.is_status) {
   305             result.body match {
   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)
   313             }
   314           }
   315           else bad_result(result)
   316       }
   317     }
   318     //}}}
   319 
   320 
   321     /* main loop */
   322 
   323     //{{{
   324     var finished = false
   325     while (!finished) {
   326       receive {
   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)
   331           }
   332 
   333         case Stop =>
   334           if (phase == Session.Ready) {
   335             global_state.change(_ => Document.State.init)  // FIXME event bus!?
   336             phase = Session.Shutdown
   337             prover.get.terminate
   338             prover = None
   339             phase = Session.Inactive
   340           }
   341           finished = true
   342           reply(())
   343 
   344         case Interrupt if prover.isDefined =>
   345           prover.get.interrupt
   346 
   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)))
   353           reply(())
   354 
   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)
   359           else
   360             handle_edits(name, master_dir, header,
   361               List(Document.Node.Edits(text_edits), Document.Node.Perspective(perspective)))
   362           reply(())
   363 
   364         case change: Change_Node if prover.isDefined =>
   365           handle_change(change)
   366 
   367         case input: Isabelle_Process.Input =>
   368           raw_messages.event(input)
   369 
   370         case result: Isabelle_Process.Result =>
   371           handle_result(result)
   372           raw_messages.event(result)
   373 
   374         case bad => System.err.println("session_actor: ignoring bad message " + bad)
   375       }
   376     }
   377     //}}}
   378   }
   379 
   380 
   381   /* actions */
   382 
   383   def start(timeout: Time, args: List[String]) { session_actor ! Start(timeout, args) }
   384 
   385   def stop() { command_change_buffer !? Stop; session_actor !? Stop }
   386 
   387   def interrupt() { session_actor ! Interrupt }
   388 
   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) }
   393 
   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) }
   398 }