wenzelm@34407
|
1 |
/*
|
wenzelm@34485
|
2 |
* Document as list of commands, consisting of lists of tokens
|
wenzelm@34407
|
3 |
*
|
wenzelm@34407
|
4 |
* @author Johannes Hölzl, TU Munich
|
immler@34532
|
5 |
* @author Fabian Immler, TU Munich
|
wenzelm@34485
|
6 |
* @author Makarius
|
wenzelm@34407
|
7 |
*/
|
wenzelm@34407
|
8 |
|
wenzelm@34318
|
9 |
package isabelle.proofdocument
|
wenzelm@34318
|
10 |
|
wenzelm@34760
|
11 |
|
wenzelm@34818
|
12 |
import scala.actors.Actor._
|
wenzelm@34818
|
13 |
|
wenzelm@34318
|
14 |
import java.util.regex.Pattern
|
wenzelm@34703
|
15 |
|
wenzelm@34318
|
16 |
|
wenzelm@34823
|
17 |
object Document
|
wenzelm@34483
|
18 |
{
|
wenzelm@34582
|
19 |
// Be careful when changing this regex. Not only must it handle the
|
wenzelm@34818
|
20 |
// spurious end of a token but also:
|
wenzelm@34318
|
21 |
// Bug ID: 5050507 Pattern.matches throws StackOverflow Error
|
wenzelm@34318
|
22 |
// http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5050507
|
wenzelm@34818
|
23 |
|
wenzelm@34818
|
24 |
val token_pattern =
|
wenzelm@34318
|
25 |
Pattern.compile(
|
wenzelm@34318
|
26 |
"\\{\\*([^*]|\\*[^}]|\\*\\z)*(\\z|\\*\\})|" +
|
wenzelm@34318
|
27 |
"\\(\\*([^*]|\\*[^)]|\\*\\z)*(\\z|\\*\\))|" +
|
wenzelm@34818
|
28 |
"(\\?'?|')[A-Za-z_0-9.]*|" +
|
wenzelm@34818
|
29 |
"[A-Za-z_0-9.]+|" +
|
wenzelm@34318
|
30 |
"[!#$%&*+-/<=>?@^_|~]+|" +
|
wenzelm@34318
|
31 |
"\"([^\\\\\"]?(\\\\(.|\\z))?)*+(\"|\\z)|" +
|
wenzelm@34318
|
32 |
"`([^\\\\`]?(\\\\(.|\\z))?)*+(`|\\z)|" +
|
wenzelm@34318
|
33 |
"[()\\[\\]{}:;]", Pattern.MULTILINE)
|
wenzelm@34485
|
34 |
|
wenzelm@34823
|
35 |
def empty(id: Isar_Document.Document_ID): Document =
|
wenzelm@34823
|
36 |
new Document(id, Linear_Set(), Map(), Linear_Set(), Map())
|
immler@34660
|
37 |
|
immler@34660
|
38 |
type StructureChange = List[(Option[Command], Option[Command])]
|
wenzelm@34778
|
39 |
}
|
immler@34538
|
40 |
|
wenzelm@34318
|
41 |
|
wenzelm@34823
|
42 |
class Document(
|
wenzelm@34818
|
43 |
val id: Isar_Document.Document_ID,
|
wenzelm@34818
|
44 |
val tokens: Linear_Set[Token], // FIXME plain List, inside Command
|
wenzelm@34818
|
45 |
val token_start: Map[Token, Int], // FIXME eliminate
|
wenzelm@34818
|
46 |
val commands: Linear_Set[Command],
|
wenzelm@34818
|
47 |
var states: Map[Command, Command]) // FIXME immutable, eliminate!?
|
wenzelm@34818
|
48 |
extends Session.Entity
|
wenzelm@34483
|
49 |
{
|
wenzelm@34823
|
50 |
import Document.StructureChange
|
immler@34653
|
51 |
|
wenzelm@34582
|
52 |
def content = Token.string_from_tokens(Nil ++ tokens, token_start)
|
wenzelm@34657
|
53 |
|
wenzelm@34657
|
54 |
|
wenzelm@34818
|
55 |
/* accumulated messages */
|
wenzelm@34818
|
56 |
|
wenzelm@34818
|
57 |
private val accumulator = actor {
|
wenzelm@34818
|
58 |
loop {
|
wenzelm@34818
|
59 |
react {
|
wenzelm@34818
|
60 |
case (session: Session, message: XML.Tree) =>
|
wenzelm@34818
|
61 |
message match {
|
wenzelm@34818
|
62 |
case XML.Elem(Markup.MESSAGE, (Markup.CLASS, Markup.STATUS) :: _, elems) =>
|
wenzelm@34818
|
63 |
for {
|
wenzelm@34818
|
64 |
XML.Elem(Markup.EDIT, (Markup.ID, cmd_id) :: (Markup.STATE, state_id) :: _, _)
|
wenzelm@34818
|
65 |
<- elems
|
wenzelm@34818
|
66 |
} {
|
wenzelm@34818
|
67 |
session.lookup_entity(cmd_id) match {
|
wenzelm@34818
|
68 |
case Some(cmd: Command) =>
|
wenzelm@34818
|
69 |
val state = cmd.finish_static(state_id)
|
wenzelm@34818
|
70 |
session.register_entity(state)
|
wenzelm@34818
|
71 |
states += (cmd -> state) // FIXME !?
|
wenzelm@34818
|
72 |
session.command_change.event(cmd) // FIXME really!?
|
wenzelm@34818
|
73 |
case _ =>
|
wenzelm@34818
|
74 |
}
|
wenzelm@34818
|
75 |
}
|
wenzelm@34818
|
76 |
case _ =>
|
wenzelm@34818
|
77 |
}
|
wenzelm@34818
|
78 |
|
wenzelm@34818
|
79 |
case bad => System.err.println("document accumulator: ignoring bad message " + bad)
|
wenzelm@34818
|
80 |
}
|
wenzelm@34818
|
81 |
}
|
wenzelm@34818
|
82 |
}
|
wenzelm@34818
|
83 |
|
wenzelm@34818
|
84 |
def consume(session: Session, message: XML.Tree) { accumulator ! (session, message) }
|
wenzelm@34818
|
85 |
|
wenzelm@34818
|
86 |
|
wenzelm@34818
|
87 |
|
wenzelm@34485
|
88 |
/** token view **/
|
wenzelm@34485
|
89 |
|
wenzelm@34823
|
90 |
def text_changed(session: Session, change: Change): (Document, StructureChange) =
|
wenzelm@34485
|
91 |
{
|
wenzelm@34823
|
92 |
def edit_doc(doc_chgs: (Document, StructureChange), edit: Edit) = {
|
immler@34660
|
93 |
val (doc, chgs) = doc_chgs
|
wenzelm@34778
|
94 |
val (new_doc, chg) = doc.text_edit(session, edit, change.id)
|
immler@34660
|
95 |
(new_doc, chgs ++ chg)
|
immler@34660
|
96 |
}
|
wenzelm@34693
|
97 |
((this, Nil: StructureChange) /: change.edits)(edit_doc)
|
immler@34660
|
98 |
}
|
immler@34660
|
99 |
|
wenzelm@34823
|
100 |
def text_edit(session: Session, e: Edit, id: String): (Document, StructureChange) =
|
immler@34660
|
101 |
{
|
immler@34660
|
102 |
case class TextChange(start: Int, added: String, removed: String)
|
immler@34660
|
103 |
val change = e match {
|
immler@34660
|
104 |
case Insert(s, a) => TextChange(s, a, "")
|
immler@34660
|
105 |
case Remove(s, r) => TextChange(s, "", r)
|
immler@34660
|
106 |
}
|
immler@34551
|
107 |
//indices of tokens
|
immler@34551
|
108 |
var start: Map[Token, Int] = token_start
|
immler@34551
|
109 |
def stop(t: Token) = start(t) + t.length
|
immler@34551
|
110 |
// split old token lists
|
wenzelm@34582
|
111 |
val tokens = Nil ++ this.tokens
|
immler@34551
|
112 |
val (begin, remaining) = tokens.span(stop(_) < change.start)
|
immler@34648
|
113 |
val (removed, end) = remaining.span(token_start(_) <= change.start + change.removed.length)
|
immler@34551
|
114 |
// update indices
|
wenzelm@34582
|
115 |
start = end.foldLeft(start)((s, t) =>
|
immler@34648
|
116 |
s + (t -> (s(t) + change.added.length - change.removed.length)))
|
wenzelm@34485
|
117 |
|
immler@34551
|
118 |
val split_begin = removed.takeWhile(start(_) < change.start).
|
immler@34554
|
119 |
map (t => {
|
immler@34554
|
120 |
val split_tok = new Token(t.content.substring(0, change.start - start(t)), t.kind)
|
immler@34554
|
121 |
start += (split_tok -> start(t))
|
immler@34554
|
122 |
split_tok
|
immler@34554
|
123 |
})
|
immler@34554
|
124 |
|
immler@34648
|
125 |
val split_end = removed.dropWhile(stop(_) < change.start + change.removed.length).
|
immler@34554
|
126 |
map (t => {
|
wenzelm@34582
|
127 |
val split_tok =
|
immler@34648
|
128 |
new Token(t.content.substring(change.start + change.removed.length - start(t)), t.kind)
|
immler@34554
|
129 |
start += (split_tok -> start(t))
|
immler@34554
|
130 |
split_tok
|
immler@34554
|
131 |
})
|
immler@34551
|
132 |
// update indices
|
immler@34554
|
133 |
start = removed.foldLeft (start) ((s, t) => s - t)
|
immler@34554
|
134 |
start = split_end.foldLeft (start) ((s, t) =>
|
immler@34554
|
135 |
s + (t -> (change.start + change.added.length)))
|
wenzelm@34318
|
136 |
|
immler@34551
|
137 |
val ins = new Token(change.added, Token.Kind.OTHER)
|
immler@34551
|
138 |
start += (ins -> change.start)
|
wenzelm@34818
|
139 |
|
wenzelm@34582
|
140 |
var invalid_tokens = split_begin ::: ins :: split_end ::: end
|
wenzelm@34582
|
141 |
var new_tokens: List[Token] = Nil
|
wenzelm@34582
|
142 |
var old_suffix: List[Token] = Nil
|
wenzelm@34318
|
143 |
|
immler@34551
|
144 |
val match_start = invalid_tokens.firstOption.map(start(_)).getOrElse(0)
|
wenzelm@34582
|
145 |
val matcher =
|
wenzelm@34823
|
146 |
Document.token_pattern.matcher(Token.string_from_tokens(invalid_tokens, start))
|
immler@34526
|
147 |
|
immler@34526
|
148 |
while (matcher.find() && invalid_tokens != Nil) {
|
wenzelm@34485
|
149 |
val kind =
|
wenzelm@34819
|
150 |
if (session.current_syntax.is_command(matcher.group))
|
wenzelm@34485
|
151 |
Token.Kind.COMMAND_START
|
wenzelm@34494
|
152 |
else if (matcher.end - matcher.start > 2 && matcher.group.substring(0, 2) == "(*")
|
wenzelm@34485
|
153 |
Token.Kind.COMMENT
|
wenzelm@34485
|
154 |
else
|
wenzelm@34485
|
155 |
Token.Kind.OTHER
|
immler@34551
|
156 |
val new_token = new Token(matcher.group, kind)
|
immler@34551
|
157 |
start += (new_token -> (match_start + matcher.start))
|
immler@34526
|
158 |
new_tokens ::= new_token
|
wenzelm@34318
|
159 |
|
immler@34660
|
160 |
invalid_tokens = invalid_tokens dropWhile (stop(_) < stop(new_token))
|
immler@34526
|
161 |
invalid_tokens match {
|
wenzelm@34582
|
162 |
case t :: ts =>
|
wenzelm@34582
|
163 |
if (start(t) == start(new_token) &&
|
wenzelm@34582
|
164 |
start(t) > change.start + change.added.length) {
|
immler@34597
|
165 |
old_suffix = t :: ts
|
immler@34592
|
166 |
new_tokens = new_tokens.tail
|
immler@34526
|
167 |
invalid_tokens = Nil
|
immler@34526
|
168 |
}
|
immler@34526
|
169 |
case _ =>
|
wenzelm@34318
|
170 |
}
|
wenzelm@34318
|
171 |
}
|
immler@34526
|
172 |
val insert = new_tokens.reverse
|
immler@34544
|
173 |
val new_token_list = begin ::: insert ::: old_suffix
|
wenzelm@34778
|
174 |
token_changed(session, id, begin.lastOption, insert,
|
immler@34597
|
175 |
old_suffix.firstOption, new_token_list, start)
|
wenzelm@34318
|
176 |
}
|
wenzelm@34582
|
177 |
|
wenzelm@34818
|
178 |
|
wenzelm@34485
|
179 |
/** command view **/
|
wenzelm@34485
|
180 |
|
wenzelm@34582
|
181 |
private def token_changed(
|
wenzelm@34778
|
182 |
session: Session,
|
wenzelm@34582
|
183 |
new_id: String,
|
wenzelm@34582
|
184 |
before_change: Option[Token],
|
wenzelm@34582
|
185 |
inserted_tokens: List[Token],
|
wenzelm@34582
|
186 |
after_change: Option[Token],
|
immler@34597
|
187 |
new_tokens: List[Token],
|
immler@34660
|
188 |
new_token_start: Map[Token, Int]):
|
wenzelm@34823
|
189 |
(Document, StructureChange) =
|
wenzelm@34485
|
190 |
{
|
wenzelm@34689
|
191 |
val new_tokenset = Linear_Set[Token]() ++ new_tokens
|
immler@34593
|
192 |
val cmd_before_change = before_change match {
|
immler@34593
|
193 |
case None => None
|
immler@34593
|
194 |
case Some(bc) =>
|
immler@34593
|
195 |
val cmd_with_bc = commands.find(_.contains(bc)).get
|
immler@34593
|
196 |
if (cmd_with_bc.tokens.last == bc) {
|
immler@34593
|
197 |
if (new_tokenset.next(bc).map(_.is_start).getOrElse(true))
|
immler@34593
|
198 |
Some(cmd_with_bc)
|
immler@34593
|
199 |
else commands.prev(cmd_with_bc)
|
immler@34593
|
200 |
}
|
immler@34593
|
201 |
else commands.prev(cmd_with_bc)
|
immler@34593
|
202 |
}
|
immler@34544
|
203 |
|
immler@34593
|
204 |
val cmd_after_change = after_change match {
|
immler@34593
|
205 |
case None => None
|
immler@34593
|
206 |
case Some(ac) =>
|
immler@34593
|
207 |
val cmd_with_ac = commands.find(_.contains(ac)).get
|
immler@34593
|
208 |
if (ac.is_start)
|
immler@34593
|
209 |
Some(cmd_with_ac)
|
immler@34593
|
210 |
else
|
immler@34593
|
211 |
commands.next(cmd_with_ac)
|
immler@34593
|
212 |
}
|
wenzelm@34485
|
213 |
|
immler@34593
|
214 |
val removed_commands = commands.dropWhile(Some(_) != cmd_before_change).drop(1).
|
immler@34554
|
215 |
takeWhile(Some(_) != cmd_after_change)
|
immler@34554
|
216 |
|
immler@34554
|
217 |
// calculate inserted commands
|
immler@34526
|
218 |
def tokens_to_commands(tokens: List[Token]): List[Command]= {
|
immler@34526
|
219 |
tokens match {
|
immler@34526
|
220 |
case Nil => Nil
|
wenzelm@34582
|
221 |
case t :: ts =>
|
wenzelm@34582
|
222 |
val (cmd, rest) =
|
wenzelm@34582
|
223 |
ts.span(t => t.kind != Token.Kind.COMMAND_START && t.kind != Token.Kind.COMMENT)
|
wenzelm@34778
|
224 |
new Command(session.create_id(), t :: cmd, new_token_start) :: tokens_to_commands(rest)
|
wenzelm@34485
|
225 |
}
|
wenzelm@34485
|
226 |
}
|
wenzelm@34485
|
227 |
|
immler@34593
|
228 |
val split_begin =
|
immler@34593
|
229 |
if (before_change.isDefined) {
|
immler@34593
|
230 |
val changed =
|
immler@34593
|
231 |
if (cmd_before_change.isDefined)
|
immler@34595
|
232 |
new_tokens.dropWhile(_ != cmd_before_change.get.tokens.last).drop(1)
|
immler@34593
|
233 |
else new_tokenset
|
immler@34593
|
234 |
if (changed.exists(_ == before_change.get))
|
immler@34597
|
235 |
changed.takeWhile(_ != before_change.get).toList :::
|
immler@34597
|
236 |
List(before_change.get)
|
immler@34593
|
237 |
else Nil
|
immler@34593
|
238 |
} else Nil
|
immler@34554
|
239 |
|
immler@34593
|
240 |
val split_end =
|
immler@34667
|
241 |
if (after_change.isDefined) {
|
immler@34595
|
242 |
val unchanged = new_tokens.dropWhile(_ != after_change.get)
|
immler@34667
|
243 |
if(cmd_after_change.isDefined) {
|
immler@34667
|
244 |
if (unchanged.exists(_ == cmd_after_change.get.tokens.first))
|
immler@34667
|
245 |
unchanged.takeWhile(_ != cmd_after_change.get.tokens.first).toList
|
immler@34667
|
246 |
else Nil
|
immler@34667
|
247 |
} else {
|
immler@34667
|
248 |
unchanged
|
immler@34667
|
249 |
}
|
immler@34593
|
250 |
} else Nil
|
immler@34593
|
251 |
|
immler@34597
|
252 |
val rescan_begin =
|
immler@34597
|
253 |
split_begin :::
|
immler@34597
|
254 |
before_change.map(bc => new_tokens.dropWhile(_ != bc).drop(1)).getOrElse(new_tokens)
|
wenzelm@34582
|
255 |
val rescanning_tokens =
|
immler@34597
|
256 |
after_change.map(ac => rescan_begin.takeWhile(_ != ac)).getOrElse(rescan_begin) :::
|
immler@34597
|
257 |
split_end
|
immler@34593
|
258 |
val inserted_commands = tokens_to_commands(rescanning_tokens.toList)
|
immler@34554
|
259 |
|
immler@34550
|
260 |
// build new document
|
wenzelm@34739
|
261 |
val new_commandset = commands.
|
wenzelm@34739
|
262 |
delete_between(cmd_before_change, cmd_after_change).
|
wenzelm@34739
|
263 |
append_after(cmd_before_change, inserted_commands)
|
wenzelm@34739
|
264 |
|
immler@34554
|
265 |
|
immler@34544
|
266 |
val doc =
|
wenzelm@34823
|
267 |
new Document(new_id, new_tokenset, new_token_start, new_commandset,
|
wenzelm@34778
|
268 |
states -- removed_commands)
|
immler@34660
|
269 |
|
immler@34660
|
270 |
val removes =
|
immler@34660
|
271 |
for (cmd <- removed_commands) yield (cmd_before_change -> None)
|
immler@34660
|
272 |
val inserts =
|
immler@34660
|
273 |
for (cmd <- inserted_commands) yield (doc.commands.prev(cmd) -> Some(cmd))
|
immler@34660
|
274 |
|
immler@34660
|
275 |
return (doc, removes.toList ++ inserts)
|
wenzelm@34485
|
276 |
}
|
immler@34596
|
277 |
|
immler@34596
|
278 |
val commands_offsets = {
|
immler@34596
|
279 |
var last_stop = 0
|
immler@34596
|
280 |
(for (c <- commands) yield {
|
immler@34596
|
281 |
val r = c -> (last_stop,c.stop(this))
|
immler@34596
|
282 |
last_stop = c.stop(this)
|
immler@34596
|
283 |
r
|
immler@34596
|
284 |
}).toArray
|
immler@34596
|
285 |
}
|
immler@34596
|
286 |
|
wenzelm@34712
|
287 |
def command_at(pos: Int): Option[Command] =
|
wenzelm@34712
|
288 |
find_command(pos, 0, commands_offsets.length)
|
wenzelm@34712
|
289 |
|
immler@34596
|
290 |
// use a binary search to find commands for a given offset
|
wenzelm@34712
|
291 |
private def find_command(pos: Int, array_start: Int, array_stop: Int): Option[Command] =
|
wenzelm@34712
|
292 |
{
|
immler@34596
|
293 |
val middle_index = (array_start + array_stop) / 2
|
wenzelm@34712
|
294 |
if (middle_index >= commands_offsets.length) return None
|
immler@34596
|
295 |
val (middle, (start, stop)) = commands_offsets(middle_index)
|
immler@34596
|
296 |
// does middle contain pos?
|
wenzelm@34712
|
297 |
if (start <= pos && pos < stop)
|
wenzelm@34712
|
298 |
Some(middle)
|
immler@34596
|
299 |
else if (start > pos)
|
wenzelm@34712
|
300 |
find_command(pos, array_start, middle_index)
|
immler@34596
|
301 |
else if (stop <= pos)
|
wenzelm@34712
|
302 |
find_command(pos, middle_index + 1, array_stop)
|
wenzelm@34712
|
303 |
else error("impossible")
|
immler@34596
|
304 |
}
|
wenzelm@34318
|
305 |
}
|