After some discussion with PVH I realise that the repo structure in the last reorg was very rust-centric. In an attempt to put each language on a level footing move the rust code and project files into ./rust
438 lines
17 KiB
Rust
438 lines
17 KiB
Rust
use std::cmp::Ordering;
|
|
|
|
use crate::types::Op;
|
|
|
|
use super::{OpTreeInternal, OpTreeNode};
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct OpTreeIter<'a>(Inner<'a>);
|
|
|
|
impl<'a> OpTreeIter<'a> {
|
|
pub(crate) fn new(tree: &'a OpTreeInternal) -> OpTreeIter<'a> {
|
|
Self(
|
|
tree.root_node
|
|
.as_ref()
|
|
.map(|root| Inner::NonEmpty {
|
|
// This is a guess at the average depth of an OpTree
|
|
ancestors: Vec::with_capacity(6),
|
|
current: NodeIter {
|
|
node: root,
|
|
index: 0,
|
|
},
|
|
cumulative_index: 0,
|
|
root_node: root,
|
|
})
|
|
.unwrap_or(Inner::Empty),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl<'a> Iterator for OpTreeIter<'a> {
|
|
type Item = &'a Op;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
self.0.next()
|
|
}
|
|
|
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
|
self.0.nth(n)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
enum Inner<'a> {
|
|
Empty,
|
|
NonEmpty {
|
|
// A stack of nodes in the optree which we have descended in to to get to the current
|
|
// element.
|
|
ancestors: Vec<NodeIter<'a>>,
|
|
current: NodeIter<'a>,
|
|
// How far through the whole optree we are
|
|
cumulative_index: usize,
|
|
root_node: &'a OpTreeNode,
|
|
},
|
|
}
|
|
|
|
/// A node in the op tree which we are iterating over
|
|
#[derive(Clone)]
|
|
struct NodeIter<'a> {
|
|
/// The node itself
|
|
node: &'a OpTreeNode,
|
|
/// The index of the next element we will pull from the node. This means something different
|
|
/// depending on whether the node is a leaf node or not. If the node is a leaf node then this
|
|
/// index is the index in `node.elements` which will be returned on the next call to `next()`.
|
|
/// If the node is not an internal node then this index is the index of `children` which we are
|
|
/// currently iterating as well as being the index of the next element of `elements` which we
|
|
/// will return once we have finished iterating over the child node.
|
|
index: usize,
|
|
}
|
|
|
|
impl<'a> Iterator for Inner<'a> {
|
|
type Item = &'a Op;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
match self {
|
|
Inner::Empty => None,
|
|
Inner::NonEmpty {
|
|
ancestors,
|
|
current,
|
|
cumulative_index,
|
|
..
|
|
} => {
|
|
if current.node.is_leaf() {
|
|
// If we're in a leaf node and we haven't exhausted it yet we just return the elements
|
|
// of the leaf node
|
|
if current.index < current.node.len() {
|
|
let result = ¤t.node.elements[current.index];
|
|
current.index += 1;
|
|
*cumulative_index += 1;
|
|
Some(result)
|
|
} else {
|
|
// We've exhausted the leaf node, we must find the nearest non-exhausted parent (lol)
|
|
let node_iter = loop {
|
|
if let Some(
|
|
node_iter @ NodeIter {
|
|
node: parent,
|
|
index: parent_index,
|
|
},
|
|
) = ancestors.pop()
|
|
{
|
|
// We've exhausted this parent
|
|
if parent_index >= parent.elements.len() {
|
|
continue;
|
|
} else {
|
|
// This parent still has elements to process, let's use it!
|
|
break node_iter;
|
|
}
|
|
} else {
|
|
// No parents left, we're done
|
|
return None;
|
|
}
|
|
};
|
|
// if we've finished the elements in a leaf node and there's a parent node then we
|
|
// return the element from the parent node which is one after the index at which we
|
|
// descended into the child
|
|
*current = node_iter;
|
|
let result = ¤t.node.elements[current.index];
|
|
current.index += 1;
|
|
*cumulative_index += 1;
|
|
Some(result)
|
|
}
|
|
} else {
|
|
// If we're in a non-leaf node then the last iteration returned an element from the
|
|
// current nodes `elements`, so we must now descend into a leaf child
|
|
ancestors.push(current.clone());
|
|
loop {
|
|
let child = ¤t.node.children[current.index];
|
|
current.index = 0;
|
|
if !child.is_leaf() {
|
|
ancestors.push(NodeIter {
|
|
node: child,
|
|
index: 0,
|
|
});
|
|
current.node = child
|
|
} else {
|
|
current.node = child;
|
|
break;
|
|
}
|
|
}
|
|
self.next()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn nth(&mut self, n: usize) -> Option<Self::Item> {
|
|
match self {
|
|
Self::Empty => None,
|
|
Self::NonEmpty {
|
|
root_node,
|
|
cumulative_index,
|
|
current,
|
|
ancestors,
|
|
..
|
|
} => {
|
|
// Make sure that we don't rewind when calling nth more than once
|
|
if n < *cumulative_index {
|
|
None
|
|
} else if n >= root_node.len() {
|
|
*cumulative_index = root_node.len() - 1;
|
|
None
|
|
} else {
|
|
// rather than trying to go back up through the ancestors to find the right
|
|
// node we just start at the root.
|
|
*current = NodeIter {
|
|
node: root_node,
|
|
index: n,
|
|
};
|
|
*cumulative_index = 0;
|
|
ancestors.clear();
|
|
while !current.node.is_leaf() {
|
|
for (child_index, child) in current.node.children.iter().enumerate() {
|
|
match (*cumulative_index + child.len()).cmp(&n) {
|
|
Ordering::Less => {
|
|
*cumulative_index += child.len() + 1;
|
|
current.index = child_index + 1;
|
|
}
|
|
Ordering::Equal => {
|
|
*cumulative_index += child.len() + 1;
|
|
current.index = child_index + 1;
|
|
return Some(¤t.node.elements[child_index]);
|
|
}
|
|
Ordering::Greater => {
|
|
current.index = child_index;
|
|
let old = std::mem::replace(
|
|
current,
|
|
NodeIter {
|
|
node: child,
|
|
index: 0,
|
|
},
|
|
);
|
|
ancestors.push(old);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// we're in a leaf node and we kept track of the cumulative index as we went,
|
|
let index_in_this_node = n.saturating_sub(*cumulative_index);
|
|
current.index = index_in_this_node + 1;
|
|
Some(¤t.node.elements[index_in_this_node])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::super::OpTreeInternal;
|
|
use crate::types::{Key, Op, OpId, OpType, ScalarValue};
|
|
use proptest::prelude::*;
|
|
|
|
#[derive(Clone)]
|
|
enum Action {
|
|
Insert(usize, Op),
|
|
Delete(usize),
|
|
}
|
|
|
|
impl std::fmt::Debug for Action {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
Self::Insert(index, ..) => write!(f, "Insert({})", index),
|
|
Self::Delete(index) => write!(f, "Delete({})", index),
|
|
}
|
|
}
|
|
}
|
|
|
|
// A struct which impls Debug by only printing the counters of the IDs of the ops it wraps.
|
|
// This is useful because the only difference between the ops that we generate is the counter
|
|
// of their IDs. Wrapping a Vec<Op> in DebugOps will result in output from assert! etc. which
|
|
// only shows the counters. For example, the output of a failing assert_eq! like this
|
|
//
|
|
// assert_eq!(DebugOps(&ops1), DebugOps(&ops2))
|
|
//
|
|
// Might look like this
|
|
//
|
|
// left: `[0,1,2,3]
|
|
// right: `[0,1,2,3,4]
|
|
//
|
|
// i.e. all the other details of the ops are elided
|
|
#[derive(PartialEq)]
|
|
struct DebugOps<'a>(&'a [Op]);
|
|
|
|
impl<'a> std::fmt::Debug for DebugOps<'a> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "[")?;
|
|
for (index, op) in self.0.iter().enumerate() {
|
|
if index < self.0.len() - 1 {
|
|
write!(f, "{},", op.id.counter())?;
|
|
} else {
|
|
write!(f, "{}]", op.id.counter())?
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
fn op(counter: u64) -> Op {
|
|
Op {
|
|
action: OpType::Put(ScalarValue::Uint(counter)),
|
|
id: OpId(counter, 0),
|
|
key: Key::Map(0),
|
|
succ: Default::default(),
|
|
pred: Default::default(),
|
|
insert: false,
|
|
}
|
|
}
|
|
|
|
/// A model for a property based test of the OpTreeIter. We generate a set of actions, each
|
|
/// action pertaining to a `model` - which is just a `Vec<Op>`. As we generate each action we
|
|
/// apply it to the model and record the action we took. In the property test we replay the
|
|
/// same actions against an `OpTree` and check that the iterator returns the same result as the
|
|
/// `model`.
|
|
#[derive(Clone)]
|
|
struct Model {
|
|
actions: Vec<Action>,
|
|
model: Vec<Op>,
|
|
}
|
|
|
|
impl std::fmt::Debug for Model {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Model")
|
|
.field("actions", &self.actions)
|
|
.field("model", &DebugOps(&self.model))
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Model {
|
|
fn insert(&self, index: usize, next_op_counter: u64) -> Self {
|
|
let mut actions = self.actions.clone();
|
|
let op = op(next_op_counter);
|
|
actions.push(Action::Insert(index, op.clone()));
|
|
let mut model = self.model.clone();
|
|
model.insert(index, op);
|
|
Self { actions, model }
|
|
}
|
|
|
|
fn delete(&self, index: usize) -> Self {
|
|
let mut actions = self.actions.clone();
|
|
actions.push(Action::Delete(index));
|
|
let mut model = self.model.clone();
|
|
model.remove(index);
|
|
Self { actions, model }
|
|
}
|
|
|
|
fn next(self, next_op_counter: u64) -> impl Strategy<Value = Model> {
|
|
if self.model.is_empty() {
|
|
Just(self.insert(0, next_op_counter)).boxed()
|
|
} else {
|
|
// Note that we have to feed `self` through the `prop_flat_map` using `Just` to
|
|
// appease the borrow checker, this is annoying because it does obscure the meaning
|
|
// of the code heere which is basically "decide whether the next action should be
|
|
// insert, if it is insert choose an index between 0..model.len() + 1 and generate
|
|
// an op to insert, otherwise choose an index between 0..model.len() and generate a
|
|
// delete action".
|
|
//
|
|
// 95% chance of inserting to make sure we deal with large lists
|
|
(proptest::bool::weighted(0.95), Just(self))
|
|
.prop_flat_map(move |(insert, model)| {
|
|
if insert {
|
|
(0..model.model.len() + 1, Just(model))
|
|
.prop_map(move |(index, model)| {
|
|
model.insert(index, next_op_counter)
|
|
})
|
|
.boxed()
|
|
} else {
|
|
((0..model.model.len()), Just(model))
|
|
.prop_map(move |(index, model)| model.delete(index))
|
|
.boxed()
|
|
}
|
|
})
|
|
.boxed()
|
|
}
|
|
}
|
|
}
|
|
|
|
fn model() -> impl Strategy<Value = Model> {
|
|
(0_u64..150).prop_flat_map(|num_steps| {
|
|
let mut strat = Just((
|
|
0,
|
|
Model {
|
|
actions: Vec::new(),
|
|
model: Vec::new(),
|
|
},
|
|
))
|
|
.boxed();
|
|
for _ in 0..num_steps {
|
|
strat = strat
|
|
// Note the counter, which we feed through each `prop_flat_map`, incrementing
|
|
// it by one each time. This mean that the generated ops have ascending (but
|
|
// not necessarily consecutive because not every `Action` is an `Insert`)
|
|
// counters. This makes it easier to debug failures - if we just used a random
|
|
// counter it would be much harder to see where things are out of order.
|
|
.prop_flat_map(|(counter, model)| {
|
|
let next_counter = counter + 1;
|
|
model.next(counter).prop_map(move |m| (next_counter, m))
|
|
})
|
|
.boxed();
|
|
}
|
|
strat.prop_map(|(_, model)| model)
|
|
})
|
|
}
|
|
|
|
fn make_optree(actions: &[Action]) -> super::OpTreeInternal {
|
|
let mut optree = OpTreeInternal::new();
|
|
for action in actions {
|
|
match action {
|
|
Action::Insert(index, op) => optree.insert(*index, op.clone()),
|
|
Action::Delete(index) => {
|
|
optree.remove(*index);
|
|
}
|
|
}
|
|
}
|
|
optree
|
|
}
|
|
|
|
/// A model for calls to `nth`. `NthModel::n` is guarnateed to be in `(0..model.len())`
|
|
#[derive(Clone)]
|
|
struct NthModel {
|
|
model: Vec<Op>,
|
|
actions: Vec<Action>,
|
|
n: usize,
|
|
}
|
|
|
|
impl std::fmt::Debug for NthModel {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Model")
|
|
.field("actions", &self.actions)
|
|
.field("model", &DebugOps(&self.model))
|
|
.field("n", &self.n)
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
fn nth_model() -> impl Strategy<Value = NthModel> {
|
|
model().prop_flat_map(|model| {
|
|
if model.model.is_empty() {
|
|
Just(NthModel {
|
|
model: model.model,
|
|
actions: model.actions,
|
|
n: 0,
|
|
})
|
|
.boxed()
|
|
} else {
|
|
(0..model.model.len(), Just(model))
|
|
.prop_map(|(index, model)| NthModel {
|
|
model: model.model,
|
|
actions: model.actions,
|
|
n: index,
|
|
})
|
|
.boxed()
|
|
}
|
|
})
|
|
}
|
|
|
|
proptest! {
|
|
#[test]
|
|
fn optree_iter_proptest(model in model()) {
|
|
let optree = make_optree(&model.actions);
|
|
let iter = super::OpTreeIter::new(&optree);
|
|
let iterated = iter.cloned().collect::<Vec<_>>();
|
|
assert_eq!(DebugOps(&model.model), DebugOps(&iterated))
|
|
}
|
|
|
|
#[test]
|
|
fn optree_iter_nth(model in nth_model()) {
|
|
let optree = make_optree(&model.actions);
|
|
let mut iter = super::OpTreeIter::new(&optree);
|
|
let mut model_iter = model.model.iter();
|
|
assert_eq!(model_iter.nth(model.n), iter.nth(model.n));
|
|
|
|
let tail = iter.cloned().collect::<Vec<_>>();
|
|
let expected_tail = model_iter.cloned().collect::<Vec<_>>();
|
|
assert_eq!(DebugOps(tail.as_slice()), DebugOps(expected_tail.as_slice()));
|
|
}
|
|
}
|
|
}
|