wenzelm@34273
|
1 |
/* Title: Pure/Thy/thy_syntax.scala
|
wenzelm@34273
|
2 |
Author: Makarius
|
wenzelm@34273
|
3 |
|
wenzelm@38656
|
4 |
Superficial theory syntax: tokens and spans.
|
wenzelm@34273
|
5 |
*/
|
wenzelm@34273
|
6 |
|
wenzelm@34273
|
7 |
package isabelle
|
wenzelm@34273
|
8 |
|
wenzelm@34273
|
9 |
|
wenzelm@38495
|
10 |
import scala.collection.mutable
|
wenzelm@38656
|
11 |
import scala.annotation.tailrec
|
wenzelm@38495
|
12 |
|
wenzelm@38495
|
13 |
|
wenzelm@34308
|
14 |
object Thy_Syntax
|
wenzelm@34273
|
15 |
{
|
wenzelm@40711
|
16 |
/** nested structure **/
|
wenzelm@40711
|
17 |
|
wenzelm@40711
|
18 |
object Structure
|
wenzelm@40711
|
19 |
{
|
wenzelm@40747
|
20 |
sealed abstract class Entry { def length: Int }
|
wenzelm@40711
|
21 |
case class Block(val name: String, val body: List[Entry]) extends Entry
|
wenzelm@40711
|
22 |
{
|
wenzelm@40711
|
23 |
val length: Int = (0 /: body)(_ + _.length)
|
wenzelm@40711
|
24 |
}
|
wenzelm@40711
|
25 |
case class Atom(val command: Command) extends Entry
|
wenzelm@40711
|
26 |
{
|
wenzelm@40711
|
27 |
def length: Int = command.length
|
wenzelm@40711
|
28 |
}
|
wenzelm@40711
|
29 |
|
wenzelm@41044
|
30 |
def parse(syntax: Outer_Syntax, root_name: String, text: CharSequence): Entry =
|
wenzelm@40711
|
31 |
{
|
wenzelm@40711
|
32 |
/* stack operations */
|
wenzelm@40711
|
33 |
|
wenzelm@40711
|
34 |
def buffer(): mutable.ListBuffer[Entry] = new mutable.ListBuffer[Entry]
|
wenzelm@40711
|
35 |
var stack: List[(Int, String, mutable.ListBuffer[Entry])] = List((0, root_name, buffer()))
|
wenzelm@40711
|
36 |
|
wenzelm@40711
|
37 |
@tailrec def close(level: Int => Boolean)
|
wenzelm@40711
|
38 |
{
|
wenzelm@40711
|
39 |
stack match {
|
wenzelm@40711
|
40 |
case (lev, name, body) :: (_, _, body2) :: rest if level(lev) =>
|
wenzelm@40711
|
41 |
body2 += Block(name, body.toList)
|
wenzelm@40711
|
42 |
stack = stack.tail
|
wenzelm@40711
|
43 |
close(level)
|
wenzelm@40711
|
44 |
case _ =>
|
wenzelm@40711
|
45 |
}
|
wenzelm@40711
|
46 |
}
|
wenzelm@40711
|
47 |
|
wenzelm@40711
|
48 |
def result(): Entry =
|
wenzelm@40711
|
49 |
{
|
wenzelm@40711
|
50 |
close(_ => true)
|
wenzelm@40711
|
51 |
val (_, name, body) = stack.head
|
wenzelm@40711
|
52 |
Block(name, body.toList)
|
wenzelm@40711
|
53 |
}
|
wenzelm@40711
|
54 |
|
wenzelm@40711
|
55 |
def add(command: Command)
|
wenzelm@40711
|
56 |
{
|
wenzelm@40711
|
57 |
syntax.heading_level(command) match {
|
wenzelm@40711
|
58 |
case Some(i) =>
|
wenzelm@40714
|
59 |
close(_ >= i)
|
wenzelm@40711
|
60 |
stack = (i, command.source, buffer()) :: stack
|
wenzelm@40711
|
61 |
case None =>
|
wenzelm@40711
|
62 |
}
|
wenzelm@40711
|
63 |
stack.head._3 += Atom(command)
|
wenzelm@40711
|
64 |
}
|
wenzelm@40711
|
65 |
|
wenzelm@40711
|
66 |
|
wenzelm@40711
|
67 |
/* result structure */
|
wenzelm@40711
|
68 |
|
wenzelm@40711
|
69 |
val spans = parse_spans(syntax.scan(text))
|
wenzelm@40711
|
70 |
spans.foreach(span => add(Command.span(span)))
|
wenzelm@40711
|
71 |
result()
|
wenzelm@40711
|
72 |
}
|
wenzelm@40711
|
73 |
}
|
wenzelm@40711
|
74 |
|
wenzelm@40711
|
75 |
|
wenzelm@40711
|
76 |
|
wenzelm@38656
|
77 |
/** parse spans **/
|
wenzelm@38656
|
78 |
|
wenzelm@38655
|
79 |
def parse_spans(toks: List[Token]): List[List[Token]] =
|
wenzelm@34273
|
80 |
{
|
wenzelm@38655
|
81 |
val result = new mutable.ListBuffer[List[Token]]
|
wenzelm@38495
|
82 |
val span = new mutable.ListBuffer[Token]
|
wenzelm@38495
|
83 |
val whitespace = new mutable.ListBuffer[Token]
|
wenzelm@34273
|
84 |
|
wenzelm@38495
|
85 |
def flush(buffer: mutable.ListBuffer[Token])
|
wenzelm@38495
|
86 |
{
|
wenzelm@38495
|
87 |
if (!buffer.isEmpty) { result += buffer.toList; buffer.clear }
|
wenzelm@34273
|
88 |
}
|
wenzelm@38495
|
89 |
for (tok <- toks) {
|
wenzelm@38495
|
90 |
if (tok.is_command) { flush(span); flush(whitespace); span += tok }
|
wenzelm@38495
|
91 |
else if (tok.is_ignored) whitespace += tok
|
wenzelm@38495
|
92 |
else { span ++= whitespace; whitespace.clear; span += tok }
|
wenzelm@38495
|
93 |
}
|
wenzelm@38495
|
94 |
flush(span); flush(whitespace)
|
wenzelm@38495
|
95 |
result.toList
|
wenzelm@34273
|
96 |
}
|
wenzelm@38656
|
97 |
|
wenzelm@38656
|
98 |
|
wenzelm@38656
|
99 |
|
wenzelm@38656
|
100 |
/** text edits **/
|
wenzelm@38656
|
101 |
|
wenzelm@44604
|
102 |
def text_edits(
|
wenzelm@44604
|
103 |
syntax: Outer_Syntax,
|
wenzelm@44604
|
104 |
previous: Document.Version,
|
wenzelm@44604
|
105 |
edits: List[Document.Edit_Text],
|
wenzelm@44604
|
106 |
headers: List[(String, Document.Node.Header)])
|
wenzelm@44604
|
107 |
: (List[Document.Edit_Command], List[(String, Thy_Header.Header)], Document.Version) =
|
wenzelm@38656
|
108 |
{
|
wenzelm@38656
|
109 |
/* phase 1: edit individual command source */
|
wenzelm@38656
|
110 |
|
wenzelm@38684
|
111 |
@tailrec def edit_text(eds: List[Text.Edit], commands: Linear_Set[Command])
|
wenzelm@38656
|
112 |
: Linear_Set[Command] =
|
wenzelm@38656
|
113 |
{
|
wenzelm@38656
|
114 |
eds match {
|
wenzelm@38656
|
115 |
case e :: es =>
|
wenzelm@38656
|
116 |
Document.Node.command_starts(commands.iterator).find {
|
wenzelm@38656
|
117 |
case (cmd, cmd_start) =>
|
wenzelm@38656
|
118 |
e.can_edit(cmd.source, cmd_start) ||
|
wenzelm@38656
|
119 |
e.is_insert && e.start == cmd_start + cmd.length
|
wenzelm@38656
|
120 |
} match {
|
wenzelm@38656
|
121 |
case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
|
wenzelm@38656
|
122 |
val (rest, text) = e.edit(cmd.source, cmd_start)
|
wenzelm@38656
|
123 |
val new_commands = commands.insert_after(Some(cmd), Command.unparsed(text)) - cmd
|
wenzelm@38656
|
124 |
edit_text(rest.toList ::: es, new_commands)
|
wenzelm@38656
|
125 |
|
wenzelm@38656
|
126 |
case Some((cmd, cmd_start)) =>
|
wenzelm@38656
|
127 |
edit_text(es, commands.insert_after(Some(cmd), Command.unparsed(e.text)))
|
wenzelm@38656
|
128 |
|
wenzelm@38656
|
129 |
case None =>
|
wenzelm@38656
|
130 |
require(e.is_insert && e.start == 0)
|
wenzelm@38656
|
131 |
edit_text(es, commands.insert_after(None, Command.unparsed(e.text)))
|
wenzelm@38656
|
132 |
}
|
wenzelm@38656
|
133 |
case Nil => commands
|
wenzelm@38656
|
134 |
}
|
wenzelm@38656
|
135 |
}
|
wenzelm@38656
|
136 |
|
wenzelm@38656
|
137 |
|
wenzelm@38656
|
138 |
/* phase 2: recover command spans */
|
wenzelm@38656
|
139 |
|
wenzelm@38656
|
140 |
@tailrec def recover_spans(commands: Linear_Set[Command]): Linear_Set[Command] =
|
wenzelm@38656
|
141 |
{
|
wenzelm@38656
|
142 |
commands.iterator.find(_.is_unparsed) match {
|
wenzelm@38656
|
143 |
case Some(first_unparsed) =>
|
wenzelm@38656
|
144 |
val first =
|
wenzelm@39204
|
145 |
commands.reverse_iterator(first_unparsed).
|
wenzelm@39204
|
146 |
dropWhile(_.newlines == 0).find(_.is_command) getOrElse commands.head
|
wenzelm@38656
|
147 |
val last =
|
wenzelm@39204
|
148 |
commands.iterator(first_unparsed).
|
wenzelm@39204
|
149 |
dropWhile(_.newlines == 0).find(_.is_command) getOrElse commands.last
|
wenzelm@38656
|
150 |
val range =
|
wenzelm@38656
|
151 |
commands.iterator(first).takeWhile(_ != last).toList ::: List(last)
|
wenzelm@38656
|
152 |
|
wenzelm@38656
|
153 |
val sources = range.flatMap(_.span.map(_.source))
|
wenzelm@44524
|
154 |
val spans0 = parse_spans(syntax.scan(sources.mkString))
|
wenzelm@38656
|
155 |
|
wenzelm@38656
|
156 |
val (before_edit, spans1) =
|
wenzelm@38656
|
157 |
if (!spans0.isEmpty && first.is_command && first.span == spans0.head)
|
wenzelm@38656
|
158 |
(Some(first), spans0.tail)
|
wenzelm@38656
|
159 |
else (commands.prev(first), spans0)
|
wenzelm@38656
|
160 |
|
wenzelm@38656
|
161 |
val (after_edit, spans2) =
|
wenzelm@38656
|
162 |
if (!spans1.isEmpty && last.is_command && last.span == spans1.last)
|
wenzelm@38656
|
163 |
(Some(last), spans1.take(spans1.length - 1))
|
wenzelm@38656
|
164 |
else (commands.next(last), spans1)
|
wenzelm@38656
|
165 |
|
wenzelm@44533
|
166 |
val inserted = spans2.map(span => new Command(Document.new_id(), span))
|
wenzelm@38656
|
167 |
val new_commands =
|
wenzelm@38656
|
168 |
commands.delete_between(before_edit, after_edit).append_after(before_edit, inserted)
|
wenzelm@38656
|
169 |
recover_spans(new_commands)
|
wenzelm@38656
|
170 |
|
wenzelm@38656
|
171 |
case None => commands
|
wenzelm@38656
|
172 |
}
|
wenzelm@38656
|
173 |
}
|
wenzelm@38656
|
174 |
|
wenzelm@38656
|
175 |
|
wenzelm@44604
|
176 |
/* node header */
|
wenzelm@44604
|
177 |
|
wenzelm@44604
|
178 |
def node_header(name: String, node: Document.Node)
|
wenzelm@44604
|
179 |
: (List[(String, Thy_Header.Header)], Document.Node.Header) =
|
wenzelm@44604
|
180 |
(node.header.thy_header, headers.find(p => p._1 == name).map(_._2)) match {
|
wenzelm@44604
|
181 |
case (Exn.Res(thy_header0), Some(header @ Document.Node.Header(_, Exn.Res(thy_header))))
|
wenzelm@44604
|
182 |
if thy_header0 != thy_header =>
|
wenzelm@44604
|
183 |
(List((name, thy_header)), header)
|
wenzelm@44604
|
184 |
case (Exn.Exn(_), Some(header @ Document.Node.Header(_, Exn.Res(thy_header)))) =>
|
wenzelm@44604
|
185 |
(List((name, thy_header)), header)
|
wenzelm@44604
|
186 |
case _ => (Nil, node.header)
|
wenzelm@44604
|
187 |
}
|
wenzelm@44604
|
188 |
|
wenzelm@44604
|
189 |
|
wenzelm@38656
|
190 |
/* resulting document edits */
|
wenzelm@38656
|
191 |
|
wenzelm@38656
|
192 |
{
|
wenzelm@40748
|
193 |
val doc_edits = new mutable.ListBuffer[Document.Edit_Command]
|
wenzelm@44604
|
194 |
val header_edits = new mutable.ListBuffer[(String, Thy_Header.Header)]
|
wenzelm@38676
|
195 |
var nodes = previous.nodes
|
wenzelm@38656
|
196 |
|
wenzelm@40747
|
197 |
edits foreach {
|
wenzelm@40747
|
198 |
case (name, None) =>
|
wenzelm@40747
|
199 |
doc_edits += (name -> None)
|
wenzelm@40747
|
200 |
nodes -= name
|
wenzelm@38656
|
201 |
|
wenzelm@40747
|
202 |
case (name, Some(text_edits)) =>
|
wenzelm@44571
|
203 |
val node = nodes(name)
|
wenzelm@44571
|
204 |
val commands0 = node.commands
|
wenzelm@40747
|
205 |
val commands1 = edit_text(text_edits, commands0)
|
wenzelm@40747
|
206 |
val commands2 = recover_spans(commands1) // FIXME somewhat slow
|
wenzelm@38656
|
207 |
|
wenzelm@40747
|
208 |
val removed_commands = commands0.iterator.filter(!commands2.contains(_)).toList
|
wenzelm@40747
|
209 |
val inserted_commands = commands2.iterator.filter(!commands0.contains(_)).toList
|
wenzelm@38656
|
210 |
|
wenzelm@40747
|
211 |
val cmd_edits =
|
wenzelm@40747
|
212 |
removed_commands.reverse.map(cmd => (commands0.prev(cmd), None)) :::
|
wenzelm@40747
|
213 |
inserted_commands.map(cmd => (commands2.prev(cmd), Some(cmd)))
|
wenzelm@40747
|
214 |
|
wenzelm@40747
|
215 |
doc_edits += (name -> Some(cmd_edits))
|
wenzelm@44604
|
216 |
|
wenzelm@44604
|
217 |
val (new_headers, new_header) = node_header(name, node)
|
wenzelm@44604
|
218 |
header_edits ++= new_headers
|
wenzelm@44604
|
219 |
|
wenzelm@44604
|
220 |
nodes += (name -> Document.Node(new_header, node.blobs, commands2))
|
wenzelm@38656
|
221 |
}
|
wenzelm@44604
|
222 |
(doc_edits.toList, header_edits.toList, Document.Version(Document.new_id(), nodes))
|
wenzelm@38656
|
223 |
}
|
wenzelm@38656
|
224 |
}
|
wenzelm@34273
|
225 |
}
|