diff --git a/benches/binary_keys.rs b/benches/binary_keys.rs index e1804cdf..c7f97cd2 100644 --- a/benches/binary_keys.rs +++ b/benches/binary_keys.rs @@ -79,7 +79,7 @@ fn binary_descend_until(bencher: Bencher, n: u64) { if start > 0 { zipper.descend_to(&key[..start]); } - if zipper.descend_until() { + if zipper.descend_until(&mut ()) { sink += 1; } } @@ -107,7 +107,7 @@ fn binary_descend_until_max_bytes(bencher: Bencher, n: u64) { if start > 0 { zipper.descend_to(&key[..start]); } - if zipper.descend_until_max_bytes(DESCEND_UNTIL_MAX_BYTES) { + if zipper.descend_until_max_bytes(DESCEND_UNTIL_MAX_BYTES, &mut ()) { sink += 1; } } diff --git a/benches/oeis.rs b/benches/oeis.rs index 4f4870c8..afb485f4 100644 --- a/benches/oeis.rs +++ b/benches/oeis.rs @@ -1,13 +1,13 @@ use std::io::Read; use std::usize; use pathmap::PathMap; -use pathmap::zipper::{Zipper, ZipperValues, ZipperMoving, ZipperWriting, ZipperCreation}; +use pathmap::zipper::{Zipper, ZipperValues, ZipperMoving, ZipperPath, ZipperWriting, ZipperCreation}; use num::BigInt; use divan::{Divan, Bencher, black_box}; const MAX_OFFSET: u8 = 10; -fn drop_symbol_head_byte + Zipper + ZipperMoving>(loc: &mut Z) { +fn drop_symbol_head_byte + Zipper + ZipperMoving + ZipperPath>(loc: &mut Z) { let mut it = loc.child_mask().iter(); let p = loc.path().to_vec(); @@ -15,7 +15,7 @@ fn drop_symbol_head_byte + Zipper + ZipperMoving>(loc: & if b == 0 { continue } loc.descend_to_existing_byte(b); loc.join_k_path_into(b as usize, true); - assert!(loc.ascend(1)); + assert_eq!(loc.ascend(1), 1); } loc.reset(); loc.descend_to(&p[..]); diff --git a/benches/sparse_keys.rs b/benches/sparse_keys.rs index e010d2f0..2fb43d96 100644 --- a/benches/sparse_keys.rs +++ b/benches/sparse_keys.rs @@ -94,7 +94,7 @@ fn sparse_descend_until(bencher: Bencher, n: u64) { if start > 0 { zipper.descend_to(&key[..start]); } - if zipper.descend_until() { + if zipper.descend_until(&mut ()) { sink += 1; } } @@ -126,7 +126,7 @@ fn sparse_descend_until_max_bytes(bencher: Bencher, n: u64) { if start > 0 { zipper.descend_to(&key[..start]); } - if zipper.descend_until_max_bytes(DESCEND_UNTIL_MAX_BYTES) { + if zipper.descend_until_max_bytes(DESCEND_UNTIL_MAX_BYTES, &mut ()) { sink += 1; } } diff --git a/benches/superdense_keys.rs b/benches/superdense_keys.rs index 03717890..58766f37 100644 --- a/benches/superdense_keys.rs +++ b/benches/superdense_keys.rs @@ -96,7 +96,7 @@ fn superdense_descend_until(bencher: Bencher, n: u64) { if start > 0 { zipper.descend_to(&key[..start]); } - if zipper.descend_until() { + if zipper.descend_until(&mut ()) { sink += 1; } } @@ -123,7 +123,7 @@ fn superdense_descend_until_max_bytes(bencher: Bencher, n: u64) { if start > 0 { zipper.descend_to_existing(&key[..start]); } - if zipper.descend_until_max_bytes(DESCEND_UNTIL_MAX_BYTES) { + if zipper.descend_until_max_bytes(DESCEND_UNTIL_MAX_BYTES, &mut ()) { sink += 1; } } diff --git a/pathmap-book/src/1.02.00_zippers.md b/pathmap-book/src/1.02.00_zippers.md index 207ac336..d697bff6 100644 --- a/pathmap-book/src/1.02.00_zippers.md +++ b/pathmap-book/src/1.02.00_zippers.md @@ -16,6 +16,7 @@ Zipper capabilities are defined across a number of traits. There are many diffe | [`ZipperValues`](./1.02.02_zipper_values.md#zippervalues) | Access values | | [`ZipperReadOnlyValues`](./1.02.02_zipper_values.md#zipperreadonlyvalues) | Access values with extended lifetime | | [`ZipperReadOnlyConditionalValues`](./1.02.02_zipper_values.md#zipperreadonlyconditionalvalues) | Access values with `witness` pattern | +| [`ZipperPath`](./1.02.03_zipper_paths.md#zipper_relative_path) | Get basic path information | | [`ZipperAbsolutePath`](./1.02.03_zipper_paths.md#zipperabsolutepath) | Get more complete path information | | [`ZipperPathBuffer`](./1.02.03_zipper_paths.md#zipperpathbuffer) | Control zipper's internal buffer allocation | | [`ZipperMoving`](./1.02.04_zipper_moving.md) | Moves the zipper's focus within the trie | diff --git a/pathmap-book/src/1.02.03_zipper_paths.md b/pathmap-book/src/1.02.03_zipper_paths.md index 07fa0fcc..5ec9ae13 100644 --- a/pathmap-book/src/1.02.03_zipper_paths.md +++ b/pathmap-book/src/1.02.03_zipper_paths.md @@ -1,10 +1,10 @@ # Paths and Absolute Paths -Some zippers maintain can expose their focus position within the trie as a `path`. The following traits expose access to the path buffer in some way. +Some zippers maintain and can expose their focus position within the trie as a contiguous `path` of type `&[u8]`. The following traits expose access to the path buffer in some way. ## Zipper Relative Path -The [`path`] method in [`ZipperMoving`] returns the current path from the zipper's root to its focus as a byte slice. This represents the sequence of bytes traversed to reach the current focus position. +The [`path`] method in the [`ZipperPath`] trait returns the current path from the zipper's root to its focus, as a byte slice. This represents the sequence of bytes traversed to reach the current focus position. -Note that this path is relative to the zipper's root, which may not be the same as the absolute path from the original data structure's root if the zipper was created with a prefix or from a subtrie. +Note that this path is relative to the zipper's root, which may not be the same as the absolute path from the original data structure's root if the zipper was created with a prefix or from within a subtrie. ## ZipperAbsolutePath The [`ZipperAbsolutePath`] trait provides methods to access a more complete path slice, including the [`origin_path`], and may extend above the zipper's root. diff --git a/pathmap-book/src/1.02.04_zipper_moving.md b/pathmap-book/src/1.02.04_zipper_moving.md index cb1b3fa8..488fb467 100644 --- a/pathmap-book/src/1.02.04_zipper_moving.md +++ b/pathmap-book/src/1.02.04_zipper_moving.md @@ -12,7 +12,7 @@ Descending is not limited in any way, and moving the focus to non-existent paths ## Absolute Positioning -- [`move_to_path`] moves the zipper's focus directly to a specific path relative to the zipper's root. +- [`move_to_path`] moves the zipper's focus directly to a specific path relative to the zipper's root. This method belongs to the [`ZipperPath`] trait, because it needs to know where the focus currently is in order to get to where it's going. ## Stepping vs. Jumping Zippers may be descended and ascended either by stepping an absolute number of elements, or by jumping to features such as branches, values, or the end of the path. In general, moving by jumping will be faster than stepping. diff --git a/pathmap-book/src/api_links.md b/pathmap-book/src/api_links.md index 1f3cb628..ad927b47 100644 --- a/pathmap-book/src/api_links.md +++ b/pathmap-book/src/api_links.md @@ -17,6 +17,7 @@ [`WriteZipperOwned`]: https://docs.rs/pathmap/latest/pathmap/zipper/struct.WriteZipperOwned.html [`WriteZipperUntracked`]: https://docs.rs/pathmap/latest/pathmap/zipper/struct.WriteZipperUntracked.html [`Zipper`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.Zipper.html +[`ZipperPath`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperPath.html [`ZipperAbsolutePath`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperAbsolutePath.html [`ZipperConcrete`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperConcrete.html [`ZipperForking`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperForking.html @@ -46,6 +47,7 @@ [`TrieRef`]: https://docs.rs/pathmap/latest/pathmap/zipper/enum.TrieRef.html [`WriteZipperOwned`]: https://docs.rs/pathmap/latest/pathmap/zipper/struct.WriteZipperOwned.html [`WriteZipper`]: https://docs.rs/pathmap/latest/pathmap/zipper/struct.WriteZipperUntracked.html +[`ZipperPath`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperPath.html [`ZipperAbsolutePath`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperAbsolutePath.html [`ZipperConcrete`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperConcrete.html [`ZipperForking`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperForking.html @@ -97,12 +99,12 @@ [`make_map`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperSubtries.html#tymethod.make_map [`meet_2`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperWriting.html#tymethod.meet_2 [`meet_into`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperWriting.html#tymethod.meet_into -[`move_to_path`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperMoving.html#tymethod.move_to_path +[`move_to_path`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperPath.html#tymethod.move_to_path [`origin_path_assert_len`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperPathBuffer.html#tymethod.origin_path_assert_len [`origin_path`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperAbsolutePath.html#tymethod.origin_path [`path_exists_at`]: https://docs.rs/pathmap/latest/pathmap/struct.PathMap.html#method.path_exists_at [`path_exists`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.Zipper.html#tymethod.path_exists -[`path`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperMoving.html#tymethod.path +[`path`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperPath.html#tymethod.path [`prepare_buffers`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperPathBuffer.html#tymethod.prepare_buffers [`prune_ascend`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperWriting.html#tymethod.prune_ascend [`prune_path`]: https://docs.rs/pathmap/latest/pathmap/zipper/trait.ZipperWriting.html#tymethod.prune_path diff --git a/pathmap-derive/src/lib.rs b/pathmap-derive/src/lib.rs index 24007101..3bbc959e 100644 --- a/pathmap-derive/src/lib.rs +++ b/pathmap-derive/src/lib.rs @@ -459,7 +459,7 @@ fn derive_poly_zipper_with_traits( quote! {} }; Some(quote! { - impl #impl_generics pathmap::zipper::ZipperMoving for #enum_name #ty_generics + impl #impl_generics pathmap::zipper::ZipperPath for #enum_name #ty_generics #zipper_moving_where { fn path(&self) -> &[u8] { @@ -467,6 +467,23 @@ fn derive_poly_zipper_with_traits( #(#variant_arms => inner.path(),)* } } + } + + impl #impl_generics pathmap::zipper::ZipperMoving for #enum_name #ty_generics + #zipper_moving_where + { + fn at_root(&self) -> bool { + match self { + #(#variant_arms => inner.at_root(),)* + } + } + + #[inline] + fn focus_byte(&self) -> Option { + match self { + #(#variant_arms => inner.focus_byte(),)* + } + } fn val_count(&self) -> usize { match self { @@ -500,19 +517,19 @@ fn derive_poly_zipper_with_traits( } } - fn ascend(&mut self, steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> usize { match self { #(#variant_arms => inner.ascend(steps),)* } } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { match self { #(#variant_arms => inner.ascend_until(),)* } } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { match self { #(#variant_arms => inner.ascend_until_branch(),)* } diff --git a/src/arena_compact.rs b/src/arena_compact.rs index 759adae7..9e15bc97 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -88,7 +88,8 @@ use crate::{ utils::{BitMask, ByteMask, find_prefix_overlap}, zipper::{ Zipper, ZipperValues, ZipperForking, ZipperAbsolutePath, ZipperIteration, - ZipperMoving, ZipperPathBuffer, ZipperReadOnlyValues, ZipperSubtries, + ZipperMoving, ZipperPath, ZipperPathBuffer, ZipperReadOnlyValues, ZipperSubtries, + PathObserver, ZipperConcrete, ZipperReadOnlyConditionalValues, TrieRef }, }; @@ -883,7 +884,7 @@ impl ArenaCompactTree> { /// let tree1 = ArenaCompactTree::from_zipper(btm.read_zipper(), |_v| 0); /// let mut zipper = tree1.read_zipper(); /// for path in items { - /// use pathmap::zipper::ZipperMoving; + /// use pathmap::zipper::{ZipperMoving, ZipperPath}; /// zipper.reset(); /// assert!(zipper.descend_to_existing(path) == path.len()); /// assert_eq!(zipper.path(), path.as_bytes()); @@ -2128,6 +2129,13 @@ where Storage: AsRef<[u8]> } } +impl<'tree, Storage, Value> ZipperPath for ACTZipper<'tree, Storage, Value> +where Storage: AsRef<[u8]> +{ + /// Returns the path from the zipper's root to the current focus + fn path(&self) -> &[u8] { &self.path[self.origin_depth..] } +} + impl<'tree, Storage, Value> ZipperAbsolutePath for ACTZipper<'tree, Storage, Value> where Storage: AsRef<[u8]> { @@ -2261,39 +2269,41 @@ where Storage: AsRef<[u8]> }) } - fn ascend_invalid(&mut self, limit: Option<&mut usize>) -> bool { + /// Ascends any non-existent portion of the path. Returns the number of steps ascended + /// + /// `limit` sets an upper bound on the number of steps that will be ascended + fn ascend_invalid(&mut self, limit: Option) -> usize { if self.invalid == 0 { - return true; + return 0; } let len = self.path.len(); let mut invalid_cut = self.invalid.min(len - self.origin_depth); if let Some(limit) = limit { - invalid_cut = invalid_cut.min(*limit); - *limit -= invalid_cut; + invalid_cut = invalid_cut.min(limit); } self.path.truncate(len - invalid_cut); self.invalid = self.invalid - invalid_cut; - self.invalid == 0 + invalid_cut } - fn ascend_to_branch(&mut self, need_value: bool) -> bool { + fn ascend_to_branch(&mut self, need_value: bool) -> usize { self.trace_pos(); - let mut moved = false; + let start_len = self.path.len(); if self.invalid > 0 { - moved = true; - if !self.ascend_invalid(None) { - return false; + self.ascend_invalid(None); + if self.invalid > 0 { + return start_len - self.path.len(); } match &self.cur_node { Node::Line(line) => { if need_value && line.value.is_some() { - return true; + return start_len - self.path.len(); } } Node::Branch(node) => { if need_value && node.value.is_some() { - return true; + return start_len - self.path.len(); } } } @@ -2303,14 +2313,12 @@ where Storage: AsRef<[u8]> let mut this_steps = top_frame.node_depth .min(self.path.len() - self.origin_depth); top_frame.node_depth = 0; - moved |= this_steps > 0; if self.stack.len() > 1 { self.stack.pop(); let prev = self.stack.last().unwrap(); self.cur_node = self.tree.get_node(prev.node_id).0; nchildren = prev.child_count; - moved = true; - this_steps += 1; + this_steps += 1; } self.path.truncate(self.path.len() - this_steps); // eprintln!("path={:?}", self.path); @@ -2324,7 +2332,7 @@ where Storage: AsRef<[u8]> break; } } - moved + start_len - self.path.len() } fn descend_cond(&mut self, path: &[u8], on_value: bool) -> usize { @@ -2389,26 +2397,29 @@ where Storage: AsRef<[u8]> descended } - fn to_sibling(&mut self, next: bool) -> bool { + fn to_sibling(&mut self, next: bool) -> Option { let top_frame = self.stack.last().unwrap(); if self.stack.len() <= 1 || top_frame.node_depth > 0 { // can't move to sibling at root, or along the path - return false; + return None; } let top2_frame = &self.stack[self.stack.len() - 2]; let sibling_idx = if next { let idx = top2_frame.child_index + 1; if idx >= top2_frame.child_count { - return false; + return None; } idx } else { if top2_frame.child_index == 0 { - return false; + return None; } top2_frame.child_index - 1 }; - self.ascend(1) && self.descend_indexed_byte(sibling_idx) + if self.ascend(1) == 0 { + return None; + } + self.descend_indexed_byte(sibling_idx) } } @@ -2517,6 +2528,11 @@ where Storage: AsRef<[u8]> /// Returns `true` if the zipper cannot ascend further, otherwise returns `false` fn at_root(&self) -> bool { self.path.len() <= self.origin_depth } + #[inline] + fn focus_byte(&self) -> Option { + self.path.last().cloned() + } + /// Resets the zipper's focus back to the root fn reset(&mut self) { // self.ascend(self.path.len() - self.origin_depth); @@ -2528,9 +2544,6 @@ where Storage: AsRef<[u8]> self.invalid = 0; } - /// Returns the path from the zipper's root to the current focus - fn path(&self) -> &[u8] { &self.path[self.origin_depth..] } - /// Returns the total number of values contained at and below the zipper's focus, including the focus itself /// /// WARNING: This is not a cheap method. It may have an order-N cost @@ -2599,34 +2612,37 @@ where Storage: AsRef<[u8]> /// WARNING: The branch represented by a given index is not guaranteed to be stable across modifications /// to the trie. This method should only be used as part of a directed traversal operation, but /// index-based paths may not be stored as locations within the trie. - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { if self.invalid > 0 { - return false; + return None; } self.trace_pos(); let mut child_id: Option = None; + let descended_byte; match &self.cur_node { Node::Line(line) => { let top_frame = self.stack.last_mut().unwrap(); let path = self.tree.get_line(line.path); let rest_path = &path[top_frame.node_depth..]; if idx != 0 || rest_path.is_empty() { - return false; + return None; } + descended_byte = Some(rest_path[0]); self.path.push(rest_path[0]); if let (true, Some(line_child)) = (rest_path.len() == 1, line.child) { child_id = Some(line_child); } else { top_frame.node_depth += 1; - return true; + return descended_byte; } } Node::Branch(node) => { let top_frame = self.stack.last_mut().unwrap(); if idx > top_frame.child_count { - return false; + return None; } let byte = node.bytemask.indexed_bit::(idx); + descended_byte = byte; if let Some(byte) = byte { if top_frame.next_id.is_some() && top_frame.child_index + 1 == idx { child_id = top_frame.next_id; @@ -2646,20 +2662,20 @@ where Storage: AsRef<[u8]> self.stack.push(StackFrame::from(&node, child_id)); self.cur_node = node; } - child_id.is_some() + if child_id.is_some() { descended_byte } else { None } } /// Descends the zipper's focus one step into the first child branch in a depth-first traversal /// /// NOTE: This method should have identical behavior to passing `0` to [descend_indexed_byte](ZipperMoving::descend_indexed_byte), /// although with less overhead - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } /// Descends the zipper's focus until a branch or a value is encountered. Returns `true` if the focus /// moved otherwise returns `false` - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.trace_pos(); let mut descended = false; 'descend: while self.child_count() == 1 { @@ -2672,6 +2688,7 @@ where Storage: AsRef<[u8]> let line_child_hack = if line.child.is_some() { 1 } else { 0 }; top_frame.node_depth += rest_path.len() - line_child_hack; self.path.extend_from_slice(rest_path); + obs.descend_to(rest_path); child_id = line.child; if line.value.is_some() { descended = true; @@ -2682,6 +2699,7 @@ where Storage: AsRef<[u8]> let Some(byte) = node.bytemask.iter().next() else { break 'descend }; self.path.push(byte); + obs.descend_to_byte(byte); child_id = node.first_child; } } @@ -2709,26 +2727,28 @@ where Storage: AsRef<[u8]> /// /// If the root is fewer than `n` steps from the zipper's position, then this method will stop at /// the root and return `false` - fn ascend(&mut self, mut steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> usize { self.trace_pos(); - if !self.ascend_invalid(Some(&mut steps)) { - return false; + let mut remaining = steps; + remaining -= self.ascend_invalid(Some(remaining)); + if self.invalid > 0 { + return steps - remaining; } while let Some(top_frame) = self.stack.last_mut() { let rest_path = &self.path[self.origin_depth..]; - let mut this_steps = steps.min(top_frame.node_depth).min(rest_path.len()); + let mut this_steps = remaining.min(top_frame.node_depth).min(rest_path.len()); top_frame.node_depth -= this_steps; - steps -= this_steps; - if top_frame.node_depth == 0 && self.stack.len() > 1 && steps > 0 { + remaining -= this_steps; + if top_frame.node_depth == 0 && self.stack.len() > 1 && remaining > 0 { self.stack.pop(); let prev = self.stack.last().unwrap(); self.cur_node = self.tree.get_node(prev.node_id).0; this_steps += 1; - steps -= 1; + remaining -= 1; } self.path.truncate(self.path.len() - this_steps); - if self.at_root() || steps == 0 { - return steps == 0 && this_steps > 0; + if self.at_root() || remaining == 0 { + return steps - remaining; } } unreachable!(); @@ -2736,29 +2756,28 @@ where Storage: AsRef<[u8]> /// Ascends the zipper up a single byte. Equivalent to passing `1` to [ascend](Self::ascend) fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1) == 1 } - /// Ascends the zipper to the nearest upstream branch point or value. Returns `true` if the zipper - /// focus moved upwards, otherwise returns `false` if the zipper was already at the root - fn ascend_until(&mut self) -> bool { + /// Ascends the zipper to the nearest upstream branch point or value. Returns the number of bytes + /// ascended. Returns `0` if the zipper was already at the root + fn ascend_until(&mut self) -> usize { self.ascend_to_branch(true) } /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. Returns - /// `true` if the zipper focus moved upwards, otherwise returns `false` if the zipper was already at the - /// root - fn ascend_until_branch(&mut self) -> bool { + /// the number of bytes ascended. Returns `0` if the zipper was already at the root + fn ascend_until_branch(&mut self) -> usize { self.ascend_to_branch(false) } #[inline] - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling(true) } #[inline] - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } @@ -2794,7 +2813,7 @@ where Storage: AsRef<[u8]> /// See: [to_next_k_path](ZipperIteration::to_next_k_path) fn descend_first_k_path(&mut self, k: usize) -> bool { for ii in 0..k { - if !self.descend_first_byte() { + if self.descend_first_byte().is_none() { self.ascend(ii); return false; } @@ -2817,7 +2836,7 @@ where Storage: AsRef<[u8]> let mut depth = k; 'outer: loop { while depth > 0 && self.child_count() <= 1 { - if !self.ascend(1) { + if self.ascend(1) == 0 { break 'outer; } depth -= 1; @@ -2825,16 +2844,16 @@ where Storage: AsRef<[u8]> let stack = self.stack.last_mut().unwrap(); let idx = stack.child_index + 1; if idx >= stack.child_count { - if depth == 0 || !self.ascend(1) { + if depth == 0 || self.ascend(1) == 0 { break 'outer; } depth -= 1; continue 'outer; } - assert!(self.descend_indexed_byte(idx)); + assert!(self.descend_indexed_byte(idx).is_some()); depth += 1; for _ii in 0..k - depth { - if !self.descend_first_byte() { + if self.descend_first_byte().is_none() { continue 'outer; } depth += 1; @@ -2850,7 +2869,7 @@ where Storage: AsRef<[u8]> mod tests { use super::{ArenaCompactTree, ACTZipper}; use crate::{ - morphisms::Catamorphism, PathMap, zipper::{zipper_iteration_tests, zipper_moving_tests, ZipperIteration, ZipperMoving, ZipperValues} + morphisms::Catamorphism, PathMap, zipper::{zipper_iteration_tests, zipper_moving_tests, ZipperIteration, ZipperPath, ZipperValues} }; zipper_moving_tests::zipper_moving_tests!(arena_compact_zipper, diff --git a/src/dependent_zipper.rs b/src/dependent_zipper.rs index 5f71e694..e9a874da 100644 --- a/src/dependent_zipper.rs +++ b/src/dependent_zipper.rs @@ -32,8 +32,8 @@ pub struct DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], usize) -> (C, Option)> DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving, - SecondaryZ: ZipperMoving, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, { /// Creates a new `DependentProductZipperG` from the provided enroll function pub fn new_enroll(primary: PrimaryZ, enroll_payload: C, enroll: F) -> Self @@ -69,13 +69,6 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], Some(factor) } - /// Returns the number of factors composing the `DependentProductZipperG` - /// - /// The minimum returned value will be 1 because the primary factor is counted. - pub fn factor_count(&self) -> usize { - self.secondary.len() + 1 - } - /// Returns a slice of the path indices that represent the end-points of the portion of the path from each /// factor /// @@ -131,28 +124,31 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], /// A combination between `ascend_until` and `ascend_until_branch`. /// if `allow_stop_on_val` is `true`, behaves as `ascend_until` - fn ascend_cond(&mut self, allow_stop_on_val: bool) -> bool { + fn ascend_cond(&mut self, allow_stop_on_val: bool) -> usize { let mut plen = self.path().len(); + let mut ascended = 0; loop { while self.factor_paths.last() == Some(&plen) { self.factor_paths.pop(); + self.secondary.pop(); } if let Some(idx) = self.factor_idx(false) { let zipper = &mut self.secondary[idx]; - let before = zipper.path().len(); - let rv = if allow_stop_on_val { + //The inner zipper reports how far it moved, so the primary can be brought along + //without measuring its path before and after + let delta = if allow_stop_on_val { zipper.ascend_until() } else { zipper.ascend_until_branch() }; - let delta = before - zipper.path().len(); plen -= delta; self.primary.ascend(delta); - if rv && (self.child_count() != 1 || (allow_stop_on_val && self.is_val())) { - return true; + ascended += delta; + if delta > 0 && (self.child_count() != 1 || (allow_stop_on_val && self.is_val())) { + return ascended; } } else { - return if allow_stop_on_val { + return ascended + if allow_stop_on_val { self.primary.ascend_until() } else { self.primary.ascend_until_branch() @@ -162,11 +158,10 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], } /// a combination between `to_next_sibling` and `to_prev_sibling` - fn to_sibling_byte(&mut self, next: bool) -> bool { - let Some(&byte) = self.path().last() else { - return false; - }; - assert!(self.ascend(1), "must ascend"); + fn to_sibling_byte(&mut self, next: bool) -> Option { + let byte = self.focus_byte()?; + let ascended = self.ascend(1); + debug_assert_eq!(ascended, 1, "must ascend"); let child_mask = self.child_mask(); let Some(sibling_byte) = (if next { child_mask.next_bit(byte) @@ -174,10 +169,10 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], child_mask.prev_bit(byte) }) else { self.descend_to_byte(byte); - return false; + return None; }; self.descend_to_byte(sibling_byte); - true + Some(sibling_byte) } } @@ -186,7 +181,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperAbsolutePath, - SecondaryZ: ZipperMoving, + SecondaryZ: ZipperMoving + ZipperPath, { fn origin_path(&self) -> &[u8] { self.primary.origin_path() } fn root_prefix_path(&self) -> &[u8] { self.primary.root_prefix_path() } @@ -196,8 +191,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperConcrete, - SecondaryZ: ZipperMoving + ZipperConcrete, + PrimaryZ: ZipperMoving + ZipperPath + ZipperConcrete, + SecondaryZ: ZipperMoving + ZipperPath + ZipperConcrete, { fn shared_node_id(&self) -> Option { if let Some(idx) = self.factor_idx(true) { @@ -219,8 +214,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperPathBuffer, - SecondaryZ: ZipperMoving + ZipperPathBuffer, + PrimaryZ: ZipperMoving + ZipperPath + ZipperPathBuffer, + SecondaryZ: ZipperMoving + ZipperPath + ZipperPathBuffer, { unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { unsafe{ self.primary.origin_path_assert_len(len) } } fn prepare_buffers(&mut self) { self.primary.prepare_buffers() } @@ -231,8 +226,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperValues, - SecondaryZ: ZipperMoving + ZipperValues, + PrimaryZ: ZipperMoving + ZipperPath + ZipperValues, + SecondaryZ: ZipperMoving + ZipperPath + ZipperValues, { fn val(&self) -> Option<&V> { if let Some(idx) = self.factor_idx(true) { @@ -254,8 +249,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, + PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, { fn get_val(&self) -> Option<&'trie V> { if let Some(idx) = self.factor_idx(true) { @@ -277,8 +272,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperReadOnlyConditionalValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperReadOnlyConditionalValues<'trie, V>, + PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyConditionalValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyConditionalValues<'trie, V>, { type WitnessT = (PrimaryZ::WitnessT, Vec); fn witness<'w>(&self) -> Self::WitnessT { @@ -298,8 +293,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], usize) -> (C, Option)> Zipper for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + Zipper, - SecondaryZ: ZipperMoving + Zipper, + PrimaryZ: ZipperMoving + ZipperPath + Zipper, + SecondaryZ: ZipperMoving + ZipperPath + Zipper, { fn path_exists(&self) -> bool { if let Some(idx) = self.factor_idx(true) { @@ -334,23 +329,21 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], usize) -> (C, Option)> ZipperMoving for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving, - SecondaryZ: ZipperMoving, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, { fn at_root(&self) -> bool { self.path().is_empty() } + #[inline] + fn focus_byte(&self) -> Option { + self.primary.focus_byte() + } fn reset(&mut self) { self.factor_paths.clear(); - for secondary in &mut self.secondary { - secondary.reset(); - } + self.secondary.clear(); self.primary.reset(); } - #[inline] - fn path(&self) -> &[u8] { - self.primary.path() - } fn val_count(&self) -> usize { unimplemented!("method will probably get removed") } @@ -392,33 +385,28 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], fn descend_to_byte(&mut self, k: u8) { self.descend_to([k]) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { let mask = self.child_mask(); - let Some(byte) = mask.indexed_bit::(child_idx) else { - return false; - }; + let byte = mask.indexed_bit::(child_idx)?; self.descend_to_byte(byte); - true + Some(byte) } #[inline] - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let mut moved = false; self.enter_factors(); while self.child_count() == 1 { moved |= if let Some(idx) = self.factor_idx(false) { + //The primary carries the whole product path, so it has to follow whatever the + //secondary descends. Mirroring the movement keeps it in step without buffering + //the bytes. let zipper = &mut self.secondary[idx]; - let before = zipper.path().len(); - let rv = zipper.descend_until(); - let path = zipper.path(); - if path.len() > before { - self.primary.descend_to(&path[before..]); - } - rv + zipper.descend_until(&mut (MirrorPathObserver(&mut self.primary), &mut *obs)) } else { - self.primary.descend_until() + self.primary.descend_until(&mut *obs) }; self.enter_factors(); if self.is_val() { @@ -428,42 +416,55 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], moved } #[inline] - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling_byte(true) } #[inline] - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling_byte(false) } - fn ascend(&mut self, mut steps: usize) -> bool { - while steps > 0 { + fn ascend(&mut self, steps: usize) -> usize { + let mut remaining = steps; + while remaining > 0 { self.exit_factors(); if let Some(idx) = self.factor_idx(false) { let len = self.path().len() - self.factor_paths[idx]; - let delta = len.min(steps); + let delta = len.min(remaining); self.secondary[idx].ascend(delta); self.primary.ascend(delta); - steps -= delta; + remaining -= delta; } else { - return self.primary.ascend(steps); + return (steps - remaining) + self.primary.ascend(remaining); } } - true + steps } #[inline] fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1) == 1 } #[inline] - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { self.ascend_cond(true) } #[inline] - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { self.ascend_cond(false) } } +impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], usize) -> (C, Option)> ZipperPath for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> + where + V: Clone + Send + Sync, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, +{ + #[inline] + fn path(&self) -> &[u8] { + self.primary.path() + } +} + impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], usize) -> (C, Option)> ZipperIteration for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where @@ -475,8 +476,8 @@ for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> impl<'trie, PrimaryZ, SecondaryZ, V: Clone + Send + Sync + Unpin, C, F : Clone + for <'a> FnOnce(C, &'a [u8], usize) -> (C, Option)> ZipperSubtries for DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, C, F> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperValues, - SecondaryZ: ZipperMoving + ZipperValues, + PrimaryZ: ZipperMoving + ZipperPath + ZipperValues, + SecondaryZ: ZipperMoving + ZipperPath + ZipperValues, { fn native_subtries(&self) -> bool { false } fn try_make_map(&self) -> Option> { None } diff --git a/src/empty_zipper.rs b/src/empty_zipper.rs index e23d3f35..294b7d36 100644 --- a/src/empty_zipper.rs +++ b/src/empty_zipper.rs @@ -33,8 +33,9 @@ impl Zipper for EmptyZipper { impl ZipperMoving for EmptyZipper { fn at_root(&self) -> bool { self.path.len() == self.path_start_idx } + #[inline] + fn focus_byte(&self) -> Option { self.path.last().cloned() } fn reset(&mut self) { self.path.truncate(self.path_start_idx) } - fn path(&self) -> &[u8] { &self.path[self.path_start_idx..] } fn val_count(&self) -> usize { 0 } fn descend_to>(&mut self, k: K) { self.path.extend_from_slice(k.as_ref()); @@ -42,17 +43,14 @@ impl ZipperMoving for EmptyZipper { fn descend_to_byte(&mut self, k: u8) { self.path.push(k); } - fn descend_indexed_byte(&mut self, _idx: usize) -> bool { false } - fn descend_first_byte(&mut self) -> bool { false } - fn descend_until(&mut self) -> bool { false } - fn ascend(&mut self, steps: usize) -> bool { - if steps > self.path.len() - self.path_start_idx { - self.reset(); - false - } else { - self.path.truncate(self.path.len() - self.path_start_idx - steps); - true - } + fn descend_indexed_byte(&mut self, _idx: usize) -> Option { None } + fn descend_first_byte(&mut self) -> Option { None } + fn descend_until(&mut self, _obs: &mut Obs) -> bool { false } + fn ascend(&mut self, steps: usize) -> usize { + let available = self.path.len() - self.path_start_idx; + let ascended = steps.min(available); + self.path.truncate(self.path.len() - ascended); + ascended } fn ascend_byte(&mut self) -> bool { if self.path.len() > self.path_start_idx { @@ -62,19 +60,20 @@ impl ZipperMoving for EmptyZipper { false } } - fn ascend_until(&mut self) -> bool { - if self.at_root() { - false - } else { - self.reset(); - true - } + fn ascend_until(&mut self) -> usize { + let ascended = self.path.len() - self.path_start_idx; + self.reset(); + ascended } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { self.ascend_until() } - fn to_next_sibling_byte(&mut self) -> bool { false } - fn to_prev_sibling_byte(&mut self) -> bool { false } + fn to_next_sibling_byte(&mut self) -> Option { None } + fn to_prev_sibling_byte(&mut self) -> Option { None } +} + +impl ZipperPath for EmptyZipper { + fn path(&self) -> &[u8] { &self.path[self.path_start_idx..] } } impl ZipperAbsolutePath for EmptyZipper { diff --git a/src/experimental.rs b/src/experimental.rs index a0406001..c0db7ee3 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -46,8 +46,9 @@ impl ZipperPathBuffer for FullZipper { impl ZipperMoving for FullZipper { fn at_root(&self) -> bool { self.path.len() == 0 } + #[inline] + fn focus_byte(&self) -> Option { self.path.last().cloned() } fn reset(&mut self) { self.path.clear() } - fn path(&self) -> &[u8] { &self.path[..] } fn val_count(&self) -> usize { usize::MAX/2 } // usize::MAX is a dangerous default for overflow fn descend_to>(&mut self, k: K) { self.path.extend_from_slice(k.as_ref()); @@ -55,52 +56,53 @@ impl ZipperMoving for FullZipper { fn descend_to_byte(&mut self, k: u8) { self.path.push(k); } - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { assert!(idx < 256); self.path.push(idx as u8); - true + Some(idx as u8) } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.path.push(0); - true + Some(0) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.path.push(0); // not sure? + obs.descend_to_byte(0); true } - fn ascend(&mut self, steps: usize) -> bool { - if steps > self.path.len() { - self.path.clear(); - false - } else { - self.path.truncate(self.path.len() - steps); - true - } + fn ascend(&mut self, steps: usize) -> usize { + let ascended = steps.min(self.path.len()); + self.path.truncate(self.path.len() - ascended); + ascended } fn ascend_byte(&mut self) -> bool { self.path.pop().is_some() } - fn ascend_until(&mut self) -> bool { - self.path.pop().is_some() // not sure? + fn ascend_until(&mut self) -> usize { + if self.path.pop().is_some() { 1 } else { 0 } // not sure? } - fn ascend_until_branch(&mut self) -> bool { - self.path.pop().is_some() // not sure? What's the difference with the previous? + fn ascend_until_branch(&mut self) -> usize { + if self.path.pop().is_some() { 1 } else { 0 } // not sure? What's the difference with the previous? } - fn to_next_sibling_byte(&mut self) -> bool { self.to_sibling(true) } - fn to_prev_sibling_byte(&mut self) -> bool { self.to_sibling(false) } + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling(true) } + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } +} + +impl ZipperPath for FullZipper { + fn path(&self) -> &[u8] { &self.path[..] } } impl FullZipper { - fn to_sibling(&mut self, next: bool) -> bool { - if self.path.is_empty() { return false } // right? + fn to_sibling(&mut self, next: bool) -> Option { + if self.path.is_empty() { return None } // right? if next { let last = self.path.last_mut().unwrap(); - if *last != 255 { *last = *last + 1; true } - else { false } + if *last != 255 { *last = *last + 1; Some(*last) } + else { None } } else { let first = self.path.first_mut().unwrap(); - if *first != 0 { *first = *first - 1; true } - else { false } + if *first != 0 { *first = *first - 1; Some(*first) } + else { None } } } } @@ -117,20 +119,25 @@ impl Zipper for NullZipper { impl ZipperMoving for NullZipper { fn at_root(&self) -> bool { true } + #[inline] + fn focus_byte(&self) -> Option { None } fn reset(&mut self) {} - fn path(&self) -> &[u8] { &[] } fn val_count(&self) -> usize { 0 } fn descend_to>(&mut self, _k: K) {} fn descend_to_byte(&mut self, _k: u8) {} - fn descend_indexed_byte(&mut self, _idx: usize) -> bool { false } - fn descend_first_byte(&mut self) -> bool { false } - fn descend_until(&mut self) -> bool { false } - fn ascend(&mut self, _steps: usize) -> bool { false } + fn descend_indexed_byte(&mut self, _idx: usize) -> Option { None } + fn descend_first_byte(&mut self) -> Option { None } + fn descend_until(&mut self, _obs: &mut Obs) -> bool { false } + fn ascend(&mut self, _steps: usize) -> usize { 0 } fn ascend_byte(&mut self) -> bool { false } - fn ascend_until(&mut self) -> bool { false } - fn ascend_until_branch(&mut self) -> bool { false } - fn to_next_sibling_byte(&mut self) -> bool { false } - fn to_prev_sibling_byte(&mut self) -> bool { false } + fn ascend_until(&mut self) -> usize { 0 } + fn ascend_until_branch(&mut self) -> usize { 0 } + fn to_next_sibling_byte(&mut self) -> Option { None } + fn to_prev_sibling_byte(&mut self) -> Option { None } +} + +impl ZipperPath for NullZipper { + fn path(&self) -> &[u8] { &[] } } impl WriteZipperPriv for NullZipper { diff --git a/src/experimental/tree_serialization.rs b/src/experimental/tree_serialization.rs index 658c0b6c..936d35e9 100644 --- a/src/experimental/tree_serialization.rs +++ b/src/experimental/tree_serialization.rs @@ -34,7 +34,7 @@ pub fn serialize_fork, F: FnMut(usize, &[u8] } /// WIP -pub fn deserialize_fork + zipper::ZipperMoving, F: Fn(usize, &[u8]) -> V>(node: usize, wz: &mut WZ, source: &[u8], fv: F) -> std::io::Result { +pub fn deserialize_fork + zipper::ZipperMoving + zipper::ZipperPath, F: Fn(usize, &[u8]) -> V>(node: usize, wz: &mut WZ, source: &[u8], fv: F) -> std::io::Result { unsafe { // let mut recovered = 0; new_map_from_ana_jumping(wz, node, |n: usize, path: &[u8]| { diff --git a/src/experimental/zipper_algebra.rs b/src/experimental/zipper_algebra.rs index 0c4e4b71..d5dde7fc 100644 --- a/src/experimental/zipper_algebra.rs +++ b/src/experimental/zipper_algebra.rs @@ -118,7 +118,7 @@ impl ZipperAlgebraExt } impl ZipperAlgebraExt for PrefixZipper<'_, Z> where - Z: ZipperInfallibleSubtries + ZipperSubtries + ZipperConcrete + ZipperMoving + Z: ZipperInfallibleSubtries + ZipperSubtries + ZipperConcrete + ZipperMoving + ZipperPath { } @@ -647,7 +647,7 @@ where break 'ascend; } - let byte_from = *lhs.path().last().expect("non-empty path when k > 0"); + let byte_from = lhs.focus_byte().expect("non-empty path when k > 0"); rhs.ascend_byte(); rhs_mask = rhs.child_mask(); @@ -955,7 +955,7 @@ where break 'ascend; } - let byte_from = *lhs.path().last().expect("non-empty path when k > 0"); + let byte_from = lhs.focus_byte().expect("non-empty path when k > 0"); rhs.ascend_byte(); rhs_mask = rhs.child_mask(); @@ -1350,7 +1350,7 @@ fn zipper_merge4( break 'ascend; } - let byte_from = *z0.path().last().expect("non-empty path when k > 0"); + let byte_from = z0.focus_byte().expect("non-empty path when k > 0"); z0.ascend_byte(); m0 = z0.child_mask(); @@ -1904,9 +1904,8 @@ where if (k == 0) { break 'ascend; } - let byte_from = *first_active_mut(zs, active) - .path() - .last() + let byte_from = first_active_mut(zs, active) + .focus_byte() .expect("non-empty path when k > 0"); // ascend @@ -2476,9 +2475,8 @@ pub fn zipper_merge_dnf( break; } - let byte_from = *first_active(zs, first_active(clauses, active).members()) - .path() - .last() + let byte_from = first_active(zs, first_active(clauses, active).members()) + .focus_byte() .expect("non-empty path at depth > 0"); let mut active_zippers = 0; diff --git a/src/lib.rs b/src/lib.rs index 8df862be..f38c215f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -131,6 +131,7 @@ mod empty_zipper; mod prefix_zipper; mod overlay_zipper; mod dependent_zipper; +mod path_tracker; mod trie_ref; mod dense_byte_node; pub(crate) mod line_list_node; diff --git a/src/line_list_node.rs b/src/line_list_node.rs index 17b80128..4f1f0850 100644 --- a/src/line_list_node.rs +++ b/src/line_list_node.rs @@ -3401,7 +3401,7 @@ mod tests { #[test] fn test_nth_child_from_key_val_and_child_share_key() { use crate::PathMap; - use crate::zipper::{ZipperMoving, ZipperValues, ZipperWriting, Zipper}; + use crate::zipper::{ZipperMoving, ZipperPath, ZipperValues, ZipperWriting, Zipper}; // Grafting puts the child in slot 0; the value added afterwards lands // in slot 1 under the same key. (Insertion alone builds the mirror @@ -3415,7 +3415,7 @@ mod tests { map.set_val_at(b"a", 9); let mut by_index = map.read_zipper(); - assert!(by_index.descend_indexed_byte(0)); + assert!(by_index.descend_indexed_byte(0).is_some()); let mut by_path = map.read_zipper(); by_path.descend_to(b"a"); diff --git a/src/morphisms.rs b/src/morphisms.rs index 2eb3d3e1..ba0aa70d 100644 --- a/src/morphisms.rs +++ b/src/morphisms.rs @@ -465,7 +465,7 @@ fn cata_side_effect_body<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(mut z: z.prepare_buffers(); //Push a stack frame for the root, and start on the first branch off the root stack.push(StackFrame::from(&z)); - if !z.descend_first_byte() { + if z.descend_first_byte().is_none() { //Empty trie is a special case return alg_f(&ByteMask::EMPTY, &mut [], 0, z.val(), z.origin_path(), &z) } @@ -474,7 +474,7 @@ fn cata_side_effect_body<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(mut z: //Descend to the next forking point, or leaf let mut is_leaf = false; while z.child_count() < 2 { - if !z.descend_until() { + if !z.descend_until(&mut ()) { is_leaf = true; break; } @@ -520,13 +520,14 @@ fn cata_side_effect_body<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(mut z: //Position to descend the next child branch let descended = z.descend_indexed_byte(stack[frame_idx].child_idx as usize); - debug_assert!(descended); + debug_assert!(descended.is_some()); } else { //Push a new stack frame for this branch Stack::push_state_raw(&mut stack, &mut frame_idx, &z); //Descend the first child branch - z.descend_first_byte(); + let descended = z.descend_first_byte(); + debug_assert!(descended.is_some()); } } } @@ -550,7 +551,7 @@ fn ascend_to_fork<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(z: &mut Z, let old_path_len = z.origin_path().len(); let old_val = z.get_val_with_witness(&z_witness); let ascended = z.ascend_until(); - debug_assert!(ascended); + debug_assert!(ascended > 0); let origin_path = unsafe{ z.origin_path_assert_len(old_path_len) }; let jump_len = if z.child_count() != 1 || z.is_val() { @@ -660,7 +661,7 @@ impl Stack { /// This function re-uses allocations for stack frames, /// to avoid allocator thrashing. pub fn push_state(&mut self, z: &Z) - where Z: Zipper, + where Z: Zipper + ZipperPath, { Self::push_state_raw(&mut self.stack, &mut self.position, z); } @@ -669,7 +670,7 @@ impl Stack { stack: &mut Vec, position: &mut usize, zipper: &Z) - where Z: Zipper, + where Z: Zipper + ZipperPath, { *position = position.wrapping_add(1); assert!(*position <= stack.len(), @@ -687,7 +688,7 @@ where V: 'static + Clone + Send + Sync + Unpin, W: Default, I: IntoIterator, - WZ: ZipperWriting + zipper::ZipperMoving, + WZ: ZipperWriting + zipper::ZipperMoving + zipper::ZipperPath, CoAlgF: Copy + FnMut(W, &[u8]) -> (&'a [u8], ByteMask, I, Option), { let (prefix, bm, ws, mv) = coalg_f(w, wz.path()); @@ -781,7 +782,8 @@ pub(crate) fn into_cata_cached_body<'a, Z, V: 'a, W, E, AlgF, Cache, const JUMPI .expect("into_cata stack is emptied before we returned to root"); // This branch represents the body of the for loop. if frame_mut.child_idx < frame_mut.child_cnt { - zipper.descend_indexed_byte(frame_mut.child_idx as usize); + let descended = zipper.descend_indexed_byte(frame_mut.child_idx as usize); + debug_assert!(descended.is_some()); frame_mut.child_idx += 1; frame_mut.child_addr = zipper.shared_node_id(); @@ -796,7 +798,7 @@ pub(crate) fn into_cata_cached_body<'a, Z, V: 'a, W, E, AlgF, Cache, const JUMPI // Descend until leaf or branch let mut is_leaf = false; 'descend: while zipper.child_count() < 2 { - if !zipper.descend_until() { + if !zipper.descend_until(&mut ()) { is_leaf = true; break 'descend; } @@ -876,7 +878,8 @@ fn into_cata_jumping_naive<'a, Z, V: 'a, W, E, AlgF, Cache, const JUMPING: bool> let mut cache = HashMap::::new(); let path = z.path().to_vec(); for ii in 0..child_count { - z.descend_indexed_byte(ii); + let descended = z.descend_indexed_byte(ii); + debug_assert!(descended.is_some()); let child_addr = z.shared_node_id(); // Read and reuse value from cache, if exists if let Some(cached) = Cache::get(&cache, child_addr) { @@ -888,7 +891,7 @@ fn into_cata_jumping_naive<'a, Z, V: 'a, W, E, AlgF, Cache, const JUMPING: bool> // Descend until leaf or branch 'descend: while z.child_count() < 2 { - if !z.descend_until() { + if !z.descend_until(&mut ()) { is_leaf = true; break 'descend; } diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index 5cae3fd3..937cea76 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -16,9 +16,10 @@ // with ZipperValues, etc. because we don't have a place to store the newly created value // +use arrayvec::ArrayVec; use fast_slice_utils::find_prefix_overlap; use crate::utils::{BitMask, ByteMask}; -use crate::zipper::{Zipper, ZipperMoving, ZipperIteration, ZipperValues}; +use crate::zipper::{Zipper, ZipperMoving, ZipperPath, PathObserver, ZipperIteration, ZipperValues}; /// Zipper that traverses a virtual trie formed by fusing the tries of two other zippers pub struct OverlayZipper @@ -69,15 +70,12 @@ impl impl OverlayZipper where - AZipper: ZipperMoving + ZipperValues, - BZipper: ZipperMoving + ZipperValues, + AZipper: ZipperMoving + ZipperPath + ZipperValues, + BZipper: ZipperMoving + ZipperPath + ZipperValues, Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, { - fn to_sibling(&mut self, next: bool) -> bool { - let path = self.path(); - let Some(&last) = path.last() else { - return false; - }; + fn to_sibling(&mut self, next: bool) -> Option { + let last = self.focus_byte()?; self.ascend(1); let child_mask = self.child_mask(); let maybe_child = if next { @@ -87,10 +85,10 @@ impl }; let Some(child) = maybe_child else { self.descend_to_byte(last); - return false; + return None; }; self.descend_to_byte(child); - true + Some(child) } } @@ -135,23 +133,26 @@ impl Zipper impl ZipperMoving for OverlayZipper where - AZipper: ZipperMoving + ZipperValues, - BZipper: ZipperMoving + ZipperValues, + AZipper: ZipperMoving + ZipperPath + ZipperValues, + BZipper: ZipperMoving + ZipperPath + ZipperValues, Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, { fn at_root(&self) -> bool { self.a.at_root() || self.b.at_root() } + #[inline] + fn focus_byte(&self) -> Option { + let byte = self.a.focus_byte(); + debug_assert_eq!(byte, self.b.focus_byte()); + byte + } + fn reset(&mut self) { self.a.reset(); self.b.reset(); } - fn path(&self) -> &[u8] { - self.a.path() - } - fn val_count(&self) -> usize { todo!() } @@ -207,114 +208,172 @@ impl ZipperMoving self.b.descend_to(&[k]); } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { let child_mask = self.child_mask(); - let Some(byte) = child_mask.indexed_bit::(idx) else { - return false; - }; + let byte = child_mask.indexed_bit::(idx)?; self.descend_to_byte(byte); - true + Some(byte) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { + //Descending happens in buffer-sized chunks, so neither source can outrun what we're able + // to capture. As long as both sources fill a whole chunk and agree on every byte of it, + // the chunk is committed and we go around again. Any other outcome ends the descent and + // is settled by the case analysis below. + const CHUNK: usize = 48; + let mut path_a = ArrayVec::::new(); + let mut path_b = ArrayVec::::new(); + + //Total bytes committed to `obs` across all completed chunks + let mut committed = 0usize; + #[cfg(debug_assertions)] let start_depth = self.a.path().len(); - let desc_a = self.a.descend_until(); - let desc_b = self.b.descend_until(); - let path_a = &self.a.path()[start_depth..]; - let path_b = &self.b.path()[start_depth..]; - if !desc_a && !desc_b { - return false; - } - if !desc_a && desc_b { - if self.a.child_count() == 0 { - self.a.descend_to(path_b); - return true; - } else { - self.b.ascend(self.b.path().len() - start_depth); - return false; + + loop { + path_a.clear(); + path_b.clear(); + let desc_a = self.a.descend_until_max_bytes(CHUNK, &mut path_a); + let desc_b = self.b.descend_until_max_bytes(CHUNK, &mut path_b); + + if !desc_a && !desc_b { + break; } - } - if desc_a && !desc_b { - if self.b.child_count() == 0 { - self.b.descend_to(path_a); - return true; - } else { - self.a.ascend(self.a.path().len() - start_depth); - return false; + if !desc_a && desc_b { + if self.a.child_count() == 0 { + self.a.descend_to(&path_b); + obs.descend_to(&path_b); + committed += path_b.len(); + } else { + let to_ascend = path_b.len(); + let ascended = self.b.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); + } + break; + } + if desc_a && !desc_b { + if self.b.child_count() == 0 { + self.b.descend_to(&path_a); + obs.descend_to(&path_a); + committed += path_a.len(); + } else { + let to_ascend = path_a.len(); + let ascended = self.a.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); + } + break; + } + + //Both moved. Keep the portion they agree on and rewind the rest + let overlap = find_prefix_overlap(&path_a, &path_b); + if path_a.len() > overlap { + let to_ascend = path_a.len() - overlap; + let ascended = self.a.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); + } + if path_b.len() > overlap { + let to_ascend = path_b.len() - overlap; + let ascended = self.b.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); + } + //Both sources must now sit at the same position: the agreed-upon prefix + debug_assert_eq!(self.a.path(), self.b.path()); + if overlap > 0 { + obs.descend_to(&path_a[..overlap]); + committed += overlap; + } + + //Only a full chunk that both sources agreed on end-to-end can be continued. Anything + // shorter means at least one source stopped on its own, so the descent is complete. + if overlap < CHUNK || path_a.len() != CHUNK || path_b.len() != CHUNK { + break; } } - let overlap = find_prefix_overlap(path_a, path_b); - if path_a.len() > overlap { - self.a.ascend(path_a.len() - overlap); - } - if path_b.len() > overlap { - self.b.ascend(path_b.len() - overlap); - } - overlap > 0 + + #[cfg(debug_assertions)] + debug_assert_eq!(start_depth + committed, self.a.path().len()); + committed > 0 } - fn ascend(&mut self, steps: usize) -> bool { - self.a.ascend(steps) | self.b.ascend(steps) + fn ascend(&mut self, steps: usize) -> usize { + //Both sources move together, so they must report the same distance + let a = self.a.ascend(steps); + let b = self.b.ascend(steps); + debug_assert_eq!(a, b); + a.max(b) } fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1) == 1 } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { debug_assert_eq!(self.a.path(), self.b.path()); - // eprintln!("asc_until i {:?} {:?}", self.base.path(), self.overlay.path()); + let start_depth = self.a.path().len(); let asc_a = self.a.ascend_until(); let path_a = self.a.path(); let depth_a = path_a.len(); let asc_b = self.b.ascend_until(); let path_b = self.b.path(); let depth_b = path_b.len(); - if !(asc_b || asc_a) { - return false; + if asc_a == 0 && asc_b == 0 { + return 0; } - // eprintln!("asc_until {path_a:?} {path_b:?}"); + //Whichever source ascended further sets the shared position; the other descends to match if depth_b > depth_a { self.a.descend_to(&path_b[depth_a..]); } else if depth_a > depth_b { self.b.descend_to(&path_a[depth_b..]); } - true + start_depth - self.a.path().len() } - fn ascend_until_branch(&mut self) -> bool { - let asc_a = self.a.ascend_until_branch(); + fn ascend_until_branch(&mut self) -> usize { + let start_depth = self.a.path().len(); + let _asc_a = self.a.ascend_until_branch(); let path_a = self.a.path(); let depth_a = path_a.len(); - let asc_b = self.b.ascend_until_branch(); + let _asc_b = self.b.ascend_until_branch(); let path_b = self.b.path(); let depth_b = path_b.len(); + //Whichever source ascended further sets the shared position; the other descends to match if depth_b > depth_a { self.a.descend_to(&path_b[depth_a..]); } else if depth_a > depth_b { self.b.descend_to(&path_a[depth_b..]); } - asc_a || asc_b + start_depth - self.a.path().len() } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling(true) } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } } +impl ZipperPath + for OverlayZipper + where + AZipper: ZipperMoving + ZipperPath + ZipperValues, + BZipper: ZipperMoving + ZipperPath + ZipperValues, + Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, +{ + fn path(&self) -> &[u8] { + self.a.path() + } +} + impl ZipperIteration for OverlayZipper where - AZipper: ZipperMoving + ZipperValues, - BZipper: ZipperMoving + ZipperValues, + AZipper: ZipperMoving + ZipperPath + ZipperValues, + BZipper: ZipperMoving + ZipperPath + ZipperValues, Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, { } @@ -326,17 +385,16 @@ crate::impl_name_only_debug!( #[cfg(test)] mod tests { - use crate::alloc::GlobalAlloc; -use super::{OverlayZipper}; use crate::{ + alloc::GlobalAlloc, PathMap, zipper::{ ReadZipperUntracked, zipper_iteration_tests, zipper_moving_tests, - // ZipperIteration, - // ZipperMoving, - // ZipperValues + ZipperMoving, + ZipperPath, + OverlayZipper }, }; @@ -391,4 +449,99 @@ use super::{OverlayZipper}; ) } ); + + /// The chunk size used by `OverlayZipper::descend_until` + const CHUNK: usize = 48; + + /// Builds an overlay over two maps, each holding the single supplied path + fn overlay_of<'a>(a_key: &[u8], b_key: &[u8], + a: &'a mut PathMap<()>, b: &'a mut PathMap<()>) -> OZ<'a, ()> { + a.set_val_at(a_key, ()); + b.set_val_at(b_key, ()); + OverlayZipper::new(a.read_zipper(), b.read_zipper()) + } + + /// Both sources share a single path of `len` bytes, so `descend_until` must descend the whole + /// thing regardless of how it lands relative to the chunk boundary + fn assert_full_descent(len: usize) { + let key = vec![b'x'; len]; + let (mut a, mut b) = (PathMap::new(), PathMap::new()); + let mut oz = overlay_of(&key, &key, &mut a, &mut b); + + let moved = oz.descend_until(&mut ()); + assert_eq!(moved, true, "len={len}: should have descended"); + assert_eq!(oz.path(), &key[..], "len={len}: should reach the end of the path"); + } + + #[test] + fn overlay_descent_shorter_than_chunk() { assert_full_descent(CHUNK - 1); } + + #[test] + fn overlay_descent_exactly_one_chunk() { assert_full_descent(CHUNK); } + + #[test] + fn overlay_descent_one_past_chunk() { assert_full_descent(CHUNK + 1); } + + #[test] + fn overlay_descent_spanning_several_chunks() { assert_full_descent(CHUNK * 3 + 7); } + + /// The observer must receive exactly the bytes the focus actually moved over, even when the + /// descent spans several chunks + #[test] + fn overlay_observer_receives_whole_descent() { + let key = vec![b'x'; CHUNK * 2 + 5]; + let (mut a, mut b) = (PathMap::new(), PathMap::new()); + let mut oz = overlay_of(&key, &key, &mut a, &mut b); + + let mut observed = Vec::new(); + let moved = oz.descend_until(&mut observed); + assert_eq!(moved, true); + assert_eq!(observed, oz.path(), "observer must match the resulting path"); + assert_eq!(observed, key); + } + + /// When the sources diverge, the focus stops at their common prefix. Divergence is placed on + /// both sides of the chunk boundary to check the loop terminates at the right byte. + fn assert_diverges_at(common_len: usize) { + let mut key_a = vec![b'x'; common_len]; key_a.push(b'A'); key_a.extend([b'x'; 4]); + let mut key_b = vec![b'x'; common_len]; key_b.push(b'B'); key_b.extend([b'x'; 4]); + let (mut a, mut b) = (PathMap::new(), PathMap::new()); + let mut oz = overlay_of(&key_a, &key_b, &mut a, &mut b); + + let mut observed = Vec::new(); + let moved = oz.descend_until(&mut observed); + + assert_eq!(moved, common_len > 0, "common_len={common_len}"); + assert_eq!(oz.path(), &vec![b'x'; common_len][..], + "common_len={common_len}: focus must stop at the common prefix"); + assert_eq!(observed, oz.path(), + "common_len={common_len}: observer must match the resulting path"); + } + + #[test] + fn overlay_diverges_within_first_chunk() { assert_diverges_at(CHUNK / 2); } + + #[test] + fn overlay_diverges_at_chunk_boundary() { assert_diverges_at(CHUNK); } + + #[test] + fn overlay_diverges_just_past_chunk_boundary() { assert_diverges_at(CHUNK + 1); } + + #[test] + fn overlay_diverges_after_several_chunks() { assert_diverges_at(CHUNK * 2 + 3); } + + /// One source runs far past the other, spanning multiple chunks. The shorter source is a + /// leaf, so the overlay follows the longer one. + #[test] + fn overlay_one_source_much_longer() { + let short = vec![b'x'; 4]; + let long = vec![b'x'; CHUNK * 2 + 9]; + let (mut a, mut b) = (PathMap::new(), PathMap::new()); + let mut oz = overlay_of(&short, &long, &mut a, &mut b); + + let mut observed = Vec::new(); + let moved = oz.descend_until(&mut observed); + assert_eq!(moved, true); + assert_eq!(observed, oz.path(), "observer must match the resulting path"); + } } diff --git a/src/path_tracker.rs b/src/path_tracker.rs new file mode 100644 index 00000000..27544733 --- /dev/null +++ b/src/path_tracker.rs @@ -0,0 +1,226 @@ +use crate::{ + utils::ByteMask, + zipper::{ + PathObserver, Zipper, ZipperAbsolutePath, ZipperMoving, ZipperIteration, + ZipperPath, ZipperPathBuffer, ZipperValues, + ZipperReadOnlyValues, ZipperReadOnlyConditionalValues, + }, +}; + +/// Zipper Wrapper to implement [`ZipperPath`] for zipper types that implement [`ZipperMoving`]. +/// This is useful for tracking the path of "blind" zipper types +/// +/// The "blind" zipper pattern enables nested virtual zippers to efficiently compose, +/// without repeating the work of copying paths. A `PathTracker` reinstates a contiguous +/// path buffer at whichever layer actually needs one. +/// +/// Example: +/// ```rust +/// use crate::pathmap::zipper::{ZipperPath, ZipperMoving}; +/// // the example uses `PathMap`, but this works with any zipper. +/// let btm = pathmap::PathMap::from_iter([(b"hello", ())]); +/// let zipper = btm.read_zipper(); +/// let mut with_path = pathmap::zipper::PathTracker::new(zipper); +/// assert_eq!(with_path.descend_to_existing("hello"), 5); +/// assert_eq!(with_path.path(), b"hello"); +/// ``` +pub struct PathTracker { + zipper: Z, + path: Vec, + origin_len: usize, +} + +impl PathTracker { + /// Returns a new `PathTracker` wrapping `zipper`, tracking the path from the zipper's root + pub fn new(mut zipper: Z) -> Self { + zipper.reset(); + Self { + zipper, + path: Vec::new(), + origin_len: 0, + } + } + /// Returns a new `PathTracker` with the supplied + /// [`root_prefix_path`](ZipperAbsolutePath::root_prefix_path) + pub fn with_origin(mut zipper: Z, origin: &[u8]) -> Self { + zipper.reset(); + Self { + zipper, + path: origin.to_vec(), + origin_len: origin.len(), + } + } +} + +impl Zipper for PathTracker { + #[inline] fn path_exists(&self) -> bool { self.zipper.path_exists() } + #[inline] fn is_val(&self) -> bool { self.zipper.is_val() } + #[inline] fn child_count(&self) -> usize { self.zipper.child_count() } + #[inline] fn child_mask(&self) -> ByteMask { self.zipper.child_mask() } +} + +impl ZipperMoving for PathTracker { + #[inline] fn at_root(&self) -> bool { self.zipper.at_root() } + fn reset(&mut self) { + self.zipper.reset(); + self.path.truncate(self.origin_len); + } + #[inline] + fn focus_byte(&self) -> Option { + if self.path.len() > self.origin_len { + self.path.last().cloned() + } else { + None + } + } + fn val_count(&self) -> usize { self.zipper.val_count() } + fn descend_to>(&mut self, path: K) { + let path = path.as_ref(); + self.path.extend_from_slice(path); + self.zipper.descend_to(path) + } + fn descend_to_existing>(&mut self, path: K) -> usize { + let path = path.as_ref(); + let descended = self.zipper.descend_to_existing(path); + self.path.extend_from_slice(&path[..descended]); + descended + } + fn descend_to_existing_byte(&mut self, k: u8) -> bool { + if self.zipper.descend_to_existing_byte(k) { + self.path.push(k); + true + } else { + false + } + } + fn descend_to_val>(&mut self, path: K) -> usize { + let path = path.as_ref(); + let descended = self.zipper.descend_to_val(path); + self.path.extend_from_slice(&path[..descended]); + descended + } + fn descend_to_byte(&mut self, k: u8) { + self.path.push(k); + self.zipper.descend_to_byte(k) + } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { + let byte = self.zipper.descend_indexed_byte(child_idx)?; + self.path.push(byte); + Some(byte) + } + fn descend_first_byte(&mut self) -> Option { + let byte = self.zipper.descend_first_byte()?; + self.path.push(byte); + Some(byte) + } + fn descend_until(&mut self, obs: &mut Obs) -> bool { + //Fan the descended bytes out to our own path buffer as well as the caller's observer + self.zipper.descend_until(&mut (&mut self.path, &mut *obs)) + } + fn ascend(&mut self, steps: usize) -> usize { + let ascended = self.zipper.ascend(steps); + self.path.truncate(self.path.len() - ascended); + ascended + } + fn ascend_byte(&mut self) -> bool { + if self.zipper.ascend_byte() { + self.path.pop(); + true + } else { + false + } + } + fn ascend_until(&mut self) -> usize { + let ascended = self.zipper.ascend_until(); + self.path.truncate(self.path.len() - ascended); + ascended + } + fn ascend_until_branch(&mut self) -> usize { + let ascended = self.zipper.ascend_until_branch(); + self.path.truncate(self.path.len() - ascended); + ascended + } + fn to_next_sibling_byte(&mut self) -> Option { + let byte = self.zipper.to_next_sibling_byte()?; + *self.path.last_mut().expect("path must not be empty") = byte; + Some(byte) + } + fn to_prev_sibling_byte(&mut self) -> Option { + let byte = self.zipper.to_prev_sibling_byte()?; + *self.path.last_mut().expect("path must not be empty") = byte; + Some(byte) + } +} + + +impl ZipperIteration for PathTracker { } + +impl ZipperPath for PathTracker { + fn path(&self) -> &[u8] { &self.path[self.origin_len..] } +} + +impl ZipperAbsolutePath for PathTracker { + fn origin_path(&self) -> &[u8] { &self.path } + fn root_prefix_path(&self) -> &[u8] { &self.path[..self.origin_len] } +} + +impl, V> ZipperValues for PathTracker { + fn val(&self) -> Option<&V> { self.zipper.val() } + fn val_at>(&self, path: K) -> Option<&V> { self.zipper.val_at(path) } +} + +impl<'a, Z: ZipperReadOnlyValues<'a, V>, V: Clone + Send + Sync> ZipperReadOnlyValues<'a, V> for PathTracker + where Self: ZipperValues +{ + fn get_val(&self) -> Option<&'a V> { self.zipper.get_val() } + fn get_val_at>(&self, path: K) -> Option<&'a V> { self.zipper.get_val_at(path) } +} + +impl<'a, Z: ZipperReadOnlyConditionalValues<'a, V>, V: Clone + Send + Sync> ZipperReadOnlyConditionalValues<'a, V> for PathTracker + where Self: ZipperValues +{ + type WitnessT = Z::WitnessT; + fn witness<'w>(&self) -> Self::WitnessT { self.zipper.witness() } + fn get_val_with_witness<'w>(&self, witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { + self.zipper.get_val_with_witness(witness) + } +} + +impl ZipperPathBuffer for PathTracker { + unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { + assert!(len <= self.path.capacity()); + let ptr = self.path.as_ptr(); + unsafe { core::slice::from_raw_parts(ptr, len) } + } + fn prepare_buffers(&mut self) { } + fn reserve_buffers(&mut self, path_len: usize, _stack: usize) { + self.path.reserve(path_len); + } +} + +#[cfg(test)] +mod tests { + use super::PathTracker; + use crate::{ + PathMap, + zipper::{zipper_iteration_tests, zipper_moving_tests}, + }; + + zipper_moving_tests::zipper_moving_tests!(path_tracker, + |keys: &[&[u8]]| { + keys.into_iter().map(|k| (k, ())).collect::>() + }, + |trie: &mut PathMap<()>, path: &[u8]| { + PathTracker::with_origin(trie.read_zipper_at_path(path), path) + } + ); + + zipper_iteration_tests::zipper_iteration_tests!(path_tracker, + |keys: &[&[u8]]| { + keys.into_iter().map(|k| (k, ())).collect::>() + }, + |trie: &mut PathMap<()>, path: &[u8]| { + PathTracker::with_origin(trie.read_zipper_at_path(path), path) + } + ); +} diff --git a/src/paths_serialization.rs b/src/paths_serialization.rs index a05d1d38..3f6e05df 100644 --- a/src/paths_serialization.rs +++ b/src/paths_serialization.rs @@ -13,6 +13,7 @@ use crate::zipper::{ ZipperReadOnlyConditionalIteration, ZipperWriting, ZipperIteration, + ZipperPath, ZipperValues, }; @@ -58,7 +59,7 @@ pub fn serialize_paths<'a, V, W, RZ>(rz: RZ, target: &mut W) -> std::io::Result< /// /// The `fv` closure is called for each path, permitting values to be serialized separately /// and associated with path indices -pub fn serialize_paths_with_auxdata<'a, V : TrieValue, RZ : ZipperValues + ZipperIteration, W: std::io::Write, F: FnMut(usize, &[u8], &V) -> ()>(mut rz: RZ, target: &mut W, mut fv: F) -> std::io::Result { +pub fn serialize_paths_with_auxdata<'a, V : TrieValue, RZ : ZipperValues + ZipperIteration + ZipperPath, W: std::io::Write, F: FnMut(usize, &[u8], &V) -> ()>(mut rz: RZ, target: &mut W, mut fv: F) -> std::io::Result { let mut k = 0; //GOAT, old implementation. Delete. // serialize_paths_from_func(target, &mut rz, |rz| { @@ -278,7 +279,7 @@ pub fn for_each_deserialized_path st #[cfg(test)] mod test { - use crate::zipper::{ZipperIteration, ZipperValues, ZipperMoving}; + use crate::zipper::{ZipperIteration, ZipperValues}; use super::*; #[cfg(not(miri))] // miri really hates the zlib-ng-sys C API diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index c2238166..778c7b6a 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -133,15 +133,12 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> }; } if self.position.is_source() { - // let Err(remaining) = self.source.ascend(steps) else { - // return Ok(()); - // }; - let len_before = self.source.path().len(); - if self.source.ascend(steps) { + //The source reports how far it moved, so there's no need to measure its path + let ascended = self.source.ascend(steps); + if ascended == steps { return Ok(()) } - let len_after = self.source.path().len(); - steps -= len_before - len_after; + steps -= ascended; self.position = PrefixPos::Prefix { valid: self.prefix.len() - self.origin_depth }; // Intermediate state: self.position points one off } @@ -161,20 +158,18 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> } let mut ascended = 0; if self.position.is_source() { - // if let Some(moved) = self.source.ascend_until() { - // return Some(moved); - // } - let len_before = self.source.path().len(); - let was_good = if VAL { + let moved = if VAL { self.source.ascend_until() } else { self.source.ascend_until_branch() }; - if was_good && ((VAL && self.source.is_val()) || self.source.child_count() > 1) { - let len_after = self.source.path().len(); - return Some(len_before - len_after); + if moved > 0 && ((VAL && self.source.is_val()) || self.source.child_count() > 1) { + return Some(moved); } - ascended += len_before; + //Falling through here means the source ascended all the way to its own root, so the + //distance it reports is the whole of the path it had descended + debug_assert!(self.source.at_root()); + ascended += moved; let valid = self.prefix.len() - self.origin_depth; self.position = PrefixPos::Prefix { valid }; } @@ -355,6 +350,11 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> } } + #[inline] + fn focus_byte(&self) -> Option { + self.path.last().cloned() + } + fn reset(&mut self) { self.prepare_buffers(); self.path.truncate(self.origin_depth); @@ -363,11 +363,6 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.set_valid(0); } - #[inline] - fn path(&self) -> &[u8] { - &self.path[self.origin_depth..] - } - fn val_count(&self) -> usize { self.source.val_count() } @@ -418,95 +413,93 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.descend_to([k]) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { let mask = self.child_mask(); - let Some(byte) = mask.indexed_bit::(child_idx) else { - return false; - }; + let byte = mask.indexed_bit::(child_idx)?; self.descend_to_byte(byte); debug_assert!(self.path_exists()); - true + Some(byte) } #[inline] - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { if self.position.is_invalid() { return false; } //Consuming the remainder of the prefix is itself a movement, so it must be reflected in the // return value even when the source zipper can't descend any further let descended_prefix = if let Some(prefixed_depth) = self.position.prefixed_depth() { - self.path.extend_from_slice(&self.prefix[self.origin_depth + prefixed_depth..]); + let prefix_rest = &self.prefix[self.origin_depth + prefixed_depth..]; + self.path.extend_from_slice(prefix_rest); + obs.descend_to(prefix_rest); self.position = PrefixPos::Source; true } else { false }; - let len_before = self.source.path().len(); - if !self.source.descend_until() { - return descended_prefix; - } - let path = self.source.path(); - self.path.extend_from_slice(&path[len_before..]); - true + //Fan the descended bytes out to our own path buffer as well as the caller's observer + descended_prefix | self.source.descend_until(&mut (&mut self.path, &mut *obs)) } #[inline] - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { if !self.position.is_source() { - return false; - } - if !self.source.to_next_sibling_byte() { - return false; + return None; } - let byte = *self.source.path().last().unwrap(); + let byte = self.source.to_next_sibling_byte()?; *self.path.last_mut().unwrap() = byte; - true + Some(byte) } #[inline] - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { if !self.position.is_source() { - return false; - } - if !self.source.to_prev_sibling_byte() { - return false; + return None; } - let byte = *self.source.path().last().unwrap(); + let byte = self.source.to_prev_sibling_byte()?; *self.path.last_mut().unwrap() = byte; - true + Some(byte) } - fn ascend(&mut self, steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> usize { let ascended = match self.ascend_n(steps) { Err(remaining) => steps - remaining, Ok(()) => steps, }; self.path.truncate(self.path.len() - ascended); - ascended == steps + ascended } #[inline] fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1) == 1 } #[inline] - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { let Some(ascended) = self.ascend_until_n::() else { - return false; + return 0; }; self.path.truncate(self.path.len() - ascended); - true + ascended } #[inline] - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { let Some(ascended) = self.ascend_until_n::() else { - return false; + return 0; }; self.path.truncate(self.path.len() - ascended); - true + ascended + } +} + +impl<'prefix, Z> ZipperPath for PrefixZipper<'prefix, Z> + where Z: ZipperMoving +{ + #[inline] + fn path(&self) -> &[u8] { + &self.path[self.origin_depth..] } } @@ -685,6 +678,7 @@ mod tests { use crate::zipper::Zipper; use crate::zipper::ZipperMoving; use crate::zipper::ZipperAbsolutePath; + use crate::zipper::ZipperPath; use crate::zipper::ZipperReadOnlyValues; use crate::zipper::ZipperValues; const PATHS1: &[(&[u8], u64)] = &[ @@ -710,18 +704,18 @@ mod tests { let mut rz = PrefixZipper::new(b"prefix", map.read_zipper()); rz.set_root_prefix_path(b"pre").unwrap(); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until(), true); + assert!(rz.ascend_until() > 0); assert_eq!(rz.path(), b"fix0000"); assert_eq!(rz.origin_path(), b"prefix0000"); assert_eq!(rz.descend_to_existing(b"0"), 1); - assert_eq!(rz.ascend_until_branch(), true); + assert!(rz.ascend_until_branch() > 0); assert_eq!(rz.path(), b"fix000"); - assert_eq!(rz.ascend_until_branch(), true); + assert!(rz.ascend_until_branch() > 0); assert_eq!(rz.path(), b"fix"); - assert_eq!(rz.ascend_until_branch(), true); + assert!(rz.ascend_until_branch() > 0); assert_eq!(rz.path(), b""); assert_eq!(rz.origin_path(), b"pre"); - assert_eq!(rz.ascend_until_branch(), false); + assert_eq!(rz.ascend_until_branch(), 0); } #[test] @@ -730,21 +724,21 @@ mod tests { let mut rz = PrefixZipper::new(b"prefix", map.read_zipper()); rz.set_root_prefix_path(b"pre").unwrap(); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until(), true); + assert!(rz.ascend_until() > 0); assert_eq!(rz.path(), b"fix000"); assert_eq!(rz.origin_path(), b"prefix000"); - assert_eq!(rz.ascend_until(), true); + assert!(rz.ascend_until() > 0); assert_eq!(rz.path(), b"fix00"); - assert_eq!(rz.ascend_until(), true); + assert!(rz.ascend_until() > 0); assert_eq!(rz.path(), b""); - assert_eq!(rz.ascend_until(), false); + assert_eq!(rz.ascend_until(), 0); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until_branch(), true); + assert!(rz.ascend_until_branch() > 0); assert_eq!(rz.path(), b"fix00"); - assert_eq!(rz.ascend_until_branch(), true); + assert!(rz.ascend_until_branch() > 0); assert_eq!(rz.path(), b""); assert_eq!(rz.origin_path(), b"pre"); - assert_eq!(rz.ascend_until_branch(), false); + assert_eq!(rz.ascend_until_branch(), 0); } #[test] @@ -782,7 +776,7 @@ mod tests { let mut rz = PrefixZipper::new(b"prefix", map.read_zipper()); assert_eq!(rz.path(), b""); - let moved = rz.descend_until(); + let moved = rz.descend_until(&mut ()); //The focus advanced across the whole prefix... assert_eq!(rz.path(), b"prefix"); diff --git a/src/product_zipper.rs b/src/product_zipper.rs index edde1274..22a8903b 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -41,7 +41,7 @@ impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ProductZipp /// an implementation issue, but would be very difficult to fix and may not be worth fixing. pub fn new(mut primary_z: PrimaryZ, other_zippers: ZipperList) -> Self where - PrimaryZ: ZipperMoving + ZipperReadOnlySubtries<'trie, V, A> + 'factor_z, + PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlySubtries<'trie, V, A> + 'factor_z, OtherZ: ZipperSubtries + 'factor_z, ZipperList: IntoIterator, { @@ -165,16 +165,16 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn at_root(&self) -> bool { self.path().len() == 0 } + #[inline] + fn focus_byte(&self) -> Option { + self.z.focus_byte() + } fn reset(&mut self) { self.factor_paths.clear(); self.z.reset() } - #[inline] - fn path(&self) -> &[u8] { - self.z.path() - } fn val_count(&self) -> usize { - assert!(self.focus_factor() == self.factor_count() - 1); + debug_assert!(self.focus_factor() == self.factor_count() - 1); self.z.val_count() } fn descend_to_existing>(&mut self, k: K) -> usize { @@ -243,20 +243,20 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } descended } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { let result = self.z.descend_indexed_byte(child_idx); self.ensure_descend_next_factor(); result } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { let result = self.z.descend_first_byte(); self.ensure_descend_next_factor(); result } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let mut moved = false; while self.z.child_count() == 1 { - moved |= self.z.descend_until(); + moved |= self.z.descend_until(&mut *obs); self.ensure_descend_next_factor(); if self.z.is_val() { break; @@ -264,7 +264,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } moved } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { if self.factor_paths.last().cloned().unwrap_or(0) == self.path().len() { self.factor_paths.pop(); } @@ -272,7 +272,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.ensure_descend_next_factor(); moved } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { if self.factor_paths.last().cloned().unwrap_or(0) == self.path().len() { self.factor_paths.pop(); } @@ -280,7 +280,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.ensure_descend_next_factor(); moved } - fn ascend(&mut self, steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> usize { let ascended = self.z.ascend(steps); self.fix_after_ascend(); ascended @@ -290,18 +290,25 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.fix_after_ascend(); ascended } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { let ascended = self.z.ascend_until(); self.fix_after_ascend(); ascended } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { let ascended = self.z.ascend_until_branch(); self.fix_after_ascend(); ascended } } +impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ProductZipper<'_, 'trie, V, A> { + #[inline] + fn path(&self) -> &[u8] { + self.z.path() + } +} + impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperIteration for ProductZipper<'_, 'trie, V, A> { } //Use the default impl for all methods impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperValues for ProductZipper<'_, 'trie, V, A> { @@ -388,8 +395,8 @@ pub struct ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving, - SecondaryZ: ZipperMoving, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, { /// Creates a new `ProductZipper` from the provided zippers pub fn new(primary: PrimaryZ, other_zippers: ZipperList) -> Self @@ -452,28 +459,30 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, /// A combination between `ascend_until` and `ascend_until_branch`. /// if `allow_stop_on_val` is `true`, behaves as `ascend_until` - fn ascend_cond(&mut self, allow_stop_on_val: bool) -> bool { + fn ascend_cond(&mut self, allow_stop_on_val: bool) -> usize { let mut plen = self.path().len(); + let mut ascended = 0; loop { while self.factor_paths.last() == Some(&plen) { self.factor_paths.pop(); } if let Some(idx) = self.factor_idx(false) { let zipper = &mut self.secondary[idx]; - let before = zipper.path().len(); - let rv = if allow_stop_on_val { + //The inner zipper reports how far it moved, so the primary can be brought along + //without measuring its path before and after + let delta = if allow_stop_on_val { zipper.ascend_until() } else { zipper.ascend_until_branch() }; - let delta = before - zipper.path().len(); plen -= delta; self.primary.ascend(delta); - if rv && (self.child_count() != 1 || (allow_stop_on_val && self.is_val())) { - return true; + ascended += delta; + if delta > 0 && (self.child_count() != 1 || (allow_stop_on_val && self.is_val())) { + return ascended; } } else { - return if allow_stop_on_val { + return ascended + if allow_stop_on_val { self.primary.ascend_until() } else { self.primary.ascend_until_branch() @@ -483,11 +492,10 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, } /// a combination between `to_next_sibling` and `to_prev_sibling` - fn to_sibling_byte(&mut self, next: bool) -> bool { - let Some(&byte) = self.path().last() else { - return false; - }; - assert!(self.ascend(1), "must ascend"); + fn to_sibling_byte(&mut self, next: bool) -> Option { + let byte = self.focus_byte()?; + let ascended = self.ascend(1); + debug_assert_eq!(ascended, 1, "must ascend"); let child_mask = self.child_mask(); let Some(sibling_byte) = (if next { child_mask.next_bit(byte) @@ -495,10 +503,10 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, child_mask.prev_bit(byte) }) else { self.descend_to_byte(byte); - return false; + return None; }; self.descend_to_byte(sibling_byte); - true + Some(sibling_byte) } } @@ -507,7 +515,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperAbsolutePath where V: Clone + Send + Sync, PrimaryZ: ZipperAbsolutePath, - SecondaryZ: ZipperMoving, + SecondaryZ: ZipperMoving + ZipperPath, { fn origin_path(&self) -> &[u8] { self.primary.origin_path() } fn root_prefix_path(&self) -> &[u8] { self.primary.root_prefix_path() } @@ -517,8 +525,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperConcrete for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperConcrete, - SecondaryZ: ZipperMoving + ZipperConcrete, + PrimaryZ: ZipperMoving + ZipperPath + ZipperConcrete, + SecondaryZ: ZipperMoving + ZipperPath + ZipperConcrete, { fn shared_node_id(&self) -> Option { if let Some(idx) = self.factor_idx(true) { @@ -540,8 +548,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperPathBuffer for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperPathBuffer, - SecondaryZ: ZipperMoving + ZipperPathBuffer, + PrimaryZ: ZipperMoving + ZipperPath + ZipperPathBuffer, + SecondaryZ: ZipperMoving + ZipperPath + ZipperPathBuffer, { unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { unsafe{ self.primary.origin_path_assert_len(len) } } fn prepare_buffers(&mut self) { self.primary.prepare_buffers() } @@ -552,8 +560,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperValues for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperValues, - SecondaryZ: ZipperMoving + ZipperValues, + PrimaryZ: ZipperMoving + ZipperPath + ZipperValues, + SecondaryZ: ZipperMoving + ZipperPath + ZipperValues, { fn val(&self) -> Option<&V> { if let Some(idx) = self.factor_idx(true) { @@ -575,8 +583,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperReadOnlyValues<'trie, V> for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, + PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, { fn get_val(&self) -> Option<&'trie V> { if let Some(idx) = self.factor_idx(true) { @@ -598,8 +606,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperReadOnlyConditionalValues<'trie, V> for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperReadOnlyConditionalValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperReadOnlyConditionalValues<'trie, V>, + PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyConditionalValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyConditionalValues<'trie, V>, { type WitnessT = (PrimaryZ::WitnessT, Vec); fn witness<'w>(&self) -> Self::WitnessT { @@ -619,8 +627,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperReadOnlyConditionalValues<'trie, V> impl<'trie, PrimaryZ, SecondaryZ, V> Zipper for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + Zipper, - SecondaryZ: ZipperMoving + Zipper, + PrimaryZ: ZipperMoving + ZipperPath + Zipper, + SecondaryZ: ZipperMoving + ZipperPath + Zipper, { fn path_exists(&self) -> bool { if let Some(idx) = self.factor_idx(true) { @@ -655,12 +663,16 @@ impl<'trie, PrimaryZ, SecondaryZ, V> Zipper for ProductZipperG<'trie, PrimaryZ, impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving, - SecondaryZ: ZipperMoving, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, { fn at_root(&self) -> bool { self.path().is_empty() } + #[inline] + fn focus_byte(&self) -> Option { + self.primary.focus_byte() + } fn reset(&mut self) { self.factor_paths.clear(); for secondary in &mut self.secondary { @@ -669,9 +681,6 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim self.primary.reset(); } #[inline] - fn path(&self) -> &[u8] { - self.primary.path() - } fn val_count(&self) -> usize { unimplemented!("method will probably get removed") } @@ -713,33 +722,28 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim fn descend_to_byte(&mut self, k: u8) { self.descend_to([k]) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { let mask = self.child_mask(); - let Some(byte) = mask.indexed_bit::(child_idx) else { - return false; - }; + let byte = mask.indexed_bit::(child_idx)?; self.descend_to_byte(byte); - true + Some(byte) } #[inline] - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let mut moved = false; self.enter_factors(); while self.child_count() == 1 { moved |= if let Some(idx) = self.factor_idx(false) { + //The primary carries the whole product path, so it has to follow whatever the + //secondary descends. Mirroring the movement keeps it in step without buffering + //the bytes. let zipper = &mut self.secondary[idx]; - let before = zipper.path().len(); - let rv = zipper.descend_until(); - let path = zipper.path(); - if path.len() > before { - self.primary.descend_to(&path[before..]); - } - rv + zipper.descend_until(&mut (MirrorPathObserver(&mut self.primary), &mut *obs)) } else { - self.primary.descend_until() + self.primary.descend_until(&mut *obs) }; self.enter_factors(); if self.is_val() { @@ -749,42 +753,55 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim moved } #[inline] - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling_byte(true) } #[inline] - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling_byte(false) } - fn ascend(&mut self, mut steps: usize) -> bool { - while steps > 0 { + fn ascend(&mut self, steps: usize) -> usize { + let mut remaining = steps; + while remaining > 0 { self.exit_factors(); if let Some(idx) = self.factor_idx(false) { let len = self.path().len() - self.factor_paths[idx]; - let delta = len.min(steps); + let delta = len.min(remaining); self.secondary[idx].ascend(delta); self.primary.ascend(delta); - steps -= delta; + remaining -= delta; } else { - return self.primary.ascend(steps); + return (steps - remaining) + self.primary.ascend(remaining); } } - true + steps } #[inline] fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1) == 1 } #[inline] - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { self.ascend_cond(true) } #[inline] - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { self.ascend_cond(false) } } +impl<'trie, PrimaryZ, SecondaryZ, V> ZipperPath for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> + where + V: Clone + Send + Sync, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, +{ + #[inline] + fn path(&self) -> &[u8] { + self.primary.path() + } +} + impl<'trie, PrimaryZ, SecondaryZ, V> ZipperIteration for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where @@ -885,6 +902,7 @@ impl ZipperPr impl Zipper for OneFactor { zipper_impl_lens!(Zipper self => self.z); } impl ZipperAbsolutePath for OneFactor { zipper_impl_lens!(ZipperAbsolutePath self => self.z); } impl ZipperMoving for OneFactor { zipper_impl_lens!(ZipperMoving self => self.z); } +impl ZipperPath for OneFactor { zipper_impl_lens!(ZipperPath self => self.z); } impl ZipperIteration for OneFactor { zipper_impl_lens!(ZipperIteration self => self.z); } impl > ZipperValues for OneFactor { zipper_impl_lens!(ZipperValues self => self.z); } impl > ZipperForking for OneFactor { type ReadZipperT<'a> = Z::ReadZipperT<'a> where Z: 'a; zipper_impl_lens!(ZipperForking self => self.z); } @@ -915,6 +933,98 @@ mod tests { // --- START OF MACRO GENERATED MOD --- pub mod $mod { use super::*; + /// Builds a path long enough to span several trie nodes, so a `descend_until` over it is + /// reported to a [PathObserver] as several separate segments + fn long_path(len: usize) -> Vec { + (0..len).map(|i| b'a' + (i % 26) as u8).collect() + } + + /// `descend_until` through a secondary factor must leave the product zipper exactly where an + /// equivalent `descend_to` would, including when the secondary's descent spans several nodes + /// and is therefore reported in multiple segments. + #[test] + fn product_zipper_descend_until_multi_segment_secondary() { + for secondary_len in [1usize, 3, 40, 200, 500] { + let secondary_path = long_path(secondary_len); + let l = PathMap::from_iter([(b"X".as_slice(), ())]); + let r = PathMap::from_iter([(secondary_path.as_slice(), ())]); + $convert!(l); + $convert!(r); + + //Descend into the secondary factor with `descend_until` + let mut pz = $ProductZipper::new(l.read_zipper(), [r.read_zipper()]); + pz.descend_to(b"X"); + let moved = pz.descend_until(&mut ()); + + //An independent zipper walked to the same place with `descend_to` + let mut expected = $ProductZipper::new(l.read_zipper(), [r.read_zipper()]); + expected.descend_to(b"X"); + expected.descend_to(&secondary_path); + + assert_eq!(moved, true, "len={secondary_len}: should have descended"); + assert_eq!(pz.path(), expected.path(), "len={secondary_len}: path mismatch"); + assert_eq!(pz.path_exists(), expected.path_exists(), "len={secondary_len}: path_exists mismatch"); + assert_eq!(pz.val(), expected.val(), "len={secondary_len}: val mismatch"); + assert_eq!(pz.child_count(), expected.child_count(), "len={secondary_len}: child_count mismatch"); + assert_eq!(pz.child_mask(), expected.child_mask(), "len={secondary_len}: child_mask mismatch"); + } + } + + /// The observer passed to `descend_until` must receive exactly the bytes the focus moved over, + /// no matter how many segments the underlying descent was reported in + #[test] + fn product_zipper_descend_until_observer_matches_movement() { + for secondary_len in [1usize, 3, 40, 200, 500] { + let secondary_path = long_path(secondary_len); + let l = PathMap::from_iter([(b"X".as_slice(), ())]); + let r = PathMap::from_iter([(secondary_path.as_slice(), ())]); + $convert!(l); + $convert!(r); + + let mut pz = $ProductZipper::new(l.read_zipper(), [r.read_zipper()]); + pz.descend_to(b"X"); + let before = pz.path().to_vec(); + + let mut observed = Vec::new(); + pz.descend_until(&mut observed); + + let mut expected_observed = pz.path().to_vec(); + expected_observed.drain(..before.len()); + assert_eq!(observed, expected_observed, + "len={secondary_len}: observer must report exactly the bytes descended"); + } + } + + /// Repeated `descend_until` calls that cross from the primary into a secondary, and onward + /// into a third factor, must agree with a zipper descended directly to the same path + #[test] + fn product_zipper_descend_until_across_factors() { + let mid = long_path(120); + let tail = long_path(90); + let l = PathMap::from_iter([(b"X".as_slice(), ())]); + let r = PathMap::from_iter([(mid.as_slice(), ())]); + let e = PathMap::from_iter([(tail.as_slice(), ())]); + $convert!(l); + $convert!(r); + $convert!(e); + + let mut pz = $ProductZipper::new(l.read_zipper(), [r.read_zipper(), e.read_zipper()]); + //Drive the whole product path using only `descend_until` + while pz.descend_until(&mut ()) {} + + let mut full = b"X".to_vec(); + full.extend_from_slice(&mid); + full.extend_from_slice(&tail); + + let mut expected = $ProductZipper::new(l.read_zipper(), [r.read_zipper(), e.read_zipper()]); + expected.descend_to(&full); + + assert_eq!(pz.path(), expected.path(), "path mismatch after crossing factors"); + assert_eq!(pz.path_exists(), expected.path_exists()); + assert_eq!(pz.val(), expected.val()); + assert_eq!(pz.child_count(), expected.child_count()); + } + /// Tests a very simple two-level product zipper #[test] fn product_zipper_test1() { @@ -993,19 +1103,19 @@ mod tests { assert_eq!(pz.child_count(), 0); //Make sure we can ascend out of a secondary factor; in this sub-test we'll hit the path middles - assert!(pz.ascend(1)); + assert_eq!(pz.ascend(1), 1); assert_eq!(pz.val(), None); assert_eq!(pz.path(), b"AAaDDdG"); assert_eq!(pz.child_count(), 0); - assert!(pz.ascend(3)); + assert_eq!(pz.ascend(3), 3); assert_eq!(pz.path(), b"AAaD"); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); - assert!(pz.ascend(2)); + assert_eq!(pz.ascend(2), 2); assert_eq!(pz.path(), b"AA"); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 3); - assert!(!pz.ascend(3)); + assert!(pz.ascend(3) < 3); assert_eq!(pz.path(), b""); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); @@ -1018,15 +1128,15 @@ mod tests { assert_eq!(pz.child_count(), 0); //Now try to hit the path transition points - assert!(pz.ascend(2)); + assert_eq!(pz.ascend(2), 2); assert_eq!(pz.path(), b"AAaDDd"); assert_eq!(pz.val(), Some(&1000)); assert_eq!(pz.child_count(), 0); - assert!(pz.ascend(3)); + assert_eq!(pz.ascend(3), 3); assert_eq!(pz.path(), b"AAa"); assert_eq!(pz.val(), Some(&0)); assert_eq!(pz.child_count(), 3); - assert!(pz.ascend(3)); + assert_eq!(pz.ascend(3), 3); assert_eq!(pz.path(), b""); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); @@ -1160,7 +1270,7 @@ mod tests { p.descend_to("abcdefghijklmnopqrstuvwxyzbowfo"); assert!(p.path_exists()); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbowfo"); - assert!(p.descend_first_byte()); + assert!(p.descend_first_byte().is_some()); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbowfoo"); } { @@ -1190,9 +1300,9 @@ mod tests { assert!(p.path_exists()); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbowpho"); assert!(p.is_val()); - assert!(p.ascend_until()); + assert!(p.ascend_until() > 0); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbow"); - assert!(p.ascend(3)); + assert_eq!(p.ascend(3), 3); assert_eq!(vec![b'A', b'a', b'b'], p.child_mask().iter().collect::>()); p.descend_to("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); assert!(p.path_exists()); @@ -1217,7 +1327,7 @@ mod tests { p.descend_to("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); assert!(!p.path_exists()); // println!("p {}", std::str::from_utf8(p.path()).unwrap()); - assert!(!p.ascend(27)); + assert!(p.ascend(27) < 27); } } @@ -1544,7 +1654,7 @@ mod tests { assert_eq!(pz.is_val(), false); // test ascend - assert_eq!(pz.ascend(snip.len() * (repeats-1)), true); + assert_eq!(pz.ascend(snip.len() * (repeats-1)), snip.len() * (repeats-1)); assert_eq!(pz.path(), snip); assert_eq!(pz.path_exists(), true); assert_eq!(pz.child_count(), 1); @@ -1553,7 +1663,7 @@ mod tests { // test ascend_until pz.reset(); pz.descend_to(&full_path); - assert_eq!(pz.ascend_until(), true); + assert!(pz.ascend_until() > 0); assert_eq!(pz.path(), []); assert_eq!(pz.path_exists(), true); assert_eq!(pz.child_count(), 1); @@ -1561,14 +1671,14 @@ mod tests { // test ascend_until_branch pz.descend_to(&full_path); - assert_eq!(pz.ascend_until_branch(), true); + assert!(pz.ascend_until_branch() > 0); assert_eq!(pz.path(), []); assert_eq!(pz.path_exists(), true); assert_eq!(pz.child_count(), 1); assert_eq!(pz.is_val(), false); // test descend_until - assert_eq!(pz.descend_until(), true); + assert_eq!(pz.descend_until(&mut ()), true); assert_eq!(pz.path(), full_path); assert_eq!(pz.path_exists(), true); assert_eq!(pz.child_count(), 0); @@ -1588,7 +1698,6 @@ mod tests { $convert!(e); let mut pz = $ProductZipper::new(l.read_zipper_at_borrowed_path(b"abcdefghijklm"), [r.read_zipper(), e.read_zipper()]); - assert_eq!(pz.factor_count(), 3); assert_eq!(pz.focus_factor(), 0); assert_eq!(pz.path_indices().len(), 0); assert_eq!(pz.path(), b""); @@ -1628,6 +1737,31 @@ mod tests { impl_product_zipper_tests!(pz_concrete, ProductZipper, noop); impl_product_zipper_tests!(pz_generic, ProductZipperG, noop); + /// Adapts [DependentProductZipperG] to the `new(primary, [secondaries])` shape the shared + /// product-zipper test suite uses, by handing out a fixed list of factors in order. This lets + /// the suite exercise `DependentProductZipperG`, which is otherwise near-identical to + /// [ProductZipperG]. + struct DependentPZAdapter; + + impl DependentPZAdapter { + fn new<'trie, PrimaryZ, SecondaryZ, V, L>(primary: PrimaryZ, others: L) + -> DependentProductZipperG<'trie, PrimaryZ, SecondaryZ, V, + (), impl Clone + for<'a> FnOnce((), &'a [u8], usize) -> ((), Option)> + where + V: Clone + Send + Sync, + PrimaryZ: ZipperMoving + ZipperPath + ZipperValues, + SecondaryZ: ZipperMoving + ZipperPath + Clone, + L: IntoIterator, + { + let factors: Vec = others.into_iter().collect(); + DependentProductZipperG::new_enroll(primary, (), move |_, _, idx| { + ((), factors.get(idx).cloned()) + }) + } + } + + impl_product_zipper_tests!(pz_dependent, DependentPZAdapter, noop); + #[cfg(feature="arena_compact")] macro_rules! to_act { (*$x:ident) => { diff --git a/src/random.rs b/src/random.rs index d01ee19b..b3fbea1a 100644 --- a/src/random.rs +++ b/src/random.rs @@ -8,7 +8,7 @@ use std::marker::PhantomData; use rand::distr::Uniform; use crate::TrieValue; use crate::utils::{BitMask, ByteMask}; -use crate::zipper::{ReadZipperUntracked, Zipper, ZipperReadOnlyIteration, ZipperMoving, ZipperReadOnlyValues}; +use crate::zipper::{ReadZipperUntracked, Zipper, ZipperPath, ZipperReadOnlyIteration, ZipperMoving, ZipperReadOnlyValues}; // Re-export generic combinators pub use distr_combinators::*; diff --git a/src/utils/debug/diff_zipper.rs b/src/utils/debug/diff_zipper.rs index b2fb8c1f..5192c67a 100644 --- a/src/utils/debug/diff_zipper.rs +++ b/src/utils/debug/diff_zipper.rs @@ -52,6 +52,12 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } + fn focus_byte(&self) -> Option { + let a = self.a.focus_byte(); + let b = self.b.focus_byte(); + assert_eq!(a, b); + a + } fn reset(&mut self) { self.a.reset(); self.b.reset(); @@ -59,12 +65,6 @@ impl ZipperMoving for DiffZi println!("DiffZipper: reset") } } - fn path(&self) -> &[u8] { - let a = self.a.path(); - let b = self.b.path(); - assert_eq!(a, b); - a - } fn val_count(&self) -> usize { let a = self.a.val_count(); let b = self.b.val_count(); @@ -108,7 +108,7 @@ impl ZipperMoving for DiffZi } assert_eq!(self.a.path_exists(), self.b.path_exists()); } - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { let a = self.a.descend_indexed_byte(idx); let b = self.b.descend_indexed_byte(idx); if self.log_moves { @@ -117,7 +117,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { let a = self.a.descend_first_byte(); let b = self.b.descend_first_byte(); if self.log_moves { @@ -126,16 +126,21 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn descend_until(&mut self) -> bool { - let a = self.a.descend_until(); - let b = self.b.descend_until(); + fn descend_until(&mut self, obs: &mut Obs) -> bool { + //`a`'s descent is forwarded to the caller as it happens, since a digest can't reconstruct + //the bytes afterwards. `b`'s is only hashed, and the two digests are compared. + let mut hash_a = HashObserver::default(); + let mut hash_b = HashObserver::default(); + let a = self.a.descend_until(&mut (&mut hash_a, &mut *obs)); + let b = self.b.descend_until(&mut hash_b); if self.log_moves { println!("DiffZipper: descend_until") } assert_eq!(a, b); + assert_eq!(hash_a, hash_b); a } - fn ascend(&mut self, steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> usize { let a = self.a.ascend(steps); let b = self.b.ascend(steps); if self.log_moves { @@ -153,7 +158,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { let a = self.a.ascend_until(); let b = self.b.ascend_until(); if self.log_moves { @@ -162,7 +167,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { let a = self.a.ascend_until_branch(); let b = self.b.ascend_until_branch(); if self.log_moves { @@ -171,7 +176,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { let a = self.a.to_next_sibling_byte(); let b = self.b.to_next_sibling_byte(); if self.log_moves { @@ -180,7 +185,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { let a = self.a.to_prev_sibling_byte(); let b = self.b.to_prev_sibling_byte(); if self.log_moves { @@ -191,6 +196,16 @@ impl ZipperMoving for DiffZi } } +impl ZipperPath for DiffZipper +{ + fn path(&self) -> &[u8] { + let a = self.a.path(); + let b = self.b.path(); + assert_eq!(a, b); + a + } +} + impl ZipperAbsolutePath for DiffZipper { fn origin_path(&self) -> &[u8] { diff --git a/src/utils/ints.rs b/src/utils/ints.rs index b164afb9..1a656e99 100644 --- a/src/utils/ints.rs +++ b/src/utils/ints.rs @@ -363,7 +363,7 @@ fn int_range_generator_5() { drop(buildz); let mut z = zh.read_zipper_at_path(&[0]).unwrap(); - z.descend_until(); + z.descend_until(&mut ()); z.descend_first_byte(); let _z2 = zh.read_zipper_at_path(z.origin_path()).unwrap(); diff --git a/src/viz.rs b/src/viz.rs index 528584df..85f60ecf 100644 --- a/src/viz.rs +++ b/src/viz.rs @@ -262,7 +262,7 @@ fn build_ascii_graph_logical ZipperInfallibleSubt impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving for WriteZipperTracked<'a, 'path, V, A> { fn at_root(&self) -> bool { self.z.at_root() } + #[inline] fn focus_byte(&self) -> Option { self.z.focus_byte() } fn reset(&mut self) { self.z.reset() } - fn path(&self) -> &[u8] { self.z.path() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) { self.z.descend_to(k) } fn descend_to_byte(&mut self, k: u8) { self.z.descend_to_byte(k) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { self.z.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut self) -> bool { self.z.descend_first_byte() } - fn descend_until(&mut self) -> bool { self.z.descend_until() } - fn to_next_sibling_byte(&mut self) -> bool { self.z.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> bool { self.z.ascend(steps) } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.z.descend_until(obs) } + fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } + fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> bool { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> usize { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> usize { self.z.ascend_until_branch() } +} + +impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperTracked<'a, 'path, V, A> { + fn path(&self) -> &[u8] { self.z.path() } } impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPathBuffer for WriteZipperTracked<'a, 'path, V, A> { @@ -578,20 +582,24 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperInfallibleSubt impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving for WriteZipperUntracked<'a, 'path, V, A> { fn at_root(&self) -> bool { self.z.at_root() } + #[inline] fn focus_byte(&self) -> Option { self.z.focus_byte() } fn reset(&mut self) { self.z.reset() } - fn path(&self) -> &[u8] { self.z.path() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) { self.z.descend_to(k) } fn descend_to_byte(&mut self, k: u8) { self.z.descend_to_byte(k) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { self.z.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut self) -> bool { self.z.descend_first_byte() } - fn descend_until(&mut self) -> bool { self.z.descend_until() } - fn to_next_sibling_byte(&mut self) -> bool { self.z.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> bool { self.z.ascend(steps) } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.z.descend_until(obs) } + fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } + fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> bool { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> usize { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> usize { self.z.ascend_until_branch() } +} + +impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperUntracked<'a, 'path, V, A> { + fn path(&self) -> &[u8] { self.z.path() } } impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPathBuffer for WriteZipperUntracked<'a, 'path, V, A> { @@ -719,6 +727,7 @@ impl Zipper for WriteZipperOwned ZipperValues for WriteZipperOwned { zipper_impl_lens!(ZipperValues self => self.z); } impl ZipperInfallibleSubtries for WriteZipperOwned { zipper_impl_lens!(ZipperInfallibleSubtries self => self.z); } impl ZipperMoving for WriteZipperOwned { zipper_impl_lens!(ZipperMoving self => self.z); } +impl ZipperPath for WriteZipperOwned { zipper_impl_lens!(ZipperPath self => self.z); } impl ZipperPathBuffer for WriteZipperOwned { zipper_impl_lens!(ZipperPathBuffer self => self.z); } impl ZipperAbsolutePath for WriteZipperOwned { zipper_impl_lens!(ZipperAbsolutePath self => self.z); } @@ -1003,20 +1012,17 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving self.key.prefix_buf.len() <= self.key.origin_path.len() } + #[inline] + fn focus_byte(&self) -> Option { + self.key.prefix_buf.last().cloned() + } + fn reset(&mut self) { self.focus_stack.to_root(); self.key.prefix_buf.truncate(self.key.origin_path.len()); self.key.prefix_idx.clear(); } - fn path(&self) -> &[u8] { - if self.key.prefix_buf.len() > 0 { - &self.key.prefix_buf[self.key.origin_path.len()..] - } else { - &[] - } - } - fn val_count(&self) -> usize { let root_val = self.is_val() as usize; let focus = self.get_focus(); @@ -1033,32 +1039,34 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving self.descend_to_internal(); } - fn ascend(&mut self, mut steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> usize { + let mut remaining = steps; loop { if self.key.node_key().len() == 0 { self.ascend_across_nodes(); } - if steps == 0 { - return true + if remaining == 0 { + return steps } if self.at_root() { - return false + return steps - remaining } debug_assert!(self.key.node_key().len() > 0); - let cur_jump = steps.min(self.key.excess_key_len()); + let cur_jump = remaining.min(self.key.excess_key_len()); self.key.prefix_buf.truncate(self.key.prefix_buf.len() - cur_jump); - steps -= cur_jump; + remaining -= cur_jump; } } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { if self.at_root() { - return false; + return 0; } + let mut ascended = 0; loop { - self.ascend_within_node(); + ascended += self.ascend_within_node(); if self.at_root() { - return true; + return ascended; } if self.key.node_key().len() == 0 { self.ascend_across_nodes(); @@ -1068,17 +1076,18 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } debug_assert!(self.key.node_key().len() > 0); //We should never finish with a zero-length node-key - true + ascended } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { if self.at_root() { - return false; + return 0; } + let mut ascended = 0; loop { - self.ascend_within_node(); + ascended += self.ascend_within_node(); if self.at_root() { - return true; + return ascended; } if self.key.node_key().len() == 0 { self.ascend_across_nodes(); @@ -1088,7 +1097,17 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } debug_assert!(self.key.node_key().len() > 0); //We should never finish with a zero-length node-key - true + ascended + } +} + +impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperCore<'a, 'path, V, A> { + fn path(&self) -> &[u8] { + if self.key.prefix_buf.len() > 0 { + &self.key.prefix_buf[self.key.origin_path.len()..] + } else { + &[] + } } } @@ -1821,18 +1840,18 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC fn k_path_internal(&mut self, k: usize, base_idx: usize) -> bool { loop { if self.path().len() < base_idx + k { - while self.descend_first_byte() { + while self.descend_first_byte().is_some() { if self.path().len() == base_idx + k { return true } } } - if self.to_next_sibling_byte() { + if self.to_next_sibling_byte().is_some() { if self.path().len() == base_idx + k { return true } continue } while self.path().len() > base_idx { self.ascend_byte(); if self.path().len() == base_idx { return false } - if self.to_next_sibling_byte() { break } + if self.to_next_sibling_byte().is_some() { break } } } } @@ -1854,7 +1873,7 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC let downstream_node = self.get_focus().into_option(); - let fully_ascended = self.ascend(n); + let fully_ascended = self.ascend(n) == n; self.graft_internal(downstream_node); fully_ascended @@ -2508,10 +2527,12 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC } /// Internal method used to impement `ascend_until` when ascending within a node #[inline] - fn ascend_within_node(&mut self) { + fn ascend_within_node(&mut self) -> usize { let branch_key = self.focus_stack.top().unwrap().prior_branch_key(self.key.node_key()); let new_len = self.key.origin_path.len().max(self.key.node_key_start() + branch_key.len()); + let old_len = self.key.prefix_buf.len(); self.key.prefix_buf.truncate(new_len); + old_len - new_len } } @@ -3446,7 +3467,7 @@ mod tests { wz.descend_to([194, 7, 162]); assert!(wz.path_exists()); assert!(wz.val().is_some()); - assert!(wz.ascend(3)); + assert_eq!(wz.ascend(3), 3); wz.descend_to([194, 7, 163]); assert!(wz.path_exists()); assert!(wz.val().is_some()); @@ -3831,26 +3852,26 @@ mod tests { assert!(wz.path_exists()); assert_eq!(wz.path(), b"mulus"); assert_eq!(wz.child_count(), 0); - assert!(wz.ascend_until()); + assert!(wz.ascend_until() > 0); assert_eq!(wz.path(), b"m"); assert_eq!(wz.child_count(), 3); //Make sure we can't ascend above the zipper's root with ascend_until - assert!(wz.ascend_until()); + assert!(wz.ascend_until() > 0); assert_eq!(wz.path(), b""); - assert!(!wz.ascend_until()); + assert_eq!(wz.ascend_until(), 0); //Test step-wise `ascend` wz.descend_to(b"manus"); assert_eq!(wz.path(), b"manus"); - assert_eq!(wz.ascend(1), true); + assert_eq!(wz.ascend(1), 1); assert_eq!(wz.path(), b"manu"); - assert_eq!(wz.ascend(5), false); + assert!(wz.ascend(5) < 5); assert_eq!(wz.path(), b""); assert_eq!(wz.at_root(), true); wz.descend_to(b"mane"); assert_eq!(wz.path(), b"mane"); - assert_eq!(wz.ascend(3), true); + assert_eq!(wz.ascend(3), 3); assert_eq!(wz.path(), b"m"); assert_eq!(wz.child_count(), 3); } @@ -4673,9 +4694,9 @@ mod tests { assert_eq!(wz.path(), &[0, 0, 1, 0, 0, 2, 3, 4]); assert_eq!(wz.prune_path(), 6); //Prune back to the value at [0, 0, 0] assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(4), true); + assert_eq!(wz.ascend(4), 4); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(2), true); + assert_eq!(wz.ascend(2), 2); assert_eq!(wz.path_exists(), true); assert_eq!(wz.path(), &[0, 0]); @@ -4735,16 +4756,16 @@ mod tests { assert_eq!(wz.prune_path(), 0); //And try pruning above the end - assert_eq!(wz.ascend(2), true); + assert_eq!(wz.ascend(2), 2); assert_eq!(wz.prune_path(), 0); - assert_eq!(wz.descend_first_byte(), true); + assert!(wz.descend_first_byte().is_some()); //Now validate that prune goes all the way to the root assert_eq!(wz.path(), &[0, 0, 0, 1, 2, 3, 4]); assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 7); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(7), true); + assert_eq!(wz.ascend(7), 7); assert_eq!(wz.path(), &[]); assert_eq!(wz.path_exists(), true); assert_eq!(wz.child_count(), 0); @@ -4796,9 +4817,9 @@ mod tests { assert_eq!(wz.path(), &[1, 0, 3, 4, 5, 6]); assert_eq!(wz.prune_path(), 4); //Prune back to the value at [1, 0, 0] assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), true); + assert_eq!(wz.ascend(3), 3); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(1), true); + assert_eq!(wz.ascend(1), 1); assert_eq!(wz.path_exists(), true); assert_eq!(wz.path(), &[1, 0]); assert_eq!(wz.child_count(), 3); @@ -4829,7 +4850,7 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 50); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(49), true); + assert_eq!(wz.ascend(49), 49); assert_eq!(wz.path_exists(), false); assert_eq!(wz.ascend_byte(), true); assert_eq!(wz.path_exists(), true); @@ -4851,7 +4872,7 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 3); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), true); + assert_eq!(wz.ascend(3), 3); assert_eq!(wz.child_count(), 2); } @@ -4878,18 +4899,18 @@ mod tests { assert_eq!(wz.prune_path(), 3); assert_eq!(wz.path(), &[0, 0, 1, 0, 0]); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), true); + assert_eq!(wz.ascend(3), 3); assert_eq!(wz.path_exists(), true); //Recreate some new paths, remove one and try re-extending it wz.descend_to([0, 0, 0, 0]); assert_eq!(wz.path_exists(), false); assert_eq!(wz.set_val(()), None); - assert_eq!(wz.ascend(4), true); + assert_eq!(wz.ascend(4), 4); wz.descend_to([0, 0, 1, 0]); assert_eq!(wz.path_exists(), false); assert_eq!(wz.set_val(()), None); - assert_eq!(wz.ascend(2), true); + assert_eq!(wz.ascend(2), 2); wz.descend_to_byte(0); assert_eq!(wz.remove_branches(false), true); assert_eq!(wz.path_exists(), true); @@ -4947,7 +4968,7 @@ mod tests { assert_eq!(src_z.prune_path(), 3); assert_eq!(src_z.path(), &[0, 0, 1, 0, 0]); assert_eq!(src_z.path_exists(), false); - assert_eq!(src_z.ascend(3), true); + assert_eq!(src_z.ascend(3), 3); assert_eq!(src_z.path_exists(), true); //Test removing from a node boundary @@ -5021,11 +5042,11 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.create_path(), false); - assert_eq!(wz.descend_first_byte(), true); + assert!(wz.descend_first_byte().is_some()); assert_eq!(wz.path_exists(), true); assert_eq!(wz.create_path(), false); assert_eq!(wz.val(), Some(&())); - assert_eq!(wz.descend_first_byte(), false); + assert_eq!(wz.descend_first_byte(), None); wz.descend_to_byte(0); assert_eq!(wz.path_exists(), false); assert_eq!(wz.create_path(), true); @@ -5054,7 +5075,7 @@ mod tests { wz.descend_to([3, 0, 0, 0]); assert_eq!(wz.path_exists(), false); assert_eq!(wz.create_path(), true); - assert_eq!(wz.ascend(4), true); + assert_eq!(wz.ascend(4), 4); assert_eq!(wz.child_count(), 4); } @@ -5188,7 +5209,7 @@ mod tests { assert_eq!(zipper.path(), b""); assert_eq!(zipper.val_count(), 2); - assert_eq!(zipper.descend_until(), true); + assert_eq!(zipper.descend_until(&mut ()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.val_count(), 1); } diff --git a/src/zipper.rs b/src/zipper.rs index 95314fe9..1bfc3dcc 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -6,6 +6,7 @@ //! moved above the zipper's root. //! +use arrayvec::ArrayVec; use maybe_dangling::MaybeDangling; use fast_slice_utils::{find_prefix_overlap, starts_with}; @@ -20,6 +21,7 @@ pub use crate::zipper_head::*; pub use crate::product_zipper::{ProductZipper, ProductZipperG, ZipperProduct, OneFactor}; pub use crate::overlay_zipper::{OverlayZipper}; pub use crate::prefix_zipper::{PrefixZipper}; +pub use crate::path_tracker::{PathTracker}; pub use crate::empty_zipper::{EmptyZipper}; pub use crate::poly_zipper::{PolyZipper, PolyZipperExplicit}; pub use crate::dependent_zipper::DependentProductZipperG; @@ -133,9 +135,20 @@ pub trait ZipperSubtries: Zi /// will never change. pub trait ZipperMoving: Zipper { /// Returns `true` if the zipper's focus is at its root, and it cannot ascend further, otherwise returns `false` - fn at_root(&self) -> bool { - self.path().len() == 0 - } + fn at_root(&self) -> bool; + + /// Returns the byte that was last descended to reach the zipper's focus, or `None` if that + /// byte is unavailable + /// + /// After a movement that descends one byte, such as [`descend_to_byte`](ZipperMoving::descend_to_byte) + /// or [`descend_first_byte`](ZipperMoving::descend_first_byte), this returns that byte. + /// + /// When [`at_root`](ZipperMoving::at_root) returns `true`, the return value is unspecified. A + /// zipper that retains knowledge of the trie upstream of its root may return the byte leading + /// to its root, while a zipper without that knowledge, or one rooted at the trie root, returns + /// `None`. Therefore a `Some` return must not be interpreted to mean the zipper has descended, + /// and callers needing that distinction must consult [`at_root`](ZipperMoving::at_root). + fn focus_byte(&self) -> Option; /// Resets the zipper's focus back to its root fn reset(&mut self) { @@ -144,34 +157,12 @@ pub trait ZipperMoving: Zipper { } } - /// Returns the path from the zipper's root to the current focus - fn path(&self) -> &[u8]; - /// Returns the total number of values contained at and below the zipper's focus, including the focus itself /// /// WARNING: This is not a cheap method. It may have an order-N cost //GOAT! This doesn't belong here. Should be a function that uses a non-side-effect catamorphism fn val_count(&self) -> usize; - /// Moves the zipper's focus to a specific location specified by `path`, relative to the zipper's root - /// - /// Returns the number of bytes shared between the old and new location - fn move_to_path>(&mut self, path: K) -> usize { - let path = path.as_ref(); - let p = self.path(); - let overlap = find_prefix_overlap(path, p); - let to_ascend = p.len() - overlap; - if overlap == 0 { // This heuristic can be fine-tuned for performance; the behavior of the two branches is equivalent - self.reset(); - self.descend_to(path); - overlap - } else { - self.ascend(to_ascend); - self.descend_to(&path[overlap..]); - overlap - } - } - /// Moves the zipper deeper into the trie, to the `key` specified relative to the current zipper focus fn descend_to>(&mut self, k: K); @@ -259,30 +250,30 @@ pub trait ZipperMoving: Zipper { /// WARNING: The branch represented by a given index is not guaranteed to be stable across modifications /// to the trie. This method should only be used as part of a directed traversal operation, but /// index-based paths may not be stored as locations within the trie. - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { let mask = self.child_mask(); let child_byte = match mask.indexed_bit::(idx) { Some(byte) => byte, None => { - return false + return None } }; self.descend_to_byte(child_byte); debug_assert!(self.path_exists()); - true + Some(child_byte) } /// A deprecated alias for [ZipperMoving::descend_indexed_byte] #[deprecated] //GOAT-old-names fn descend_indexed_branch(&mut self, idx: usize) -> bool { - self.descend_indexed_byte(idx) + self.descend_indexed_byte(idx).is_some() } /// Descends the zipper's focus one step into the first child branch in a depth-first traversal /// /// NOTE: This method should have identical behavior to passing `0` to [descend_indexed_byte](ZipperMoving::descend_indexed_byte), /// although with less overhead - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } @@ -290,9 +281,9 @@ pub trait ZipperMoving: Zipper { /// /// NOTE: This method should have identical behavior to passing `child_count() - 1` to [descend_indexed_byte](ZipperMoving::descend_indexed_byte), /// although with less overhead - fn descend_last_byte(&mut self) -> bool { + fn descend_last_byte(&mut self) -> Option { let cc = self.child_count(); - if cc == 0 { false } + if cc == 0 { None } else { self.descend_indexed_byte( cc- 1) } } @@ -303,11 +294,13 @@ pub trait ZipperMoving: Zipper { /// zipper will not descend further if this method is called with the focus already on a branch. /// /// Does nothing and returns `false` if the zipper's focus is on a non-existent path. - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let mut descended = false; while self.child_count() == 1 { descended = true; - self.descend_first_byte(); + if let Some(byte) = self.descend_first_byte() { + obs.descend_to_byte(byte); + } if self.is_val() { break; } @@ -323,42 +316,45 @@ pub trait ZipperMoving: Zipper { /// /// Does nothing and returns `false` if the zipper's focus is on a non-existent path or if `max_bytes` /// is zero. - fn descend_until_max_bytes(&mut self, max_bytes: usize) -> bool { + fn descend_until_max_bytes(&mut self, max_bytes: usize, obs: &mut Obs) -> bool { if max_bytes == 0 { return false; } - let target_len = self.path().len() + max_bytes; - let descended = self.descend_until(); - let cur_len = self.path().len(); - if cur_len > target_len { - let _ = self.ascend(cur_len - target_len); + //`descend_until` is unbounded, so it may overshoot `max_bytes`. A [TruncatingObserver] + // forwards only the bytes within the limit to `obs` and counts the rest, so the zipper can + // be brought back to the last byte that was reported. Reporting the overshoot and then + // retracting it would expose movement the caller never asked for. + let mut truncating = TruncatingObserver::new(obs, max_bytes); + let descended = self.descend_until(&mut truncating); + let overshoot = truncating.overshoot(); + if overshoot > 0 { + self.ascend(overshoot); } descended } - /// Ascends the zipper `steps` steps. Returns `true` if the zipper sucessfully moved `steps` + /// Ascends the zipper `steps` steps. Returns the number of bytes ascended /// - /// If the root is fewer than `n` steps from the zipper's position, then this method will stop at - /// the root and return `false` - fn ascend(&mut self, steps: usize) -> bool; + /// If the zipper's focus is fewer than `steps` steps from the root, then this method will stop at + /// the root and return the number of steps ascended, which may be smaller than `steps`. + fn ascend(&mut self, steps: usize) -> usize; /// Ascends the zipper up a single byte. Equivalent to passing `1` to [ascend](Self::ascend) fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1) == 1 } - /// Ascends the zipper to the nearest upstream branch point or value. Returns `true` if the zipper - /// focus moved upwards, otherwise returns `false` if the zipper was already at the root + /// Ascends the zipper to the nearest upstream branch point or value. Returns the number of bytes + /// ascended. Returns `0` if the zipper was already at the root /// /// NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. - fn ascend_until(&mut self) -> bool; + fn ascend_until(&mut self) -> usize; /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. Returns - /// `true` if the zipper focus moved upwards, otherwise returns `false` if the zipper was already at the - /// root + /// the number of bytes ascended. Returns `0` if the zipper was already at the root /// /// NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. - fn ascend_until_branch(&mut self) -> bool; + fn ascend_until_branch(&mut self) -> usize; /// Moves the zipper's focus to the next sibling byte with the same parent /// @@ -367,24 +363,21 @@ pub trait ZipperMoving: Zipper { /// /// This method is equivalent to calling [ZipperMoving::ascend] with `1`, followed by [ZipperMoving::descend_indexed_byte] /// where the index passed is 1 more than the index of the current focus position. - fn to_next_sibling_byte(&mut self) -> bool { - let cur_byte = match self.path().last() { - Some(byte) => *byte, - None => return false - }; + fn to_next_sibling_byte(&mut self) -> Option { + let cur_byte = self.focus_byte()?; if !self.ascend_byte() { - return false + return None } let mask = self.child_mask(); match mask.next_bit(cur_byte) { Some(byte) => { self.descend_to_byte(byte); debug_assert!(self.path_exists()); - true + Some(byte) }, None => { self.descend_to_byte(cur_byte); - false + None } } } @@ -396,25 +389,22 @@ pub trait ZipperMoving: Zipper { /// /// This method is equivalent to calling [Self::ascend] with `1`, followed by [Self::descend_indexed_byte] /// where the index passed is 1 less than the index of the current focus position. - fn to_prev_sibling_byte(&mut self) -> bool { - let cur_byte = match self.path().last() { - Some(byte) => *byte, - None => return false - }; + fn to_prev_sibling_byte(&mut self) -> Option { + let cur_byte = self.focus_byte()?; if !self.ascend_byte() { - return false + return None } let mask = self.child_mask(); match mask.prev_bit(cur_byte) { Some(byte) => { self.descend_to_byte(byte); debug_assert!(self.path_exists()); - true + Some(byte) }, None => { self.descend_to_byte(cur_byte); debug_assert!(self.path_exists()); - false + None } } } @@ -428,18 +418,251 @@ pub trait ZipperMoving: Zipper { //If we're at a leaf ascend until we're not and jump to the next sibling if self.child_count() == 0 { //We can stop ascending when we succeed in moving to a sibling - while !self.to_next_sibling_byte() { + while self.to_next_sibling_byte().is_none() { if !self.ascend_byte() { return false; } } } else { - return self.descend_first_byte() + return self.descend_first_byte().is_some() } true } } +/// Receives notifications about the movements a [Zipper] makes, so a caller can track the +/// zipper's location without the zipper needing to maintain a path buffer of its own +/// +/// Implementations decide how much of the movement to retain. [`Vec`] accumulates the +/// full path, [`usize`] records only the depth, and `()` discards everything. +pub trait PathObserver { + /// Informs the `PathObserver` that the zipper is descending `path` bytes, relative + /// to the zipper's current focus + fn descend_to(&mut self, path: &[u8]); + + /// Equivalent to `self.descend_to(&[byte])` but with slightly less overhead + fn descend_to_byte(&mut self, byte: u8) { + self.descend_to(&[byte]); + } + + /// Informs the `PathObserver` that the zipper is ascending `steps` bytes, relative + /// to the zipper's current focus + fn ascend(&mut self, steps: usize); +} + +impl PathObserver for Vec { + fn descend_to(&mut self, path: &[u8]) { + self.extend_from_slice(path); + } + fn descend_to_byte(&mut self, byte: u8) { + self.push(byte) + } + fn ascend(&mut self, steps: usize) { + self.truncate(self.len() - steps) + } +} + +/// Tracks the depth of the zipper's focus, without retaining the path itself +impl PathObserver for usize { + fn descend_to(&mut self, path: &[u8]) { + *self += path.len(); + } + fn descend_to_byte(&mut self, _byte: u8) { + *self += 1; + } + fn ascend(&mut self, steps: usize) { + *self -= steps; + } +} + +impl PathObserver for () { + fn descend_to(&mut self, _path: &[u8]) { } + fn descend_to_byte(&mut self, _byte: u8) { } + fn ascend(&mut self, _steps: usize) { } +} + +/// Captures descended bytes inline, without ever allocating +/// +/// A composite zipper can use this to see exactly which bytes one of its source zippers descended, +/// without requiring that source to maintain a path buffer of its own. +/// +/// The caller must bound each descent by the remaining capacity — typically by passing it to +/// [`descend_until_max_bytes`](ZipperMoving::descend_until_max_bytes) — because descending more +/// bytes than the buffer can hold will panic. +impl PathObserver for ArrayVec { + #[inline] + fn descend_to(&mut self, path: &[u8]) { + debug_assert!(path.len() <= self.remaining_capacity(), + "ArrayVec PathObserver overflow: bound the descent by the remaining capacity"); + self.try_extend_from_slice(path) + .expect("ArrayVec PathObserver overflow: bound the descent by the remaining capacity"); + } + #[inline] + fn descend_to_byte(&mut self, byte: u8) { + debug_assert!(self.remaining_capacity() >= 1, + "ArrayVec PathObserver overflow: bound the descent by the remaining capacity"); + self.push(byte); + } + #[inline] + fn ascend(&mut self, steps: usize) { + let new_len = self.len().saturating_sub(steps); + self.truncate(new_len); + } +} + +impl PathObserver for &mut Obs { + fn descend_to(&mut self, path: &[u8]) { (**self).descend_to(path) } + fn descend_to_byte(&mut self, byte: u8) { (**self).descend_to_byte(byte) } + fn ascend(&mut self, steps: usize) { (**self).ascend(steps) } +} + +/// Forwards every movement to both observers, so a zipper can record a movement for itself +/// while also reporting it onward to its caller +impl PathObserver for (A, B) { + fn descend_to(&mut self, path: &[u8]) { + self.0.descend_to(path); + self.1.descend_to(path); + } + fn descend_to_byte(&mut self, byte: u8) { + self.0.descend_to_byte(byte); + self.1.descend_to_byte(byte); + } + fn ascend(&mut self, steps: usize) { + self.0.ascend(steps); + self.1.ascend(steps); + } +} + +/// A [PathObserver] that replays the movements it observes onto another zipper +/// +/// This lets a composite zipper keep a second zipper in step with a movement made by the first, +/// without needing to buffer the bytes that were moved over. +/// +/// NOTE: the mirrored zipper is moved with [`descend_to`](ZipperMoving::descend_to), so it will +/// follow the movement even where that leads off the end of its own trie. +pub struct MirrorPathObserver<'z, Z>(pub &'z mut Z); + +impl PathObserver for MirrorPathObserver<'_, Z> { + #[inline] + fn descend_to(&mut self, path: &[u8]) { + self.0.descend_to(path); + } + #[inline] + fn descend_to_byte(&mut self, byte: u8) { + self.0.descend_to_byte(byte); + } + #[inline] + fn ascend(&mut self, steps: usize) { + self.0.ascend(steps); + } +} + +/// A [PathObserver] that forwards at most a fixed number of bytes onward to another observer, +/// while counting everything it sees +/// +/// This lets a caller impose a byte limit on a descent that reports more than the limit allows. +/// The bytes beyond the limit are counted but not forwarded, so the caller can ascend by +/// [`overshoot`](Self::overshoot) to bring the zipper back to the last byte that was reported. +/// +/// Nothing is buffered; bytes are passed through as they arrive. +struct TruncatingObserver<'a, Obs> { + obs: &'a mut Obs, + /// The most bytes that may be forwarded to `obs` + limit: usize, + /// The total number of bytes observed, including any beyond `limit`. The number of bytes + /// forwarded to `obs` is always `descended.min(limit)`, so it doesn't need its own field. + descended: usize, +} + +impl<'a, Obs: PathObserver> TruncatingObserver<'a, Obs> { + #[inline] + fn new(obs: &'a mut Obs, limit: usize) -> Self { + Self { obs, limit, descended: 0 } + } + /// The number of bytes forwarded to `obs` so far + #[inline] + fn forwarded(&self) -> usize { + self.descended.min(self.limit) + } + /// The number of bytes observed beyond the limit, which were not forwarded to `obs` + #[inline] + fn overshoot(&self) -> usize { + self.descended.saturating_sub(self.limit) + } +} + +impl PathObserver for TruncatingObserver<'_, Obs> { + #[inline] + fn descend_to(&mut self, path: &[u8]) { + let take = self.limit.saturating_sub(self.descended).min(path.len()); + if take > 0 { + self.obs.descend_to(&path[..take]); + } + self.descended += path.len(); + } + #[inline] + fn descend_to_byte(&mut self, byte: u8) { + if self.descended < self.limit { + self.obs.descend_to_byte(byte); + } + self.descended += 1; + } + #[inline] + fn ascend(&mut self, steps: usize) { + //Any overshoot unwinds first, because it was never forwarded. Only the portion of the + //ascent that eats into the forwarded bytes is passed onward. + let before = self.forwarded(); + self.descended = self.descended.saturating_sub(steps); + let after = self.forwarded(); + if before > after { + self.obs.ascend(before - after); + } + } +} + +/// A [PathObserver] that reduces the path it observes to a 64-bit digest +/// +/// The digest is independent of how the movements were chunked, so +`12` +`34`, +`1234`, and +/// +`1` +`23` +`4` all produce the same value. This makes it suitable for comparing two zippers +/// that visit identical paths but report them in different-sized pieces. +/// +/// Note this is invariance over *chunking*, not over byte order. So +`4321` puts different bytes +/// at every depth and therefore does not match +`1234`. +/// +/// Ascending zeroes the digest, because the bytes being ascended over aren't reported and their +/// contributions can't be removed. Two observers tracking the same movements zero together, so they +/// still agree; the digest simply stops carrying information from movements prior to the last ascend. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct HashObserver { + /// The digest of the observed path + pub hash: u64, + /// The depth of the observed path + pub depth: usize, +} + +impl PathObserver for HashObserver { + #[inline] + fn descend_to(&mut self, path: &[u8]) { + for &byte in path { + self.descend_to_byte(byte); + } + } + #[inline] + fn descend_to_byte(&mut self, byte: u8) { + //SplitMix64's finalizer, a bijection, so distinct (depth, byte) pairs contribute + //distinct values + let mut z = (((self.depth as u64) << 8) | byte as u64).wrapping_mul(0x9E3779B97F4A7C15); + z = (z ^ (z >> 30)).wrapping_mul(0xBF58476D1CE4E5B9); + self.hash ^= z ^ (z >> 27); + self.depth += 1; + } + #[inline] + fn ascend(&mut self, steps: usize) { + self.hash = 0; + self.depth = self.depth.saturating_sub(steps); + } +} + /// An interface to access values through a [Zipper] that cannot modify the trie. Allows /// references with lifetimes that may outlive the zipper /// @@ -595,7 +818,11 @@ pub trait ZipperReadOnlySubtries<'a, V: Clone + Send + Sync, A: Allocator = Glob /// An interface for advanced [Zipper] movements used for various types of iteration; such as iterating /// every value, or iterating all paths descending from a common root at a certain depth -pub trait ZipperIteration: ZipperMoving { +/// +/// NOTE: the [ZipperPath] bound will be removed when these methods are changed to take a +/// [PathObserver], as [ZipperMoving::descend_until] does. Until then, the `k_path` methods locate +/// themselves by measuring [`path`](ZipperPath::path), so a zipper must maintain one to iterate. +pub trait ZipperIteration: ZipperMoving + ZipperPath { /// Systematically advances to the next value accessible from the zipper, traversing in a depth-first /// order /// @@ -603,18 +830,18 @@ pub trait ZipperIteration: ZipperMoving { /// encountered the root. fn to_next_val(&mut self) -> bool { loop { - if self.descend_first_byte() { + if self.descend_first_byte().is_some() { if self.is_val() { return true } - if self.descend_until() { + if self.descend_until(&mut ()) { if self.is_val() { return true } } } else { 'ascending: loop { - if self.to_next_sibling_byte() { + if self.to_next_sibling_byte().is_some() { if self.is_val() { return true } @@ -640,9 +867,9 @@ pub trait ZipperIteration: ZipperMoving { /// position. fn descend_last_path(&mut self) -> bool { let mut any = false; - while self.descend_last_byte() { + while self.descend_last_byte().is_some() { any = true; - self.descend_until(); + self.descend_until(&mut ()); } any } @@ -683,28 +910,64 @@ pub trait ZipperIteration: ZipperMoving { } /// The default implementation of both [ZipperIteration::to_next_k_path] and [ZipperIteration::descend_first_k_path] +/// +/// NOTE: `base_idx` is an absolute depth, compared against the length of [`path`](ZipperPath::path) +/// to tell how far the traversal has descended. When the [ZipperIteration] methods are changed to +/// take a [PathObserver], the depth will be tracked directly and the [ZipperPath] bound will go away. #[inline] -fn k_path_default_internal(z: &mut Z, k: usize, base_idx: usize) -> bool { +fn k_path_default_internal(z: &mut Z, k: usize, base_idx: usize) -> bool { loop { if z.path().len() < base_idx + k { - while z.descend_first_byte() { + while z.descend_first_byte().is_some() { if z.path().len() == base_idx + k { return true } } } - if z.to_next_sibling_byte() { + if z.to_next_sibling_byte().is_some() { if z.path().len() == base_idx + k { return true } continue } while z.path().len() > base_idx { z.ascend_byte(); if z.path().len() == base_idx { return false } - if z.to_next_sibling_byte() { break } + if z.to_next_sibling_byte().is_some() { break } + } + } +} + +/// An interface for a [Zipper] that maintains the path from its root to its current focus +/// +/// Zippers that do not retain a contiguous path buffer of their own will not implement this +/// trait. A caller can track the path of such a zipper by supplying a [PathObserver] to the +/// movement methods that accept one. +pub trait ZipperPath: ZipperMoving { + /// Returns the path from the zipper's root to the current focus + /// + /// This method will always return a zero-length slice when [`at_root`](ZipperMoving::at_root) + /// returns `true`, and a non-zero-length slice when it returns `false`. + fn path(&self) -> &[u8]; + + /// Moves the zipper's focus to a specific location specified by `path`, relative to the zipper's root + /// + /// Returns the number of bytes shared between the old and new location + fn move_to_path>(&mut self, path: K) -> usize { + let path = path.as_ref(); + let p = self.path(); + let overlap = find_prefix_overlap(path, p); + let to_ascend = p.len() - overlap; + if overlap == 0 { // This heuristic can be fine-tuned for performance; the behavior of the two branches is equivalent + self.reset(); + self.descend_to(path); + overlap + } else { + self.ascend(to_ascend); + self.descend_to(&path[overlap..]); + overlap } } } /// An interface for a [Zipper] to support accessing the full path buffer used to create the zipper -pub trait ZipperAbsolutePath: ZipperMoving { +pub trait ZipperAbsolutePath: ZipperPath { /// Returns the entire path from the zipper's origin to its current focus /// /// The zipper's origin depends on how the zipper was created. For zippers created directly from a @@ -813,6 +1076,10 @@ macro_rules! zipper_impl_lens { fn origin_path(&$s) -> &[u8] { $e.origin_path() } fn root_prefix_path(&$s) -> &[u8] { $e.root_prefix_path() } }; + (ZipperPath $s: ident => $e:expr) => { + #[inline] fn path(&$s) -> &[u8] { $e.path() } + fn move_to_path>(&mut $s, path: K) -> usize { $e.move_to_path(path) } + }; (Zipper $s: ident => $e:expr) => { #[inline] fn path_exists(&$s) -> bool { $e.path_exists() } fn is_val(&$s) -> bool { $e.is_val() } @@ -834,8 +1101,8 @@ macro_rules! zipper_impl_lens { }; (ZipperMoving $s: ident => $e:expr) => { fn at_root(&$s) -> bool { $e.at_root() } + #[inline] fn focus_byte(&$s) -> Option { $e.focus_byte() } fn reset(&mut $s) { $e.reset() } - #[inline] fn path(&$s) -> &[u8] { $e.path() } fn val_count(&$s) -> usize { $e.val_count() } fn descend_to>(&mut $s, k: K) { $e.descend_to(k) } fn descend_to_check>(&mut $s, k: K) -> bool { $e.descend_to_check(k) } @@ -843,16 +1110,16 @@ macro_rules! zipper_impl_lens { fn descend_to_val>(&mut $s, k: K) -> usize { $e.descend_to_val(k) } fn descend_to_byte(&mut $s, k: u8) { $e.descend_to_byte(k) } fn descend_to_existing_byte(&mut $s, k: u8) -> bool { $e.descend_to_existing_byte(k) } - fn descend_indexed_byte(&mut $s, child_idx: usize) -> bool { $e.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut $s) -> bool { $e.descend_first_byte() } - fn descend_until(&mut $s) -> bool { $e.descend_until() } - fn descend_until_max_bytes(&mut $s, max_bytes: usize) -> bool { $e.descend_until_max_bytes(max_bytes) } - fn to_next_sibling_byte(&mut $s) -> bool { $e.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut $s) -> bool { $e.to_prev_sibling_byte() } - fn ascend(&mut $s, steps: usize) -> bool { $e.ascend(steps) } + fn descend_indexed_byte(&mut $s, child_idx: usize) -> Option { $e.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut $s) -> Option { $e.descend_first_byte() } + fn descend_until(&mut $s, obs: &mut Obs) -> bool { $e.descend_until(obs) } + fn descend_until_max_bytes(&mut $s, max_bytes: usize, obs: &mut Obs) -> bool { $e.descend_until_max_bytes(max_bytes, obs) } + fn to_next_sibling_byte(&mut $s) -> Option { $e.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut $s) -> Option { $e.to_prev_sibling_byte() } + fn ascend(&mut $s, steps: usize) -> usize { $e.ascend(steps) } fn ascend_byte(&mut $s) -> bool { $e.ascend_byte() } - fn ascend_until(&mut $s) -> bool { $e.ascend_until() } - fn ascend_until_branch(&mut $s) -> bool { $e.ascend_until_branch() } + fn ascend_until(&mut $s) -> usize { $e.ascend_until() } + fn ascend_until_branch(&mut $s) -> usize { $e.ascend_until_branch() } fn to_next_step(&mut $s) -> bool { $e.to_next_step() } }; (ZipperInfallibleSubtries $s: ident => $e:expr) => { @@ -899,6 +1166,7 @@ macro_rules! zipper_impl_lens { impl Zipper for Box { zipper_impl_lens!(Zipper self => (**self)); } impl ZipperAbsolutePath for Box { zipper_impl_lens!(ZipperAbsolutePath self => (**self)); } impl ZipperMoving for Box { zipper_impl_lens!(ZipperMoving self => (**self)); } +impl ZipperPath for Box { zipper_impl_lens!(ZipperPath self => (**self)); } impl ZipperIteration for Box { zipper_impl_lens!(ZipperIteration self => (**self)); } impl > ZipperValues for Box { zipper_impl_lens!(ZipperValues self => (**self)); } impl > ZipperForking for Box { type ReadZipperT<'a> = Z::ReadZipperT<'a> where Self: 'a; zipper_impl_lens!(ZipperForking self => (**self)); } @@ -916,6 +1184,7 @@ impl<'a, V: Clone + Send + Sync, Z, A: Allocator> ZipperReadOnlyPriv<'a, V, A> f impl Zipper for &mut Z { zipper_impl_lens!(Zipper self => (**self)); } impl ZipperAbsolutePath for &mut Z { zipper_impl_lens!(ZipperAbsolutePath self => (**self)); } impl ZipperMoving for &mut Z { zipper_impl_lens!(ZipperMoving self => (**self)); } +impl ZipperPath for &mut Z { zipper_impl_lens!(ZipperPath self => (**self)); } impl ZipperIteration for &mut Z { zipper_impl_lens!(ZipperIteration self => (**self)); } impl > ZipperValues for &mut Z { zipper_impl_lens!(ZipperValues self => (**self)); } impl > ZipperForking for &mut Z { type ReadZipperT<'a> = Z::ReadZipperT<'a> where Self: 'a; zipper_impl_lens!(ZipperForking self => (**self)); } @@ -973,6 +1242,7 @@ impl ZipperValues for ReadZippe impl ZipperSubtries for ReadZipperTracked<'_, '_, V, A> { zipper_impl_lens!(ZipperSubtries self => self.z); } impl ZipperInfallibleSubtries for ReadZipperTracked<'_, '_, V, A> { zipper_impl_lens!(ZipperInfallibleSubtries self => self.z); } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ReadZipperTracked<'trie, '_, V, A> { zipper_impl_lens!(ZipperMoving self => self.z); } +impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ReadZipperTracked<'trie, '_, V, A> { zipper_impl_lens!(ZipperPath self => self.z); } impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyConditionalValues<'a, V> for ReadZipperTracked<'a, '_, V, A> { type WitnessT = ReadZipperWitness; zipper_impl_lens!(ZipperReadOnlyConditionalValues self => self.z); } impl ZipperConcrete for ReadZipperTracked<'_, '_, V, A> { zipper_impl_lens!(ZipperConcrete self => self.z); } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPathBuffer for ReadZipperTracked<'trie, '_, V, A> { zipper_impl_lens!(ZipperPathBuffer self => self.z); } @@ -1065,6 +1335,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper impl ZipperSubtries for ReadZipperUntracked<'_, '_, V, A> { zipper_impl_lens!(ZipperSubtries self => self.z); } impl ZipperInfallibleSubtries for ReadZipperUntracked<'_, '_, V, A> { zipper_impl_lens!(ZipperInfallibleSubtries self => self.z); } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ReadZipperUntracked<'trie, '_, V, A> { zipper_impl_lens!(ZipperMoving self => self.z); } +impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ReadZipperUntracked<'trie, '_, V, A> { zipper_impl_lens!(ZipperPath self => self.z); } impl ZipperConcrete for ReadZipperUntracked<'_, '_, V, A> { zipper_impl_lens!(ZipperConcrete self => self.z); } impl ZipperForking for ReadZipperUntracked<'_, '_, V, A> { @@ -1223,6 +1494,7 @@ impl Zipper for ReadZipperOwned ZipperSubtries for ReadZipperOwned { zipper_impl_lens!(ZipperSubtries self => self.z); } impl ZipperInfallibleSubtries for ReadZipperOwned { zipper_impl_lens!(ZipperInfallibleSubtries self => self.z); } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ReadZipperOwned { zipper_impl_lens!(ZipperMoving self => self.z); } +impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ReadZipperOwned { zipper_impl_lens!(ZipperPath self => self.z); } impl ZipperConcrete for ReadZipperOwned { zipper_impl_lens!(ZipperConcrete self => self.z); } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPathBuffer for ReadZipperOwned { zipper_impl_lens!(ZipperPathBuffer self => self.z); } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperIteration for ReadZipperOwned { zipper_impl_lens!(ZipperIteration self => self.z); } @@ -1557,6 +1829,11 @@ pub(crate) mod read_zipper_core { self.prefix_buf.len() <= self.origin_path.len() } + #[inline] + fn focus_byte(&self) -> Option { + self.prefix_buf.last().cloned() + } + fn reset(&mut self) { self.ancestors.truncate(1); match self.ancestors.pop() { @@ -1569,15 +1846,6 @@ pub(crate) mod read_zipper_core { self.prefix_buf.truncate(self.origin_path.len()); } - #[inline] - fn path(&self) -> &[u8] { - if self.prefix_buf.len() > 0 { - &self.prefix_buf[self.origin_path.len()..] - } else { - &[] - } - } - fn val_count(&self) -> usize { let root_val = self.is_val() as usize; if self.node_key().len() == 0 { @@ -1657,7 +1925,7 @@ pub(crate) mod read_zipper_core { } } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.prepare_buffers(); debug_assert!(self.is_regularized()); @@ -1667,18 +1935,18 @@ pub(crate) mod read_zipper_core { self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; - true + Some(prefix) }, (Some(prefix), None) => { self.prefix_buf.push(prefix); self.focus_iter_token = NODE_ITER_INVALID; - true + Some(prefix) }, - (None, _) => false + (None, _) => None } } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.prepare_buffers(); debug_assert!(self.is_regularized()); let cur_tok = self.focus_node.iter_token_for_path(self.node_key()); @@ -1695,10 +1963,11 @@ pub(crate) mod read_zipper_core { // actually continues the path we're on. if byte_idx >= key_bytes.len() || !starts_with(key_bytes, node_key) { debug_assert!(self.is_regularized()); - return false; //We can't go any deeper down this path + return None; //We can't go any deeper down this path } self.focus_iter_token = new_tok; - self.prefix_buf.push(key_bytes[byte_idx]); + let descended_byte = key_bytes[byte_idx]; + self.prefix_buf.push(descended_byte); if key_bytes.len() == byte_idx+1 { match child_node { @@ -1711,20 +1980,20 @@ pub(crate) mod read_zipper_core { } } debug_assert!(self.is_regularized()); - true + Some(descended_byte) } else { self.focus_iter_token = new_tok; debug_assert!(self.is_regularized()); - false + None } } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { debug_assert!(self.is_regularized()); let mut moved = false; while self.child_count() == 1 { moved = true; - self.descend_first(); + self.descend_first(obs); if self.is_val_internal() { break; } @@ -1732,7 +2001,7 @@ pub(crate) mod read_zipper_core { moved } - fn descend_until_max_bytes(&mut self, max_bytes: usize) -> bool { + fn descend_until_max_bytes(&mut self, max_bytes: usize, obs: &mut Obs) -> bool { if max_bytes == 0 { return false; } @@ -1760,6 +2029,7 @@ pub(crate) mod read_zipper_core { let take = remaining.min(prefix.len()); moved = true; self.prefix_buf.extend(&prefix[..take]); + obs.descend_to(&prefix[..take]); remaining -= take; if take < prefix.len() { @@ -1844,10 +2114,10 @@ pub(crate) mod read_zipper_core { // self.focus_node.node_first_val_depth_along_key(); // } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.prepare_buffers(); if self.prefix_buf.len() == 0 { - return false + return None } debug_assert!(self.is_regularized()); self.deregularize(); @@ -1858,7 +2128,7 @@ pub(crate) mod read_zipper_core { if self.focus_iter_token == NODE_ITER_FINISHED { self.regularize(); - return false + return None } let (mut new_tok, mut key_bytes, mut child_node, mut _value) = self.focus_node.next_items(self.focus_iter_token); @@ -1867,16 +2137,17 @@ pub(crate) mod read_zipper_core { let node_key = self.node_key(); if node_key.len() == 0 { self.focus_iter_token = NODE_ITER_INVALID; - return false; + return None; } let fixed_len = node_key.len() - 1; if fixed_len >= key_bytes.len() || key_bytes[..fixed_len] != node_key[..fixed_len] { self.regularize(); - return false; + return None; } if key_bytes[fixed_len] > node_key[fixed_len] { - *self.prefix_buf.last_mut().unwrap() = key_bytes[node_key.len()-1]; + let byte = key_bytes[node_key.len()-1]; + *self.prefix_buf.last_mut().unwrap() = byte; self.focus_iter_token = new_tok; //If this operation landed us at the end of the path within the node, then we @@ -1893,7 +2164,7 @@ pub(crate) mod read_zipper_core { } debug_assert!(self.is_regularized()); - return true + return Some(byte) } (new_tok, key_bytes, child_node, _value) = self.focus_node.next_items(new_tok); @@ -1901,16 +2172,17 @@ pub(crate) mod read_zipper_core { self.focus_iter_token = NODE_ITER_FINISHED; self.regularize(); - false + None } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } - fn ascend(&mut self, mut steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> usize { + let mut remaining = steps; debug_assert!(self.is_regularized()); - while steps > 0 { + while remaining > 0 { if self.excess_key_len() == 0 { match self.ancestors.pop() { Some((node, iter_tok, _prefix_offset)) => { @@ -1919,16 +2191,16 @@ pub(crate) mod read_zipper_core { }, None => { debug_assert!(self.is_regularized()); - return false + return steps - remaining; } }; } - let cur_jump = steps.min(self.excess_key_len()); + let cur_jump = remaining.min(self.excess_key_len()); self.prefix_buf.truncate(self.prefix_buf.len() - cur_jump); - steps -= cur_jump; + remaining -= cur_jump; } debug_assert!(self.is_regularized()); - true + steps } fn ascend_byte(&mut self) -> bool { @@ -1950,39 +2222,52 @@ pub(crate) mod read_zipper_core { true } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { debug_assert!(self.is_regularized()); if self.at_root() { - return false; + return 0; } + let mut ascended = 0; loop { if self.node_key().len() == 0 { self.ascend_across_nodes(); } - self.ascend_within_node(); + ascended += self.ascend_within_node(); if self.child_count() > 1 || self.is_val() || self.at_root() { - return true; + return ascended; } } } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { debug_assert!(self.is_regularized()); if self.at_root() { - return false; + return 0; } + let mut ascended = 0; loop { if self.node_key().len() == 0 { self.ascend_across_nodes(); } - self.ascend_within_node(); + ascended += self.ascend_within_node(); if self.child_count() > 1 || self.at_root() { - return true; + return ascended; } } } } + impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ReadZipperCore<'trie, '_, V, A> { + #[inline] + fn path(&self) -> &[u8] { + if self.prefix_buf.len() > 0 { + &self.prefix_buf[self.origin_path.len()..] + } else { + &[] + } + } + } + impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPathBuffer for ReadZipperCore<'trie, '_, V, A> { unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { if self.prefix_buf.capacity() > 0 { @@ -2493,7 +2778,7 @@ pub(crate) mod read_zipper_core { /// performs about as well as the `to_next_sibling_byte` that is there, but doesn't /// update the zipper's iter tokens #[inline] - fn to_sibling(&mut self, next: bool) -> bool { + fn to_sibling(&mut self, next: bool) -> Option { self.prepare_buffers(); debug_assert!(self.is_regularized()); if self.node_key().len() != 0 { @@ -2503,33 +2788,33 @@ pub(crate) mod read_zipper_core { self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; - true + Some(prefix) }, (Some(prefix), None) => { *self.prefix_buf.last_mut().unwrap() = prefix; - true + Some(prefix) }, - (None, _) => false + (None, _) => None } } else { let mut should_pop = false; let result = match self.ancestors.last() { - None => { false } + None => { None } Some((parent, _iter_tok, _prefix_offset)) => { match parent.get_sibling_of_child(self.parent_key(), next) { (Some(prefix), Some(child_node)) => { *self.prefix_buf.last_mut().unwrap() = prefix; *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; - true + Some(prefix) }, (Some(prefix), None) => { *self.prefix_buf.last_mut().unwrap() = prefix; should_pop = true; - true + Some(prefix) }, (None, _) => { - false + None } } } @@ -2689,24 +2974,26 @@ pub(crate) mod read_zipper_core { /// Internal method implementing part of [Self::descend_until], but doesn't pay attention to to [Self::child_count] #[inline] - fn descend_first(&mut self) { + fn descend_first(&mut self, obs: &mut Obs) { self.prepare_buffers(); match self.focus_node.first_child_from_key(self.node_key()) { (Some(prefix), Some(child_node)) => { //Step to a new node self.prefix_buf.extend(prefix); + obs.descend_to(prefix); self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; //If we're at the root of the new node, descend to the first child if prefix.len() == 0 { - self.descend_first() + self.descend_first(obs) } }, (Some(prefix), None) => { //Stay within the same node self.prefix_buf.extend(prefix); + obs.descend_to(prefix); }, (None, _) => unreachable!() } @@ -2794,10 +3081,12 @@ pub(crate) mod read_zipper_core { } /// Internal method used to impement `ascend_until` when ascending within a node #[inline] - fn ascend_within_node(&mut self) { + fn ascend_within_node(&mut self) -> usize { let branch_key = self.focus_node.prior_branch_key(self.node_key()); let new_len = self.origin_path.len().max(self.node_key_start() + branch_key.len()); + let old_len = self.prefix_buf.len(); self.prefix_buf.truncate(new_len); + old_len - new_len } /// Push a new node-path pair onto the zipper. This is used in the internal implementation of /// the [crate::zipper::ProductZipper] @@ -3227,7 +3516,7 @@ pub(crate) mod zipper_moving_tests { /// from https://en.wikipedia.org/wiki/Radix_tree#/media/File:Patricia_trie.svg pub const ZIPPER_MOVING_BASIC_TEST_KEYS: &[&[u8]] = &[b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; - pub fn zipper_moving_basic_test(mut zipper: Z) { + pub fn zipper_moving_basic_test(mut zipper: Z) { fn assert_in_list(val: &[u8], list: &[&[u8]]) { for test_val in list { if *test_val == val { @@ -3240,32 +3529,32 @@ pub(crate) mod zipper_moving_tests { zipper.descend_to(&[b'r']); zipper.descend_to(&[b'o']); zipper.descend_to(&[b'm']); // focus = rom zipper.descend_to(&[b'\'']); assert!(zipper.path_exists()); // focus = rom' (' is the lowest byte) - assert!(zipper.to_next_sibling_byte()); // focus = roma (a is the second byte), but we can't actually guarantee whether we land on 'a' or 'u' + assert!(zipper.to_next_sibling_byte().is_some()); // focus = roma (a is the second byte), but we can't actually guarantee whether we land on 'a' or 'u' assert_in_list(zipper.path(), &[b"roma", b"romu"]); assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'n']); // both follow-ups romane and romanus have n following a - assert!(zipper.to_next_sibling_byte()); // focus = romu (u is the third byte) + assert!(zipper.to_next_sibling_byte().is_some()); // focus = romu (u is the third byte) assert_in_list(zipper.path(), &[b"roma", b"romu"]); assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'l']); // and romu is followed by lus - assert!(!zipper.to_next_sibling_byte()); // fails because there were only 3 children ['\'', 'a', 'u'] - assert!(zipper.to_prev_sibling_byte()); // focus = roma or romu (we stepped back) + assert_eq!(zipper.to_next_sibling_byte(), None); // fails because there were only 3 children ['\'', 'a', 'u'] + assert!(zipper.to_prev_sibling_byte().is_some()); // focus = roma or romu (we stepped back) assert_in_list(zipper.path(), &[b"roma", b"romu"]); - assert!(zipper.to_prev_sibling_byte()); // focus = rom' (we stepped back to where we began) + assert_eq!(zipper.to_prev_sibling_byte(), Some(39)); // focus = rom' (we stepped back to where we began) assert_eq!(zipper.path(), b"rom'"); assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'i']); - assert!(zipper.ascend(1)); // focus = rom + assert_eq!(zipper.ascend(1), 1); // focus = rom assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'\'', b'a', b'u']); // all three options we visited - assert!(zipper.descend_indexed_byte(0)); // focus = rom' + assert_eq!(zipper.descend_indexed_byte(0), Some(39)); // focus = rom' assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'i']); - assert!(zipper.ascend(1)); // focus = rom - assert!(zipper.descend_indexed_byte(1)); // focus = roma + assert_eq!(zipper.ascend(1), 1); // focus = rom + assert_eq!(zipper.descend_indexed_byte(1), Some(b'a')); // focus = roma assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'n']); - assert!(zipper.ascend(1)); - assert!(zipper.descend_indexed_byte(2)); // focus = romu + assert_eq!(zipper.ascend(1), 1); + assert_eq!(zipper.descend_indexed_byte(2), Some(b'u')); // focus = romu assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'l']); - assert!(zipper.ascend(1)); - assert!(zipper.descend_indexed_byte(1)); // focus = roma + assert_eq!(zipper.ascend(1), 1); + assert_eq!(zipper.descend_indexed_byte(1), Some(b'a')); // focus = roma assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'n']); - assert!(zipper.ascend(1)); + assert_eq!(zipper.ascend(1), 1); // ' < a < u // 39 105 117 } @@ -3274,7 +3563,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_WITH_ROOT_PATH_PATH: &[u8] = b"ro"; /// Tests creating a zipper at a specific key within a map - pub fn zipper_with_root_path(mut zipper: Z) { + pub fn zipper_with_root_path(mut zipper: Z) { //Test `descend_to` and `ascend_until` assert_eq!(zipper.path(), b""); @@ -3288,34 +3577,35 @@ pub(crate) mod zipper_moving_tests { zipper.descend_to(b"e"); assert_eq!(zipper.path(), b"mane"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend_until(), true); + assert_eq!(zipper.ascend_until(), 1); zipper.descend_to(b"us"); assert_eq!(zipper.path(), b"manus"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend_until(), true); + assert_eq!(zipper.ascend_until(), 2); assert_eq!(zipper.path(), b"man"); assert_eq!(zipper.child_count(), 2); - assert_eq!(zipper.ascend_until(), true); + assert_eq!(zipper.ascend_until(), 2); assert_eq!(zipper.path(), b"m"); assert_eq!(zipper.child_count(), 3); - assert_eq!(zipper.ascend_until(), true); + assert_eq!(zipper.ascend_until(), 1); assert_eq!(zipper.path(), b""); assert_eq!(zipper.child_count(), 1); assert_eq!(zipper.at_root(), true); - assert_eq!(zipper.ascend_until(), false); + assert_eq!(zipper.ascend_until(), 0); //Test `ascend` zipper.descend_to(b"manus"); assert_eq!(zipper.path(), b"manus"); - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), 1); assert_eq!(zipper.path(), b"manu"); - assert_eq!(zipper.ascend(5), false); + assert_eq!(zipper.ascend(5), 4); assert_eq!(zipper.path(), b""); assert_eq!(zipper.at_root(), true); zipper.descend_to(b"mane"); assert_eq!(zipper.path(), b"mane"); - assert_eq!(zipper.ascend(3), true); + assert_eq!(zipper.ascend(3), 3); assert_eq!(zipper.path(), b"m"); + eprintln!("child_mask = {:?}", zipper.child_mask()); assert_eq!(zipper.child_count(), 3); } @@ -3324,7 +3614,7 @@ pub(crate) mod zipper_moving_tests { /// `descend_first_byte` must agree with `descend_indexed_byte(0)`, including when the focus /// sits on a non-existent (dangling) path, where there are no children to descend into - pub fn zipper_dangling_descend_test(mut zip: Z) { + pub fn zipper_dangling_descend_test(mut zip: Z) { //Descend to a path that doesn't exist in the trie. `b"bq"` exists as a prefix of // `b"bqqq"`, but `b"bb"` does not. zip.descend_to(b"bb"); @@ -3332,33 +3622,33 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zip.child_count(), 0); //With no children, `descend_indexed_byte(0)` is out of range and must not move - assert_eq!(zip.descend_indexed_byte(0), false); + assert_eq!(zip.descend_indexed_byte(0), None); assert_eq!(zip.path(), b"bb"); //`descend_first_byte` is documented to behave identically to `descend_indexed_byte(0)` - assert_eq!(zip.descend_first_byte(), false); + assert_eq!(zip.descend_first_byte(), None); assert_eq!(zip.path(), b"bb"); } pub const ZIPPER_INDEXED_BYTE_TEST1_KEYS: &[&[u8]] = &[b"0", b"1", b"2", b"3", b"4", b"5", b"6"]; - pub fn zipper_indexed_bytes_test1(mut zip: Z) { + pub fn zipper_indexed_bytes_test1(mut zip: Z) { zip.descend_to("2"); assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 0); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.path(), b"2"); zip.reset(); - assert!(zip.descend_indexed_byte(2)); + assert_eq!(zip.descend_indexed_byte(2), Some(b'2')); assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 0); assert_eq!(zip.path(), b"2"); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.path(), b"2"); zip.reset(); - assert!(!zip.descend_indexed_byte(7)); + assert_eq!(zip.descend_indexed_byte(7), None); assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 7); assert_eq!(zip.path(), b""); @@ -3372,26 +3662,26 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zip.val(), Some(&())); assert_eq!(zip.path(), b"000"); assert_eq!(zip.child_count(), 0); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.path(), b"000"); zip.reset(); - assert!(!zip.descend_indexed_byte(2)); + assert_eq!(zip.descend_indexed_byte(2), None); assert_eq!(zip.child_count(), 2); - assert!(zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), Some(b'1')); assert_eq!(zip.path(), b"1"); assert_eq!(zip.val(), None); assert_eq!(zip.child_count(), 1); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.val(), None); assert_eq!(zip.path(), b"1"); zip.reset(); - assert!(zip.descend_indexed_byte(0)); + assert_eq!(zip.descend_indexed_byte(0), Some(b'0')); assert_eq!(zip.path(), b"0"); assert_eq!(zip.val(), None); assert_eq!(zip.child_count(), 1); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.val(), None); assert_eq!(zip.path(), b"0"); } @@ -3399,31 +3689,31 @@ pub(crate) mod zipper_moving_tests { // A narrow deeper trie pub const ZIPPER_INDEXED_BYTE_TEST2_KEYS: &[&[u8]] = &[b"000", b"1Z", b"00AAA", b"00AA000", b"00AA00AAA"]; - pub fn zipper_indexed_bytes_test2(mut zip: Z) { + pub fn zipper_indexed_bytes_test2(mut zip: Z) { zip.descend_to("000"); assert_eq!(zip.is_val(), true); assert_eq!(zip.path(), b"000"); assert_eq!(zip.child_count(), 0); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.path(), b"000"); zip.reset(); - assert!(!zip.descend_indexed_byte(2)); + assert_eq!(zip.descend_indexed_byte(2), None); assert_eq!(zip.child_count(), 2); - assert!(zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), Some(b'1')); assert_eq!(zip.path(), b"1"); assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 1); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.is_val(), false); assert_eq!(zip.path(), b"1"); zip.reset(); - assert!(zip.descend_indexed_byte(0)); + assert_eq!(zip.descend_indexed_byte(0), Some(b'0')); assert_eq!(zip.path(), b"0"); assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 1); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.is_val(), false); assert_eq!(zip.path(), b"0"); } @@ -3431,9 +3721,9 @@ pub(crate) mod zipper_moving_tests { // Tests how descend_until treats values along paths pub const ZIPPER_DESCEND_UNTIL_TEST1_KEYS: &[&[u8]] = &[b"a", b"ab", b"abCDEf", b"abCDEfGHi"]; - pub fn zipper_descend_until_test1(mut zip: Z) { + pub fn zipper_descend_until_test1(mut zip: Z) { for key in ZIPPER_DESCEND_UNTIL_TEST1_KEYS { - assert!(zip.descend_until()); + assert!(zip.descend_until(&mut ())); assert_eq!(zip.path(), *key); } } @@ -3441,93 +3731,93 @@ pub(crate) mod zipper_moving_tests { // Tests how descend_until_max_bytes enforces a max descent length pub const ZIPPER_DESCEND_UNTIL_MAX_BYTES_TEST1_KEYS: &[&[u8]] = &[b"a0abcdef", b"a0abcxy", b"a1mnopqr"]; - pub fn zipper_descend_until_max_bytes_test1(mut zip: Z) { + pub fn zipper_descend_until_max_bytes_test1(mut zip: Z) { zip.descend_to(b"a0"); assert_eq!(zip.path(), b"a0"); - assert!(zip.descend_until_max_bytes(2)); + assert!(zip.descend_until_max_bytes(2, &mut ())); assert_eq!(zip.path(), b"a0ab"); zip.reset(); zip.descend_to(b"a1"); assert_eq!(zip.path(), b"a1"); - assert!(zip.descend_until_max_bytes(3)); + assert!(zip.descend_until_max_bytes(3, &mut ())); assert_eq!(zip.path(), b"a1mno"); zip.reset(); zip.descend_to(b"a0"); assert_eq!(zip.path(), b"a0"); - assert!(zip.descend_until_max_bytes(10)); + assert!(zip.descend_until_max_bytes(10, &mut ())); assert_eq!(zip.path(), b"a0abc"); zip.reset(); zip.descend_to(b"a0"); assert_eq!(zip.path(), b"a0"); - assert!(!zip.descend_until_max_bytes(0)); + assert!(!zip.descend_until_max_bytes(0, &mut ())); assert_eq!(zip.path(), b"a0"); } // Test a 3-way branch, so we definitely don't have a pair node pub const ZIPPER_ASCEND_UNTIL_TEST1_KEYS: &[&[u8]] = &[b"AAa", b"AAb", b"AAc"]; - pub fn zipper_ascend_until_test1(mut zip: Z) { + pub fn zipper_ascend_until_test1(mut zip: Z) { zip.descend_to(b"AAaDDd"); assert!(!zip.path_exists()); assert_eq!(zip.path(), b"AAaDDd"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 3); assert_eq!(zip.path(), b"AAa"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"AA"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 2); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), 0); } // Test what's likely to be represented as a pair node pub const ZIPPER_ASCEND_UNTIL_TEST2_KEYS: &[&[u8]] = &[b"AAa", b"AAb"]; - pub fn zipper_ascend_until_test2(mut zip: Z) { + pub fn zipper_ascend_until_test2(mut zip: Z) { zip.descend_to(b"AAaDDd"); assert!(!zip.path_exists()); assert_eq!(zip.path(), b"AAaDDd"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 3); assert_eq!(zip.path(), b"AAa"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"AA"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 2); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), 0); } /// Test a straight-line trie pub const ZIPPER_ASCEND_UNTIL_TEST3_KEYS: &[&[u8]] = &[b"1", b"12", b"123", b"1234", b"12345"]; - pub fn zipper_ascend_until_test3(mut zip: Z) { + pub fn zipper_ascend_until_test3(mut zip: Z) { //First test that ascend_until stops when transitioning from non-existent path zip.descend_to(b"123456"); assert_eq!(zip.path_exists(), false); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"12345"); //Test that ascend_until stops at each value - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"1234"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"123"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"12"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"1"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), 0); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values zip.descend_to(b"12345"); assert!(zip.path_exists()); assert_eq!(zip.path(), b"12345"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 5); assert_eq!(zip.path(), b""); assert!(zip.at_root()); @@ -3540,34 +3830,34 @@ pub(crate) mod zipper_moving_tests { zip.descend_to(b"12345"); assert!(zip.path_exists()); assert_eq!(zip.path(), b"12345"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"1234"); // "1234" is a branch only assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 2); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"123"); // "123" is a value only assert_eq!(zip.child_count(), 1); assert_eq!(zip.is_val(), true); - assert!(zip.ascend_until()); // Jump over "12" because it's neither a branch nor a value + assert_eq!(zip.ascend_until(), 2); // Jump over "12" because it's neither a branch nor a value assert_eq!(zip.path(), b"1"); // "1" is both a branch and a value assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 2); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b""); assert_eq!(zip.child_count(), 1); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), 0); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values zip.descend_to(b"12345"); assert!(zip.path_exists()); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 1); assert_eq!(zip.path(), b"1234"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 3); assert_eq!(zip.path(), b"1"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 1); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 0); assert!(zip.at_root()); } @@ -3575,72 +3865,73 @@ pub(crate) mod zipper_moving_tests { /// Some paths encountered will be values only, some will be branches only, and some will be both pub const ZIPPER_ASCEND_UNTIL_TEST4_KEYS: &[&[u8]] = &[b"1", b"123", b"12345", b"1abc", b"1234abc"]; - pub fn zipper_ascend_until_test4(mut zip: Z) { + pub fn zipper_ascend_until_test4(mut zip: Z) { zip.descend_to(b"12345"); assert!(zip.path_exists()); assert_eq!(zip.path(), b"12345"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"1234"); // "1234" is a branch only assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 2); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"123"); // "123" is a value only assert_eq!(zip.child_count(), 1); assert_eq!(zip.is_val(), true); - assert!(zip.ascend_until()); // Jump over "12" because it's neither a branch nor a value + assert_eq!(zip.ascend_until(), 2); // Jump over "12" because it's neither a branch nor a value assert_eq!(zip.path(), b"1"); // "1" is both a branch and a value assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 2); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b""); assert_eq!(zip.child_count(), 1); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), 0); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values zip.descend_to(b"12345"); assert!(zip.path_exists()); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 1); assert_eq!(zip.path(), b"1234"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 3); assert_eq!(zip.path(), b"1"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 1); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), 0); assert!(zip.at_root()); } /// Test ascending over a long key that spans multiple nodes pub const ZIPPER_ASCEND_UNTIL_TEST5_KEYS: &[&[u8]] = &[b"A", b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"]; - pub fn zipper_ascend_until_test5(mut zip: Z) { + pub fn zipper_ascend_until_test5(mut zip: Z) { //Test that ascend_until stops when transitioning from non-existent path zip.descend_to(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB"); assert_eq!(zip.path_exists(), false); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); //Test that jump all the way back to where we want to be - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 126); assert_eq!(zip.path(), b"A"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b""); - assert_eq!(zip.ascend_until(), false); + assert_eq!(zip.ascend_until(), 0); } pub const ZIPPER_INDEXED_MOVEMENT_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; - pub fn indexed_zipper_movement1(mut zipper: Z) { - //descends a single specific byte using `descend_indexed_byte`. Just for testing. A real user would use `descend_towards` - fn descend_byte(zipper: &mut Z, byte: u8) { + pub fn indexed_zipper_movement1(mut zipper: Z) { + //descends a single specific byte using `descend_indexed_byte`. Just for testing. A real user would use `descend_to` or `descend_to_byte` + fn descend_byte(zipper: &mut Z, byte: u8) { for i in 0..zipper.child_count() { - assert_eq!(zipper.descend_indexed_byte(i), true); + let descended = zipper.descend_indexed_byte(i); + assert!(descended.is_some()); if *zipper.path().last().unwrap() == byte { break } else { - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), 1); } } } @@ -3650,24 +3941,24 @@ pub(crate) mod zipper_moving_tests { descend_byte(&mut zipper, b'r'); assert_eq!(zipper.path(), b"r"); assert_eq!(zipper.child_count(), 2); - assert_eq!(zipper.descend_until(), false); + assert_eq!(zipper.descend_until(&mut ()), false); descend_byte(&mut zipper, b'o'); assert_eq!(zipper.path(), b"ro"); assert_eq!(zipper.child_count(), 1); - assert_eq!(zipper.descend_until(), true); + assert_eq!(zipper.descend_until(&mut ()), true); assert_eq!(zipper.path(), b"rom"); assert_eq!(zipper.child_count(), 3); zipper.reset(); - assert_eq!(zipper.descend_until(), false); + assert_eq!(zipper.descend_until(&mut ()), false); descend_byte(&mut zipper, b'a'); assert_eq!(zipper.path(), b"a"); assert_eq!(zipper.child_count(), 1); - assert_eq!(zipper.descend_until(), true); + assert_eq!(zipper.descend_until(&mut ()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend(3), true); + assert_eq!(zipper.ascend(3), 3); assert_eq!(zipper.path(), b"ar"); assert_eq!(zipper.child_count(), 1); } @@ -3688,10 +3979,10 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.is_val(), true); zipper.descend_to(b"e"); assert_eq!(zipper.is_val(), true); - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), 1); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); - zipper.descend_until(); + zipper.descend_until(&mut ()); assert_eq!(zipper.is_val(), true); } @@ -3741,7 +4032,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_DESCEND_TO_EXISTING_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"roman", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; - pub fn descend_to_existing_test1(mut zipper: Z) { + pub fn descend_to_existing_test1(mut zipper: Z) { assert_eq!(3, zipper.descend_to_existing("bowling")); assert_eq!("bow".as_bytes(), zipper.path()); @@ -3759,7 +4050,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_DESCEND_TO_EXISTING_TEST2_KEYS: &[&[u8]] = &[b"arrow"]; /// Tests a really long path that doesn't exist, to exercise the chunk-descending code - pub fn descend_to_existing_test2(mut zipper: Z) { + pub fn descend_to_existing_test2(mut zipper: Z) { assert_eq!(5, zipper.descend_to_existing("arrow0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); assert_eq!(zipper.path(), &b"arrow"[..]); @@ -3772,7 +4063,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_DESCEND_TO_EXISTING_TEST3_KEYS: &[&[u8]] = &[b"arrow"]; /// Tests calling the method when the focus is already on a non-existent path - pub fn descend_to_existing_test3(mut zipper: Z) { + pub fn descend_to_existing_test3(mut zipper: Z) { zipper.descend_to("arrow00000"); assert_eq!(false, zipper.path_exists()); @@ -3784,7 +4075,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_TO_NEXT_STEP_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"roman", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; - pub fn to_next_step_test1(mut zipper: Z) { + pub fn to_next_step_test1(mut zipper: Z) { let mut i = 0; while zipper.to_next_step() { match i { @@ -3816,45 +4107,45 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_BYTES_ITER_TEST1_KEYS: &[&[u8]] = &[b"ABCDEFGHIJKLMNOPQRSTUVWXYZ", b"ab",]; - pub fn zipper_byte_iter_test1(mut zipper: Z) { + pub fn zipper_byte_iter_test1(mut zipper: Z) { zipper.descend_to_byte(b'A'); assert_eq!(zipper.path_exists(), true); - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(b'B')); assert_eq!(zipper.path(), b"AB"); - assert_eq!(zipper.to_next_sibling_byte(), false); + assert_eq!(zipper.to_next_sibling_byte(), None); assert_eq!(zipper.path(), b"AB"); } pub const ZIPPER_BYTES_ITER_TEST2_KEYS: &[&[u8]] = &[&[2, 194, 1, 1, 193, 5], &[3, 194, 1, 0, 193, 6, 193, 5], &[3, 193, 4, 193]]; pub const ZIPPER_BYTES_ITER_TEST2_PATH: &[u8] = &[2, 194]; - pub fn zipper_byte_iter_test2(mut zipper: Z) { - assert_eq!(zipper.descend_first_byte(), true); + pub fn zipper_byte_iter_test2(mut zipper: Z) { + assert_eq!(zipper.descend_first_byte(), Some(1)); assert_eq!(zipper.path(), &[1]); - assert_eq!(zipper.to_next_sibling_byte(), false); + assert_eq!(zipper.to_next_sibling_byte(), None); assert_eq!(zipper.path(), &[1]); } pub const ZIPPER_BYTES_ITER_TEST3_KEYS: &[&[u8]] = &[&[3, 193, 4, 193, 5, 2, 193, 6, 193, 7], &[3, 193, 4, 193, 5, 2, 193, 6, 255]]; pub const ZIPPER_BYTES_ITER_TEST3_PATH: &[u8] = &[3, 193, 4, 193, 5, 2, 193]; - pub fn zipper_byte_iter_test3(mut zipper: Z) { + pub fn zipper_byte_iter_test3(mut zipper: Z) { assert_eq!(zipper.path(), &[]); - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(6)); assert_eq!(zipper.path(), &[6]); - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(193)); assert_eq!(zipper.path(), &[6, 193]); - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(7)); assert_eq!(zipper.path(), &[6, 193, 7]); } pub const ZIPPER_BYTES_ITER_TEST4_KEYS: &[&[u8]] = &[b"ABC", b"ABCDEF", b"ABCdef"]; - pub fn zipper_byte_iter_test4(mut zipper: Z) { + pub fn zipper_byte_iter_test4(mut zipper: Z) { //Check that we end up at the first leaf by depth-first search - while zipper.descend_first_byte() {} + while zipper.descend_first_byte().is_some() {} assert_eq!(zipper.path(), b"ABCDEF"); //Try taking a different branch @@ -3862,13 +4153,13 @@ pub(crate) mod zipper_moving_tests { zipper.descend_to(b"ABC"); assert!(zipper.path_exists()); assert_eq!(zipper.path(), b"ABC"); - assert!(zipper.descend_indexed_byte(1)); + assert_eq!(zipper.descend_indexed_byte(1), Some(b'd')); assert_eq!(zipper.path(), b"ABCd"); - assert!(zipper.descend_first_byte()); + assert_eq!(zipper.descend_first_byte(), Some(b'e')); assert_eq!(zipper.path(), b"ABCde"); - assert!(zipper.descend_first_byte()); + assert_eq!(zipper.descend_first_byte(), Some(b'f')); assert_eq!(zipper.path(), b"ABCdef"); - assert!(!zipper.descend_first_byte()); + assert_eq!(zipper.descend_first_byte(), None); } pub const ZIPPER_BYTES_ITER_TEST5_KEYS: &[&[u8]] = &[ @@ -3884,16 +4175,16 @@ pub(crate) mod zipper_moving_tests { zipper.reset(); zipper.descend_to(&keys[0][..i]); if i != 18 && i != 5 { - assert_eq!(zipper.to_next_sibling_byte(), false); + assert_eq!(zipper.to_next_sibling_byte(), None); } } zipper.reset(); zipper.descend_to([2, 197, 97, 120, 105, 111, 109, 3, 193, 61, 4, 193, 97, 192, 192, 3, 193, 75]); - assert_eq!(zipper.to_next_sibling_byte(), true); + assert_eq!(zipper.to_next_sibling_byte(), Some(84)); zipper.reset(); zipper.descend_to([2, 197, 97, 120, 105]); - assert_eq!(zipper.to_next_sibling_byte(), true); + assert_eq!(zipper.to_next_sibling_byte(), Some(255)); } pub const ZIPPER_VAL_AT_TEST_KEYS: &[&[u8]] = &[ @@ -4109,12 +4400,12 @@ pub(crate) mod zipper_iteration_tests { pub fn k_path_test1<'a, Z: ZipperIteration>(mut zipper: Z) { //This is a cheesy way to encode lengths, but it's is more readable than unprintable chars - assert!(zipper.descend_indexed_byte(0)); + assert!(zipper.descend_indexed_byte(0).is_some()); let sym_len = usize::from_str_radix(std::str::from_utf8(&[zipper.path()[0]]).unwrap(), 10).unwrap(); assert_eq!(sym_len, 5); //Step over the ':' character - assert!(zipper.descend_indexed_byte(0)); + assert!(zipper.descend_indexed_byte(0).is_some()); assert_eq!(zipper.child_count(), 6); //Start iterating over all the symbols of length=sym_len @@ -4224,7 +4515,7 @@ pub(crate) mod zipper_iteration_tests { assert!(zipper.path_exists()); assert_eq!(zipper.descend_first_k_path(1), true); assert_eq!(zipper.path(), b"1a"); - assert_eq!(zipper.descend_indexed_byte(0), true); + assert!(zipper.descend_indexed_byte(0).is_some()); assert_eq!(zipper.path(), b"1a1"); assert_eq!(zipper.descend_first_k_path(1), true); assert_eq!(zipper.path(), b"1a1A"); @@ -4234,7 +4525,7 @@ pub(crate) mod zipper_iteration_tests { assert_eq!(zipper.path(), b"1a1C"); assert_eq!(zipper.to_next_k_path(1), false); assert_eq!(zipper.path(), b"1a1"); - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), 1); assert_eq!(zipper.path(), b"1a"); assert_eq!(zipper.to_next_k_path(1), true); assert_eq!(zipper.path(), b"1b"); @@ -4378,7 +4669,7 @@ pub(crate) mod zipper_iteration_tests { zipper.descend_to(path); assert!(zipper.path_exists()); }, - |zipper, steps| assert!(zipper.ascend(steps)), + |zipper, steps| assert_eq!(zipper.ascend(steps), steps), ); //Try with a `descend_to_byte` & `ascend_byte` @@ -4400,7 +4691,7 @@ pub(crate) mod zipper_iteration_tests { test_loop(&mut zipper, |zipper, path| { for _ in 0..path.len() { - assert!(zipper.descend_first_byte()); + assert!(zipper.descend_first_byte().is_some()); } }, |zipper, steps| { @@ -4471,6 +4762,142 @@ mod tests { use crate::{alloc::global_alloc, PathMap}; use super::*; + /// Drives a [TruncatingObserver] with the supplied movements, returning what reached the + /// downstream observer along with the observer's final overshoot + fn run_truncating(limit: usize, movements: &[&[u8]]) -> (Vec, usize) { + let mut downstream = Vec::new(); + let mut trunc = TruncatingObserver::new(&mut downstream, limit); + for m in movements { + trunc.descend_to(m); + } + let overshoot = trunc.overshoot(); + (downstream, overshoot) + } + + #[test] + fn truncating_observer_forwards_within_limit() { + //Everything fits, so all of it is forwarded and nothing overshoots + let (fwd, over) = run_truncating(10, &[b"abc", b"de"]); + assert_eq!(fwd, b"abcde"); + assert_eq!(over, 0); + } + + #[test] + fn truncating_observer_stops_exactly_at_limit() { + let (fwd, over) = run_truncating(5, &[b"abc", b"de"]); + assert_eq!(fwd, b"abcde"); + assert_eq!(over, 0); + } + + #[test] + fn truncating_observer_splits_a_segment() { + //The limit falls in the middle of the second movement + let (fwd, over) = run_truncating(4, &[b"abc", b"de"]); + assert_eq!(fwd, b"abcd"); + assert_eq!(over, 1); + } + + #[test] + fn truncating_observer_drops_whole_segments_past_limit() { + let (fwd, over) = run_truncating(3, &[b"abc", b"de", b"fgh"]); + assert_eq!(fwd, b"abc"); + assert_eq!(over, 5); + } + + #[test] + fn truncating_observer_zero_limit_forwards_nothing() { + let (fwd, over) = run_truncating(0, &[b"abc"]); + assert_eq!(fwd, b""); + assert_eq!(over, 3); + } + + #[test] + fn truncating_observer_descend_to_byte_respects_limit() { + let mut downstream = Vec::new(); + let mut trunc = TruncatingObserver::new(&mut downstream, 2); + trunc.descend_to_byte(b'a'); + trunc.descend_to_byte(b'b'); + trunc.descend_to_byte(b'c'); + assert_eq!(trunc.overshoot(), 1); + assert_eq!(downstream, b"ab"); + } + + /// An ascent that only unwinds overshoot must not be forwarded, because those bytes were + /// never reported downstream in the first place + #[test] + fn truncating_observer_ascend_unwinds_overshoot_first() { + let mut downstream = Vec::new(); + let mut trunc = TruncatingObserver::new(&mut downstream, 3); + trunc.descend_to(b"abcde"); // forwards "abc", overshoots by 2 + assert_eq!(trunc.overshoot(), 2); + trunc.ascend(2); // unwinds only the overshoot + assert_eq!(trunc.overshoot(), 0); + assert_eq!(downstream, b"abc", "overshoot ascent must not reach downstream"); + } + + /// An ascent that eats past the overshoot must forward only the portion below the limit + #[test] + fn truncating_observer_ascend_straddling_the_limit() { + let mut downstream = Vec::new(); + let mut trunc = TruncatingObserver::new(&mut downstream, 3); + trunc.descend_to(b"abcde"); // forwards "abc", overshoots by 2 + trunc.ascend(4); // 2 of overshoot, then 2 of forwarded + assert_eq!(trunc.overshoot(), 0); + assert_eq!(downstream, b"a", "only the forwarded portion should be retracted"); + } + + /// With no overshoot, an ascent passes through unchanged + #[test] + fn truncating_observer_ascend_without_overshoot() { + let mut downstream = Vec::new(); + let mut trunc = TruncatingObserver::new(&mut downstream, 10); + trunc.descend_to(b"abcde"); + trunc.ascend(2); + assert_eq!(trunc.overshoot(), 0); + assert_eq!(downstream, b"abc"); + } + + /// `descend_until_max_bytes`'s default impl is used by zippers with no native override. + /// `PrefixZipper` is one, so it exercises the [TruncatingObserver] path over a real trie. + #[test] + fn default_descend_until_max_bytes_respects_limit() { + use crate::prefix_zipper::PrefixZipper; + + //A single long path, so `descend_until` would run well past any small limit + let key: Vec = (0..40u32).map(|i| b'a' + (i % 26) as u8).collect(); + let map = PathMap::from_iter([(key.as_slice(), ())]); + + for limit in [1usize, 2, 7, 39, 40, 41, 100] { + let mut pz = PrefixZipper::new(b"", map.read_zipper()); + let mut observed = Vec::new(); + let moved = pz.descend_until_max_bytes(limit, &mut observed); + + let expected_len = limit.min(key.len()); + assert_eq!(moved, true, "limit={limit}: should have descended"); + assert_eq!(pz.path().len(), expected_len, + "limit={limit}: focus must stop at the limit"); + assert_eq!(observed, &key[..expected_len], + "limit={limit}: observer must receive exactly the bytes descended"); + assert_eq!(observed, pz.path(), + "limit={limit}: observer must agree with the resulting path"); + } + } + + /// A zero limit must not move the zipper or report anything + #[test] + fn default_descend_until_max_bytes_zero_limit() { + use crate::prefix_zipper::PrefixZipper; + + let map = PathMap::from_iter([(b"abcdef".as_slice(), ())]); + let mut pz = PrefixZipper::new(b"", map.read_zipper()); + let mut observed = Vec::new(); + let moved = pz.descend_until_max_bytes(0, &mut observed); + + assert_eq!(moved, false); + assert_eq!(pz.path(), b""); + assert_eq!(observed, b""); + } + super::zipper_moving_tests::zipper_moving_tests!(read_zipper, |keys: &[&[u8]]| { let mut btm = PathMap::new(); @@ -4532,11 +4959,11 @@ mod tests { zipper.descend_to(b"e"); assert_eq!(zipper.is_val(), true); assert_eq!(zipper.val(), Some(&"romane")); - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), 1); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); assert_eq!(zipper.val(), None); - zipper.descend_until(); + zipper.descend_until(&mut ()); assert_eq!(zipper.is_val(), true); assert_eq!(zipper.val(), Some(&"romanus")); } @@ -4683,16 +5110,16 @@ mod tests { r0.descend_to_byte(0); assert_eq!(r0.path_exists(), true); let mut r1 = r0.fork_read_zipper(); - assert_eq!(r1.to_next_sibling_byte(), false); + assert_eq!(r1.to_next_sibling_byte(), None); assert_eq!(r1.child_mask().0[0], (1<<3) | (1<<4) | (1<<5)); r1.descend_to_byte(3); assert_eq!(r1.path_exists(), true); assert_eq!(r1.child_mask().0[0], 0); - assert_eq!(r1.to_next_sibling_byte(), true); + assert!(r1.to_next_sibling_byte().is_some()); assert_eq!(r1.origin_path(), &[0, 4]); assert_eq!(r1.path(), &[4]); - assert_eq!(r1.to_next_sibling_byte(), true); - assert_eq!(r1.to_next_sibling_byte(), false); + assert!(r1.to_next_sibling_byte().is_some()); + assert_eq!(r1.to_next_sibling_byte(), None); } #[test] @@ -4899,7 +5326,7 @@ mod tests { assert_eq!(zipper.path(), b""); assert_eq!(zipper.val_count(), 2); - assert_eq!(zipper.descend_until(), true); + assert_eq!(zipper.descend_until(&mut ()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.val_count(), 1); } diff --git a/src/zipper_head.rs b/src/zipper_head.rs index f9dc38ab..0b8305e3 100644 --- a/src/zipper_head.rs +++ b/src/zipper_head.rs @@ -829,7 +829,7 @@ mod tests { let mut wz = zh.write_zipper_at_exclusive_path(&[3, 194, 22]).unwrap(); assert_eq!(rz.val(), None); - assert!(rz.descend_first_byte()); + assert!(rz.descend_first_byte().is_some()); assert_eq!(rz.val(), Some(&())); assert_eq!(wz.val(), None); @@ -837,7 +837,7 @@ mod tests { rz.reset(); assert_eq!(rz.val(), None); - assert!(rz.descend_first_byte()); + assert!(rz.descend_first_byte().is_some()); assert_eq!(rz.val(), Some(&())); drop(wz); @@ -859,7 +859,7 @@ mod tests { let mut wz = zh.write_zipper_at_exclusive_path(&[3, 194, 22]).unwrap(); assert_eq!(rz.val(), None); - assert!(rz.descend_first_byte()); + assert!(rz.descend_first_byte().is_some()); assert_eq!(rz.val(), Some(&1004)); assert_eq!(rz2.val(), Some(&1003)); @@ -1021,7 +1021,7 @@ mod tests { assert_eq!(z.child_count(), 0); assert_eq!(z.child_mask(), ByteMask::EMPTY); assert_eq!(z.path_exists(), true); - assert_eq!(z.to_next_sibling_byte(), false); + assert_eq!(z.to_next_sibling_byte(), None); assert_eq!(z.ascend_byte(), false); // Test creating a zipper at a path that doesn't exist @@ -1029,7 +1029,7 @@ mod tests { assert_eq!(z2.val(), None); assert_eq!(z2.child_count(), 0); assert_eq!(z2.child_mask(), ByteMask::EMPTY); - assert_eq!(z2.to_next_sibling_byte(), false); + assert_eq!(z2.to_next_sibling_byte(), None); assert_eq!(z2.ascend_byte(), false); //Conceptually this should be `false`, but the act of creating the ReadZipper currently creates @@ -1049,28 +1049,28 @@ mod tests { // Create a zipper and test to make sure it behaves properly let mut z = zh.read_zipper_at_path(b"A").unwrap(); assert_eq!(z.val(), Some(&24)); - assert_eq!(z.to_next_sibling_byte(), false); - z.descend_until(); + assert_eq!(z.to_next_sibling_byte(), None); + z.descend_until(&mut ()); assert_eq!(z.path(), b"BCDEFG"); assert_eq!(z.origin_path(), b"ABCDEFG"); assert_eq!(z.val(), Some(&42)); - assert_eq!(z.to_next_sibling_byte(), false); + assert_eq!(z.to_next_sibling_byte(), None); // Create a second zipper and ensure it's valid let mut z2 = zh.read_zipper_at_path(z.origin_path()).unwrap(); assert_eq!(z2.path(), b""); assert_eq!(z2.origin_path(), b"ABCDEFG"); assert_eq!(z2.val(), Some(&42)); - assert_eq!(z2.to_next_sibling_byte(), false); + assert_eq!(z2.to_next_sibling_byte(), None); // Test the original zipper assert_eq!(z.val(), Some(&42)); - assert_eq!(z.to_next_sibling_byte(), false); - assert_eq!(z.ascend_until(), true); + assert_eq!(z.to_next_sibling_byte(), None); + assert!(z.ascend_until() > 0); assert_eq!(z.path(), b""); assert_eq!(z.origin_path(), b"A"); assert_eq!(z.val(), Some(&24)); - assert_eq!(z.to_next_sibling_byte(), false); + assert_eq!(z.to_next_sibling_byte(), None); } /// Similar test to zipper_headc, but here we are testing to make sure there are no issues when @@ -1447,7 +1447,7 @@ mod tests { //Make sure we cleaned up the dangling path, but nothing else let mut rz = zh.read_zipper_at_borrowed_path(b"a_path_").unwrap(); - assert!(rz.descend_until()); + assert!(rz.descend_until(&mut ())); assert_eq!(rz.path(), b"to_somewhere"); drop(rz); @@ -1456,7 +1456,7 @@ mod tests { zh.cleanup_write_zipper(wz); let mut rz = zh.read_zipper_at_borrowed_path(b"a_path_").unwrap(); assert_eq!(rz.path(), b""); - assert!(rz.descend_until()); + assert!(rz.descend_until(&mut ())); assert_eq!(rz.path(), b"to_somewhere"); drop(rz); diff --git a/src/zipper_tracking.rs b/src/zipper_tracking.rs index 8fa2cd0c..ce046f36 100644 --- a/src/zipper_tracking.rs +++ b/src/zipper_tracking.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use std::sync::RwLock; use crate::PathMap; -use crate::zipper::{ReadZipperUntracked, Zipper, ZipperAbsolutePath, ZipperForking, ZipperMoving, ZipperReadOnlyValues, ZipperWriting, ZipperIteration, ZipperReadOnlyIteration, }; +use crate::zipper::{ReadZipperUntracked, Zipper, ZipperAbsolutePath, ZipperForking, ZipperMoving, ZipperPath, ZipperReadOnlyValues, ZipperWriting, ZipperIteration, ZipperReadOnlyIteration, }; /// Marker to track an outstanding read zipper pub struct TrackingRead;