src/Pure/Thy/thy_syntax.scala
author wenzelm
Wed Aug 07 11:44:17 2013 +0200 (2013-08-07)
changeset 52887 98eac7b7eec3
parent 52861 e93d73b51fd0
child 52901 8be75f53db82
permissions -rw-r--r--
tuned signature;
     1 /*  Title:      Pure/Thy/thy_syntax.scala
     2     Author:     Makarius
     3 
     4 Superficial theory syntax: tokens and spans.
     5 */
     6 
     7 package isabelle
     8 
     9 
    10 import scala.collection.mutable
    11 import scala.annotation.tailrec
    12 
    13 
    14 object Thy_Syntax
    15 {
    16   /** nested structure **/
    17 
    18   object Structure
    19   {
    20     sealed abstract class Entry { def length: Int }
    21     case class Block(val name: String, val body: List[Entry]) extends Entry
    22     {
    23       val length: Int = (0 /: body)(_ + _.length)
    24     }
    25     case class Atom(val command: Command) extends Entry
    26     {
    27       def length: Int = command.length
    28     }
    29 
    30     def parse(syntax: Outer_Syntax, node_name: Document.Node.Name, text: CharSequence): Entry =
    31     {
    32       /* stack operations */
    33 
    34       def buffer(): mutable.ListBuffer[Entry] = new mutable.ListBuffer[Entry]
    35       var stack: List[(Int, String, mutable.ListBuffer[Entry])] =
    36         List((0, node_name.theory, buffer()))
    37 
    38       @tailrec def close(level: Int => Boolean)
    39       {
    40         stack match {
    41           case (lev, name, body) :: (_, _, body2) :: rest if level(lev) =>
    42             body2 += Block(name, body.toList)
    43             stack = stack.tail
    44             close(level)
    45           case _ =>
    46         }
    47       }
    48 
    49       def result(): Entry =
    50       {
    51         close(_ => true)
    52         val (_, name, body) = stack.head
    53         Block(name, body.toList)
    54       }
    55 
    56       def add(command: Command)
    57       {
    58         syntax.heading_level(command) match {
    59           case Some(i) =>
    60             close(_ > i)
    61             stack = (i + 1, command.source, buffer()) :: stack
    62           case None =>
    63         }
    64         stack.head._3 += Atom(command)
    65       }
    66 
    67 
    68       /* result structure */
    69 
    70       val spans = parse_spans(syntax.scan(text))
    71       spans.foreach(span => add(Command(Document_ID.none, node_name, span)))
    72       result()
    73     }
    74   }
    75 
    76 
    77 
    78   /** parse spans **/
    79 
    80   def parse_spans(toks: List[Token]): List[List[Token]] =
    81   {
    82     val result = new mutable.ListBuffer[List[Token]]
    83     val span = new mutable.ListBuffer[Token]
    84 
    85     def flush() { if (!span.isEmpty) { result += span.toList; span.clear } }
    86     for (tok <- toks) { if (tok.is_command) flush(); span += tok }
    87     flush()
    88     result.toList
    89   }
    90 
    91 
    92 
    93   /** perspective **/
    94 
    95   def command_perspective(
    96       node: Document.Node,
    97       perspective: Text.Perspective,
    98       overlays: Document.Node.Overlays): (Command.Perspective, Command.Perspective) =
    99   {
   100     if (perspective.is_empty && overlays.is_empty)
   101       (Command.Perspective.empty, Command.Perspective.empty)
   102     else {
   103       val has_overlay = overlays.commands
   104       val visible = new mutable.ListBuffer[Command]
   105       val visible_overlay = new mutable.ListBuffer[Command]
   106       @tailrec
   107       def check_ranges(ranges: List[Text.Range], commands: Stream[(Command, Text.Offset)])
   108       {
   109         (ranges, commands) match {
   110           case (range :: more_ranges, (command, offset) #:: more_commands) =>
   111             val command_range = command.range + offset
   112             range compare command_range match {
   113               case 0 =>
   114                 visible += command
   115                 visible_overlay += command
   116                 check_ranges(ranges, more_commands)
   117               case c =>
   118                 if (has_overlay(command)) visible_overlay += command
   119 
   120                 if (c < 0) check_ranges(more_ranges, commands)
   121                 else check_ranges(ranges, more_commands)
   122             }
   123 
   124           case (Nil, (command, _) #:: more_commands) =>
   125             if (has_overlay(command)) visible_overlay += command
   126 
   127             check_ranges(Nil, more_commands)
   128 
   129           case _ =>
   130         }
   131       }
   132 
   133       val commands =
   134         if (overlays.is_empty) node.command_range(perspective.range)
   135         else node.command_range()
   136       check_ranges(perspective.ranges, commands.toStream)
   137       (Command.Perspective(visible.toList), Command.Perspective(visible_overlay.toList))
   138     }
   139   }
   140 
   141 
   142 
   143   /** header edits: structure and outer syntax **/
   144 
   145   private def header_edits(
   146     base_syntax: Outer_Syntax,
   147     previous: Document.Version,
   148     edits: List[Document.Edit_Text])
   149     : (Outer_Syntax, List[Document.Node.Name], Document.Nodes, List[Document.Edit_Command]) =
   150   {
   151     var updated_imports = false
   152     var updated_keywords = false
   153     var nodes = previous.nodes
   154     val doc_edits = new mutable.ListBuffer[Document.Edit_Command]
   155 
   156     edits foreach {
   157       case (name, Document.Node.Deps(header)) =>
   158         val node = nodes(name)
   159         val update_header =
   160           !node.header.errors.isEmpty || !header.errors.isEmpty || node.header != header
   161         if (update_header) {
   162           val node1 = node.update_header(header)
   163           updated_imports = updated_imports || (node.header.imports != node1.header.imports)
   164           updated_keywords = updated_keywords || (node.header.keywords != node1.header.keywords)
   165           nodes += (name -> node1)
   166           doc_edits += (name -> Document.Node.Deps(header))
   167         }
   168       case _ =>
   169     }
   170 
   171     val syntax =
   172       if (previous.is_init || updated_keywords)
   173         (base_syntax /: nodes.entries) {
   174           case (syn, (_, node)) => syn.add_keywords(node.header.keywords)
   175         }
   176       else previous.syntax
   177 
   178     val reparse =
   179       if (updated_imports || updated_keywords)
   180         nodes.descendants(doc_edits.iterator.map(_._1).toList)
   181       else Nil
   182 
   183     (syntax, reparse, nodes, doc_edits.toList)
   184   }
   185 
   186 
   187 
   188   /** text edits **/
   189 
   190   /* edit individual command source */
   191 
   192   @tailrec def edit_text(eds: List[Text.Edit], commands: Linear_Set[Command]): Linear_Set[Command] =
   193   {
   194     eds match {
   195       case e :: es =>
   196         Document.Node.command_starts(commands.iterator).find {
   197           case (cmd, cmd_start) =>
   198             e.can_edit(cmd.source, cmd_start) ||
   199               e.is_insert && e.start == cmd_start + cmd.length
   200         } match {
   201           case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
   202             val (rest, text) = e.edit(cmd.source, cmd_start)
   203             val new_commands = commands.insert_after(Some(cmd), Command.unparsed(text)) - cmd
   204             edit_text(rest.toList ::: es, new_commands)
   205 
   206           case Some((cmd, cmd_start)) =>
   207             edit_text(es, commands.insert_after(Some(cmd), Command.unparsed(e.text)))
   208 
   209           case None =>
   210             require(e.is_insert && e.start == 0)
   211             edit_text(es, commands.insert_after(None, Command.unparsed(e.text)))
   212         }
   213       case Nil => commands
   214     }
   215   }
   216 
   217 
   218   /* reparse range of command spans */
   219 
   220   @tailrec private def chop_common(
   221       cmds: List[Command], spans: List[List[Token]]): (List[Command], List[List[Token]]) =
   222     (cmds, spans) match {
   223       case (c :: cs, s :: ss) if c.span == s => chop_common(cs, ss)
   224       case _ => (cmds, spans)
   225     }
   226 
   227   private def reparse_spans(
   228     syntax: Outer_Syntax,
   229     name: Document.Node.Name,
   230     commands: Linear_Set[Command],
   231     first: Command, last: Command): Linear_Set[Command] =
   232   {
   233     val cmds0 = commands.iterator(first, last).toList
   234     val spans0 = parse_spans(syntax.scan(cmds0.iterator.map(_.source).mkString))
   235 
   236     val (cmds1, spans1) = chop_common(cmds0, spans0)
   237 
   238     val (rev_cmds2, rev_spans2) = chop_common(cmds1.reverse, spans1.reverse)
   239     val cmds2 = rev_cmds2.reverse
   240     val spans2 = rev_spans2.reverse
   241 
   242     cmds2 match {
   243       case Nil =>
   244         assert(spans2.isEmpty)
   245         commands
   246       case cmd :: _ =>
   247         val hook = commands.prev(cmd)
   248         val inserted = spans2.map(span => Command(Document_ID.make(), name, span))
   249         (commands /: cmds2)(_ - _).append_after(hook, inserted)
   250     }
   251   }
   252 
   253 
   254   /* recover command spans after edits */
   255 
   256   // FIXME somewhat slow
   257   private def recover_spans(
   258     syntax: Outer_Syntax,
   259     name: Document.Node.Name,
   260     perspective: Command.Perspective,
   261     commands: Linear_Set[Command]): Linear_Set[Command] =
   262   {
   263     val visible = perspective.commands.toSet
   264 
   265     def next_invisible_command(cmds: Linear_Set[Command], from: Command): Command =
   266       cmds.iterator(from).dropWhile(cmd => !cmd.is_command || visible(cmd))
   267         .find(_.is_command) getOrElse cmds.last
   268 
   269     @tailrec def recover(cmds: Linear_Set[Command]): Linear_Set[Command] =
   270       cmds.find(_.is_unparsed) match {
   271         case Some(first_unparsed) =>
   272           val first = next_invisible_command(cmds.reverse, first_unparsed)
   273           val last = next_invisible_command(cmds, first_unparsed)
   274           recover(reparse_spans(syntax, name, cmds, first, last))
   275         case None => cmds
   276       }
   277     recover(commands)
   278   }
   279 
   280 
   281   /* consolidate unfinished spans */
   282 
   283   private def consolidate_spans(
   284     syntax: Outer_Syntax,
   285     reparse_limit: Int,
   286     name: Document.Node.Name,
   287     perspective: Command.Perspective,
   288     commands: Linear_Set[Command]): Linear_Set[Command] =
   289   {
   290     if (perspective.commands.isEmpty) commands
   291     else {
   292       commands.find(_.is_unfinished) match {
   293         case Some(first_unfinished) =>
   294           val visible = perspective.commands.toSet
   295           commands.reverse.find(visible) match {
   296             case Some(last_visible) =>
   297               val it = commands.iterator(last_visible)
   298               var last = last_visible
   299               var i = 0
   300               while (i < reparse_limit && it.hasNext) {
   301                 last = it.next
   302                 i += last.length
   303               }
   304               reparse_spans(syntax, name, commands, first_unfinished, last)
   305             case None => commands
   306           }
   307         case None => commands
   308       }
   309     }
   310   }
   311 
   312 
   313   /* main */
   314 
   315   def diff_commands(old_cmds: Linear_Set[Command], new_cmds: Linear_Set[Command])
   316     : List[Command.Edit] =
   317   {
   318     val removed = old_cmds.iterator.filter(!new_cmds.contains(_)).toList
   319     val inserted = new_cmds.iterator.filter(!old_cmds.contains(_)).toList
   320 
   321     removed.reverse.map(cmd => (old_cmds.prev(cmd), None)) :::
   322     inserted.map(cmd => (new_cmds.prev(cmd), Some(cmd)))
   323   }
   324 
   325   private def text_edit(syntax: Outer_Syntax, reparse_limit: Int,
   326     node: Document.Node, edit: Document.Edit_Text): Document.Node =
   327   {
   328     edit match {
   329       case (_, Document.Node.Clear()) => node.clear
   330 
   331       case (name, Document.Node.Edits(text_edits)) =>
   332         val commands0 = node.commands
   333         val commands1 = edit_text(text_edits, commands0)
   334         val commands2 = recover_spans(syntax, name, node.perspective.visible, commands1)
   335         node.update_commands(commands2)
   336 
   337       case (_, Document.Node.Deps(_)) => node
   338 
   339       case (name, Document.Node.Perspective(required, text_perspective, overlays)) =>
   340         val (visible, visible_overlay) = command_perspective(node, text_perspective, overlays)
   341         val perspective: Document.Node.Perspective_Command =
   342           Document.Node.Perspective(required, visible_overlay, overlays)
   343         if (node.same_perspective(perspective)) node
   344         else
   345           node.update_perspective(perspective).update_commands(
   346             consolidate_spans(syntax, reparse_limit, name, visible, node.commands))
   347     }
   348   }
   349 
   350   def text_edits(
   351       base_syntax: Outer_Syntax,
   352       reparse_limit: Int,
   353       previous: Document.Version,
   354       edits: List[Document.Edit_Text])
   355     : (List[Document.Edit_Command], Document.Version) =
   356   {
   357     val (syntax, reparse, nodes0, doc_edits0) = header_edits(base_syntax, previous, edits)
   358     val reparse_set = reparse.toSet
   359 
   360     var nodes = nodes0
   361     val doc_edits = new mutable.ListBuffer[Document.Edit_Command]; doc_edits ++= doc_edits0
   362 
   363     val node_edits =
   364       (edits ::: reparse.map((_, Document.Node.Edits(Nil)))).groupBy(_._1)
   365         .asInstanceOf[Map[Document.Node.Name, List[Document.Edit_Text]]]  // FIXME ???
   366 
   367     node_edits foreach {
   368       case (name, edits) =>
   369         val node = nodes(name)
   370         val commands = node.commands
   371 
   372         val node1 =
   373           if (reparse_set(name) && !commands.isEmpty)
   374             node.update_commands(reparse_spans(syntax, name, commands, commands.head, commands.last))
   375           else node
   376         val node2 = (node1 /: edits)(text_edit(syntax, reparse_limit, _, _))
   377 
   378         if (!(node.same_perspective(node2.perspective)))
   379           doc_edits += (name -> node2.perspective)
   380 
   381         doc_edits += (name -> Document.Node.Edits(diff_commands(commands, node2.commands)))
   382 
   383         nodes += (name -> node2)
   384     }
   385 
   386     (doc_edits.toList, Document.Version.make(syntax, nodes))
   387   }
   388 }