src/Pure/Thy/thy_syntax.scala
author wenzelm
Fri Mar 13 12:58:49 2015 +0100 (2015-03-13)
changeset 59689 7968c57ea240
parent 59684 86a76300137e
child 59695 a03e0561bdbf
permissions -rw-r--r--
simplified Command.resolve_files in ML, using blobs_index from Scala;
clarified modules;
     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   /** perspective **/
    17 
    18   def command_perspective(
    19       node: Document.Node,
    20       perspective: Text.Perspective,
    21       overlays: Document.Node.Overlays): (Command.Perspective, Command.Perspective) =
    22   {
    23     if (perspective.is_empty && overlays.is_empty)
    24       (Command.Perspective.empty, Command.Perspective.empty)
    25     else {
    26       val has_overlay = overlays.commands
    27       val visible = new mutable.ListBuffer[Command]
    28       val visible_overlay = new mutable.ListBuffer[Command]
    29       @tailrec
    30       def check_ranges(ranges: List[Text.Range], commands: Stream[(Command, Text.Offset)])
    31       {
    32         (ranges, commands) match {
    33           case (range :: more_ranges, (command, offset) #:: more_commands) =>
    34             val command_range = command.range + offset
    35             range compare command_range match {
    36               case 0 =>
    37                 visible += command
    38                 visible_overlay += command
    39                 check_ranges(ranges, more_commands)
    40               case c =>
    41                 if (has_overlay(command)) visible_overlay += command
    42 
    43                 if (c < 0) check_ranges(more_ranges, commands)
    44                 else check_ranges(ranges, more_commands)
    45             }
    46 
    47           case (Nil, (command, _) #:: more_commands) =>
    48             if (has_overlay(command)) visible_overlay += command
    49 
    50             check_ranges(Nil, more_commands)
    51 
    52           case _ =>
    53         }
    54       }
    55 
    56       val commands =
    57         (if (overlays.is_empty) node.command_iterator(perspective.range)
    58          else node.command_iterator()).toStream
    59       check_ranges(perspective.ranges, commands)
    60       (Command.Perspective(visible.toList), Command.Perspective(visible_overlay.toList))
    61     }
    62   }
    63 
    64 
    65 
    66   /** header edits: structure and outer syntax **/
    67 
    68   private def header_edits(
    69     resources: Resources,
    70     previous: Document.Version,
    71     edits: List[Document.Edit_Text]):
    72     (List[Document.Node.Name], Document.Nodes, List[Document.Edit_Command]) =
    73   {
    74     val syntax_changed0 = new mutable.ListBuffer[Document.Node.Name]
    75     var nodes = previous.nodes
    76     val doc_edits = new mutable.ListBuffer[Document.Edit_Command]
    77 
    78     edits foreach {
    79       case (name, Document.Node.Deps(header)) =>
    80         val node = nodes(name)
    81         val update_header =
    82           node.header.errors.nonEmpty || header.errors.nonEmpty || node.header != header
    83         if (update_header) {
    84           val node1 = node.update_header(header)
    85           if (node.header.imports != node1.header.imports ||
    86               node.header.keywords != node1.header.keywords) syntax_changed0 += name
    87           nodes += (name -> node1)
    88           doc_edits += (name -> Document.Node.Deps(header))
    89         }
    90       case _ =>
    91     }
    92 
    93     val syntax_changed = nodes.descendants(syntax_changed0.toList)
    94 
    95     for (name <- syntax_changed) {
    96       val node = nodes(name)
    97       val syntax =
    98         if (node.is_empty) None
    99         else {
   100           val header = node.header
   101           val imports_syntax = header.imports.flatMap(a => nodes(a).syntax)
   102           Some((resources.base_syntax /: imports_syntax)(_ ++ _).add_keywords(header.keywords))
   103         }
   104       nodes += (name -> node.update_syntax(syntax))
   105     }
   106 
   107     (syntax_changed, nodes, doc_edits.toList)
   108   }
   109 
   110 
   111 
   112   /** text edits **/
   113 
   114   /* edit individual command source */
   115 
   116   @tailrec def edit_text(eds: List[Text.Edit], commands: Linear_Set[Command]): Linear_Set[Command] =
   117   {
   118     eds match {
   119       case e :: es =>
   120         Document.Node.Commands.starts(commands.iterator).find {
   121           case (cmd, cmd_start) =>
   122             e.can_edit(cmd.source, cmd_start) ||
   123               e.is_insert && e.start == cmd_start + cmd.length
   124         } match {
   125           case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
   126             val (rest, text) = e.edit(cmd.source, cmd_start)
   127             val new_commands = commands.insert_after(Some(cmd), Command.unparsed(text)) - cmd
   128             edit_text(rest.toList ::: es, new_commands)
   129 
   130           case Some((cmd, cmd_start)) =>
   131             edit_text(es, commands.insert_after(Some(cmd), Command.unparsed(e.text)))
   132 
   133           case None =>
   134             require(e.is_insert && e.start == 0)
   135             edit_text(es, commands.insert_after(None, Command.unparsed(e.text)))
   136         }
   137       case Nil => commands
   138     }
   139   }
   140 
   141 
   142   /* reparse range of command spans */
   143 
   144   @tailrec private def chop_common(
   145       cmds: List[Command],
   146       blobs_spans: List[((List[Command.Blob], Int), Command_Span.Span)])
   147     : (List[Command], List[((List[Command.Blob], Int), Command_Span.Span)]) =
   148   {
   149     (cmds, blobs_spans) match {
   150       case (cmd :: cmds, (blobs, span) :: rest) if cmd.blobs == blobs && cmd.span == span =>
   151         chop_common(cmds, rest)
   152       case _ => (cmds, blobs_spans)
   153     }
   154   }
   155 
   156   private def reparse_spans(
   157     resources: Resources,
   158     syntax: Prover.Syntax,
   159     get_blob: Document.Node.Name => Option[Document.Blob],
   160     name: Document.Node.Name,
   161     commands: Linear_Set[Command],
   162     first: Command, last: Command): Linear_Set[Command] =
   163   {
   164     val cmds0 = commands.iterator(first, last).toList
   165     val blobs_spans0 =
   166       syntax.parse_spans(cmds0.iterator.map(_.source).mkString).
   167         map(span => (Command.resolve_files(resources, syntax, name, span, get_blob), span))
   168 
   169     val (cmds1, blobs_spans1) = chop_common(cmds0, blobs_spans0)
   170 
   171     val (rev_cmds2, rev_blobs_spans2) = chop_common(cmds1.reverse, blobs_spans1.reverse)
   172     val cmds2 = rev_cmds2.reverse
   173     val blobs_spans2 = rev_blobs_spans2.reverse
   174 
   175     cmds2 match {
   176       case Nil =>
   177         assert(blobs_spans2.isEmpty)
   178         commands
   179       case cmd :: _ =>
   180         val hook = commands.prev(cmd)
   181         val inserted =
   182           blobs_spans2.map({ case (blobs, span) =>
   183             Command(Document_ID.make(), name, Some(blobs), span) })
   184         (commands /: cmds2)(_ - _).append_after(hook, inserted)
   185     }
   186   }
   187 
   188 
   189   /* recover command spans after edits */
   190 
   191   // FIXME somewhat slow
   192   private def recover_spans(
   193     resources: Resources,
   194     syntax: Prover.Syntax,
   195     get_blob: Document.Node.Name => Option[Document.Blob],
   196     name: Document.Node.Name,
   197     perspective: Command.Perspective,
   198     commands: Linear_Set[Command]): Linear_Set[Command] =
   199   {
   200     val visible = perspective.commands.toSet
   201 
   202     def next_invisible_command(cmds: Linear_Set[Command], from: Command): Command =
   203       cmds.iterator(from).dropWhile(cmd => !cmd.is_proper || visible(cmd))
   204         .find(_.is_proper) getOrElse cmds.last
   205 
   206     @tailrec def recover(cmds: Linear_Set[Command]): Linear_Set[Command] =
   207       cmds.find(_.is_unparsed) match {
   208         case Some(first_unparsed) =>
   209           val first = next_invisible_command(cmds.reverse, first_unparsed)
   210           val last = next_invisible_command(cmds, first_unparsed)
   211           recover(reparse_spans(resources, syntax, get_blob, name, cmds, first, last))
   212         case None => cmds
   213       }
   214     recover(commands)
   215   }
   216 
   217 
   218   /* consolidate unfinished spans */
   219 
   220   private def consolidate_spans(
   221     resources: Resources,
   222     syntax: Prover.Syntax,
   223     get_blob: Document.Node.Name => Option[Document.Blob],
   224     reparse_limit: Int,
   225     name: Document.Node.Name,
   226     perspective: Command.Perspective,
   227     commands: Linear_Set[Command]): Linear_Set[Command] =
   228   {
   229     if (perspective.commands.isEmpty) commands
   230     else {
   231       commands.find(_.is_unfinished) match {
   232         case Some(first_unfinished) =>
   233           val visible = perspective.commands.toSet
   234           commands.reverse.find(visible) match {
   235             case Some(last_visible) =>
   236               val it = commands.iterator(last_visible)
   237               var last = last_visible
   238               var i = 0
   239               while (i < reparse_limit && it.hasNext) {
   240                 last = it.next
   241                 i += last.length
   242               }
   243               reparse_spans(resources, syntax, get_blob, name, commands, first_unfinished, last)
   244             case None => commands
   245           }
   246         case None => commands
   247       }
   248     }
   249   }
   250 
   251 
   252   /* main */
   253 
   254   def diff_commands(old_cmds: Linear_Set[Command], new_cmds: Linear_Set[Command])
   255     : List[Command.Edit] =
   256   {
   257     val removed = old_cmds.iterator.filter(!new_cmds.contains(_)).toList
   258     val inserted = new_cmds.iterator.filter(!old_cmds.contains(_)).toList
   259 
   260     removed.reverse.map(cmd => (old_cmds.prev(cmd), None)) :::
   261     inserted.map(cmd => (new_cmds.prev(cmd), Some(cmd)))
   262   }
   263 
   264   private def text_edit(
   265     resources: Resources,
   266     syntax: Prover.Syntax,
   267     get_blob: Document.Node.Name => Option[Document.Blob],
   268     reparse_limit: Int,
   269     node: Document.Node, edit: Document.Edit_Text): Document.Node =
   270   {
   271     edit match {
   272       case (_, Document.Node.Clear()) => node.clear
   273 
   274       case (_, Document.Node.Blob(blob)) => node.init_blob(blob)
   275 
   276       case (name, Document.Node.Edits(text_edits)) =>
   277         if (name.is_theory) {
   278           val commands0 = node.commands
   279           val commands1 = edit_text(text_edits, commands0)
   280           val commands2 =
   281             recover_spans(resources, syntax, get_blob, name, node.perspective.visible, commands1)
   282           node.update_commands(commands2)
   283         }
   284         else node
   285 
   286       case (_, Document.Node.Deps(_)) => node
   287 
   288       case (name, Document.Node.Perspective(required, text_perspective, overlays)) =>
   289         val (visible, visible_overlay) = command_perspective(node, text_perspective, overlays)
   290         val perspective: Document.Node.Perspective_Command =
   291           Document.Node.Perspective(required, visible_overlay, overlays)
   292         if (node.same_perspective(text_perspective, perspective)) node
   293         else
   294           node.update_perspective(text_perspective, perspective).
   295             update_commands(
   296               consolidate_spans(resources, syntax, get_blob, reparse_limit,
   297                 name, visible, node.commands))
   298     }
   299   }
   300 
   301   def parse_change(
   302       resources: Resources,
   303       reparse_limit: Int,
   304       previous: Document.Version,
   305       doc_blobs: Document.Blobs,
   306       edits: List[Document.Edit_Text]): Session.Change =
   307   {
   308     def get_blob(name: Document.Node.Name) =
   309       doc_blobs.get(name) orElse previous.nodes(name).get_blob
   310 
   311     val (syntax_changed, nodes0, doc_edits0) = header_edits(resources, previous, edits)
   312 
   313     val (doc_edits, version) =
   314       if (edits.isEmpty) (Nil, Document.Version.make(previous.nodes))
   315       else {
   316         val reparse =
   317           (syntax_changed /: nodes0.iterator)({
   318             case (reparse, (name, node)) =>
   319               if (node.load_commands.exists(_.blobs_changed(doc_blobs)))
   320                 name :: reparse
   321               else reparse
   322             })
   323         val reparse_set = reparse.toSet
   324 
   325         var nodes = nodes0
   326         val doc_edits = new mutable.ListBuffer[Document.Edit_Command]; doc_edits ++= doc_edits0
   327 
   328         val node_edits =
   329           (edits ::: reparse.map((_, Document.Node.Edits(Nil)))).groupBy(_._1)
   330             .asInstanceOf[Map[Document.Node.Name, List[Document.Edit_Text]]]  // FIXME ???
   331 
   332         node_edits foreach {
   333           case (name, edits) =>
   334             val node = nodes(name)
   335             val syntax = node.syntax getOrElse resources.base_syntax
   336             val commands = node.commands
   337 
   338             val node1 =
   339               if (reparse_set(name) && commands.nonEmpty)
   340                 node.update_commands(
   341                   reparse_spans(resources, syntax, get_blob,
   342                     name, commands, commands.head, commands.last))
   343               else node
   344             val node2 =
   345               (node1 /: edits)(text_edit(resources, syntax, get_blob, reparse_limit, _, _))
   346             val node3 =
   347               if (reparse_set.contains(name))
   348                 text_edit(resources, syntax, get_blob, reparse_limit,
   349                   node2, (name, node2.edit_perspective))
   350               else node2
   351 
   352             if (!(node.same_perspective(node3.text_perspective, node3.perspective)))
   353               doc_edits += (name -> node3.perspective)
   354 
   355             doc_edits += (name -> Document.Node.Edits(diff_commands(commands, node3.commands)))
   356 
   357             nodes += (name -> node3)
   358         }
   359         (doc_edits.toList.filterNot(_._2.is_void), Document.Version.make(nodes))
   360       }
   361 
   362     Session.Change(previous, syntax_changed, syntax_changed.nonEmpty, doc_edits, version)
   363   }
   364 }