src/Pure/PIDE/document.scala
author wenzelm
Sat, 14 Aug 2010 11:52:24 +0200
changeset 38655 e8197eea3cd0
parent 38652 8b15d0f98962
child 38656 7eb0f6991e25
permissions -rw-r--r--
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.
     6 */
     7 
     8 package isabelle
     9 
    10 
    11 import scala.collection.mutable
    12 import scala.annotation.tailrec
    13 
    14 
    15 object Document
    16 {
    17   /* formal identifiers */
    18 
    19   type ID = Long
    20   type Version_ID = ID
    21   type Command_ID = ID
    22   type Exec_ID = ID
    23 
    24   val NO_ID: ID = 0
    25 
    26   def parse_id(s: String): ID = java.lang.Long.parseLong(s)
    27   def print_id(id: ID): String = id.toString
    28 
    29 
    30 
    31   /** named document nodes **/
    32 
    33   object Node
    34   {
    35     val empty: Node = new Node(Linear_Set())
    36 
    37     def command_starts(commands: Iterator[Command], offset: Int = 0): Iterator[(Command, Int)] =
    38     {
    39       var i = offset
    40       for (command <- commands) yield {
    41         val start = i
    42         i += command.length
    43         (command, start)
    44       }
    45     }
    46   }
    47 
    48   class Node(val commands: Linear_Set[Command])
    49   {
    50     /* command ranges */
    51 
    52     def command_starts: Iterator[(Command, Int)] =
    53       Node.command_starts(commands.iterator)
    54 
    55     def command_start(cmd: Command): Option[Int] =
    56       command_starts.find(_._1 == cmd).map(_._2)
    57 
    58     def command_range(i: Int): Iterator[(Command, Int)] =
    59       command_starts dropWhile { case (cmd, start) => start + cmd.length <= i }
    60 
    61     def command_range(i: Int, j: Int): Iterator[(Command, Int)] =
    62       command_range(i) takeWhile { case (_, start) => start < j }
    63 
    64     def command_at(i: Int): Option[(Command, Int)] =
    65     {
    66       val range = command_range(i)
    67       if (range.hasNext) Some(range.next) else None
    68     }
    69 
    70     def proper_command_at(i: Int): Option[Command] =
    71       command_at(i) match {
    72         case Some((command, _)) =>
    73           commands.reverse_iterator(command).find(cmd => !cmd.is_ignored)
    74         case None => None
    75       }
    76   }
    77 
    78 
    79   /* initial document */
    80 
    81   val init: Document = new Document(NO_ID, Map().withDefaultValue(Node.empty))
    82 
    83 
    84 
    85   /** changes of plain text, eventually resulting in document edits **/
    86 
    87   type Node_Text_Edit = (String, List[Text_Edit])  // FIXME None: remove
    88 
    89   type Edit[C] =
    90    (String,  // node name
    91     Option[List[(Option[C], Option[C])]])  // None: remove, Some: insert/remove commands
    92 
    93   abstract class Snapshot
    94   {
    95     val document: Document
    96     val node: Document.Node
    97     val is_outdated: Boolean
    98     def convert(offset: Int): Int
    99     def revert(offset: Int): Int
   100     def lookup_command(id: Command_ID): Option[Command]
   101     def state(command: Command): Command.State
   102   }
   103 
   104   object Change
   105   {
   106     val init = new Change(Future.value(Document.init), Nil, Future.value(Nil, Document.init))
   107   }
   108 
   109   class Change(
   110     val prev: Future[Document],
   111     val edits: List[Node_Text_Edit],
   112     val result: Future[(List[Edit[Command]], Document)])
   113   {
   114     val document: Future[Document] = result.map(_._2)
   115     def is_finished: Boolean = prev.is_finished && document.is_finished
   116   }
   117 
   118 
   119 
   120   /** editing **/
   121 
   122   def text_edits(session: Session, old_doc: Document, edits: List[Node_Text_Edit])
   123       : (List[Edit[Command]], Document) =
   124   {
   125     /* phase 1: edit individual command source */
   126 
   127     @tailrec def edit_text(eds: List[Text_Edit],
   128         commands: Linear_Set[Command]): Linear_Set[Command] =
   129     {
   130       eds match {
   131         case e :: es =>
   132           Node.command_starts(commands.iterator).find {
   133             case (cmd, cmd_start) =>
   134               e.can_edit(cmd.source, cmd_start) ||
   135                 e.is_insert && e.start == cmd_start + cmd.length
   136           } match {
   137             case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
   138               val (rest, text) = e.edit(cmd.source, cmd_start)
   139               val new_commands = commands.insert_after(Some(cmd), Command.unparsed(text)) - cmd
   140               edit_text(rest.toList ::: es, new_commands)
   141 
   142             case Some((cmd, cmd_start)) =>
   143               edit_text(es, commands.insert_after(Some(cmd), Command.unparsed(e.text)))
   144 
   145             case None =>
   146               require(e.is_insert && e.start == 0)
   147               edit_text(es, commands.insert_after(None, Command.unparsed(e.text)))
   148           }
   149         case Nil => commands
   150       }
   151     }
   152 
   153 
   154     /* phase 2: recover command spans */
   155 
   156     @tailrec def parse_spans(commands: Linear_Set[Command]): Linear_Set[Command] =
   157     {
   158       commands.iterator.find(_.is_unparsed) match {
   159         case Some(first_unparsed) =>
   160           val first =
   161             commands.reverse_iterator(first_unparsed).find(_.is_command) getOrElse commands.head
   162           val last =
   163             commands.iterator(first_unparsed).find(_.is_command) getOrElse commands.last
   164           val range =
   165             commands.iterator(first).takeWhile(_ != last).toList ::: List(last)
   166 
   167           val sources = range.flatMap(_.span.map(_.source))
   168           val spans0 = Thy_Syntax.parse_spans(session.current_syntax.scan(sources.mkString))
   169 
   170           val (before_edit, spans1) =
   171             if (!spans0.isEmpty && first.is_command && first.span == spans0.head)
   172               (Some(first), spans0.tail)
   173             else (commands.prev(first), spans0)
   174 
   175           val (after_edit, spans2) =
   176             if (!spans1.isEmpty && last.is_command && last.span == spans1.last)
   177               (Some(last), spans1.take(spans1.length - 1))
   178             else (commands.next(last), spans1)
   179 
   180           val inserted = spans2.map(span => new Command(session.create_id(), span))
   181           val new_commands =
   182             commands.delete_between(before_edit, after_edit).append_after(before_edit, inserted)
   183           parse_spans(new_commands)
   184 
   185         case None => commands
   186       }
   187     }
   188 
   189 
   190     /* resulting document edits */
   191 
   192     {
   193       val doc_edits = new mutable.ListBuffer[Edit[Command]]
   194       var nodes = old_doc.nodes
   195 
   196       for ((name, text_edits) <- edits) {
   197         val commands0 = nodes(name).commands
   198         val commands1 = edit_text(text_edits, commands0)
   199         val commands2 = parse_spans(commands1)   // FIXME somewhat slow
   200 
   201         val removed_commands = commands0.iterator.filter(!commands2.contains(_)).toList
   202         val inserted_commands = commands2.iterator.filter(!commands0.contains(_)).toList
   203 
   204         val cmd_edits =
   205           removed_commands.reverse.map(cmd => (commands0.prev(cmd), None)) :::
   206           inserted_commands.map(cmd => (commands2.prev(cmd), Some(cmd)))
   207 
   208         doc_edits += (name -> Some(cmd_edits))
   209         nodes += (name -> new Node(commands2))
   210       }
   211       (doc_edits.toList, new Document(session.create_id(), nodes))
   212     }
   213   }
   214 
   215 
   216 
   217   /** global state -- accumulated prover results **/
   218 
   219   object State
   220   {
   221     class Fail(state: State) extends Exception
   222 
   223     val init = State().define_document(Document.init, Map()).assign(Document.init.id, Nil)
   224 
   225     class Assignment(former_assignment: Map[Command, Exec_ID])
   226     {
   227       @volatile private var tmp_assignment = former_assignment
   228       private val promise = Future.promise[Map[Command, Exec_ID]]
   229       def is_finished: Boolean = promise.is_finished
   230       def join: Map[Command, Exec_ID] = promise.join
   231       def assign(command_execs: List[(Command, Exec_ID)])
   232       {
   233         promise.fulfill(tmp_assignment ++ command_execs)
   234         tmp_assignment = Map()
   235       }
   236     }
   237   }
   238 
   239   case class State(
   240     val documents: Map[Version_ID, Document] = Map(),
   241     val commands: Map[Command_ID, Command.State] = Map(),
   242     val execs: Map[Exec_ID, (Command.State, Set[Document])] = Map(),
   243     val assignments: Map[Document, State.Assignment] = Map(),
   244     val disposed: Set[ID] = Set())  // FIXME unused!?
   245   {
   246     private def fail[A]: A = throw new State.Fail(this)
   247 
   248     def define_document(document: Document, former_assignment: Map[Command, Exec_ID]): State =
   249     {
   250       val id = document.id
   251       if (documents.isDefinedAt(id) || disposed(id)) fail
   252       copy(documents = documents + (id -> document),
   253         assignments = assignments + (document -> new State.Assignment(former_assignment)))
   254     }
   255 
   256     def define_command(command: Command): State =
   257     {
   258       val id = command.id
   259       if (commands.isDefinedAt(id) || disposed(id)) fail
   260       copy(commands = commands + (id -> command.empty_state))
   261     }
   262 
   263     def lookup_command(id: Command_ID): Option[Command] = commands.get(id).map(_.command)
   264 
   265     def the_document(id: Version_ID): Document = documents.getOrElse(id, fail)
   266     def the_command(id: Command_ID): Command.State = commands.getOrElse(id, fail)
   267     def the_exec_state(id: Exec_ID): Command.State = execs.getOrElse(id, fail)._1
   268     def the_assignment(document: Document): State.Assignment = assignments.getOrElse(document, fail)
   269 
   270     def accumulate(id: ID, message: XML.Tree): (Command.State, State) =
   271       execs.get(id) match {
   272         case Some((st, docs)) =>
   273           val new_st = st.accumulate(message)
   274           (new_st, copy(execs = execs + (id -> (new_st, docs))))
   275         case None =>
   276           commands.get(id) match {
   277             case Some(st) =>
   278               val new_st = st.accumulate(message)
   279               (new_st, copy(commands = commands + (id -> new_st)))
   280             case None => fail
   281           }
   282       }
   283 
   284     def assign(id: Version_ID, edits: List[(Command_ID, Exec_ID)]): State =
   285     {
   286       val doc = the_document(id)
   287       val docs = Set(doc)  // FIXME unused (!?)
   288 
   289       var new_execs = execs
   290       val assigned_execs =
   291         for ((cmd_id, exec_id) <- edits) yield {
   292           val st = the_command(cmd_id)
   293           if (new_execs.isDefinedAt(exec_id) || disposed(exec_id)) fail
   294           new_execs += (exec_id -> (st, docs))
   295           (st.command, exec_id)
   296         }
   297       the_assignment(doc).assign(assigned_execs)  // FIXME explicit value instead of promise (!?)
   298       copy(execs = new_execs)
   299     }
   300 
   301     def is_assigned(document: Document): Boolean =
   302       assignments.get(document) match {
   303         case Some(assgn) => assgn.is_finished
   304         case None => false
   305       }
   306 
   307     def command_state(document: Document, command: Command): Command.State =
   308     {
   309       val assgn = the_assignment(document)
   310       require(assgn.is_finished)
   311       the_exec_state(assgn.join.getOrElse(command, fail))
   312     }
   313   }
   314 }
   315 
   316 
   317 class Document(
   318     val id: Document.Version_ID,
   319     val nodes: Map[String, Document.Node])
   320