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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
//! This module defines a custom kind of list used in the [`numbat::Value::List`].
//! The specificities of this list are that:
//! - It's based on a `VecDeque`, which means insertion at the beginning or the end is `O(1)`.
//! - It's stored behind an `Arc`, which makes cloning the [`numbat::Value`] very cheap.
//! - It tries to reduce the number of allocations as much as possible, even when shared between multiple values.
//! It never re-allocates unless there are multiple owners and elements are being added to the list.
use std::{collections::VecDeque, fmt, sync::Arc};
use crate::{interpreter::RuntimeErrorKind, value::Value};
/// Reference counted list / list view
#[derive(Clone, Eq)]
pub struct NumbatList<T> {
/// The original alloc shared between all values
alloc: Arc<VecDeque<T>>,
/// The indexes accessible to us. If this is `None`, we own the whole allocation
view: Option<(usize, usize)>,
}
impl<T> Default for NumbatList<T> {
fn default() -> Self {
Self {
alloc: Default::default(),
view: Default::default(),
}
}
}
impl<T: fmt::Debug + Clone> fmt::Debug for NumbatList<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl<T: PartialEq> PartialEq for NumbatList<T> {
fn eq(&self, other: &Self) -> bool {
// Best case scenario, the slice doesn't have the same length; exit early
if self.len() != other.len() {
return false;
}
// Second best case, the other slice comes from the same allocation and
// has the same view => they are equal
if Arc::ptr_eq(&self.alloc, &other.alloc) && self.view == other.view {
true
} else {
// Worst case scenario, we need to compare all the elements one by one
self.iter().zip(other.iter()).all(|(l, r)| l == r)
}
}
}
impl<T> NumbatList<T> {
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn len(&self) -> usize {
if let Some(view) = self.view {
view.1 - view.0
} else {
self.alloc.len()
}
}
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
alloc: Arc::new(VecDeque::with_capacity(capacity)),
view: None,
}
}
/// Return the tail of the list without the first element.
/// Return an error if the list is empty.
pub fn tail(&mut self) -> Result<(), Box<RuntimeErrorKind>> {
if self.is_empty() {
return Err(Box::new(RuntimeErrorKind::EmptyList));
}
if let Some(view) = &mut self.view {
view.0 += 1;
// should be ensured by the if above
debug_assert!(view.0 <= view.1);
} else {
self.view = Some((1, self.len()));
}
Ok(())
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
let (start, end) = self.view.map_or((0, self.alloc.len()), |view| view);
self.alloc.iter().skip(start).take(end - start)
}
}
impl<T: Clone> NumbatList<T> {
fn make_mut(&mut self) -> (&mut Option<(usize, usize)>, &mut VecDeque<T>) {
if Arc::strong_count(&self.alloc) != 1 {
// If someone else is using the list we must clone it
self.alloc = Arc::new(self.iter().cloned().collect());
self.view = None;
}
// With our usage, this should never allocate since we know we're the only
// one holding a reference to this `Arc` and we don't use weak references.
(&mut self.view, Arc::make_mut(&mut self.alloc))
}
/// Return the first element of the list. If we're the only owner of the list,
/// drop the list and do not copy anything. If another list is alive, only
/// clone the value that's being returned.
pub fn head(self) -> Option<T> {
let front = self.view.map_or(0, |(start, _end)| start);
match Arc::try_unwrap(self.alloc) {
Ok(mut solely_owned) => solely_owned.swap_remove_front(front),
Err(shared) => shared.get(front).cloned(),
}
}
/// Allocate if the list is being used by another value at the same time
pub fn push_front(&mut self, element: T) {
let (view, inner) = self.make_mut();
if let Some((start, end)) = view {
// if we were alone on the allocation and had a view of the inner allocation
// we can keep the allocation and overwrite the start-1 element.
// but we need to take care of the special case where the start is 0.
if *start == 0 {
inner.push_front(element);
*end += 1;
} else {
*start -= 1;
inner[*start] = element;
}
} else {
inner.push_front(element);
}
}
/// Allocate if the list is being used by another value at the same time
pub fn push_back(&mut self, element: T) {
let (view, inner) = self.make_mut();
if let Some((_start, end)) = view {
// if we were alone on the allocation and had a view of the inner allocation
// we can keep the allocation and overwrite the end+1 element.
// but we need to take care of the special case where the end is `alloc.len()`.
if *end == inner.len() {
inner.push_back(element);
*end += 1;
} else {
*end += 1;
inner[*end] = element;
}
} else {
inner.push_back(element);
}
}
}
impl From<NumbatList<Value>> for Value {
fn from(list: NumbatList<Value>) -> Self {
Value::List(list)
}
}
impl From<VecDeque<Value>> for Value {
fn from(list: VecDeque<Value>) -> Self {
Value::List(NumbatList {
alloc: Arc::new(list),
view: None,
})
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn basic() {
let mut list = NumbatList::<usize>::new();
let alloc = Arc::as_ptr(&list.alloc);
assert_eq!(list.len(), 0);
list.push_front(1);
assert_eq!(list.len(), 1);
list.push_front(0);
assert_eq!(list.len(), 2);
list.push_back(2);
assert_eq!(list.len(), 3);
list.push_back(3);
assert_eq!(list.len(), 4);
assert_eq!(alloc, Arc::as_ptr(&list.alloc));
insta::assert_debug_snapshot!(list, @r###"
[
0,
1,
2,
3,
]
"###);
let iter: Vec<_> = list.iter().collect();
insta::assert_debug_snapshot!(iter, @r###"
[
0,
1,
2,
3,
]
"###);
list.tail().unwrap();
assert_eq!(list.len(), 3);
list.tail().unwrap();
assert_eq!(list.len(), 2);
assert_eq!(alloc, Arc::as_ptr(&list.alloc));
insta::assert_debug_snapshot!(list, @r###"
[
2,
3,
]
"###);
let iter: Vec<_> = list.iter().collect();
insta::assert_debug_snapshot!(iter, @r###"
[
2,
3,
]
"###);
list.tail().unwrap();
list.tail().unwrap();
assert!(list.is_empty());
assert_eq!(alloc, Arc::as_ptr(&list.alloc));
assert_eq!(list.tail(), Err(Box::new(RuntimeErrorKind::EmptyList)));
}
#[test]
fn allocate() {
let mut list1 = NumbatList::<usize>::new();
list1.push_front(1);
list1.push_back(0);
let mut list2 = list1.clone();
assert!(Arc::ptr_eq(&list1.alloc, &list2.alloc));
list2.tail().unwrap();
// Even after advancing the list2 the alloc can still be shared between both instance
assert!(Arc::ptr_eq(&list1.alloc, &list2.alloc));
// Pushing something new in the first list should re-allocate
list1.push_front(2);
assert!(!Arc::ptr_eq(&list1.alloc, &list2.alloc));
// Now that list2 is alone on its allocation it should be able
// to push an element to the front without re-allocating anything
let alloc = Arc::as_ptr(&list2.alloc);
// Pushing something new in the first list should not re-allocate
list2.push_front(2);
assert_eq!(alloc, Arc::as_ptr(&list2.alloc));
}
#[test]
fn equality() {
let mut list1 = NumbatList::<usize>::new();
list1.push_front(1);
list1.push_back(0);
let mut list2 = list1.clone();
assert_eq!(list1, list2);
list2.tail().unwrap();
assert_ne!(list1, list2);
// Even if the lists don't share the same allocation, they should be equal
list2.push_front(1);
assert_eq!(list1, list2);
}
#[test]
fn bug_1() {
let mut list1 = NumbatList::<usize>::new();
list1.push_front(1);
let _list2 = list1.clone();
list1.tail().unwrap();
list1.push_front(2);
}
}