src/Pure/PIDE/document.scala
author wenzelm
Sun Aug 15 18:41:23 2010 +0200 (2010-08-15)
changeset 38418 9a7af64d71bb
parent 38417 b8922ae21111
child 38424 940a404e45e2
permissions -rw-r--r--
more explicit / functional ML version of document model;
tuned;
     1 /*  Title:      Pure/PIDE/document.scala
     2     Author:     Makarius
     3 
     4 Document as collection of named nodes, each consisting of an editable
     5 list of commands, associated with asynchronous execution process.
     6 */
     7 
     8 package isabelle
     9 
    10 
    11 import scala.collection.mutable
    12 
    13 
    14 object Document
    15 {
    16   /* formal identifiers */
    17 
    18   type ID = Long
    19 
    20   object ID {
    21     def apply(id: ID): String = Markup.Long.apply(id)
    22     def unapply(s: String): Option[ID] = Markup.Long.unapply(s)
    23   }
    24 
    25   type Version_ID = ID
    26   type Command_ID = ID
    27   type Exec_ID = ID
    28 
    29   val NO_ID: ID = 0
    30 
    31 
    32 
    33   /** named document nodes **/
    34 
    35   object Node
    36   {
    37     val empty: Node = new Node(Linear_Set())
    38 
    39     def command_starts(commands: Iterator[Command], offset: Int = 0): Iterator[(Command, Int)] =
    40     {
    41       var i = offset
    42       for (command <- commands) yield {
    43         val start = i
    44         i += command.length
    45         (command, start)
    46       }
    47     }
    48   }
    49 
    50   class Node(val commands: Linear_Set[Command])
    51   {
    52     /* command ranges */
    53 
    54     def command_starts: Iterator[(Command, Int)] =
    55       Node.command_starts(commands.iterator)
    56 
    57     def command_start(cmd: Command): Option[Int] =
    58       command_starts.find(_._1 == cmd).map(_._2)
    59 
    60     def command_range(i: Int): Iterator[(Command, Int)] =
    61       command_starts dropWhile { case (cmd, start) => start + cmd.length <= i }
    62 
    63     def command_range(i: Int, j: Int): Iterator[(Command, Int)] =
    64       command_range(i) takeWhile { case (_, start) => start < j }
    65 
    66     def command_at(i: Int): Option[(Command, Int)] =
    67     {
    68       val range = command_range(i)
    69       if (range.hasNext) Some(range.next) else None
    70     }
    71 
    72     def proper_command_at(i: Int): Option[Command] =
    73       command_at(i) match {
    74         case Some((command, _)) =>
    75           commands.reverse_iterator(command).find(cmd => !cmd.is_ignored)
    76         case None => None
    77       }
    78   }
    79 
    80 
    81   /* document versions */
    82 
    83   object Version
    84   {
    85     val init: Version = new Version(NO_ID, Map().withDefaultValue(Node.empty))
    86   }
    87 
    88   class Version(val id: Version_ID, val nodes: Map[String, Node])
    89 
    90 
    91 
    92   /** changes of plain text, eventually resulting in document edits **/
    93 
    94   type Node_Text_Edit = (String, List[Text_Edit])  // FIXME None: remove
    95 
    96   type Edit[C] =
    97    (String,  // node name
    98     Option[List[(Option[C], Option[C])]])  // None: remove, Some: insert/remove commands
    99 
   100   abstract class Snapshot
   101   {
   102     val version: Version
   103     val node: Node
   104     val is_outdated: Boolean
   105     def convert(offset: Int): Int
   106     def revert(offset: Int): Int
   107     def lookup_command(id: Command_ID): Option[Command]
   108     def state(command: Command): Command.State
   109   }
   110 
   111   object Change
   112   {
   113     val init = new Change(Future.value(Version.init), Nil, Future.value(Nil, Version.init))
   114   }
   115 
   116   class Change(
   117     val previous: Future[Version],
   118     val edits: List[Node_Text_Edit],
   119     val result: Future[(List[Edit[Command]], Version)])
   120   {
   121     val current: Future[Version] = result.map(_._2)
   122     def is_finished: Boolean = previous.is_finished && current.is_finished
   123   }
   124 
   125 
   126 
   127   /** global state -- document structure and execution process **/
   128 
   129   object State
   130   {
   131     class Fail(state: State) extends Exception
   132 
   133     val init = State().define_version(Version.init, Map()).assign(Version.init.id, Nil)
   134 
   135     class Assignment(former_assignment: Map[Command, Exec_ID])
   136     {
   137       @volatile private var tmp_assignment = former_assignment
   138       private val promise = Future.promise[Map[Command, Exec_ID]]
   139       def is_finished: Boolean = promise.is_finished
   140       def join: Map[Command, Exec_ID] = promise.join
   141       def assign(command_execs: List[(Command, Exec_ID)])
   142       {
   143         promise.fulfill(tmp_assignment ++ command_execs)
   144         tmp_assignment = Map()
   145       }
   146     }
   147   }
   148 
   149   case class State(
   150     val versions: Map[Version_ID, Version] = Map(),
   151     val commands: Map[Command_ID, Command.State] = Map(),
   152     val execs: Map[Exec_ID, (Command.State, Set[Version])] = Map(),
   153     val assignments: Map[Version, State.Assignment] = Map(),
   154     val disposed: Set[ID] = Set())  // FIXME unused!?
   155   {
   156     private def fail[A]: A = throw new State.Fail(this)
   157 
   158     def define_version(version: Version, former_assignment: Map[Command, Exec_ID]): State =
   159     {
   160       val id = version.id
   161       if (versions.isDefinedAt(id) || disposed(id)) fail
   162       copy(versions = versions + (id -> version),
   163         assignments = assignments + (version -> new State.Assignment(former_assignment)))
   164     }
   165 
   166     def define_command(command: Command): State =
   167     {
   168       val id = command.id
   169       if (commands.isDefinedAt(id) || disposed(id)) fail
   170       copy(commands = commands + (id -> command.empty_state))
   171     }
   172 
   173     def lookup_command(id: Command_ID): Option[Command] = commands.get(id).map(_.command)
   174 
   175     def the_version(id: Version_ID): Version = versions.getOrElse(id, fail)
   176     def the_command(id: Command_ID): Command.State = commands.getOrElse(id, fail)
   177     def the_exec_state(id: Exec_ID): Command.State = execs.getOrElse(id, fail)._1
   178     def the_assignment(version: Version): State.Assignment = assignments.getOrElse(version, fail)
   179 
   180     def accumulate(id: ID, message: XML.Tree): (Command.State, State) =
   181       execs.get(id) match {
   182         case Some((st, occs)) =>
   183           val new_st = st.accumulate(message)
   184           (new_st, copy(execs = execs + (id -> (new_st, occs))))
   185         case None =>
   186           commands.get(id) match {
   187             case Some(st) =>
   188               val new_st = st.accumulate(message)
   189               (new_st, copy(commands = commands + (id -> new_st)))
   190             case None => fail
   191           }
   192       }
   193 
   194     def assign(id: Version_ID, edits: List[(Command_ID, Exec_ID)]): State =
   195     {
   196       val version = the_version(id)
   197       val occs = Set(version)  // FIXME unused (!?)
   198 
   199       var new_execs = execs
   200       val assigned_execs =
   201         for ((cmd_id, exec_id) <- edits) yield {
   202           val st = the_command(cmd_id)
   203           if (new_execs.isDefinedAt(exec_id) || disposed(exec_id)) fail
   204           new_execs += (exec_id -> (st, occs))
   205           (st.command, exec_id)
   206         }
   207       the_assignment(version).assign(assigned_execs)  // FIXME explicit value instead of promise (!?)
   208       copy(execs = new_execs)
   209     }
   210 
   211     def is_assigned(version: Version): Boolean =
   212       assignments.get(version) match {
   213         case Some(assgn) => assgn.is_finished
   214         case None => false
   215       }
   216 
   217     def command_state(version: Version, command: Command): Command.State =
   218     {
   219       val assgn = the_assignment(version)
   220       require(assgn.is_finished)
   221       the_exec_state(assgn.join.getOrElse(command, fail))
   222     }
   223   }
   224 }
   225