src/Pure/System/session.scala
author wenzelm
Thu Aug 12 13:43:55 2010 +0200 (2010-08-12)
changeset 38359 96b22dfeb56a
parent 38355 8cb265fb12fe
child 38360 53224a4d2f0e
permissions -rw-r--r--
consider snapshot as service of Session, not Document.Change;
     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 
    11 import scala.actors.TIMEOUT
    12 import scala.actors.Actor
    13 import scala.actors.Actor._
    14 
    15 
    16 object Session
    17 {
    18   /* events */
    19 
    20   case object Global_Settings
    21   case object Perspective
    22 
    23 
    24   /* managed entities */
    25 
    26   type Entity_ID = Long
    27 
    28   trait Entity
    29   {
    30     val id: Entity_ID
    31     def consume(message: XML.Tree, forward: Command => Unit): Unit
    32   }
    33 }
    34 
    35 
    36 class Session(system: Isabelle_System)
    37 {
    38   /* real time parameters */  // FIXME properties or settings (!?)
    39 
    40   // user input (e.g. text edits, cursor movement)
    41   val input_delay = 300
    42 
    43   // prover output (markup, common messages)
    44   val output_delay = 100
    45 
    46   // GUI layout updates
    47   val update_delay = 500
    48 
    49 
    50   /* pervasive event buses */
    51 
    52   val global_settings = new Event_Bus[Session.Global_Settings.type]
    53   val raw_results = new Event_Bus[Isabelle_Process.Result]
    54   val raw_output = new Event_Bus[Isabelle_Process.Result]
    55   val commands_changed = new Event_Bus[Command_Set]
    56   val perspective = new Event_Bus[Session.Perspective.type]
    57 
    58 
    59   /* unique ids */
    60 
    61   private var id_count: Long = 0
    62   def create_id(): Session.Entity_ID = synchronized {
    63     require(id_count > java.lang.Long.MIN_VALUE)
    64     id_count -= 1
    65     id_count
    66   }
    67 
    68 
    69 
    70   /** main actor **/
    71 
    72   @volatile private var syntax = new Outer_Syntax(system.symbols)
    73   def current_syntax: Outer_Syntax = syntax
    74 
    75   @volatile private var entities = Map[Session.Entity_ID, Session.Entity]()
    76   def lookup_entity(id: Session.Entity_ID): Option[Session.Entity] = entities.get(id)
    77   def lookup_command(id: Session.Entity_ID): Option[Command] =
    78     lookup_entity(id) match {
    79       case Some(cmd: Command) => Some(cmd)
    80       case _ => None
    81     }
    82 
    83   private case class Started(timeout: Int, args: List[String])
    84   private case object Stop
    85 
    86   private lazy val session_actor = actor {
    87 
    88     var prover: Isabelle_Process with Isar_Document = null
    89 
    90     def register(entity: Session.Entity) { entities += (entity.id -> entity) }
    91 
    92     var documents = Map[Document.Version_ID, Document]()
    93     def register_document(doc: Document) { documents += (doc.id -> doc) }
    94     register_document(Document.init)
    95 
    96 
    97     /* document changes */
    98 
    99     def handle_change(change: Document.Change)
   100     //{{{
   101     {
   102       require(change.parent.isDefined)
   103 
   104       val (node_edits, doc) = change.result.join
   105       val id_edits =
   106         node_edits map {
   107           case (name, None) => (name, None)
   108           case (name, Some(cmd_edits)) =>
   109             val chs =
   110               cmd_edits map {
   111                 case (c1, c2) =>
   112                   val id1 = c1.map(_.id)
   113                   val id2 =
   114                     c2 match {
   115                       case None => None
   116                       case Some(command) =>
   117                         if (!lookup_command(command.id).isDefined) {
   118                           register(command)
   119                           prover.define_command(command.id, system.symbols.encode(command.source))
   120                         }
   121                         Some(command.id)
   122                     }
   123                   (id1, id2)
   124               }
   125             (name -> Some(chs))
   126         }
   127       register_document(doc)
   128       prover.edit_document(change.parent.get.id, doc.id, id_edits)
   129     }
   130     //}}}
   131 
   132 
   133     /* prover results */
   134 
   135     def bad_result(result: Isabelle_Process.Result)
   136     {
   137       System.err.println("Ignoring prover result: " + result.message.toString)
   138     }
   139 
   140     def handle_result(result: Isabelle_Process.Result)
   141     //{{{
   142     {
   143       raw_results.event(result)
   144 
   145       val target_id: Option[Session.Entity_ID] = Position.get_id(result.properties)
   146       val target: Option[Session.Entity] =
   147         target_id match {
   148           case None => None
   149           case Some(id) => lookup_entity(id)
   150         }
   151       if (target.isDefined) target.get.consume(result.message, indicate_command_change)
   152       else if (result.is_status) {
   153         // global status message
   154         result.body match {
   155 
   156           // document state assignment
   157           case List(Isar_Document.Assign(edits)) if target_id.isDefined =>
   158             documents.get(target_id.get) match {
   159               case Some(doc) =>
   160                 val states =
   161                   for {
   162                     Isar_Document.Edit(cmd_id, state_id) <- edits
   163                     cmd <- lookup_command(cmd_id)
   164                   } yield {
   165                     val st = cmd.assign_state(state_id)
   166                     register(st)
   167                     (cmd, st)
   168                   }
   169                 doc.assign_states(states)
   170               case None => bad_result(result)
   171             }
   172 
   173           // keyword declarations
   174           case List(Keyword.Command_Decl(name, kind)) => syntax += (name, kind)
   175           case List(Keyword.Keyword_Decl(name)) => syntax += name
   176 
   177           case _ => if (!result.is_ready) bad_result(result)
   178         }
   179       }
   180       else if (result.kind == Markup.EXIT)
   181         prover = null
   182       else if (result.is_raw)
   183         raw_output.event(result)
   184       else if (!result.is_system)   // FIXME syslog (!?)
   185         bad_result(result)
   186     }
   187     //}}}
   188 
   189 
   190     /* prover startup */
   191 
   192     def startup_error(): String =
   193     {
   194       val buf = new StringBuilder
   195       while (
   196         receiveWithin(0) {
   197           case result: Isabelle_Process.Result =>
   198             if (result.is_raw) {
   199               for (text <- XML.content(result.message))
   200                 buf.append(text)
   201             }
   202             true
   203           case TIMEOUT => false
   204         }) {}
   205       buf.toString
   206     }
   207 
   208     def prover_startup(timeout: Int): Option[String] =
   209     {
   210       receiveWithin(timeout) {
   211         case result: Isabelle_Process.Result
   212           if result.kind == Markup.INIT =>
   213           while (receive {
   214             case result: Isabelle_Process.Result =>
   215               handle_result(result); !result.is_ready
   216             }) {}
   217           None
   218 
   219         case result: Isabelle_Process.Result
   220           if result.kind == Markup.EXIT =>
   221           Some(startup_error())
   222 
   223         case TIMEOUT =>  // FIXME clarify
   224           prover.kill; Some(startup_error())
   225       }
   226     }
   227 
   228 
   229     /* main loop */
   230 
   231     val xml_cache = new XML.Cache(131071)
   232 
   233     loop {
   234       react {
   235         case Started(timeout, args) =>
   236           if (prover == null) {
   237             prover = new Isabelle_Process(system, self, args:_*) with Isar_Document
   238             val origin = sender
   239             val opt_err = prover_startup(timeout)
   240             if (opt_err.isDefined) prover = null
   241             origin ! opt_err
   242           }
   243           else reply(None)
   244 
   245         case Stop =>  // FIXME clarify; synchronous
   246           if (prover != null) {
   247             prover.kill
   248             prover = null
   249           }
   250 
   251         case change: Document.Change if prover != null =>
   252           handle_change(change)
   253 
   254         case result: Isabelle_Process.Result =>
   255           handle_result(result.cache(xml_cache))
   256 
   257         case TIMEOUT =>  // FIXME clarify!
   258 
   259         case bad if prover != null =>
   260           System.err.println("session_actor: ignoring bad message " + bad)
   261       }
   262     }
   263   }
   264 
   265 
   266 
   267   /** buffered command changes (delay_first discipline) **/
   268 
   269   private lazy val command_change_buffer = actor
   270   //{{{
   271   {
   272     import scala.compat.Platform.currentTime
   273 
   274     var changed: Set[Command] = Set()
   275     var flush_time: Option[Long] = None
   276 
   277     def flush_timeout: Long =
   278       flush_time match {
   279         case None => 5000L
   280         case Some(time) => (time - currentTime) max 0
   281       }
   282 
   283     def flush()
   284     {
   285       if (!changed.isEmpty) commands_changed.event(Command_Set(changed))
   286       changed = Set()
   287       flush_time = None
   288     }
   289 
   290     def invoke()
   291     {
   292       val now = currentTime
   293       flush_time match {
   294         case None => flush_time = Some(now + output_delay)
   295         case Some(time) => if (now >= time) flush()
   296       }
   297     }
   298 
   299     loop {
   300       reactWithin(flush_timeout) {
   301         case command: Command => changed += command; invoke()
   302         case TIMEOUT => flush()
   303         case bad => System.err.println("command_change_buffer: ignoring bad message " + bad)
   304       }
   305     }
   306   }
   307   //}}}
   308 
   309   def indicate_command_change(command: Command)
   310   {
   311     command_change_buffer ! command
   312   }
   313 
   314 
   315 
   316   /** editor history **/
   317 
   318   private case class Edit_Document(edits: List[Document.Node_Text_Edit])
   319 
   320   private val editor_history = new Actor
   321   {
   322     @volatile private var history = Document.Change.init
   323     def current_change(): Document.Change = history
   324 
   325     def act
   326     {
   327       loop {
   328         react {
   329           case Edit_Document(edits) =>
   330             val old_change = history
   331             val new_id = create_id()
   332             val result: isabelle.Future[(List[Document.Edit[Command]], Document)] =
   333               isabelle.Future.fork {
   334                 val old_doc = old_change.join_document
   335                 old_doc.await_assignment
   336                 Document.text_edits(Session.this, old_doc, new_id, edits)
   337               }
   338             val new_change = new Document.Change(new_id, Some(old_change), edits, result)
   339             history = new_change
   340             new_change.result.map(_ => session_actor ! new_change)
   341             reply(())
   342 
   343           case bad => System.err.println("editor_model: ignoring bad message " + bad)
   344         }
   345       }
   346     }
   347   }
   348   editor_history.start
   349 
   350 
   351 
   352   /** main methods **/
   353 
   354   def started(timeout: Int, args: List[String]): Option[String] =
   355     (session_actor !? Started(timeout, args)).asInstanceOf[Option[String]]
   356 
   357   def stop() { session_actor ! Stop }
   358 
   359   def snapshot(name: String, pending_edits: List[Text_Edit]): Document.Snapshot =
   360     editor_history.current_change().snapshot(name, pending_edits)
   361 
   362   def edit_document(edits: List[Document.Node_Text_Edit]) { editor_history !? Edit_Document(edits) }
   363 }