src/Pure/System/session.scala
author wenzelm
Sun, 03 Jul 2011 19:53:35 +0200
changeset 44526 a912f0b02359
parent 44525 e32de528b5ef
child 44528 511df47bcadc
permissions -rw-r--r--
eliminated null;
     1 /*  Title:      Pure/System/session.scala
     2     Author:     Makarius
     3     Options:    :folding=explicit:collapseFolds=1:
     4 
     5 Isabelle session, potentially with running prover.
     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   /* abstract file store */
    20 
    21   abstract class File_Store
    22   {
    23     def read(path: Path): String
    24   }
    25 
    26 
    27   /* events */
    28 
    29   case object Global_Settings
    30   case object Perspective
    31   case object Assignment
    32   case class Commands_Changed(set: Set[Command])
    33 
    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
    40 }
    41 
    42 
    43 class Session(val system: Isabelle_System, val file_store: Session.File_Store)
    44 {
    45   /* real time parameters */  // FIXME properties or settings (!?)
    46 
    47   // user input (e.g. text edits, cursor movement)
    48   val input_delay = Time.seconds(0.3)
    49 
    50   // prover output (markup, common messages)
    51   val output_delay = Time.seconds(0.1)
    52 
    53   // GUI layout updates
    54   val update_delay = Time.seconds(0.5)
    55 
    56 
    57   /* pervasive event buses */
    58 
    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]
    65 
    66 
    67   /* unique ids */
    68 
    69   private var id_count: Document.ID = 0
    70   def new_id(): Document.ID = synchronized {
    71     require(id_count > java.lang.Long.MIN_VALUE)
    72     id_count -= 1
    73     id_count
    74   }
    75 
    76 
    77 
    78   /** buffered command changes (delay_first discipline) **/
    79 
    80   private case object Stop
    81 
    82   private val (_, command_change_buffer) =
    83     Simple_Thread.actor("command_change_buffer", daemon = true)
    84   //{{{
    85   {
    86     var changed: Set[Command] = Set()
    87     var flush_time: Option[Long] = None
    88 
    89     def flush_timeout: Long =
    90       flush_time match {
    91         case None => 5000L
    92         case Some(time) => (time - System.currentTimeMillis()) max 0
    93       }
    94 
    95     def flush()
    96     {
    97       if (!changed.isEmpty) commands_changed.event(Session.Commands_Changed(changed))
    98       changed = Set()
    99       flush_time = None
   100     }
   101 
   102     def invoke()
   103     {
   104       val now = System.currentTimeMillis()
   105       flush_time match {
   106         case None => flush_time = Some(now + output_delay.ms)
   107         case Some(time) => if (now >= time) flush()
   108       }
   109     }
   110 
   111     var finished = false
   112     while (!finished) {
   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)
   118       }
   119     }
   120   }
   121   //}}}
   122 
   123 
   124 
   125   /** main protocol actor **/
   126 
   127   val thy_header = new Thy_Header(system.symbols)
   128 
   129   @volatile private var syntax = new Outer_Syntax(system.symbols)
   130   def current_syntax(): Outer_Syntax = syntax
   131 
   132   @volatile private var reverse_syslog = List[XML.Elem]()
   133   def syslog(): String = reverse_syslog.reverse.map(msg => XML.content(msg).mkString).mkString("\n")
   134 
   135   @volatile private var _phase: Session.Phase = Session.Inactive
   136   def phase = _phase
   137   private def phase_=(new_phase: Session.Phase)
   138   {
   139     _phase = new_phase
   140     phase_changed.event(new_phase)
   141   }
   142   def is_ready: Boolean = phase == Session.Ready
   143 
   144   private val global_state = new Volatile(Document.State.init)
   145   def current_state(): Document.State = global_state.peek()
   146 
   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])
   153 
   154   var verbose: Boolean = false
   155 
   156   private val (_, session_actor) = Simple_Thread.actor("session_actor", daemon = true)
   157   {
   158     val this_actor = self
   159     var prover: Option[Isabelle_Process with Isar_Document] = None
   160 
   161 
   162     /* document changes */
   163 
   164     def handle_change(change: Document.Change)
   165     //{{{
   166     {
   167       val previous = change.previous.get_finished
   168       val (node_edits, version) = change.result.get_finished
   169 
   170       var former_assignment = global_state.peek().the_assignment(previous).get_finished
   171       for {
   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
   176 
   177       val id_edits =
   178         node_edits map {
   179           case (name, None) => (name, None)
   180           case (name, Some(cmd_edits)) =>
   181             val ids =
   182               cmd_edits map {
   183                 case (c1, c2) =>
   184                   val id1 = c1.map(_.id)
   185                   val id2 =
   186                     c2 match {
   187                       case None => None
   188                       case Some(command) =>
   189                         if (global_state.peek().lookup_command(command.id).isEmpty) {
   190                           global_state.change(_.define_command(command))
   191                           prover.get.
   192                             define_command(command.id, system.symbols.encode(command.source))
   193                         }
   194                         Some(command.id)
   195                     }
   196                   (id1, id2)
   197               }
   198             (name -> Some(ids))
   199         }
   200       global_state.change(_.define_version(version, former_assignment))
   201       prover.get.edit_version(previous.id, version.id, id_edits)
   202     }
   203     //}}}
   204 
   205 
   206     /* prover results */
   207 
   208     def bad_result(result: Isabelle_Process.Result)
   209     {
   210       if (verbose)
   211         System.err.println("Ignoring prover result: " + result.message.toString)
   212     }
   213 
   214     def handle_result(result: Isabelle_Process.Result)
   215     //{{{
   216     {
   217       result.properties match {
   218         case Position.Id(state_id) =>
   219           try {
   220             val st = global_state.change_yield(_.accumulate(state_id, result.message))
   221             command_change_buffer ! st.command
   222           }
   223           catch { case _: Document.State.Fail => bad_result(result) }
   224         case _ =>
   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
   232             }
   233             else if (result.is_exit && phase == Session.Startup) phase = Session.Failed
   234             else if (result.is_exit) phase = Session.Inactive
   235           }
   236           else if (result.is_stdout) { }
   237           else if (result.is_status) {
   238             result.body match {
   239               case List(Isar_Document.Assign(id, edits)) =>
   240                 try {
   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)
   244                 }
   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)
   249             }
   250           }
   251           else bad_result(result)
   252         }
   253 
   254       raw_messages.event(result)
   255     }
   256     //}}}
   257 
   258 
   259     def edit_version(edits: List[Document.Edit_Text])
   260     //{{{
   261     {
   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))
   266 
   267       change.version.map(_ => {
   268         assignments.await { global_state.peek().is_assigned(previous.get_finished) }
   269         this_actor ! change })
   270     }
   271     //}}}
   272 
   273 
   274     /* main loop */
   275 
   276     var finished = false
   277     while (!finished) {
   278       receive {
   279         case Interrupt_Prover =>
   280           prover.map(_.interrupt)
   281 
   282         case Edit_Node(thy_name, header, text_edits) if prover.isDefined =>
   283           edit_version(List((thy_name, Some(text_edits))))
   284           reply(())
   285 
   286         case Init_Node(thy_name, header, text) if prover.isDefined =>
   287           // FIXME compare with existing node
   288           edit_version(List(
   289             (thy_name, None),
   290             (thy_name, Some(List(Text.Edit.insert(0, text))))))
   291           reply(())
   292 
   293         case change: Document.Change if prover.isDefined =>
   294           handle_change(change)
   295 
   296         case result: Isabelle_Process.Result => handle_result(result)
   297 
   298         case Start(timeout, args) if prover.isEmpty =>
   299           if (phase == Session.Inactive || phase == Session.Failed) {
   300             phase = Session.Startup
   301             prover =
   302               Some(new Isabelle_Process(system, timeout, this_actor, args:_*) with Isar_Document)
   303           }
   304 
   305         case Stop =>
   306           if (phase == Session.Ready) {
   307             global_state.change(_ => Document.State.init)  // FIXME event bus!?
   308             phase = Session.Shutdown
   309             prover.get.terminate
   310             phase = Session.Inactive
   311           }
   312           finished = true
   313           reply(())
   314 
   315         case bad if prover.isDefined =>
   316           System.err.println("session_actor: ignoring bad message " + bad)
   317       }
   318     }
   319   }
   320 
   321 
   322 
   323   /** main methods **/
   324 
   325   def start(timeout: Time, args: List[String]) { session_actor ! Start(timeout, args) }
   326 
   327   def stop() { command_change_buffer !? Stop; session_actor !? Stop }
   328 
   329   def interrupt() { session_actor ! Interrupt_Prover }
   330 
   331   def edit_node(thy_name: String, header: Exn.Result[Thy_Header.Header], edits: List[Text.Edit])
   332   {
   333     session_actor !? Edit_Node(thy_name, header, edits)
   334   }
   335 
   336   def init_node(thy_name: String, header: Exn.Result[Thy_Header.Header], text: String)
   337   {
   338     session_actor !? Init_Node(thy_name, header, text)
   339   }
   340 
   341   def snapshot(name: String, pending_edits: List[Text.Edit]): Document.Snapshot =
   342     global_state.peek().snapshot(name, pending_edits)
   343 }