src/Pure/Thy/thy_syntax.scala
author wenzelm
Thu Nov 02 10:16:22 2017 +0100 (20 months ago)
changeset 66987 352b23c97ac8
parent 66772 a66f11a0b5b1
child 68336 09ac56914b29
permissions -rw-r--r--
support focus_session, for much faster startup of Isabelle/jEdit;
more options for "isabelle jedit";
     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: graph 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.map(_._1) != node1.header.imports.map(_._1) ||
    86               node.header.keywords != node1.header.keywords ||
    87               node.header.abbrevs != node1.header.abbrevs ||
    88               node.header.errors != node1.header.errors) syntax_changed0 += name
    89           nodes += (name -> node1)
    90           doc_edits += (name -> Document.Node.Deps(header))
    91         }
    92       case _ =>
    93     }
    94 
    95     val syntax_changed = nodes.descendants(syntax_changed0.toList)
    96 
    97     for (name <- syntax_changed) {
    98       val node = nodes(name)
    99       val syntax =
   100         if (node.is_empty) None
   101         else {
   102           val header = node.header
   103           val imports_syntax =
   104             if (header.imports.nonEmpty) {
   105               Outer_Syntax.merge(
   106                 header.imports.map(p => resources.session_base.node_syntax(nodes, p._1)))
   107             }
   108             else resources.session_base.overall_syntax
   109           Some(imports_syntax + header)
   110         }
   111       nodes += (name -> node.update_syntax(syntax))
   112     }
   113 
   114     (syntax_changed, nodes, doc_edits.toList)
   115   }
   116 
   117 
   118 
   119   /** text edits **/
   120 
   121   /* edit individual command source */
   122 
   123   @tailrec def edit_text(eds: List[Text.Edit], commands: Linear_Set[Command]): Linear_Set[Command] =
   124   {
   125     eds match {
   126       case e :: es =>
   127         def insert_text(cmd: Option[Command], text: String): Linear_Set[Command] =
   128           if (text == "") commands else commands.insert_after(cmd, Command.text(text))
   129 
   130         Document.Node.Commands.starts(commands.iterator).find {
   131           case (cmd, cmd_start) =>
   132             e.can_edit(cmd.source, cmd_start) ||
   133               e.is_insert && e.start == cmd_start + cmd.length
   134         } match {
   135           case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
   136             val (rest, text) = e.edit(cmd.source, cmd_start)
   137             val new_commands = insert_text(Some(cmd), text) - cmd
   138             edit_text(rest.toList ::: es, new_commands)
   139 
   140           case Some((cmd, cmd_start)) =>
   141             edit_text(es, insert_text(Some(cmd), e.text))
   142 
   143           case None =>
   144             require(e.is_insert && e.start == 0)
   145             edit_text(es, insert_text(None, e.text))
   146         }
   147       case Nil => commands
   148     }
   149   }
   150 
   151 
   152   /* reparse range of command spans */
   153 
   154   @tailrec private def chop_common(
   155       cmds: List[Command],
   156       blobs_spans: List[(Command.Blobs_Info, Command_Span.Span)])
   157     : (List[Command], List[(Command.Blobs_Info, Command_Span.Span)]) =
   158   {
   159     (cmds, blobs_spans) match {
   160       case (cmd :: cmds, (blobs_info, span) :: rest)
   161       if cmd.blobs_info == blobs_info && cmd.span == span => chop_common(cmds, rest)
   162       case _ => (cmds, blobs_spans)
   163     }
   164   }
   165 
   166   private def reparse_spans(
   167     resources: Resources,
   168     syntax: Outer_Syntax,
   169     get_blob: Document.Node.Name => Option[Document.Blob],
   170     can_import: Document.Node.Name => Boolean,
   171     node_name: Document.Node.Name,
   172     commands: Linear_Set[Command],
   173     first: Command, last: Command): Linear_Set[Command] =
   174   {
   175     val cmds0 = commands.iterator(first, last).toList
   176     val blobs_spans0 =
   177       syntax.parse_spans(cmds0.iterator.map(_.source).mkString).map(span =>
   178         (Command.blobs_info(resources, syntax, get_blob, can_import, node_name, span), span))
   179 
   180     val (cmds1, blobs_spans1) = chop_common(cmds0, blobs_spans0)
   181 
   182     val (rev_cmds2, rev_blobs_spans2) = chop_common(cmds1.reverse, blobs_spans1.reverse)
   183     val cmds2 = rev_cmds2.reverse
   184     val blobs_spans2 = rev_blobs_spans2.reverse
   185 
   186     cmds2 match {
   187       case Nil =>
   188         assert(blobs_spans2.isEmpty)
   189         commands
   190       case cmd :: _ =>
   191         val hook = commands.prev(cmd)
   192         val inserted =
   193           blobs_spans2.map({ case (blobs, span) =>
   194             Command(Document_ID.make(), node_name, blobs, span) })
   195         (commands /: cmds2)(_ - _).append_after(hook, inserted)
   196     }
   197   }
   198 
   199 
   200   /* main */
   201 
   202   def diff_commands(old_cmds: Linear_Set[Command], new_cmds: Linear_Set[Command])
   203     : List[Command.Edit] =
   204   {
   205     val removed = old_cmds.iterator.filter(!new_cmds.contains(_)).toList
   206     val inserted = new_cmds.iterator.filter(!old_cmds.contains(_)).toList
   207 
   208     removed.map(cmd => (old_cmds.prev(cmd), None)) reverse_:::
   209     inserted.map(cmd => (new_cmds.prev(cmd), Some(cmd)))
   210   }
   211 
   212   private def text_edit(
   213     resources: Resources,
   214     syntax: Outer_Syntax,
   215     get_blob: Document.Node.Name => Option[Document.Blob],
   216     can_import: Document.Node.Name => Boolean,
   217     reparse_limit: Int,
   218     node: Document.Node, edit: Document.Edit_Text): Document.Node =
   219   {
   220     /* recover command spans after edits */
   221     // FIXME somewhat slow
   222     def recover_spans(
   223       name: Document.Node.Name,
   224       perspective: Command.Perspective,
   225       commands: Linear_Set[Command]): Linear_Set[Command] =
   226     {
   227       val is_visible = perspective.commands.toSet
   228 
   229       def next_invisible(cmds: Linear_Set[Command], from: Command): Command =
   230         cmds.iterator(from).dropWhile(cmd => !cmd.is_proper || is_visible(cmd))
   231           .find(_.is_proper) getOrElse cmds.last
   232 
   233       @tailrec def recover(cmds: Linear_Set[Command]): Linear_Set[Command] =
   234         cmds.find(_.is_unparsed) match {
   235           case Some(first_unparsed) =>
   236             val first = next_invisible(cmds.reverse, first_unparsed)
   237             val last = next_invisible(cmds, first_unparsed)
   238             recover(
   239               reparse_spans(resources, syntax, get_blob, can_import, name, cmds, first, last))
   240           case None => cmds
   241         }
   242       recover(commands)
   243     }
   244 
   245     edit match {
   246       case (_, Document.Node.Blob(blob)) => node.init_blob(blob)
   247 
   248       case (name, Document.Node.Edits(text_edits)) =>
   249         if (name.is_theory) {
   250           val commands0 = node.commands
   251           val commands1 = edit_text(text_edits, commands0)
   252           val commands2 = recover_spans(name, node.perspective.visible, commands1)
   253           node.update_commands(commands2)
   254         }
   255         else node
   256 
   257       case (_, Document.Node.Deps(_)) => node
   258 
   259       case (name, Document.Node.Perspective(required, text_perspective, overlays)) =>
   260         val (visible, visible_overlay) = command_perspective(node, text_perspective, overlays)
   261         val perspective: Document.Node.Perspective_Command =
   262           Document.Node.Perspective(required, visible_overlay, overlays)
   263         if (node.same_perspective(text_perspective, perspective)) node
   264         else {
   265           /* consolidate unfinished spans */
   266           val is_visible = visible.commands.toSet
   267           val commands = node.commands
   268           val commands1 =
   269             if (is_visible.isEmpty) commands
   270             else {
   271               commands.find(_.is_unfinished) match {
   272                 case Some(first_unfinished) =>
   273                   commands.reverse.find(is_visible) match {
   274                     case Some(last_visible) =>
   275                       val it = commands.iterator(last_visible)
   276                       var last = last_visible
   277                       var i = 0
   278                       while (i < reparse_limit && it.hasNext) {
   279                         last = it.next
   280                         i += last.length
   281                       }
   282                       reparse_spans(resources, syntax, get_blob, can_import,
   283                         name, commands, first_unfinished, last)
   284                     case None => commands
   285                   }
   286                 case None => commands
   287               }
   288             }
   289           node.update_perspective(text_perspective, perspective).update_commands(commands1)
   290         }
   291     }
   292   }
   293 
   294   def parse_change(
   295       resources: Resources,
   296       reparse_limit: Int,
   297       previous: Document.Version,
   298       doc_blobs: Document.Blobs,
   299       edits: List[Document.Edit_Text]): Session.Change =
   300   {
   301     val (syntax_changed, nodes0, doc_edits0) = header_edits(resources, previous, edits)
   302 
   303     def get_blob(name: Document.Node.Name) =
   304       doc_blobs.get(name) orElse previous.nodes(name).get_blob
   305 
   306     def can_import(name: Document.Node.Name): Boolean =
   307       resources.session_base.loaded_theory(name) || nodes0(name).has_header
   308 
   309     val (doc_edits, version) =
   310       if (edits.isEmpty) (Nil, Document.Version.make(previous.nodes))
   311       else {
   312         val reparse =
   313           (syntax_changed /: nodes0.iterator)({
   314             case (reparse, (name, node)) =>
   315               if (node.load_commands_changed(doc_blobs) && !reparse.contains(name))
   316                 name :: reparse
   317               else reparse
   318             })
   319         val reparse_set = reparse.toSet
   320 
   321         var nodes = nodes0
   322         val doc_edits = new mutable.ListBuffer[Document.Edit_Command]; doc_edits ++= doc_edits0
   323 
   324         val node_edits =
   325           (edits ::: reparse.map((_, Document.Node.Edits(Nil)))).groupBy(_._1)
   326             .asInstanceOf[Map[Document.Node.Name, List[Document.Edit_Text]]]  // FIXME ???
   327 
   328         node_edits foreach {
   329           case (name, edits) =>
   330             val node = nodes(name)
   331             val syntax = resources.session_base.node_syntax(nodes, name)
   332             val commands = node.commands
   333 
   334             val node1 =
   335               if (reparse_set(name) && commands.nonEmpty)
   336                 node.update_commands(
   337                   reparse_spans(resources, syntax, get_blob, can_import, name,
   338                     commands, commands.head, commands.last))
   339               else node
   340             val node2 =
   341               (node1 /: edits)(
   342                 text_edit(resources, syntax, get_blob, can_import, reparse_limit, _, _))
   343             val node3 =
   344               if (reparse_set.contains(name))
   345                 text_edit(resources, syntax, get_blob, can_import, reparse_limit,
   346                   node2, (name, node2.edit_perspective))
   347               else node2
   348 
   349             if (!node.same_perspective(node3.text_perspective, node3.perspective))
   350               doc_edits += (name -> node3.perspective)
   351 
   352             doc_edits += (name -> Document.Node.Edits(diff_commands(commands, node3.commands)))
   353 
   354             nodes += (name -> node3)
   355         }
   356         (doc_edits.toList.filterNot(_._2.is_void), Document.Version.make(nodes))
   357       }
   358 
   359     Session.Change(previous, syntax_changed, syntax_changed.nonEmpty, doc_edits, version)
   360   }
   361 }