src/Pure/Thy/thy_syntax.scala
author wenzelm
Thu Apr 03 20:53:35 2014 +0200 (2014-04-03)
changeset 56393 22f533e6a049
parent 56373 0605d90be6fc
child 56394 bbf4d512f395
permissions -rw-r--r--
more abstract Prover.Syntax, as proposed by Carst Tankink;
     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.toString, 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, Nil, 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     val improper = new mutable.ListBuffer[Token]
    85 
    86     def flush()
    87     {
    88       if (!span.isEmpty) { result += span.toList; span.clear }
    89       if (!improper.isEmpty) { result += improper.toList; improper.clear }
    90     }
    91     for (tok <- toks) {
    92       if (tok.is_command) { flush(); span += tok }
    93       else if (tok.is_improper) improper += tok
    94       else { span ++= improper; improper.clear; span += tok }
    95     }
    96     flush()
    97 
    98     result.toList
    99   }
   100 
   101 
   102 
   103   /** perspective **/
   104 
   105   def command_perspective(
   106       node: Document.Node,
   107       perspective: Text.Perspective,
   108       overlays: Document.Node.Overlays): (Command.Perspective, Command.Perspective) =
   109   {
   110     if (perspective.is_empty && overlays.is_empty)
   111       (Command.Perspective.empty, Command.Perspective.empty)
   112     else {
   113       val has_overlay = overlays.commands
   114       val visible = new mutable.ListBuffer[Command]
   115       val visible_overlay = new mutable.ListBuffer[Command]
   116       @tailrec
   117       def check_ranges(ranges: List[Text.Range], commands: Stream[(Command, Text.Offset)])
   118       {
   119         (ranges, commands) match {
   120           case (range :: more_ranges, (command, offset) #:: more_commands) =>
   121             val command_range = command.range + offset
   122             range compare command_range match {
   123               case 0 =>
   124                 visible += command
   125                 visible_overlay += command
   126                 check_ranges(ranges, more_commands)
   127               case c =>
   128                 if (has_overlay(command)) visible_overlay += command
   129 
   130                 if (c < 0) check_ranges(more_ranges, commands)
   131                 else check_ranges(ranges, more_commands)
   132             }
   133 
   134           case (Nil, (command, _) #:: more_commands) =>
   135             if (has_overlay(command)) visible_overlay += command
   136 
   137             check_ranges(Nil, more_commands)
   138 
   139           case _ =>
   140         }
   141       }
   142 
   143       val commands =
   144         (if (overlays.is_empty) node.command_iterator(perspective.range)
   145          else node.command_iterator()).toStream
   146       check_ranges(perspective.ranges, commands)
   147       (Command.Perspective(visible.toList), Command.Perspective(visible_overlay.toList))
   148     }
   149   }
   150 
   151 
   152 
   153   /** header edits: structure and outer syntax **/
   154 
   155   private def header_edits(
   156     base_syntax: Prover.Syntax,
   157     previous: Document.Version,
   158     edits: List[Document.Edit_Text]):
   159     (Prover.Syntax, Boolean, Boolean, List[Document.Node.Name], Document.Nodes,
   160       List[Document.Edit_Command]) =
   161   {
   162     var updated_imports = false
   163     var updated_keywords = false
   164     var nodes = previous.nodes
   165     val doc_edits = new mutable.ListBuffer[Document.Edit_Command]
   166 
   167     edits foreach {
   168       case (name, Document.Node.Deps(header)) =>
   169         val node = nodes(name)
   170         val update_header =
   171           !node.header.errors.isEmpty || !header.errors.isEmpty || node.header != header
   172         if (update_header) {
   173           val node1 = node.update_header(header)
   174           updated_imports = updated_imports || (node.header.imports != node1.header.imports)
   175           updated_keywords = updated_keywords || (node.header.keywords != node1.header.keywords)
   176           nodes += (name -> node1)
   177           doc_edits += (name -> Document.Node.Deps(header))
   178         }
   179       case _ =>
   180     }
   181 
   182     val (syntax, syntax_changed) =
   183       if (previous.is_init || updated_keywords) {
   184         val syntax =
   185           (base_syntax /: nodes.iterator) {
   186             case (syn, (_, node)) => syn.add_keywords(node.header.keywords)
   187           }
   188         (syntax, true)
   189       }
   190       else (previous.syntax, false)
   191 
   192     val reparse =
   193       if (updated_imports || updated_keywords)
   194         nodes.descendants(doc_edits.iterator.map(_._1).toList)
   195       else Nil
   196 
   197     (syntax, syntax_changed, updated_imports, reparse, nodes, doc_edits.toList)
   198   }
   199 
   200 
   201 
   202   /** text edits **/
   203 
   204   /* edit individual command source */
   205 
   206   @tailrec def edit_text(eds: List[Text.Edit], commands: Linear_Set[Command]): Linear_Set[Command] =
   207   {
   208     eds match {
   209       case e :: es =>
   210         Document.Node.Commands.starts(commands.iterator).find {
   211           case (cmd, cmd_start) =>
   212             e.can_edit(cmd.source, cmd_start) ||
   213               e.is_insert && e.start == cmd_start + cmd.length
   214         } match {
   215           case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
   216             val (rest, text) = e.edit(cmd.source, cmd_start)
   217             val new_commands = commands.insert_after(Some(cmd), Command.unparsed(text)) - cmd
   218             edit_text(rest.toList ::: es, new_commands)
   219 
   220           case Some((cmd, cmd_start)) =>
   221             edit_text(es, commands.insert_after(Some(cmd), Command.unparsed(e.text)))
   222 
   223           case None =>
   224             require(e.is_insert && e.start == 0)
   225             edit_text(es, commands.insert_after(None, Command.unparsed(e.text)))
   226         }
   227       case Nil => commands
   228     }
   229   }
   230 
   231 
   232   /* inlined files */
   233 
   234   private def find_file(tokens: List[Token]): Option[String] =
   235   {
   236     def clean(toks: List[Token]): List[Token] =
   237       toks match {
   238         case t :: _ :: ts if t.is_keyword && (t.source == "%" || t.source == "--") => clean(ts)
   239         case t :: ts => t :: clean(ts)
   240         case Nil => Nil
   241       }
   242     clean(tokens.filter(_.is_proper)) match {
   243       case tok :: toks if tok.is_command => toks.find(_.is_name).map(_.content)
   244       case _ => None
   245     }
   246   }
   247 
   248   def span_files(syntax: Prover.Syntax, span: List[Token]): List[String] =
   249     syntax.load(span) match {
   250       case Some(exts) =>
   251         find_file(span) match {
   252           case Some(file) =>
   253             if (exts.isEmpty) List(file)
   254             else exts.map(ext => file + "." + ext)
   255           case None => Nil
   256         }
   257       case None => Nil
   258     }
   259 
   260   def resolve_files(
   261       resources: Resources,
   262       syntax: Prover.Syntax,
   263       node_name: Document.Node.Name,
   264       span: List[Token],
   265       get_blob: Document.Node.Name => Option[Document.Blob])
   266     : List[Command.Blob] =
   267   {
   268     span_files(syntax, span).map(file_name =>
   269       Exn.capture {
   270         val name =
   271           Document.Node.Name(resources.append(node_name.master_dir, Path.explode(file_name)))
   272         val blob = get_blob(name).map(blob => ((blob.bytes.sha1_digest, blob.file)))
   273         (name, blob)
   274       })
   275   }
   276 
   277 
   278   /* reparse range of command spans */
   279 
   280   @tailrec private def chop_common(
   281       cmds: List[Command],
   282       blobs_spans: List[(List[Command.Blob], List[Token])])
   283     : (List[Command], List[(List[Command.Blob], List[Token])]) =
   284   {
   285     (cmds, blobs_spans) match {
   286       case (cmd :: cmds, (blobs, span) :: rest) if cmd.blobs == blobs && cmd.span == span =>
   287         chop_common(cmds, rest)
   288       case _ => (cmds, blobs_spans)
   289     }
   290   }
   291 
   292   private def reparse_spans(
   293     resources: Resources,
   294     syntax: Prover.Syntax,
   295     get_blob: Document.Node.Name => Option[Document.Blob],
   296     name: Document.Node.Name,
   297     commands: Linear_Set[Command],
   298     first: Command, last: Command): Linear_Set[Command] =
   299   {
   300     val cmds0 = commands.iterator(first, last).toList
   301     val blobs_spans0 =
   302       parse_spans(syntax.scan(cmds0.iterator.map(_.source).mkString)).
   303         map(span => (resolve_files(resources, syntax, name, span, get_blob), span))
   304 
   305     val (cmds1, blobs_spans1) = chop_common(cmds0, blobs_spans0)
   306 
   307     val (rev_cmds2, rev_blobs_spans2) = chop_common(cmds1.reverse, blobs_spans1.reverse)
   308     val cmds2 = rev_cmds2.reverse
   309     val blobs_spans2 = rev_blobs_spans2.reverse
   310 
   311     cmds2 match {
   312       case Nil =>
   313         assert(blobs_spans2.isEmpty)
   314         commands
   315       case cmd :: _ =>
   316         val hook = commands.prev(cmd)
   317         val inserted =
   318           blobs_spans2.map({ case (blobs, span) => Command(Document_ID.make(), name, blobs, span) })
   319         (commands /: cmds2)(_ - _).append_after(hook, inserted)
   320     }
   321   }
   322 
   323 
   324   /* recover command spans after edits */
   325 
   326   // FIXME somewhat slow
   327   private def recover_spans(
   328     resources: Resources,
   329     syntax: Prover.Syntax,
   330     get_blob: Document.Node.Name => Option[Document.Blob],
   331     name: Document.Node.Name,
   332     perspective: Command.Perspective,
   333     commands: Linear_Set[Command]): Linear_Set[Command] =
   334   {
   335     val visible = perspective.commands.toSet
   336 
   337     def next_invisible_command(cmds: Linear_Set[Command], from: Command): Command =
   338       cmds.iterator(from).dropWhile(cmd => !cmd.is_command || visible(cmd))
   339         .find(_.is_command) getOrElse cmds.last
   340 
   341     @tailrec def recover(cmds: Linear_Set[Command]): Linear_Set[Command] =
   342       cmds.find(_.is_unparsed) match {
   343         case Some(first_unparsed) =>
   344           val first = next_invisible_command(cmds.reverse, first_unparsed)
   345           val last = next_invisible_command(cmds, first_unparsed)
   346           recover(reparse_spans(resources, syntax, get_blob, name, cmds, first, last))
   347         case None => cmds
   348       }
   349     recover(commands)
   350   }
   351 
   352 
   353   /* consolidate unfinished spans */
   354 
   355   private def consolidate_spans(
   356     resources: Resources,
   357     syntax: Prover.Syntax,
   358     get_blob: Document.Node.Name => Option[Document.Blob],
   359     reparse_limit: Int,
   360     name: Document.Node.Name,
   361     perspective: Command.Perspective,
   362     commands: Linear_Set[Command]): Linear_Set[Command] =
   363   {
   364     if (perspective.commands.isEmpty) commands
   365     else {
   366       commands.find(_.is_unfinished) match {
   367         case Some(first_unfinished) =>
   368           val visible = perspective.commands.toSet
   369           commands.reverse.find(visible) match {
   370             case Some(last_visible) =>
   371               val it = commands.iterator(last_visible)
   372               var last = last_visible
   373               var i = 0
   374               while (i < reparse_limit && it.hasNext) {
   375                 last = it.next
   376                 i += last.length
   377               }
   378               reparse_spans(resources, syntax, get_blob, name, commands, first_unfinished, last)
   379             case None => commands
   380           }
   381         case None => commands
   382       }
   383     }
   384   }
   385 
   386 
   387   /* main */
   388 
   389   def diff_commands(old_cmds: Linear_Set[Command], new_cmds: Linear_Set[Command])
   390     : List[Command.Edit] =
   391   {
   392     val removed = old_cmds.iterator.filter(!new_cmds.contains(_)).toList
   393     val inserted = new_cmds.iterator.filter(!old_cmds.contains(_)).toList
   394 
   395     removed.reverse.map(cmd => (old_cmds.prev(cmd), None)) :::
   396     inserted.map(cmd => (new_cmds.prev(cmd), Some(cmd)))
   397   }
   398 
   399   private def text_edit(
   400     resources: Resources,
   401     syntax: Prover.Syntax,
   402     get_blob: Document.Node.Name => Option[Document.Blob],
   403     reparse_limit: Int,
   404     node: Document.Node, edit: Document.Edit_Text): Document.Node =
   405   {
   406     edit match {
   407       case (_, Document.Node.Clear()) => node.clear
   408 
   409       case (_, Document.Node.Blob(blob)) => node.init_blob(blob)
   410 
   411       case (name, Document.Node.Edits(text_edits)) =>
   412         if (name.is_theory) {
   413           val commands0 = node.commands
   414           val commands1 = edit_text(text_edits, commands0)
   415           val commands2 =
   416             recover_spans(resources, syntax, get_blob, name, node.perspective.visible, commands1)
   417           node.update_commands(commands2)
   418         }
   419         else node
   420 
   421       case (_, Document.Node.Deps(_)) => node
   422 
   423       case (name, Document.Node.Perspective(required, text_perspective, overlays)) =>
   424         val (visible, visible_overlay) = command_perspective(node, text_perspective, overlays)
   425         val perspective: Document.Node.Perspective_Command =
   426           Document.Node.Perspective(required, visible_overlay, overlays)
   427         if (node.same_perspective(perspective)) node
   428         else
   429           node.update_perspective(perspective).update_commands(
   430             consolidate_spans(resources, syntax, get_blob, reparse_limit,
   431               name, visible, node.commands))
   432     }
   433   }
   434 
   435   def parse_change(
   436       resources: Resources,
   437       reparse_limit: Int,
   438       previous: Document.Version,
   439       doc_blobs: Document.Blobs,
   440       edits: List[Document.Edit_Text]): Session.Change =
   441   {
   442     def get_blob(name: Document.Node.Name) =
   443       doc_blobs.get(name) orElse previous.nodes(name).get_blob
   444 
   445     val (syntax, syntax_changed, deps_changed, reparse0, nodes0, doc_edits0) =
   446       header_edits(resources.base_syntax, previous, edits)
   447 
   448     val (doc_edits, version) =
   449       if (edits.isEmpty) (Nil, Document.Version.make(syntax, previous.nodes))
   450       else {
   451         val reparse =
   452           (reparse0 /: nodes0.iterator)({
   453             case (reparse, (name, node)) =>
   454               if (node.load_commands.exists(_.blobs_changed(doc_blobs)))
   455                 name :: reparse
   456               else reparse
   457             })
   458         val reparse_set = reparse.toSet
   459 
   460         var nodes = nodes0
   461         val doc_edits = new mutable.ListBuffer[Document.Edit_Command]; doc_edits ++= doc_edits0
   462 
   463         val node_edits =
   464           (edits ::: reparse.map((_, Document.Node.Edits(Nil)))).groupBy(_._1)
   465             .asInstanceOf[Map[Document.Node.Name, List[Document.Edit_Text]]]  // FIXME ???
   466 
   467         node_edits foreach {
   468           case (name, edits) =>
   469             val node = nodes(name)
   470             val commands = node.commands
   471 
   472             val node1 =
   473               if (reparse_set(name) && !commands.isEmpty)
   474                 node.update_commands(
   475                   reparse_spans(resources, syntax, get_blob,
   476                     name, commands, commands.head, commands.last))
   477               else node
   478             val node2 =
   479               (node1 /: edits)(text_edit(resources, syntax, get_blob, reparse_limit, _, _))
   480 
   481             if (!(node.same_perspective(node2.perspective)))
   482               doc_edits += (name -> node2.perspective)
   483 
   484             doc_edits += (name -> Document.Node.Edits(diff_commands(commands, node2.commands)))
   485 
   486             nodes += (name -> node2)
   487         }
   488         (doc_edits.toList, Document.Version.make(syntax, nodes))
   489       }
   490 
   491     Session.Change(previous, doc_blobs, syntax_changed, deps_changed, doc_edits, version)
   492   }
   493 }