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