1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
//! Helpers to deal with several changes in events, batching them together.
//!
//! Preferably, changes should be kept to a minumum.
//! Sometimes, it’s needed to change the list of events, because parsing can be
//! messy, and it helps to expose a cleaner interface of events to the compiler
//! and other users.
//! It can also help to merge many adjacent similar events.
//! And, in other cases, it’s needed to parse subcontent: pass some events
//! through another tokenizer and inject the result.
use crate::event::Event;
/// Shift `previous` and `next` links according to `jumps`.
///
/// This fixes links in case there are events removed or added between them.
fn shift_links(events: &mut [Event], jumps: &[(usize, usize, usize)]) {
let mut jump_index = 0;
let mut index = 0;
let mut add = 0;
let mut rm = 0;
while index < events.len() {
let rm_curr = rm;
while jump_index < jumps.len() && jumps[jump_index].0 <= index {
add = jumps[jump_index].2;
rm = jumps[jump_index].1;
jump_index += 1;
}
// Ignore items that will be removed.
if rm > rm_curr {
index += rm - rm_curr;
} else {
if let Some(link) = &events[index].link {
if let Some(next) = link.next {
events[next].link.as_mut().unwrap().previous = Some(index + add - rm);
while jump_index < jumps.len() && jumps[jump_index].0 <= next {
add = jumps[jump_index].2;
rm = jumps[jump_index].1;
jump_index += 1;
}
events[index].link.as_mut().unwrap().next = Some(next + add - rm);
index = next;
continue;
}
}
index += 1;
}
}
}
/// Make it easy to insert and remove things while being performant and keeping
/// links in check.
#[derive(Debug)]
pub struct EditMap {
/// Record of changes.
map: Vec<(usize, usize, Vec<Event>)>,
}
impl EditMap {
/// Create a new edit map.
pub fn new() -> EditMap {
EditMap { map: vec![] }
}
/// Create an edit: a remove and/or add at a certain place.
pub fn add(&mut self, index: usize, remove: usize, add: Vec<Event>) {
add_impl(self, index, remove, add, false);
}
/// Create an edit: but insert `add` before existing additions.
pub fn add_before(&mut self, index: usize, remove: usize, add: Vec<Event>) {
add_impl(self, index, remove, add, true);
}
/// Done, change the events.
pub fn consume(&mut self, events: &mut Vec<Event>) {
self.map
.sort_unstable_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
if self.map.is_empty() {
return;
}
// Calculate jumps: where items in the current list move to.
let mut jumps = Vec::with_capacity(self.map.len());
let mut index = 0;
let mut add_acc = 0;
let mut remove_acc = 0;
while index < self.map.len() {
let (at, remove, add) = &self.map[index];
remove_acc += remove;
add_acc += add.len();
jumps.push((*at, remove_acc, add_acc));
index += 1;
}
shift_links(events, &jumps);
let len_before = events.len();
let mut index = self.map.len();
let mut vecs = Vec::with_capacity(index * 2 + 1);
while index > 0 {
index -= 1;
vecs.push(events.split_off(self.map[index].0 + self.map[index].1));
vecs.push(self.map[index].2.split_off(0));
events.truncate(self.map[index].0);
}
vecs.push(events.split_off(0));
events.reserve(len_before + add_acc - remove_acc);
while let Some(mut slice) = vecs.pop() {
events.append(&mut slice);
}
self.map.truncate(0);
}
}
/// Create an edit.
fn add_impl(edit_map: &mut EditMap, at: usize, remove: usize, mut add: Vec<Event>, before: bool) {
let mut index = 0;
if remove == 0 && add.is_empty() {
return;
}
while index < edit_map.map.len() {
if edit_map.map[index].0 == at {
edit_map.map[index].1 += remove;
if before {
add.append(&mut edit_map.map[index].2);
edit_map.map[index].2 = add;
} else {
edit_map.map[index].2.append(&mut add);
}
return;
}
index += 1;
}
edit_map.map.push((at, remove, add));
}
|