src/Pure/Thy/thy_syntax.scala
author wenzelm
Wed Aug 24 16:49:48 2011 +0200 (2011-08-24)
changeset 44443 35d67b2056cc
parent 44436 546adfa8a6fc
child 44473 4f264fdf8d0e
permissions -rw-r--r--
clarified Document.Node.clear -- retain header (cf. ML version);
     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, root_name: String, 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])] = List((0, root_name, buffer()))
    36 
    37       @tailrec def close(level: Int => Boolean)
    38       {
    39         stack match {
    40           case (lev, name, body) :: (_, _, body2) :: rest if level(lev) =>
    41             body2 += Block(name, body.toList)
    42             stack = stack.tail
    43             close(level)
    44           case _ =>
    45         }
    46       }
    47 
    48       def result(): Entry =
    49       {
    50         close(_ => true)
    51         val (_, name, body) = stack.head
    52         Block(name, body.toList)
    53       }
    54 
    55       def add(command: Command)
    56       {
    57         syntax.heading_level(command) match {
    58           case Some(i) =>
    59             close(_ >= i)
    60             stack = (i, command.source, buffer()) :: stack
    61           case None =>
    62         }
    63         stack.head._3 += Atom(command)
    64       }
    65 
    66 
    67       /* result structure */
    68 
    69       val spans = parse_spans(syntax.scan(text))
    70       spans.foreach(span => add(Command.span(span)))
    71       result()
    72     }
    73   }
    74 
    75 
    76 
    77   /** parse spans **/
    78 
    79   def parse_spans(toks: List[Token]): List[List[Token]] =
    80   {
    81     val result = new mutable.ListBuffer[List[Token]]
    82     val span = new mutable.ListBuffer[Token]
    83     val whitespace = new mutable.ListBuffer[Token]
    84 
    85     def flush(buffer: mutable.ListBuffer[Token])
    86     {
    87       if (!buffer.isEmpty) { result += buffer.toList; buffer.clear }
    88     }
    89     for (tok <- toks) {
    90       if (tok.is_command) { flush(span); flush(whitespace); span += tok }
    91       else if (tok.is_ignored) whitespace += tok
    92       else { span ++= whitespace; whitespace.clear; span += tok }
    93     }
    94     flush(span); flush(whitespace)
    95     result.toList
    96   }
    97 
    98 
    99 
   100   /** perspective **/
   101 
   102   def command_perspective(node: Document.Node, perspective: Text.Perspective): Command.Perspective =
   103   {
   104     if (perspective.isEmpty) Nil
   105     else {
   106       val result = new mutable.ListBuffer[Command]
   107       @tailrec
   108       def check_ranges(ranges: List[Text.Range], commands: Stream[(Command, Text.Offset)])
   109       {
   110         (ranges, commands) match {
   111           case (range :: more_ranges, (command, offset) #:: more_commands) =>
   112             val command_range = command.range + offset
   113             range compare command_range match {
   114               case -1 => check_ranges(more_ranges, commands)
   115               case 0 =>
   116                 result += command
   117                 check_ranges(ranges, more_commands)
   118               case 1 => check_ranges(ranges, more_commands)
   119             }
   120           case _ =>
   121         }
   122       }
   123       val perspective_range = Text.Range(perspective.head.start, perspective.last.stop)
   124       check_ranges(perspective, node.command_range(perspective_range).toStream)
   125       result.toList
   126     }
   127   }
   128 
   129   def update_perspective(nodes: Document.Nodes, name: String, text_perspective: Text.Perspective)
   130     : (Command.Perspective, Option[Document.Nodes]) =
   131   {
   132     val node = nodes(name)
   133     val perspective = command_perspective(node, text_perspective)
   134     val new_nodes =
   135       if (Command.equal_perspective(node.perspective, perspective)) None
   136       else Some(nodes + (name -> node.copy(perspective = perspective)))
   137     (perspective, new_nodes)
   138   }
   139 
   140   def edit_perspective(previous: Document.Version, name: String, text_perspective: Text.Perspective)
   141     : (Command.Perspective, Document.Version) =
   142   {
   143     val nodes = previous.nodes
   144     val (perspective, new_nodes) = update_perspective(nodes, name, text_perspective)
   145     val version = Document.Version(Document.new_id(), new_nodes getOrElse nodes)
   146     (perspective, version)
   147   }
   148 
   149 
   150 
   151   /** text edits **/
   152 
   153   def text_edits(
   154       syntax: Outer_Syntax,
   155       previous: Document.Version,
   156       edits: List[Document.Edit_Text])
   157     : (List[Document.Edit_Command], Document.Version) =
   158   {
   159     /* phase 1: edit individual command source */
   160 
   161     @tailrec def edit_text(eds: List[Text.Edit], commands: Linear_Set[Command])
   162         : Linear_Set[Command] =
   163     {
   164       eds match {
   165         case e :: es =>
   166           Document.Node.command_starts(commands.iterator).find {
   167             case (cmd, cmd_start) =>
   168               e.can_edit(cmd.source, cmd_start) ||
   169                 e.is_insert && e.start == cmd_start + cmd.length
   170           } match {
   171             case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
   172               val (rest, text) = e.edit(cmd.source, cmd_start)
   173               val new_commands = commands.insert_after(Some(cmd), Command.unparsed(text)) - cmd
   174               edit_text(rest.toList ::: es, new_commands)
   175 
   176             case Some((cmd, cmd_start)) =>
   177               edit_text(es, commands.insert_after(Some(cmd), Command.unparsed(e.text)))
   178 
   179             case None =>
   180               require(e.is_insert && e.start == 0)
   181               edit_text(es, commands.insert_after(None, Command.unparsed(e.text)))
   182           }
   183         case Nil => commands
   184       }
   185     }
   186 
   187 
   188     /* phase 2: recover command spans */
   189 
   190     @tailrec def recover_spans(commands: Linear_Set[Command]): Linear_Set[Command] =
   191     {
   192       commands.iterator.find(cmd => !cmd.is_defined) match {
   193         case Some(first_unparsed) =>
   194           val first =
   195             commands.reverse_iterator(first_unparsed).
   196               dropWhile(_.newlines == 0).find(_.is_command) getOrElse commands.head
   197           val last =
   198             commands.iterator(first_unparsed).
   199               dropWhile(_.newlines == 0).find(_.is_command) getOrElse commands.last
   200           val range =
   201             commands.iterator(first).takeWhile(_ != last).toList ::: List(last)
   202 
   203           val sources = range.flatMap(_.span.map(_.source))
   204           val spans0 = parse_spans(syntax.scan(sources.mkString))
   205 
   206           val (before_edit, spans1) =
   207             if (!spans0.isEmpty && first.is_command && first.span == spans0.head)
   208               (Some(first), spans0.tail)
   209             else (commands.prev(first), spans0)
   210 
   211           val (after_edit, spans2) =
   212             if (!spans1.isEmpty && last.is_command && last.span == spans1.last)
   213               (Some(last), spans1.take(spans1.length - 1))
   214             else (commands.next(last), spans1)
   215 
   216           val inserted = spans2.map(span => new Command(Document.new_id(), span))
   217           val new_commands =
   218             commands.delete_between(before_edit, after_edit).append_after(before_edit, inserted)
   219           recover_spans(new_commands)
   220 
   221         case None => commands
   222       }
   223     }
   224 
   225 
   226     /* resulting document edits */
   227 
   228     {
   229       val doc_edits = new mutable.ListBuffer[Document.Edit_Command]
   230       var nodes = previous.nodes
   231 
   232       edits foreach {
   233         case (name, Document.Node.Clear()) =>
   234           doc_edits += (name -> Document.Node.Clear())
   235           nodes += (name -> nodes(name).clear)
   236 
   237         case (name, Document.Node.Edits(text_edits)) =>
   238           val node = nodes(name)
   239           val commands0 = node.commands
   240           val commands1 = edit_text(text_edits, commands0)
   241           val commands2 = recover_spans(commands1)   // FIXME somewhat slow
   242 
   243           val removed_commands = commands0.iterator.filter(!commands2.contains(_)).toList
   244           val inserted_commands = commands2.iterator.filter(!commands0.contains(_)).toList
   245 
   246           val cmd_edits =
   247             removed_commands.reverse.map(cmd => (commands0.prev(cmd), None)) :::
   248             inserted_commands.map(cmd => (commands2.prev(cmd), Some(cmd)))
   249 
   250           doc_edits += (name -> Document.Node.Edits(cmd_edits))
   251           nodes += (name -> node.copy(commands = commands2))
   252 
   253         case (name, Document.Node.Header(header)) =>
   254           val node = nodes(name)
   255           val update_header =
   256             (node.header, header) match {
   257               case (Exn.Res(thy_header0), Exn.Res(thy_header)) => thy_header0 != thy_header
   258               case _ => true
   259             }
   260           if (update_header) {
   261             doc_edits += (name -> Document.Node.Header(header))
   262             nodes += (name -> node.copy(header = header))
   263           }
   264 
   265         case (name, Document.Node.Perspective(text_perspective)) =>
   266           update_perspective(nodes, name, text_perspective) match {
   267             case (_, None) =>
   268             case (perspective, Some(nodes1)) =>
   269               doc_edits += (name -> Document.Node.Perspective(perspective))
   270               nodes = nodes1
   271           }
   272       }
   273       (doc_edits.toList, Document.Version(Document.new_id(), nodes))
   274     }
   275   }
   276 }