diff --git a/benches/parallel.rs b/benches/parallel.rs index d28db571..292c5301 100644 --- a/benches/parallel.rs +++ b/benches/parallel.rs @@ -315,7 +315,7 @@ fn parallel_copy_traverse(bencher: Bencher, (elements, thread_cnt): (usize, &str Ok((mut reader_z, mut writer_z)) => { //We got the zippers, do the stuff let witness = reader_z.witness(); - while let Some(val) = reader_z.to_next_get_val_with_witness(&witness) { + while let Some(val) = reader_z.to_next_get_val_with_witness(&witness, &mut ()) { writer_z.move_to_path(reader_z.path()); writer_z.set_val(*val); @@ -363,7 +363,7 @@ fn parallel_copy_traverse(bencher: Bencher, (elements, thread_cnt): (usize, &str let mut writer_z = unsafe{ zipper_head.write_zipper_at_exclusive_path_unchecked(&[b'o', b'u', b't', 0]) }; let mut reader_z = unsafe{ zipper_head.read_zipper_at_path_unchecked(&[b'i', b'n', 0]) }; let witness = reader_z.witness(); - while let Some(val) = reader_z.to_next_get_val_with_witness(&witness) { + while let Some(val) = reader_z.to_next_get_val_with_witness(&witness, &mut ()) { writer_z.move_to_path(reader_z.path()); writer_z.set_val(*val); sanity_counter += 1; diff --git a/pathmap-derive/src/lib.rs b/pathmap-derive/src/lib.rs index 3bbc959e..5111c6d4 100644 --- a/pathmap-derive/src/lib.rs +++ b/pathmap-derive/src/lib.rs @@ -15,6 +15,7 @@ enum PolyZipperTrait { ZipperMoving, ZipperIteration, ZipperConcrete, + ZipperPath, ZipperAbsolutePath, ZipperPathBuffer, ZipperSubtries, @@ -33,6 +34,7 @@ impl PolyZipperTrait { "ZipperMoving" => Some(Self::ZipperMoving), "ZipperIteration" => Some(Self::ZipperIteration), "ZipperConcrete" => Some(Self::ZipperConcrete), + "ZipperPath" => Some(Self::ZipperPath), "ZipperAbsolutePath" => Some(Self::ZipperAbsolutePath), "ZipperPathBuffer" => Some(Self::ZipperPathBuffer), "ZipperSubtries" => Some(Self::ZipperSubtries), @@ -54,6 +56,7 @@ fn all_poly_zipper_traits() -> BTreeSet { ZipperMoving, ZipperIteration, ZipperConcrete, + ZipperPath, ZipperAbsolutePath, ZipperPathBuffer, ZipperSubtries, @@ -98,6 +101,11 @@ fn add_trait_dependencies(traits: &mut BTreeSet) { } } if traits.contains(&ZipperAbsolutePath) { + if traits.insert(ZipperPath) { + changed = true; + } + } + if traits.contains(&ZipperPath) { if traits.insert(ZipperMoving) { changed = true; } @@ -459,19 +467,16 @@ fn derive_poly_zipper_with_traits( quote! {} }; Some(quote! { - impl #impl_generics pathmap::zipper::ZipperPath for #enum_name #ty_generics + impl #impl_generics pathmap::zipper::ZipperMoving for #enum_name #ty_generics #zipper_moving_where { - fn path(&self) -> &[u8] { + #[inline] + fn depth(&self) -> usize { match self { - #(#variant_arms => inner.path(),)* + #(#variant_arms => inner.depth(),)* } } - } - 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(),)* @@ -572,6 +577,33 @@ fn derive_poly_zipper_with_traits( None }; + // Generate ZipperPath trait implementation + let zipper_path_impl = if traits.contains(&PolyZipperTrait::ZipperPath) { + let variant_arms = &variant_arms; + let zipper_path_where = if include_where_clause { + quote! { + where + #(#inner_types: pathmap::zipper::ZipperPath,)* + #where_clause + } + } else { + quote! {} + }; + Some(quote! { + impl #impl_generics pathmap::zipper::ZipperPath for #enum_name #ty_generics + #zipper_path_where + { + fn path(&self) -> &[u8] { + match self { + #(#variant_arms => inner.path(),)* + } + } + } + }) + } else { + None + }; + // Generate ZipperAbsolutePath trait implementation let zipper_absolute_path_impl = if traits.contains(&PolyZipperTrait::ZipperAbsolutePath) { let variant_arms = &variant_arms; @@ -660,27 +692,27 @@ fn derive_poly_zipper_with_traits( impl #impl_generics pathmap::zipper::ZipperIteration for #enum_name #ty_generics #zipper_iteration_where { - fn to_next_val(&mut self) -> bool { + fn to_next_val_observed(&mut self, obs: &mut Obs) -> bool { match self { - #(#variant_arms => inner.to_next_val(),)* + #(#variant_arms => inner.to_next_val_observed(obs),)* } } - fn descend_last_path(&mut self) -> bool { + fn descend_last_path_observed(&mut self, obs: &mut Obs) -> bool { match self { - #(#variant_arms => inner.descend_last_path(),)* + #(#variant_arms => inner.descend_last_path_observed(obs),)* } } - fn descend_first_k_path(&mut self, k: usize) -> bool { + fn descend_first_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { match self { - #(#variant_arms => inner.descend_first_k_path(k),)* + #(#variant_arms => inner.descend_first_k_path_observed(k, obs),)* } } - fn to_next_k_path(&mut self, k: usize) -> bool { + fn to_next_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { match self { - #(#variant_arms => inner.to_next_k_path(k),)* + #(#variant_arms => inner.to_next_k_path_observed(k, obs),)* } } } @@ -705,9 +737,9 @@ fn derive_poly_zipper_with_traits( impl #impl_generics pathmap::zipper::ZipperReadOnlyIteration<'trie, V> for #enum_name #ty_generics #zipper_read_only_iteration_where { - fn to_next_get_val(&mut self) -> Option<&'trie V> { + fn to_next_get_val_observed(&mut self, obs: &mut Obs) -> Option<&'trie V> { match self { - #(#variant_arms => inner.to_next_get_val(),)* + #(#variant_arms => inner.to_next_get_val_observed(obs),)* } } } @@ -731,9 +763,9 @@ fn derive_poly_zipper_with_traits( impl #impl_generics pathmap::zipper::ZipperReadOnlyConditionalIteration<'trie, V> for #enum_name #ty_generics #zipper_read_only_conditional_iteration_where { - fn to_next_get_val_with_witness<'w>(&mut self, witness: &'w Self::WitnessT) -> Option<&'w V> where 'trie: 'w { + fn to_next_get_val_with_witness_observed<'w, Obs: pathmap::zipper::PathObserver>(&mut self, witness: &'w Self::WitnessT, obs: &mut Obs) -> Option<&'w V> where 'trie: 'w { match (self, witness) { - #((Self::#variant_names(inner), #witness_enum_name::#variant_names(w)) => inner.to_next_get_val_with_witness(w),)* + #((Self::#variant_names(inner), #witness_enum_name::#variant_names(w)) => inner.to_next_get_val_with_witness_observed(w, obs),)* _ => { debug_assert!(false, "Witness variant must match zipper variant"); None @@ -847,6 +879,7 @@ fn derive_poly_zipper_with_traits( // #zipper_forking_impl #zipper_moving_impl #zipper_concrete_impl + #zipper_path_impl #zipper_absolute_path_impl #zipper_path_buffer_impl #zipper_iteration_impl diff --git a/src/arena_compact.rs b/src/arena_compact.rs index 1aacaf76..199d7cf5 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -2892,6 +2892,9 @@ where Storage: AsRef<[u8]> impl<'tree, Storage, Value> ZipperMoving for ACTZipper<'tree, Storage, Value> where Storage: AsRef<[u8]> { + #[inline] + fn depth(&self) -> usize { self.path.len().saturating_sub(self.origin_depth) } + /// Returns `true` if the zipper cannot ascend further, otherwise returns `false` fn at_root(&self) -> bool { self.path.len() <= self.origin_depth } @@ -3149,7 +3152,7 @@ where Storage: AsRef<[u8]> } // default - // fn to_next_step(&mut self) -> bool; + // fn to_next_step(&mut self, obs: &mut Obs) -> bool; } impl ZipperIteration for ACTZipper<'_, Storage, Value> @@ -3159,8 +3162,8 @@ where Storage: AsRef<[u8]> /// order /// /// Returns a reference to the value or `None` if the zipper has encountered the root. - fn to_next_val(&mut self) -> bool { - while self.to_next_step() { + fn to_next_val_observed(&mut self, obs: &mut Obs) -> bool { + while self.to_next_step_observed(obs) { if self.is_val() { return true; } @@ -3178,11 +3181,15 @@ where Storage: AsRef<[u8]> /// below the zipper's focus. Although a typical cost is `order log n` or better. /// /// See: [to_next_k_path](ZipperIteration::to_next_k_path) - fn descend_first_k_path(&mut self, k: usize) -> bool { + fn descend_first_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { for ii in 0..k { - if self.descend_first_byte().is_none() { - self.ascend(ii); - return false; + match self.descend_first_byte() { + Some(byte) => obs.descend_to_byte(byte), + None => { + self.ascend(ii); + obs.ascend(ii); + return false; + } } } return true; @@ -3199,35 +3206,46 @@ where Storage: AsRef<[u8]> /// below the zipper's focus. Although a typical cost is `order log n` or better. /// /// See: [descend_first_k_path](ZipperIteration::descend_first_k_path) - fn to_next_k_path(&mut self, k: usize) -> bool { + fn to_next_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { let mut depth = k; 'outer: loop { while depth > 0 && self.child_count() <= 1 { if self.ascend(1) == 0 { break 'outer; } + obs.ascend(1); depth -= 1; } let stack = self.stack.last_mut().unwrap(); let idx = stack.child_index + 1; if idx >= stack.child_count { - if depth == 0 || self.ascend(1) == 0 { + if depth == 0 { break 'outer; } + if self.ascend(1) == 0 { + break 'outer; + } + obs.ascend(1); depth -= 1; continue 'outer; } - assert!(self.descend_indexed_byte(idx).is_some()); + //The loops above already ascended, so this is a plain descent of one byte + match self.descend_indexed_byte(idx) { + Some(byte) => obs.descend_to_byte(byte), + None => unreachable!("idx was bounds-checked against child_count above"), + } depth += 1; for _ii in 0..k - depth { - if self.descend_first_byte().is_none() { - continue 'outer; + match self.descend_first_byte() { + Some(byte) => obs.descend_to_byte(byte), + None => continue 'outer, } depth += 1; } return true; } self.ascend(depth); + obs.ascend(depth); false } } @@ -3326,14 +3344,18 @@ mod tests { let mut btm_zipper = btm.read_zipper(); let mut act_zipper = act.read_zipper_u64(); + let mut btm_observed = Vec::::new(); + let mut act_observed = Vec::::new(); loop { - btm_zipper.to_next_val(); - act_zipper.to_next_val(); + btm_zipper.to_next_val_observed(&mut btm_observed); + act_zipper.to_next_val_observed(&mut act_observed); let btm_val = btm_zipper.val().copied(); let act_val = act_zipper.val().copied(); assert_eq!(btm_zipper.path(), act_zipper.path()); + assert_eq!(btm_observed, act_observed); + assert_eq!(&btm_observed[..], btm_zipper.path()); assert_eq!(btm_val, act_val); if act_val.is_none() { @@ -3641,11 +3663,15 @@ mod tests { let btm = PathMap::from_iter(items.iter().map(|&(k, v)| (k, v))); let mut bz = btm.read_zipper(); let mut az = act.read_zipper_u64(); + let mut b_observed = Vec::::new(); + let mut a_observed = Vec::::new(); loop { - let more_b = bz.to_next_val(); - let more_a = az.to_next_val(); + let more_b = bz.to_next_val_observed(&mut b_observed); + let more_a = az.to_next_val_observed(&mut a_observed); assert_eq!(more_b, more_a, "walks end together"); assert_eq!(bz.path(), az.path()); + assert_eq!(b_observed, a_observed); + assert_eq!(&b_observed[..], bz.path()); assert_eq!(bz.val().copied(), az.val().copied()); if !more_a { break; @@ -3826,11 +3852,15 @@ mod tests { let act = ArenaCompactTree::from_zipper(btm.read_zipper(), |&v| v); let mut cata_zipper = act.read_zipper_u64(); let mut stream_zipper = tree.read_zipper_u64(); + let mut cata_observed = Vec::::new(); + let mut stream_observed = Vec::::new(); loop { - let cata_next = cata_zipper.to_next_val(); - let stream_next = stream_zipper.to_next_val(); + let cata_next = cata_zipper.to_next_val_observed(&mut cata_observed); + let stream_next = stream_zipper.to_next_val_observed(&mut stream_observed); assert_eq!(cata_next, stream_next); assert_eq!(cata_zipper.path(), stream_zipper.path()); + assert_eq!(cata_observed, stream_observed); + assert_eq!(&cata_observed[..], cata_zipper.path()); assert_eq!(cata_zipper.get_val(), stream_zipper.get_val()); if !cata_next { break; @@ -3948,10 +3978,14 @@ mod tests { fn assert_act_matches_map(map: &PathMap, tree: &ArenaCompactTree) { let mut map_zipper = map.read_zipper(); let mut act_zipper = tree.read_zipper_u64(); + let mut map_observed = Vec::::new(); + let mut act_observed = Vec::::new(); loop { - let map_next = map_zipper.to_next_val(); - assert_eq!(map_next, act_zipper.to_next_val()); + let map_next = map_zipper.to_next_val_observed(&mut map_observed); + assert_eq!(map_next, act_zipper.to_next_val_observed(&mut act_observed)); assert_eq!(map_zipper.path(), act_zipper.path()); + assert_eq!(map_observed, act_observed); + assert_eq!(&map_observed[..], map_zipper.path()); assert_eq!(map_zipper.val().copied(), act_zipper.val().copied()); if !map_next { break } } diff --git a/src/counters.rs b/src/counters.rs index 78baf39e..2980401c 100644 --- a/src/counters.rs +++ b/src/counters.rs @@ -104,7 +104,7 @@ impl Counters { let mut zipper = map.read_zipper(); while zipper.to_next_step() { - let depth = zipper.path().len(); + let depth = zipper.depth(); counters.run_counter_update(depth); if let Some(focus_node) = zipper.get_focus().try_as_tagged() { @@ -187,7 +187,7 @@ impl Counters { } } -pub fn print_traversal<'a, V: 'a + Clone + Unpin, Z: ZipperIteration + Clone>(zipper: &Z) { +pub fn print_traversal<'a, V: 'a + Clone + Unpin, Z: ZipperIteration + ZipperPath + Clone>(zipper: &Z) { let mut zipper = zipper.clone(); println!("{:?}", zipper.path()); diff --git a/src/dependent_zipper.rs b/src/dependent_zipper.rs index e086695c..fe328c79 100644 --- a/src/dependent_zipper.rs +++ b/src/dependent_zipper.rs @@ -33,7 +33,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath, - SecondaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving, { /// Creates a new `DependentProductZipperG` from the provided enroll function pub fn new_enroll(primary: PrimaryZ, enroll_payload: C, enroll: F) -> Self @@ -63,7 +63,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], fn factor_idx(&self, truncate_up: bool) -> Option { debug_assert_eq!(self.factor_paths.len(), self.secondary.len(), "factor_paths and secondary must stay in step"); - let len = self.path().len(); + let len = self.depth(); let mut factor = self.factor_paths.len().checked_sub(1)?; while truncate_up && self.factor_paths[factor] == len { factor = factor.checked_sub(1)?; @@ -77,7 +77,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], /// The returned slice will have a length of [`focus_factor`](Self::focus_factor), so the factor /// containing the current focus has will not be included. /// - /// Indices will be offsets into the buffer returned by [path](ZipperMoving::path). To get an offset into + /// Indices will be offsets into the buffer returned by [path](ZipperPath::path). To get an offset into /// [origin_path](ZipperAbsolutePath::origin_path), add the length of the prefix path from /// [root_prefix_path](ZipperAbsolutePath::root_prefix_path). pub fn path_indices(&self) -> &[usize] { @@ -96,7 +96,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], /// Remove top factors if they are at root fn exit_factors(&mut self) -> bool { - let len = self.path().len(); + let len = self.depth(); let mut exited = false; while self.factor_paths.last() == Some(&len) { self.factor_paths.pop(); @@ -108,7 +108,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], /// Enter factors at current location if we're on the end of the factor's path fn enter_factors(&mut self) -> bool { - let len = self.path().len(); + let len = self.depth(); // enter the next factor if we can let mut entered = false; if self.is_path_end() { @@ -127,7 +127,7 @@ 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) -> usize { - let mut plen = self.path().len(); + let mut plen = self.depth(); let mut ascended = 0; loop { while self.factor_paths.last() == Some(&plen) { @@ -183,7 +183,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperAbsolutePath, - SecondaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving, { fn origin_path(&self) -> &[u8] { self.primary.origin_path() } fn root_prefix_path(&self) -> &[u8] { self.primary.root_prefix_path() } @@ -194,7 +194,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath + ZipperConcrete, - SecondaryZ: ZipperMoving + ZipperPath + ZipperConcrete, + SecondaryZ: ZipperMoving + ZipperConcrete, { fn shared_node_id(&self) -> Option { if let Some(idx) = self.factor_idx(true) { @@ -217,7 +217,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath + ZipperPathBuffer, - SecondaryZ: ZipperMoving + ZipperPath + ZipperPathBuffer, + SecondaryZ: ZipperMoving + 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() } @@ -229,7 +229,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath + ZipperValues, - SecondaryZ: ZipperMoving + ZipperPath + ZipperValues, + SecondaryZ: ZipperMoving + ZipperValues, { fn val(&self) -> Option<&V> { if let Some(idx) = self.factor_idx(true) { @@ -252,7 +252,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, { fn get_val(&self) -> Option<&'trie V> { if let Some(idx) = self.factor_idx(true) { @@ -275,7 +275,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyConditionalValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyConditionalValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperReadOnlyConditionalValues<'trie, V>, { type WitnessT = (PrimaryZ::WitnessT, Vec); fn witness<'w>(&self) -> Self::WitnessT { @@ -296,7 +296,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath + Zipper, - SecondaryZ: ZipperMoving + ZipperPath + Zipper, + SecondaryZ: ZipperMoving + Zipper, { fn path_exists(&self) -> bool { if let Some(idx) = self.factor_idx(true) { @@ -332,10 +332,11 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath, - SecondaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving, { - fn at_root(&self) -> bool { - self.path().is_empty() + #[inline] + fn depth(&self) -> usize { + self.primary.depth() } #[inline] fn focus_byte(&self) -> Option { @@ -430,7 +431,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], while remaining > 0 { self.exit_factors(); if let Some(idx) = self.factor_idx(false) { - let len = self.path().len() - self.factor_paths[idx]; + let len = self.depth() - self.factor_paths[idx]; let delta = len.min(remaining); self.secondary[idx].ascend(delta); self.primary.ascend(delta); @@ -459,7 +460,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V, C, F : Clone + for <'a> FnOnce(C, &'a [u8], where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath, - SecondaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving, { #[inline] fn path(&self) -> &[u8] { @@ -471,7 +472,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: ZipperIteration, + //The `enroll` closure receives the path traversed so far, which comes from the primary + PrimaryZ: ZipperIteration + ZipperPath, SecondaryZ: ZipperIteration, { } //Use the default impl for all methods @@ -479,7 +481,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V: Clone + Send + Sync + Unpin, C, F : Clone + where V: Clone + Send + Sync, PrimaryZ: ZipperMoving + ZipperPath + ZipperValues, - SecondaryZ: ZipperMoving + ZipperPath + ZipperValues, + SecondaryZ: ZipperMoving + ZipperValues, { fn native_subtries(&self) -> bool { false } fn try_make_map(&self) -> Option> { None } @@ -503,7 +505,9 @@ mod tests { }); let mut full = String::new(); - while dpz.to_next_val() { + let mut observed = Vec::::new(); + while dpz.to_next_val_observed(&mut observed) { + assert_eq!(&observed[..], dpz.path()); if dpz.child_count() == 0 { full.push_str(std::str::from_utf8(dpz.path()).unwrap()); full.push('\n'); @@ -534,7 +538,9 @@ rubicundus.postfix }); let mut full = String::new(); - while dpz.to_next_val() { + let mut observed = Vec::::new(); + while dpz.to_next_val_observed(&mut observed) { + assert_eq!(&observed[..], dpz.path()); if dpz.child_count() == 0 { full.push_str(std::str::from_utf8(dpz.path()).unwrap()); full.push('\n'); diff --git a/src/empty_zipper.rs b/src/empty_zipper.rs index 0fb50987..16860fc1 100644 --- a/src/empty_zipper.rs +++ b/src/empty_zipper.rs @@ -32,7 +32,7 @@ impl Zipper for EmptyZipper { } impl ZipperMoving for EmptyZipper { - fn at_root(&self) -> bool { self.path.len() == self.path_start_idx } + #[inline] fn depth(&self) -> usize { 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) } @@ -82,9 +82,10 @@ impl ZipperAbsolutePath for EmptyZipper { } impl ZipperIteration for EmptyZipper { - fn to_next_val(&mut self) -> bool { false } - fn descend_first_k_path(&mut self, _k: usize) -> bool { false } - fn to_next_k_path(&mut self, _k: usize) -> bool { false } + fn to_next_val_observed(&mut self, _obs: &mut Obs) -> bool { false } + fn descend_last_path_observed(&mut self, _obs: &mut Obs) -> bool { false } + fn descend_first_k_path_observed(&mut self, _k: usize, _obs: &mut Obs) -> bool { false } + fn to_next_k_path_observed(&mut self, _k: usize, _obs: &mut Obs) -> bool { false } } impl ZipperValues for EmptyZipper { @@ -109,11 +110,11 @@ impl<'a, V: Clone + Send + Sync> ZipperReadOnlyConditionalValues<'a, V> for Empt } impl<'a, V: Clone + Send + Sync> ZipperReadOnlyIteration<'a, V> for EmptyZipper { - fn to_next_get_val(&mut self) -> Option<&'a V> { None } + fn to_next_get_val_observed(&mut self, _obs: &mut Obs) -> Option<&'a V> { None } } impl<'a, V: Clone + Send + Sync> ZipperReadOnlyConditionalIteration<'a, V> for EmptyZipper { - fn to_next_get_val_with_witness<'w>(&mut self, _witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { None } + fn to_next_get_val_with_witness_observed<'w, Obs: PathObserver>(&mut self, _witness: &'w Self::WitnessT, _obs: &mut Obs) -> Option<&'w V> where 'a: 'w { None } } impl ZipperPathBuffer for EmptyZipper { diff --git a/src/experimental.rs b/src/experimental.rs index cb9c1f67..f39c2caa 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -45,7 +45,7 @@ impl ZipperPathBuffer for FullZipper { } impl ZipperMoving for FullZipper { - fn at_root(&self) -> bool { self.path.len() == 0 } + #[inline] fn depth(&self) -> usize { self.path.len() } #[inline] fn focus_byte(&self) -> Option { self.path.last().cloned() } fn reset(&mut self) { self.path.clear() } @@ -118,7 +118,7 @@ impl Zipper for NullZipper { } impl ZipperMoving for NullZipper { - fn at_root(&self) -> bool { true } + #[inline] fn depth(&self) -> usize { 0 } #[inline] fn focus_byte(&self) -> Option { None } fn reset(&mut self) {} diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index db96d655..96b847e8 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -137,6 +137,13 @@ impl ZipperMoving BZipper: ZipperMoving + ZipperPath + ZipperValues, Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, { + #[inline] + fn depth(&self) -> usize { + let depth = self.a.depth(); + debug_assert_eq!(depth, self.b.depth()); + depth + } + fn at_root(&self) -> bool { self.a.at_root() || self.b.at_root() } diff --git a/src/path_tracker.rs b/src/path_tracker.rs index d3c1123f..e86ca669 100644 --- a/src/path_tracker.rs +++ b/src/path_tracker.rs @@ -60,6 +60,7 @@ impl Zipper for PathTracker { } impl ZipperMoving for PathTracker { + #[inline] fn depth(&self) -> usize { self.path.len() - self.origin_len } #[inline] fn at_root(&self) -> bool { self.zipper.at_root() } fn reset(&mut self) { self.zipper.reset(); @@ -140,6 +141,9 @@ impl ZipperMoving for PathTracker { self.path.truncate(self.path.len() - ascended); ascended } + fn to_next_step_observed(&mut self, obs: &mut Obs) -> bool { + self.zipper.to_next_step_observed(&mut (&mut self.path, &mut *obs)) + } 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; @@ -153,7 +157,22 @@ impl ZipperMoving for PathTracker { } -impl ZipperIteration for PathTracker { } +impl ZipperIteration for PathTracker { + //Each method delegates to the wrapped zipper, so it can use its own native implementation, and + //fans the reported movements out to this tracker's path buffer as well as the caller's observer + fn to_next_val_observed(&mut self, obs: &mut Obs) -> bool { + self.zipper.to_next_val_observed(&mut (&mut self.path, &mut *obs)) + } + fn descend_last_path_observed(&mut self, obs: &mut Obs) -> bool { + self.zipper.descend_last_path_observed(&mut (&mut self.path, &mut *obs)) + } + fn descend_first_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { + self.zipper.descend_first_k_path_observed(k, &mut (&mut self.path, &mut *obs)) + } + fn to_next_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { + self.zipper.to_next_k_path_observed(k, &mut (&mut self.path, &mut *obs)) + } +} impl ZipperPath for PathTracker { fn path(&self) -> &[u8] { &self.path[self.origin_len..] } diff --git a/src/paths_serialization.rs b/src/paths_serialization.rs index 3f6e05df..b9a08d29 100644 --- a/src/paths_serialization.rs +++ b/src/paths_serialization.rs @@ -49,7 +49,7 @@ pub struct DeserializationStats { pub fn serialize_paths<'a, V, W, RZ>(rz: RZ, target: &mut W) -> std::io::Result where V: TrieValue, - RZ: ZipperReadOnlyConditionalIteration<'a, V>, + RZ: ZipperReadOnlyConditionalIteration<'a, V> + ZipperPath, W: std::io::Write { serialize_paths_with_auxdata(rz, target, |_, _, _| {}) diff --git a/src/poly_zipper.rs b/src/poly_zipper.rs index 7eb19fab..4655cec0 100644 --- a/src/poly_zipper.rs +++ b/src/poly_zipper.rs @@ -13,6 +13,7 @@ use crate::zipper::*; /// * [`ZipperConcrete`] /// * [`ZipperIteration`] /// * [`ZipperMoving`] +/// * [`ZipperPath`] /// * [`ZipperPathBuffer`] /// * [`ZipperReadOnlyConditionalIteration`] /// * [`ZipperReadOnlyConditionalValues`] @@ -142,7 +143,7 @@ mod tests { // ====================================================================================== // Cocktail of recursive zipper madness #[derive(PolyZipperExplicit)] - #[poly_zipper_explicit(traits(Zipper, ZipperValues, ZipperMoving, ZipperIteration))] + #[poly_zipper_explicit(traits(Zipper, ZipperValues, ZipperMoving, ZipperPath, ZipperIteration))] pub enum ExprFactor<'trie, V: Clone + Send + Sync + Unpin + 'static = ()> { Specific(ReadZipperOwned), Generic(PrefixZipper<'trie, diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index 6d2ecc6d..e9353594 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -97,7 +97,7 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> /// Sets the portion of the zipper's `prefix` to treat as the [`root_prefix_path`](ZipperAbsolutePath::root_prefix_path) /// - /// The remaining portion of the `prefix` will be part of the [`path`](ZipperMoving::path). + /// The remaining portion of the `prefix` will be part of the [`path`](ZipperPath::path). /// This method resets the zipper, and typically it is called immediately after creating the `PrefixZipper`. pub fn set_root_prefix_path(&mut self, root_prefix_path: &[u8]) -> Result<(), &'static str> { if !starts_with(&*self.prefix, root_prefix_path) { @@ -117,6 +117,23 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> }; } + /// Descends over whatever remains of the `prefix`, leaving the focus at the source's root + /// + /// Returns `true` if the focus moved. The descended bytes are appended to this zipper's path + /// buffer and reported to `obs`. Does nothing if the focus is already within the source. + fn consume_prefix(&mut self, obs: &mut Obs) -> bool { + match self.position.prefixed_depth() { + Some(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 + }, + None => false, + } + } + fn ascend_n(&mut self, mut steps: usize) -> Result<(), usize> { if let PrefixPos::PrefixOff { valid, mut invalid } = self.position { if invalid > steps { @@ -342,6 +359,11 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> where Z: ZipperMoving { + #[inline] + fn depth(&self) -> usize { + self.path.len() - self.origin_depth + } + fn at_root(&self) -> bool { match self.position { PrefixPos::Prefix { valid } => valid == 0, @@ -432,15 +454,7 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> } //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() { - 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 descended_prefix = self.consume_prefix(obs); //Fan the descended bytes out to our own path buffer as well as the caller's observer descended_prefix | self.source.descend_until_observed(&mut (&mut self.path, &mut *obs)) } @@ -518,22 +532,85 @@ impl<'prefix, Z> ZipperAbsolutePath for PrefixZipper<'prefix, Z> impl<'prefix, Z> ZipperIteration for PrefixZipper<'prefix, Z> where Z: ZipperIteration { - //TODO: The default impls are highly sub-optimal. However we need "blind" versions of `ZipperIteration` to make this do the right thing - // fn to_next_val(&mut self) -> bool { todo!() } - // fn descend_first_k_path(&mut self, k: usize) -> bool { todo!() } - // fn to_next_k_path(&mut self, k: usize) -> bool { todo!() } -} + fn to_next_val_observed(&mut self, obs: &mut Obs) -> bool { + if self.position.is_invalid() { + return false; + } + //Values only exist within the source, and the source's own root may hold one, so the prefix + //is consumed without descending any further before handing off + self.consume_prefix(obs); + self.source.to_next_val_observed(&mut (&mut self.path, &mut *obs)) + } -impl<'prefix, 'a, V, Z> ZipperReadOnlyIteration<'a, V> for PrefixZipper<'prefix, Z> where Z: ZipperReadOnlyIteration<'a, V>, Self: ZipperReadOnlyValues<'a, V> + ZipperIteration { - //TODO: same as above. Default impls are highly sub-optimal - // fn to_next_get_val(&mut self) -> Option<&'a V> { todo!() } -} + fn descend_last_path_observed(&mut self, obs: &mut Obs) -> bool { + if self.position.is_invalid() { + return false; + } + //As in `descend_until`, consuming the prefix is movement in its own right + let descended_prefix = self.consume_prefix(obs); + descended_prefix | self.source.descend_last_path_observed(&mut (&mut self.path, &mut *obs)) + } -impl<'prefix, 'a, V, Z> ZipperReadOnlyConditionalIteration<'a, V> for PrefixZipper<'prefix, Z> where Z: ZipperReadOnlyConditionalIteration<'a, V>, Self: ZipperReadOnlyConditionalValues<'a, V, WitnessT = Z::WitnessT> + ZipperIteration { - //TODO: same as above. Default impls are highly sub-optimal - // fn to_next_get_val_with_witness<'w>(&mut self, witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { todo!() } + fn descend_first_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { + if self.position.is_invalid() { + return false; + } + //The prefix is a single forced path, so the bytes it contributes always exist and never + //branch. Descend as much of `k` as the prefix covers, then ask the source for the rest. + let prefix_rest = match self.position.prefixed_depth() { + Some(prefixed_depth) => self.prefix.len() - self.origin_depth - prefixed_depth, + None => 0, + }; + if k <= prefix_rest { + let taken = &self.prefix[self.path.len()..self.path.len() + k]; + self.path.extend_from_slice(taken); + obs.descend_to(taken); + self.set_valid(self.path.len() - self.origin_depth); + return true + } + self.consume_prefix(obs); + if self.source.descend_first_k_path_observed(k - prefix_rest, &mut (&mut self.path, &mut *obs)) { + true + } else { + self.ascend(prefix_rest); + obs.ascend(prefix_rest); + false + } + } + + fn to_next_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { + if self.position.is_invalid() || self.depth() < k { + return false; + } + //Only the portion of `k` inside the source can have alternatives to step to, so `k` is + //clamped to the source's depth. Iterating at the clamped depth visits exactly the same + //positions, because the prefix above it is a single forced path. + let source_depth = self.source.depth(); + if source_depth == 0 { + //Entirely within the prefix, which offers no alternatives + return false + } + let clamped = k.min(source_depth); + if self.source.to_next_k_path_observed(clamped, &mut (&mut self.path, &mut *obs)) { + true + } else { + //The source rewound to its root; ascend the rest of `k` back up through the prefix + let remaining = k - clamped; + if remaining > 0 { + self.ascend(remaining); + obs.ascend(remaining); + } + false + } + } } +//The default impl is built on `to_next_val`, which is implemented natively above, so it picks up +//that implementation without needing its own +impl<'prefix, 'a, V, Z> ZipperReadOnlyIteration<'a, V> for PrefixZipper<'prefix, Z> where Z: ZipperReadOnlyIteration<'a, V>, Self: ZipperReadOnlyValues<'a, V> + ZipperIteration { } + +impl<'prefix, 'a, V, Z> ZipperReadOnlyConditionalIteration<'a, V> for PrefixZipper<'prefix, Z> where Z: ZipperReadOnlyConditionalIteration<'a, V>, Self: ZipperReadOnlyConditionalValues<'a, V, WitnessT = Z::WitnessT> + ZipperIteration { } + impl<'prefix, Z, V> ZipperForking for PrefixZipper<'prefix, Z> where Z: ZipperIteration + ZipperForking @@ -678,9 +755,97 @@ mod tests { use crate::zipper::Zipper; use crate::zipper::ZipperMoving; use crate::zipper::ZipperAbsolutePath; + use crate::zipper::ZipperIteration; use crate::zipper::ZipperPath; use crate::zipper::ZipperReadOnlyValues; use crate::zipper::ZipperValues; + + //The whole prefix is the root prefix, so these run the shared suites against a `PrefixZipper` + //whose focus begins in the source + crate::zipper::zipper_moving_tests::zipper_moving_tests!(prefix_zipper, + |keys: &[&[u8]]| { + keys.iter().map(|k| (k, ())).collect::>() + }, + |btm: &mut PathMap<()>, path: &[u8]| -> _ { + let mut z = PrefixZipper::new(path.to_vec(), btm.read_zipper_at_path(path)); + z.set_root_prefix_path(path).unwrap(); + z + } + ); + + crate::zipper::zipper_iteration_tests::zipper_iteration_tests!(prefix_zipper, + |keys: &[&[u8]]| { + keys.iter().map(|k| (k, ())).collect::>() + }, + |btm: &mut PathMap<()>, path: &[u8]| -> _ { + let mut z = PrefixZipper::new(path.to_vec(), btm.read_zipper_at_path(path)); + z.set_root_prefix_path(path).unwrap(); + z + } + ); + + /// Iteration when part of the `prefix` lies below the zipper's root, so the focus starts inside + /// the prefix and the methods must cross the seam into the source + #[test] + fn prefix_zipper_iter_across_seam() { + let keys: &[&[u8]] = &[b"pre.fix.aa", b"pre.fix.ab", b"pre.fix.b"]; + let btm: PathMap<()> = keys.iter().map(|k| (k, ())).collect(); + + let make = || { + let mut z = PrefixZipper::new(b"pre.fix.".to_vec(), btm.read_zipper_at_path(b"pre.fix.")); + //Only `pre.` is the root prefix, so `fix.` remains part of the zipper's own path + z.set_root_prefix_path(b"pre.").unwrap(); + z + }; + + //`to_next_val` must traverse the rest of the prefix before reaching source values + let mut z = make(); + let mut obs = Vec::::new(); + let mut found = vec![]; + while z.to_next_val_observed(&mut obs) { + assert_eq!(&obs[..], z.path(), "observer diverged from path"); + found.push(z.path().to_vec()); + } + assert_eq!(found, vec![b"fix.aa".to_vec(), b"fix.ab".to_vec(), b"fix.b".to_vec()]); + + //`descend_last_path` crosses the seam and lands on the last path by sort order + let mut z = make(); + let mut obs = Vec::::new(); + assert!(z.descend_last_path_observed(&mut obs)); + assert_eq!(z.path(), b"fix.b"); + assert_eq!(&obs[..], z.path()); + + //k_path where `k` spans the seam: 5 bytes covers `fix.` plus one source byte + let mut z = make(); + let mut obs = Vec::::new(); + assert!(z.descend_first_k_path_observed(5, &mut obs)); + assert_eq!(z.path(), b"fix.a"); + assert_eq!(&obs[..], z.path()); + assert!(z.to_next_k_path_observed(5, &mut obs)); + assert_eq!(z.path(), b"fix.b"); + assert_eq!(&obs[..], z.path()); + assert!(!z.to_next_k_path_observed(5, &mut obs)); + assert_eq!(&obs[..], z.path()); + + //k_path where `k` lands inside the prefix, which is a single forced path with no siblings + let mut z = make(); + let mut obs = Vec::::new(); + assert!(z.descend_first_k_path_observed(2, &mut obs)); + assert_eq!(z.path(), b"fi"); + assert_eq!(&obs[..], z.path()); + assert!(!z.to_next_k_path_observed(2, &mut obs)); + assert_eq!(&obs[..], z.path()); + + //`k` deeper than anything the source can supply, so the prefix bytes descended on the way + //must be rewound and the focus left where it started + let mut z = make(); + let mut obs = Vec::::new(); + assert_eq!(z.path(), b""); + assert!(!z.descend_first_k_path_observed(99, &mut obs)); + assert_eq!(z.path(), b"", "a failed descent must leave the focus unmoved"); + assert_eq!(&obs[..], z.path()); + } + const PATHS1: &[(&[u8], u64)] = &[ (b"0000", 0), (b"00000", 1), diff --git a/src/product_zipper.rs b/src/product_zipper.rs index edfeea88..83bb67f7 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -128,7 +128,7 @@ impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ProductZipp self.z.deregularize(); self.z.push_node(secondary_root); - self.factor_paths.push(self.path().len()); + self.factor_paths.push(self.depth()); } /// Internal method to descend across the boundary between two factor zippers if the focus is on a value /// @@ -140,7 +140,7 @@ impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ProductZipp //We don't want to push the same factor on the stack twice if let Some(factor_path_len) = self.factor_paths.last() { - if *factor_path_len == self.path().len() { + if *factor_path_len == self.depth() { return } } @@ -152,7 +152,7 @@ impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ProductZipp #[inline] fn fix_after_ascend(&mut self) { while let Some(factor_path_start) = self.factor_paths.last() { - if self.z.path().len() < *factor_path_start { + if self.z.depth() < *factor_path_start { self.factor_paths.pop(); } else { break @@ -162,8 +162,9 @@ impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ProductZipp } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ProductZipper<'_, 'trie, V, A> { - fn at_root(&self) -> bool { - self.path().len() == 0 + #[inline] + fn depth(&self) -> usize { + self.z.depth() } #[inline] fn focus_byte(&self) -> Option { @@ -189,7 +190,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper descended += this_step; if self.has_next_factor() { - if self.z.child_count() == 0 && self.factor_paths.last().map(|l| *l).unwrap_or(0) < self.path().len() { + if self.z.child_count() == 0 && self.factor_paths.last().map(|l| *l).unwrap_or(0) < self.depth() { self.enroll_next_factor(); } } else { @@ -220,7 +221,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper if self.z.child_count() == 0 { if self.has_next_factor() { if self.z.path_exists() { - debug_assert!(self.factor_paths.last().map(|l| *l).unwrap_or(0) < self.path().len()); + debug_assert!(self.factor_paths.last().map(|l| *l).unwrap_or(0) < self.depth()); self.enroll_next_factor(); if self.z.node_key().len() > 0 { self.z.regularize(); @@ -234,7 +235,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper let descended = self.z.descend_to_existing_byte(k); if descended && self.z.child_count() == 0 { if self.has_next_factor() { - debug_assert!(self.factor_paths.last().map(|l| *l).unwrap_or(0) < self.path().len()); + debug_assert!(self.factor_paths.last().map(|l| *l).unwrap_or(0) < self.depth()); self.enroll_next_factor(); if self.z.node_key().len() > 0 { self.z.regularize(); @@ -265,7 +266,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper moved } fn to_next_sibling_byte(&mut self) -> Option { - if self.factor_paths.last().cloned().unwrap_or(0) == self.path().len() { + if self.factor_paths.last().cloned().unwrap_or(0) == self.depth() { self.factor_paths.pop(); } let moved = self.z.to_next_sibling_byte(); @@ -273,7 +274,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper moved } fn to_prev_sibling_byte(&mut self) -> Option { - if self.factor_paths.last().cloned().unwrap_or(0) == self.path().len() { + if self.factor_paths.last().cloned().unwrap_or(0) == self.depth() { self.factor_paths.pop(); } let moved = self.z.to_prev_sibling_byte(); @@ -395,8 +396,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 + ZipperPath, - SecondaryZ: ZipperMoving + ZipperPath, + PrimaryZ: ZipperMoving, + SecondaryZ: ZipperMoving, { /// Creates a new `ProductZipper` from the provided zippers pub fn new(primary: PrimaryZ, other_zippers: ZipperList) -> Self @@ -416,7 +417,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, /// Actual focus factor calculation. /// Returns a valid index into `self.factor_paths`, truncating to parents if requested. fn factor_idx(&self, truncate_up: bool) -> Option { - let len = self.path().len(); + let len = self.depth(); let mut factor = self.factor_paths.len().checked_sub(1)?; while truncate_up && self.factor_paths[factor] == len { factor = factor.checked_sub(1)?; @@ -436,7 +437,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, /// Remove top factors if they are at root fn exit_factors(&mut self) -> bool { - let len = self.path().len(); + let len = self.depth(); let mut exited = false; while self.factor_paths.last() == Some(&len) { self.factor_paths.pop(); @@ -447,7 +448,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, /// Enter factors at current location if we're on the end of the factor's path fn enter_factors(&mut self) -> bool { - let len = self.path().len(); + let len = self.depth(); // enter the next factor if we can let mut entered = false; if self.factor_paths.len() < self.secondary.len() && self.is_path_end() { @@ -460,7 +461,7 @@ 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) -> usize { - let mut plen = self.path().len(); + let mut plen = self.depth(); let mut ascended = 0; loop { while self.factor_paths.last() == Some(&plen) { @@ -627,8 +628,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 + ZipperPath + Zipper, - SecondaryZ: ZipperMoving + ZipperPath + Zipper, + PrimaryZ: ZipperMoving + Zipper, + SecondaryZ: ZipperMoving + Zipper, { fn path_exists(&self) -> bool { if let Some(idx) = self.factor_idx(true) { @@ -663,11 +664,12 @@ 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 + ZipperPath, - SecondaryZ: ZipperMoving + ZipperPath, + PrimaryZ: ZipperMoving, + SecondaryZ: ZipperMoving, { - fn at_root(&self) -> bool { - self.path().is_empty() + #[inline] + fn depth(&self) -> usize { + self.primary.depth() } #[inline] fn focus_byte(&self) -> Option { @@ -765,7 +767,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim while remaining > 0 { self.exit_factors(); if let Some(idx) = self.factor_idx(false) { - let len = self.path().len() - self.factor_paths[idx]; + let len = self.depth() - self.factor_paths[idx]; let delta = len.min(remaining); self.secondary[idx].ascend(delta); self.primary.ascend(delta); @@ -834,7 +836,7 @@ pub trait ZipperProduct : ZipperMoving + Zipper + ZipperAbsolutePath + ZipperIte /// The returned slice will have a length of [`focus_factor`](ZipperProduct::focus_factor), so the factor /// containing the current focus has will not be included. /// - /// Indices will be offsets into the buffer returned by [path](ZipperMoving::path). To get an offset into + /// Indices will be offsets into the buffer returned by [path](ZipperPath::path). To get an offset into /// [origin_path](ZipperAbsolutePath::origin_path), add the length of the prefix path from /// [root_prefix_path](ZipperAbsolutePath::root_prefix_path). fn path_indices(&self) -> &[usize]; @@ -848,7 +850,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin, A: Allocator> ZipperProduct for Prod match self.factor_paths.last() { Some(factor_path_len) => { let factor_idx = self.factor_paths.len(); - if *factor_path_len < self.path().len() { + if *factor_path_len < self.depth() { factor_idx } else { factor_idx - 1 @@ -1473,8 +1475,11 @@ mod tests { z.descend_to([3, 196, 50, 193, 52, 3, 196, 50, 194, 49, 54]); assert_eq!(z.path_exists(), true); - assert_eq!(z.to_next_k_path(2), true); + //This steps across a factor boundary, so it checks the reporting through the seam + let mut observed = z.path().to_vec(); + assert_eq!(z.to_next_k_path_observed(2, &mut observed), true); assert_eq!(z.path_exists(), true); + assert_eq!(&observed[..], z.path()); assert_eq!(z.child_mask(), ByteMask::from_iter([3])); } diff --git a/src/utils/debug/diff_zipper.rs b/src/utils/debug/diff_zipper.rs index a1d07144..9808df12 100644 --- a/src/utils/debug/diff_zipper.rs +++ b/src/utils/debug/diff_zipper.rs @@ -46,6 +46,12 @@ impl Zipper for DiffZipper impl ZipperMoving for DiffZipper { + fn depth(&self) -> usize { + let a = self.a.depth(); + let b = self.b.depth(); + assert_eq!(a, b); + a + } fn at_root(&self) -> bool { let a = self.a.at_root(); let b = self.b.at_root(); @@ -243,8 +249,8 @@ impl ZipperPathBuffe impl ZipperIteration for DiffZipper { - fn to_next_val(&mut self) -> bool { - let a = self.a.to_next_val(); + fn to_next_val_observed(&mut self, obs: &mut Obs) -> bool { + let a = self.a.to_next_val_observed(obs); let b = self.b.to_next_val(); if self.log_moves { println!("DiffZipper: to_next_val") @@ -252,8 +258,22 @@ impl ZipperIteration f assert_eq!(a, b); a } - fn descend_first_k_path(&mut self, k: usize) -> bool { - let a = self.a.descend_first_k_path(k); + fn descend_last_path_observed(&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_last_path_observed(&mut (&mut hash_a, &mut *obs)); + let b = self.b.descend_last_path_observed(&mut hash_b); + if self.log_moves { + println!("DiffZipper: descend_last_path") + } + assert_eq!(a, b); + assert_eq!(hash_a, hash_b); + a + } + fn descend_first_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { + let a = self.a.descend_first_k_path_observed(k, obs); let b = self.b.descend_first_k_path(k); if self.log_moves { println!("DiffZipper: descend_first_k_path k={k}") @@ -261,8 +281,8 @@ impl ZipperIteration f assert_eq!(a, b); a } - fn to_next_k_path(&mut self, k: usize) -> bool { - let a = self.a.to_next_k_path(k); + fn to_next_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { + let a = self.a.to_next_k_path_observed(k, obs); let b = self.b.to_next_k_path(k); if self.log_moves { println!("DiffZipper: to_next_k_path k={k}") diff --git a/src/viz.rs b/src/viz.rs index 85f60ecf..5109a945 100644 --- a/src/viz.rs +++ b/src/viz.rs @@ -233,7 +233,7 @@ enum AsciiEdgeTarget { Value(*const V), } -fn build_ascii_graph_logical + ZipperMoving + ZipperIteration + ZipperValues>( +fn build_ascii_graph_logical + ZipperMoving + ZipperPath + ZipperIteration + ZipperValues>( mut z: Z, dc: &DrawConfig, ds: &mut DrawState, @@ -301,7 +301,7 @@ fn build_ascii_graph_logical(node: &TrieNode } } -fn viz_zipper_logical + ZipperMoving + ZipperIteration + ZipperValues>(mut z: Z, dc: &DrawConfig, ds: &mut DrawState) { +fn viz_zipper_logical + ZipperMoving + ZipperPath + ZipperIteration + ZipperValues>(mut z: Z, dc: &DrawConfig, ds: &mut DrawState) { let root_focus = z.get_focus(); let root_node = root_focus.borrow().unwrap(); let root_node_id = hash_pair(root_node.shared_node_id(), &[]); @@ -611,7 +611,7 @@ fn viz_zipper_logical ZipperInfallibleSubt } impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving for WriteZipperTracked<'a, 'path, V, A> { + #[inline] fn depth(&self) -> usize { self.z.depth() } 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() } @@ -581,6 +582,7 @@ 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> { + #[inline] fn depth(&self) -> usize { self.z.depth() } 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() } @@ -1007,6 +1009,11 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperCore<'a, } impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving for WriteZipperCore<'a, 'path, V, A> { + #[inline] + fn depth(&self) -> usize { + self.key.prefix_buf.len().saturating_sub(self.key.origin_path.len()) + } + #[inline] fn at_root(&self) -> bool { self.key.prefix_buf.len() <= self.key.origin_path.len() diff --git a/src/zipper.rs b/src/zipper.rs index efececff..65bc7d07 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -134,8 +134,17 @@ pub trait ZipperSubtries: Zi /// on how the zipper was created, and the origin will never be below the root. The origin of a given zipper /// will never change. pub trait ZipperMoving: Zipper { + /// Returns the number of bytes between the zipper's root and its current focus + /// + /// This is equal to `self.path().len()` for zippers that implement [`ZipperPath`], although + /// implementations are usually able to supply it more cheaply than by measuring a path. Zippers + /// that do not retain a contiguous path buffer must still be able to report their depth. + fn depth(&self) -> usize; + /// Returns `true` if the zipper's focus is at its root, and it cannot ascend further, otherwise returns `false` - fn at_root(&self) -> bool; + fn at_root(&self) -> bool { + self.depth() == 0 + } /// Returns the byte that was last descended to reach the zipper's focus, or `None` if that /// byte is unavailable @@ -370,8 +379,8 @@ pub trait ZipperMoving: Zipper { /// Moves the zipper's focus to the next sibling byte with the same parent /// - /// Returns `true` if the focus was moved. If the focus is already on the last byte among its siblings, - /// this method returns false, leving the focus unmodified. + /// Returns `Some(byte)` if the focus was moved. If the focus is already on the last byte among its siblings, + /// this method returns None, leving the focus unmodified. /// /// 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. @@ -396,8 +405,8 @@ pub trait ZipperMoving: Zipper { /// Moves the zipper's focus to the previous sibling byte with the same parent /// - /// Returns `true` if the focus was moved. If the focus is already on the first byte among its siblings, - /// this method returns false, leving the focus unmodified. + /// Returns `Some(byte)` if the focus was moved. If the focus is already on the first byte among its siblings, + /// this method returns None, leving the focus unmodified. /// /// 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. @@ -421,22 +430,48 @@ pub trait ZipperMoving: Zipper { } } - /// Advances the zipper to visit every existing path within the trie in a depth-first order + /// Convenience form of [`ZipperMoving::to_next_step_observed`] that does not observe path movement. + fn to_next_step(&mut self) -> bool { + self.to_next_step_observed(&mut ()) + } + + /// Advances the zipper to visit every existing path within the trie in a depth-first order, + /// reporting movement to `obs`. /// /// Returns `true` if the position of the zipper has moved, or `false` if the zipper has returned /// to the root - fn to_next_step(&mut self) -> bool { + /// `obs` is notified of every movement made, so a caller can track the zipper's location without + /// the zipper needing to maintain a path buffer of its own. Pass `&mut ()` to discard them. + fn to_next_step_observed(&mut self, obs: &mut Obs) -> bool { //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().is_none() { - if !self.ascend_byte() { - return false; + loop { + match self.to_next_sibling_byte() { + Some(byte) => { + //A sibling step replaces the last byte rather than adding one, so the + //observer sees the old byte retracted before the new one arrives. + obs.ascend(1); + obs.descend_to_byte(byte); + break + }, + None => { + if !self.ascend_byte() { + return false; + } + obs.ascend(1); + } } } } else { - return self.descend_first_byte().is_some() + return match self.descend_first_byte() { + Some(byte) => { + obs.descend_to_byte(byte); + true + }, + None => false + } } true } @@ -731,8 +766,17 @@ pub trait ZipperReadOnlyConditionalValues<'a, V>: ZipperValues { pub trait ZipperReadOnlyIteration<'a, V>: ZipperReadOnlyValues<'a, V> + ZipperIteration { /// Advances to the next value with behavior identical to [ZipperIteration::to_next_val], but returns /// a reference to the value or `None` if the zipper has encountered the root + /// + /// `obs` is notified of every movement made, so a caller can track the zipper's location without + /// the zipper needing to maintain a path buffer of its own. Pass `&mut ()` to discard them. + /// Convenience form of [`ZipperReadOnlyIteration::to_next_get_val_observed`] that does not observe path movement. fn to_next_get_val(&mut self) -> Option<&'a V> { - if self.to_next_val() { + self.to_next_get_val_observed(&mut ()) + } + + /// Advances to the next value and reports movement to `obs`. + fn to_next_get_val_observed(&mut self, obs: &mut Obs) -> Option<&'a V> { + if self.to_next_val_observed(obs) { let val = self.get_val(); debug_assert!(val.is_some()); val @@ -741,19 +785,33 @@ pub trait ZipperReadOnlyIteration<'a, V>: ZipperReadOnlyValues<'a, V> + ZipperIt } } - /// Deprecated alias for [ZipperReadOnlyIteration::to_next_get_val] + /// Convenience form of [`ZipperReadOnlyIteration::to_next_get_value_observed`] that does not observe path movement. #[deprecated] //GOAT-old-names fn to_next_get_value(&mut self) -> Option<&'a V> { - self.to_next_get_val() + self.to_next_get_val_observed(&mut ()) + } + + /// Deprecated observed alias for [`ZipperReadOnlyIteration::to_next_get_val_observed`]. + #[deprecated] //GOAT-old-names + fn to_next_get_value_observed(&mut self, obs: &mut Obs) -> Option<&'a V> { + self.to_next_get_val_observed(obs) } } /// Similar to [ZipperReadOnlyIteration]. See [ZipperReadOnlyConditionalValues] for an explanation about /// why this trait exists pub trait ZipperReadOnlyConditionalIteration<'a, V>: ZipperReadOnlyConditionalValues<'a, V> + ZipperIteration { - /// See [ZipperReadOnlyIteration::to_next_get_val] and [`witness`](ZipperReadOnlyConditionalValues::witness) + /// Convenience form of [`ZipperReadOnlyConditionalIteration::to_next_get_val_with_witness_observed`] that does not observe path movement. fn to_next_get_val_with_witness<'w>(&mut self, witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { - if self.to_next_val() { + self.to_next_get_val_with_witness_observed(witness, &mut ()) + } + + /// See [`ZipperReadOnlyIteration::to_next_get_val_observed`] and [`witness`](ZipperReadOnlyConditionalValues::witness). + /// + /// `obs` is notified of every movement made, so a caller can track the zipper's location without + /// the zipper needing to maintain a path buffer of its own. Pass `&mut ()` to discard them. + fn to_next_get_val_with_witness_observed<'w, Obs: PathObserver>(&mut self, witness: &'w Self::WitnessT, obs: &mut Obs) -> Option<&'w V> where 'a: 'w { + if self.to_next_val_observed(obs) { let val = self.get_val_with_witness(witness); debug_assert!(val.is_some()); val @@ -831,35 +889,49 @@ 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 /// -/// NOTE: the [ZipperPath] bound will be removed when these methods are changed to take a -/// [PathObserver], as [ZipperMoving::descend_until_observed] 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 { +/// Every method takes a [PathObserver], so these movements are available to "blind" zippers that do +/// not maintain a contiguous path buffer of their own. +pub trait ZipperIteration: ZipperMoving { + /// Convenience form of [`ZipperIteration::to_next_val_observed`] that does not observe path movement. + fn to_next_val(&mut self) -> bool { + self.to_next_val_observed(&mut ()) + } + /// Systematically advances to the next value accessible from the zipper, traversing in a depth-first - /// order + /// order and reporting movement to `obs`. /// /// Returns `true` if the zipper is positioned at the next value, or `false` if the zipper has /// encountered the root. - fn to_next_val(&mut self) -> bool { + /// + /// `obs` is notified of every movement made, so a caller can track the zipper's location without + /// the zipper needing to maintain a path buffer of its own. Pass `&mut ()` to discard them. + fn to_next_val_observed(&mut self, obs: &mut Obs) -> bool { loop { - if self.descend_first_byte().is_some() { + if let Some(byte) = self.descend_first_byte() { + obs.descend_to_byte(byte); if self.is_val() { return true } - if self.descend_until() { + if self.descend_until_observed(obs) { if self.is_val() { return true } } } else { 'ascending: loop { - if self.to_next_sibling_byte().is_some() { + if let Some(byte) = self.to_next_sibling_byte() { + //A sibling step replaces the last byte rather than adding one, so the + //observer sees the old byte retracted before the new one arrives. + obs.ascend(1); + obs.descend_to_byte(byte); if self.is_val() { return true } break 'ascending } else { - self.ascend_byte(); + if self.ascend_byte() { + obs.ascend(1); + } if self.at_root() { return false } @@ -869,25 +941,41 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { } } + /// Convenience form of [`ZipperIteration::descend_last_path_observed`] that does not observe path movement. + fn descend_last_path(&mut self) -> bool { + self.descend_last_path_observed(&mut ()) + } + /// Descends the zipper to the end of the last path (last by sort order) reachable by descent - /// from the current focus. + /// from the current focus, reporting movement to `obs`. /// /// This is equivalent to calling [ZipperMoving::descend_last_byte] in a loop. /// /// Returns `true` if the zipper has sucessfully descended, or `false` if the zipper was already /// at the end of a path. If this method returns `false` then the zipper will be in its original /// position. - fn descend_last_path(&mut self) -> bool { + /// + /// `obs` is notified of every movement made, so a caller can track the zipper's location without + /// the zipper needing to maintain a path buffer of its own. Pass `&mut ()` to discard them. + fn descend_last_path_observed(&mut self, obs: &mut Obs) -> bool { let mut any = false; - while self.descend_last_byte().is_some() { + while let Some(byte) = self.descend_last_byte() { any = true; - self.descend_until(); + obs.descend_to_byte(byte); + self.descend_until_observed(obs); } any } + /// Convenience form of [`ZipperIteration::descend_first_k_path_observed`] that does not observe + /// path movement. + fn descend_first_k_path(&mut self, k: usize) -> bool { + self.descend_first_k_path_observed(k, &mut ()) + } + /// Descends the zipper's focus `k` bytes, following the first child at each branch, and continuing - /// with depth-first exploration until a path that is `k` bytes from the focus has been found + /// with depth-first exploration until a path that is `k` bytes from the focus has been found, reporting + /// movement to `obs`. /// /// Returns `true` if the zipper has sucessfully descended `k` steps, or `false` otherwise. If this /// method returns `false` then the zipper will be in its original position. @@ -895,13 +983,22 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { /// WARNING: This is not a constant-time operation, and may be as bad as `order n` with respect to the paths /// below the zipper's focus. Although a typical cost is `order log n` or better. /// + /// `obs` is notified of every movement made, so a caller can track the zipper's location without + /// the zipper needing to maintain a path buffer of its own. Pass `&mut ()` to discard them. + /// /// See: [to_next_k_path](ZipperIteration::to_next_k_path) - fn descend_first_k_path(&mut self, k: usize) -> bool { - k_path_default_internal(self, k, self.path().len()) + fn descend_first_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { + k_path_default_internal(self, k, self.depth(), obs) + } + + /// Convenience form of [`ZipperIteration::to_next_k_path_observed`] that does not observe path movement. + fn to_next_k_path(&mut self, k: usize) -> bool { + self.to_next_k_path_observed(k, &mut ()) } /// Moves the zipper's focus to the next location with the same path length as the current focus, - /// following a depth-first exploration from a common root `k` steps above the current focus + /// following a depth-first exploration from a common root `k` steps above the current focus, reporting + /// movement to `obs`. /// /// Returns `true` if the zipper has sucessfully moved to a new location at the same level, or `false` /// if no further locations exist. If this method returns `false` then the zipper will be ascended `k` @@ -910,38 +1007,52 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { /// WARNING: This is not a constant-time operation, and may be as bad as `order n` with respect to the paths /// below the zipper's focus. Although a typical cost is `order log n` or better. /// + /// `obs` is notified of every movement made, so a caller can track the zipper's location without + /// the zipper needing to maintain a path buffer of its own. Pass `&mut ()` to discard them. + /// /// See: [descend_first_k_path](ZipperIteration::descend_first_k_path) - fn to_next_k_path(&mut self, k: usize) -> bool { - let base_idx = if self.path().len() >= k { - self.path().len() - k + fn to_next_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { + let depth = self.depth(); + let base_idx = if depth >= k { + depth - k } else { return false }; - k_path_default_internal(self, k, base_idx) + k_path_default_internal(self, k, base_idx, obs) } } /// 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. +/// `base_idx` is the [`depth`](ZipperMoving::depth) of the common root the traversal explores from, and +/// the traversal is finished when the depth returns to it. #[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, obs: &mut Obs) -> bool { loop { - if z.path().len() < base_idx + k { - while z.descend_first_byte().is_some() { - if z.path().len() == base_idx + k { return true } + if z.depth() < base_idx + k { + while let Some(byte) = z.descend_first_byte() { + obs.descend_to_byte(byte); + if z.depth() == base_idx + k { return true } } } - if z.to_next_sibling_byte().is_some() { - if z.path().len() == base_idx + k { return true } + //A sibling step replaces the last byte rather than adding one, so the observer sees the old + //byte retracted before the new one arrives + if let Some(byte) = z.to_next_sibling_byte() { + obs.ascend(1); + obs.descend_to_byte(byte); + if z.depth() == 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().is_some() { break } + while z.depth() > base_idx { + if z.ascend_byte() { + obs.ascend(1); + } + if z.depth() == base_idx { return false } + if let Some(byte) = z.to_next_sibling_byte() { + obs.ascend(1); + obs.descend_to_byte(byte); + break + } } } } @@ -1080,9 +1191,10 @@ use zipper_priv::*; macro_rules! zipper_impl_lens { (ZipperIteration $s: ident => $e:expr) => { - fn to_next_val(&mut $s) -> bool { $e.to_next_val() } - fn descend_first_k_path(&mut $s, k: usize) -> bool { $e.descend_first_k_path(k) } - fn to_next_k_path(&mut $s, k: usize) -> bool { $e.to_next_k_path(k) } + fn to_next_val_observed(&mut $s, obs: &mut Obs) -> bool { $e.to_next_val_observed(obs) } + fn descend_last_path_observed(&mut $s, obs: &mut Obs) -> bool { $e.descend_last_path_observed(obs) } + fn descend_first_k_path_observed(&mut $s, k: usize, obs: &mut Obs) -> bool { $e.descend_first_k_path_observed(k, obs) } + fn to_next_k_path_observed(&mut $s, k: usize, obs: &mut Obs) -> bool { $e.to_next_k_path_observed(k, obs) } }; (ZipperAbsolutePath $s: ident => $e:expr) => { fn origin_path(&$s) -> &[u8] { $e.origin_path() } @@ -1112,6 +1224,7 @@ macro_rules! zipper_impl_lens { fn alloc(&$s) -> A { $e.alloc() } }; (ZipperMoving $s: ident => $e:expr) => { + #[inline] fn depth(&$s) -> usize { $e.depth() } fn at_root(&$s) -> bool { $e.at_root() } #[inline] fn focus_byte(&$s) -> Option { $e.focus_byte() } fn reset(&mut $s) { $e.reset() } @@ -1132,7 +1245,7 @@ macro_rules! zipper_impl_lens { fn ascend_byte(&mut $s) -> bool { $e.ascend_byte() } 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() } + fn to_next_step_observed(&mut $s, obs: &mut Obs) -> bool { $e.to_next_step_observed(obs) } }; (ZipperInfallibleSubtries $s: ident => $e:expr) => { fn make_map(&$s) -> crate::PathMap { $e.make_map() } @@ -1150,10 +1263,10 @@ macro_rules! zipper_impl_lens { fn get_val_with_witness<'w>(&$s, witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { $e.get_val_with_witness(witness) } }; (ZipperReadOnlyIteration $s: ident => $e:expr) => { - fn to_next_get_val(&mut $s) -> Option<&'a V> { $e.to_next_get_val() } + fn to_next_get_val_observed(&mut $s, obs: &mut Obs) -> Option<&'a V> { $e.to_next_get_val_observed(obs) } }; (ZipperReadOnlyConditionalIteration $s: ident => $e:expr) => { - fn to_next_get_val_with_witness<'w>(&mut $s, witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { $e.to_next_get_val_with_witness(witness) } + fn to_next_get_val_with_witness_observed<'w, Obs: PathObserver>(&mut $s, witness: &'w Self::WitnessT, obs: &mut Obs) -> Option<&'w V> where 'a: 'w { $e.to_next_get_val_with_witness_observed(witness, obs) } }; (ZipperReadOnlySubtries $s: ident => $e:expr) => { type TrieRefT = >::TrieRefT; @@ -1388,7 +1501,7 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyP impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyIteration<'trie, V> for ReadZipperUntracked<'trie, '_, V, A> { - fn to_next_get_val(&mut self) -> Option<&'trie V> { unsafe{ self.z.to_next_get_val() } } + fn to_next_get_val_observed(&mut self, obs: &mut Obs) -> Option<&'trie V> { unsafe{ self.z.to_next_get_val_observed(obs) } } } impl<'a, 'path, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ReadZipperUntracked<'a, 'path, V, A> { @@ -1837,6 +1950,11 @@ pub(crate) mod read_zipper_core { impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ReadZipperCore<'trie, '_, V, A> { + #[inline] + fn depth(&self) -> usize { + self.prefix_buf.len().saturating_sub(self.origin_path.len()) + } + fn at_root(&self) -> bool { self.prefix_buf.len() <= self.origin_path.len() } @@ -2405,28 +2523,28 @@ pub(crate) mod read_zipper_core { //Then I need to port all the iter() conveniences over to use that new method impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperIteration for ReadZipperCore<'trie, '_, V, A> { - fn to_next_val(&mut self) -> bool { - unsafe{ self.to_next_get_val() }.is_some() + fn to_next_val_observed(&mut self, obs: &mut Obs) -> bool { + unsafe{ self.to_next_get_val_observed(obs) }.is_some() } - fn descend_first_k_path(&mut self, k: usize) -> bool { + fn descend_first_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { self.prepare_buffers(); debug_assert!(self.is_regularized()); let cur_tok = self.focus_node.iter_token_for_path(self.node_key()); self.focus_iter_token = cur_tok; - self.k_path_internal(k, self.prefix_buf.len()) + self.k_path_internal(k, self.prefix_buf.len(), obs) } - fn to_next_k_path(&mut self, k: usize) -> bool { + fn to_next_k_path_observed(&mut self, k: usize, obs: &mut Obs) -> bool { let base_idx = if self.path_len() >= k { self.prefix_buf.len() - k } else { - self.origin_path.len() + return false }; //De-regularize the zipper debug_assert!(self.is_regularized()); self.deregularize(); - self.k_path_internal(k, base_idx) + self.k_path_internal(k, base_idx, obs) } } @@ -2654,8 +2772,18 @@ pub(crate) mod read_zipper_core { unsafe { core::mem::transmute::, Option<&'a V>>(val) } } - /// See [ReadZipperCore::get_val] for explanation as to why this is unsafe + /// Convenience form of [`ReadZipperCore::to_next_get_val_observed`] that does not observe path movement. pub(crate) unsafe fn to_next_get_val(&mut self) -> Option<&'a V> { + unsafe { self.to_next_get_val_observed(&mut ()) } + } + + /// See [ReadZipperCore::get_val] for explanation as to why this is unsafe + /// + /// `obs` is notified of every movement made. This zipper advances by rewinding `prefix_buf` + /// to a node boundary and then extending it by a whole key run, so the movements reported are + /// multi-byte and a descent may re-tread bytes the observer already held. The net position is + /// always correct; only the granularity differs from a byte-at-a-time traversal. + pub(crate) unsafe fn to_next_get_val_observed(&mut self, obs: &mut Obs) -> Option<&'a V> { self.prepare_buffers(); loop { if self.focus_iter_token == NODE_ITER_INVALID { @@ -2682,13 +2810,25 @@ pub(crate) mod read_zipper_core { let unmodifiable_len = origin_path_len - key_start; let unmodifiable_subkey = &self.prefix_buf[key_start..origin_path_len]; if unmodifiable_len > key_bytes.len() || &key_bytes[..unmodifiable_len] != unmodifiable_subkey { + obs.ascend(self.prefix_buf.len() - origin_path_len); self.prefix_buf.truncate(origin_path_len); return None } } + //The truncate may rewind past `origin_path_len`, but the bytes below the root are + //re-supplied identically by `key_bytes` (checked above), so the focus stays at or + //below the root. The observer tracks the path from the root, so `key_bytes` is + //reported from the point where the rewind lands relative to it. + //`get` rather than indexing, so the slicing carries no panic path and can be + //optimized away entirely when `obs` discards what it is given + let obs_skip = origin_path_len.saturating_sub(key_start); + obs.ascend(self.prefix_buf.len() - key_start - obs_skip); self.prefix_buf.truncate(key_start); self.prefix_buf.extend(key_bytes); + if let Some(reported) = key_bytes.get(obs_skip..) { + obs.descend_to(reported); + } match child_node { None => {}, @@ -2708,10 +2848,12 @@ pub(crate) mod read_zipper_core { if let Some((focus_node, iter_tok, prefix_offset)) = self.ancestors.pop() { *self.focus_node = focus_node; self.focus_iter_token = iter_tok; + obs.ascend(self.prefix_buf.len() - prefix_offset); self.prefix_buf.truncate(prefix_offset); } else { let new_len = self.origin_path.len(); self.focus_iter_token = NODE_ITER_INVALID; + obs.ascend(self.prefix_buf.len() - new_len); self.prefix_buf.truncate(new_len); return None } @@ -2841,8 +2983,13 @@ pub(crate) mod read_zipper_core { } /// Internal method that implements both `k_path...` methods above + /// + /// `obs` is notified of every movement made. As in [Self::to_next_get_val], the movements are + /// multi-byte because this zipper advances by rewinding `prefix_buf` to a node boundary and + /// extending it by a whole key run. #[inline] - fn k_path_internal(&mut self, k: usize, base_idx: usize) -> bool { + fn k_path_internal(&mut self, k: usize, base_idx: usize, obs: &mut Obs) -> bool { + let obs_floor = self.origin_path.len(); loop { //If either of these trip, the caller is probably misusing the API and likely didn't call // `descend_first_k_path` before calling `to_next_k_path` @@ -2870,6 +3017,7 @@ pub(crate) mod read_zipper_core { //Have we reached the root of this k_path iteration? if self.node_key_start() <= base_idx { self.focus_iter_token = NODE_ITER_FINISHED; + obs.ascend(self.prefix_buf.len().saturating_sub(base_idx.max(obs_floor))); self.prefix_buf.truncate(base_idx); return false } @@ -2877,10 +3025,12 @@ pub(crate) mod read_zipper_core { if let Some((focus_node, iter_tok, prefix_offset)) = self.ancestors.pop() { *self.focus_node = focus_node; self.focus_iter_token = iter_tok; + obs.ascend(self.prefix_buf.len().saturating_sub(prefix_offset.max(obs_floor))); self.prefix_buf.truncate(prefix_offset); } else { let new_len = self.origin_path.len(); self.focus_iter_token = NODE_ITER_INVALID; + obs.ascend(self.prefix_buf.len().saturating_sub(new_len)); self.prefix_buf.truncate(new_len); return false } @@ -2896,6 +3046,7 @@ pub(crate) mod read_zipper_core { if key_start < base_idx { let base_key_len = base_idx - key_start; //The number of bytes we should not modify if base_key_len > key_bytes.len() || &key_bytes[..base_key_len] != &self.prefix_buf[key_start..base_idx] { + obs.ascend(self.prefix_buf.len().saturating_sub(base_idx.max(obs_floor))); self.prefix_buf.truncate(base_idx); return false; } @@ -2906,8 +3057,17 @@ pub(crate) mod read_zipper_core { //If we got here, it means we're either going to continue to descend, or return a // result at `path_len == base_idx+k` let key_start = self.node_key_start(); + //`key_bytes` re-supplies everything from `key_start`, so the observer retreats to + //that point (or the origin, whichever is lower) and re-descends + //`get` rather than indexing, so the slicing carries no panic path and can be + //optimized away entirely when `obs` discards what it is given + let obs_skip = obs_floor.saturating_sub(key_start); + obs.ascend(self.prefix_buf.len().saturating_sub(key_start.max(obs_floor))); self.prefix_buf.truncate(key_start); self.prefix_buf.extend(key_bytes); + if let Some(reported) = key_bytes.get(obs_skip..) { + obs.descend_to(reported); + } if self.prefix_buf.len() <= k+base_idx { match child_node { @@ -2919,6 +3079,8 @@ pub(crate) mod read_zipper_core { }, } } else { + //The descent overshot `k`, so retract the excess from the observer as well + obs.ascend(self.prefix_buf.len() - (k+base_idx)); self.prefix_buf.truncate(k+base_idx); } @@ -3552,6 +3714,7 @@ pub(crate) mod zipper_moving_tests { assert_in_list(zipper.path(), &[b"roma", b"romu"]); 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.depth(), zipper.path().len()); assert_eq!(zipper.child_mask().iter().collect::>(), vec![b'i']); 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 @@ -3585,6 +3748,8 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.child_count(), 3); zipper.descend_to(b"an"); assert_eq!(zipper.path(), b"man"); + //`depth` is relative to the zipper's root, not the map root + assert_eq!(zipper.depth(), zipper.path().len()); assert_eq!(zipper.child_count(), 2); zipper.descend_to(b"e"); assert_eq!(zipper.path(), b"mane"); @@ -3601,6 +3766,7 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.child_count(), 3); assert_eq!(zipper.ascend_until(), 1); assert_eq!(zipper.path(), b""); + assert_eq!(zipper.depth(), 0); assert_eq!(zipper.child_count(), 1); assert_eq!(zipper.at_root(), true); assert_eq!(zipper.ascend_until(), 0); @@ -3775,12 +3941,16 @@ pub(crate) mod zipper_moving_tests { zip.descend_to(b"AAaDDd"); assert!(!zip.path_exists()); assert_eq!(zip.path(), b"AAaDDd"); + assert_eq!(zip.depth(), zip.path().len()); assert_eq!(zip.ascend_until(), 3); assert_eq!(zip.path(), b"AAa"); + assert_eq!(zip.depth(), zip.path().len()); assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"AA"); + assert_eq!(zip.depth(), zip.path().len()); assert_eq!(zip.ascend_until(), 2); assert_eq!(zip.path(), b""); + assert_eq!(zip.depth(), 0); assert_eq!(zip.ascend_until(), 0); } @@ -4089,7 +4259,9 @@ pub(crate) mod zipper_moving_tests { pub fn to_next_step_test1(mut zipper: Z) { let mut i = 0; - while zipper.to_next_step() { + let mut observed = Vec::::new(); + while zipper.to_next_step_observed(&mut observed) { + assert_eq!(&observed[..], zipper.path(), "observer disagreed with path at step {i}"); match i { 0 => assert_eq!(zipper.path(), b"a"), 4 => assert_eq!(zipper.path(), b"arrow"), @@ -4363,15 +4535,17 @@ pub(crate) mod zipper_iteration_tests { pub const ZIPPER_ITER_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"rom'i", b"roman", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus"]; /// Simply calls `to_next_val` over the whole trie, ensuring all paths are visited exactly once - pub fn zipper_iter_test1<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn zipper_iter_test1<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { let keys = ZIPPER_ITER_TEST1_KEYS; //Test iteration of the whole tree let mut idx = 0; + let mut observed = Vec::::new(); assert_eq!(zipper.is_val(), false); - while zipper.to_next_val() { + while zipper.to_next_val_observed(&mut observed) { println!("{idx} {}", std::str::from_utf8(zipper.path()).unwrap()); assert_eq!(keys[idx], zipper.path()); + assert_eq!(&observed[..], zipper.path(), "observer disagreed with path at value {idx}"); idx += 1; } assert_eq!(idx, keys.len()); @@ -4384,13 +4558,15 @@ pub(crate) mod zipper_iteration_tests { }).collect() } - pub fn zipper_iter_test2<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn zipper_iter_test2<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { //Test iterating using a zipper that has a root that is not the map root let mut count: usize = 0; - while zipper.to_next_val() { + let mut observed = Vec::::new(); + while zipper.to_next_val_observed(&mut observed) { assert_eq!(zipper.is_val(), true); assert_eq!(zipper.path(), count.to_be_bytes()); + assert_eq!(&observed[..], zipper.path(), "observer disagreed with path at value {count}"); count += 1; } assert_eq!(count, ZIPPER_ITER_TEST2_COUNT); @@ -4409,7 +4585,7 @@ pub(crate) mod zipper_iteration_tests { b":9:muckymuck:5:raker:", ]; - pub fn k_path_test1<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test1<'a, Z: ZipperIteration + ZipperPath>(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).is_some()); @@ -4420,36 +4596,37 @@ pub(crate) mod zipper_iteration_tests { assert!(zipper.descend_indexed_byte(0).is_some()); assert_eq!(zipper.child_count(), 6); + //The observer starts in sync with the zipper, and must track it through every k_path call + let mut observed = zipper.path().to_vec(); + //Start iterating over all the symbols of length=sym_len - assert_eq!(zipper.descend_first_k_path(sym_len+1), true); + assert_eq!(zipper.descend_first_k_path_observed(sym_len+1, &mut observed), true); //This should have taken us to "above:" assert_eq!(zipper.path(), b"5:above:"); + assert_eq!(&observed[..], zipper.path()); //Go to the next symbol. // Two interesting things will happen. First, we blow past "err" because its path length is // shorter than `k`. Second, we will stop in the middle of "erronious". // These situations would be caused by an encode bug. Which hopefully we won't have in real // paths. But the parser should recognize the last digit of the path isn't ':' - assert_eq!(zipper.to_next_k_path(sym_len+1), true); + assert_eq!(zipper.to_next_k_path_observed(sym_len+1, &mut observed), true); assert_eq!(zipper.path(), b"5:erroni"); + assert_eq!(&observed[..], zipper.path()); assert_ne!(zipper.path().last(), Some(&b':')); //Now we'll move on to some correctly formed symbols - assert_eq!(zipper.to_next_k_path(sym_len+1), true); - assert_eq!(zipper.path(), b"5:error:"); - assert_eq!(zipper.to_next_k_path(sym_len+1), true); - assert_eq!(zipper.path(), b"5:hello:"); - assert_eq!(zipper.to_next_k_path(sym_len+1), true); - assert_eq!(zipper.path(), b"5:mucky:"); - assert_eq!(zipper.to_next_k_path(sym_len+1), true); - assert_eq!(zipper.path(), b"5:roger:"); - assert_eq!(zipper.to_next_k_path(sym_len+1), true); - assert_eq!(zipper.path(), b"5:zebra:"); + for expected in [b"5:error:", b"5:hello:", b"5:mucky:", b"5:roger:", b"5:zebra:"] { + assert_eq!(zipper.to_next_k_path_observed(sym_len+1, &mut observed), true); + assert_eq!(zipper.path(), expected); + assert_eq!(&observed[..], zipper.path()); + } //The last step should return false, and put us back to where we began - assert_eq!(zipper.to_next_k_path(sym_len+1), false); + assert_eq!(zipper.to_next_k_path_observed(sym_len+1, &mut observed), false); assert_eq!(zipper.path(), b"5:"); + assert_eq!(&observed[..], zipper.path()); assert_eq!(zipper.child_count(), 6); } @@ -4461,11 +4638,13 @@ pub(crate) mod zipper_iteration_tests { }).collect() } - pub fn k_path_test2<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test2<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { - zipper.descend_first_k_path(5); + let mut observed = Vec::::new(); + zipper.descend_first_k_path_observed(5, &mut observed); let mut count = 1; - while zipper.to_next_k_path(5) { + while zipper.to_next_k_path_observed(5, &mut observed) { + assert_eq!(&observed[..], zipper.path()); count += 1; } assert_eq!(count, K_PATH_TEST2_COUNT); @@ -4473,7 +4652,7 @@ pub(crate) mod zipper_iteration_tests { pub const K_PATH_TEST3_KEYS: &[&[u8]] = &[b":1a1A", b":1a1B", b":1a1C", b":1b1A", b":1b1B", b":1b1C", b":1c1A"]; - pub fn k_path_test3<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test3<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { //Scan over the first symbols in the path (lower case letters) zipper.descend_to(b"1"); @@ -4501,25 +4680,35 @@ pub(crate) mod zipper_iteration_tests { assert_eq!(zipper.path(), b"1a1"); //Recursively scan nested symbols within a scan of the first outer symbols + //A single observer tracks this whole nested sequence, since the only movements are k_path calls zipper.reset(); zipper.descend_to(b"1"); assert!(zipper.path_exists()); - assert_eq!(zipper.descend_first_k_path(1), true); + let mut observed = zipper.path().to_vec(); + assert_eq!(zipper.descend_first_k_path_observed(1, &mut observed), true); assert_eq!(zipper.path(), b"1a"); - assert_eq!(zipper.descend_first_k_path(2), true); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.descend_first_k_path_observed(2, &mut observed), true); assert_eq!(zipper.path(), b"1a1A"); - assert_eq!(zipper.to_next_k_path(2), true); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(2, &mut observed), true); assert_eq!(zipper.path(), b"1a1B"); - assert_eq!(zipper.to_next_k_path(2), true); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(2, &mut observed), true); assert_eq!(zipper.path(), b"1a1C"); - assert_eq!(zipper.to_next_k_path(2), false); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(2, &mut observed), false); assert_eq!(zipper.path(), b"1a"); - assert_eq!(zipper.to_next_k_path(1), true); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(1, &mut observed), true); assert_eq!(zipper.path(), b"1b"); - assert_eq!(zipper.to_next_k_path(1), true); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(1, &mut observed), true); assert_eq!(zipper.path(), b"1c"); - assert_eq!(zipper.to_next_k_path(1), false); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(1, &mut observed), false); assert_eq!(zipper.path(), b"1"); + assert_eq!(&observed[..], zipper.path()); //Similar to above, but inter-operating with `descend_indexed_byte` zipper.reset(); @@ -4600,11 +4789,13 @@ pub(crate) mod zipper_iteration_tests { &[192, 215, 69, 171, 218, 187, 202, 120, 92, 33, 14, 77, 34, 46, 40, 93, 135, 117, 152], ]; - pub fn k_path_test4<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test4<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { - zipper.descend_first_k_path(5); + let mut observed = Vec::::new(); + zipper.descend_first_k_path_observed(5, &mut observed); let mut count = 1; - while zipper.to_next_k_path(5) { + while zipper.to_next_k_path_observed(5, &mut observed) { + assert_eq!(&observed[..], zipper.path()); count += 1; } assert_eq!(count, K_PATH_TEST4_KEYS.len()); @@ -4620,13 +4811,17 @@ pub(crate) mod zipper_iteration_tests { &[3, 193, 4, 194, 1, 43, 3, 193, 34, 193], ]; - pub fn k_path_test5<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test5<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { zipper.descend_to(&[3, 193, 4, 194, 1, 43, 3, 193, 8, 194, 1, 45, 194]); assert!(zipper.path_exists()); - assert_eq!(zipper.descend_first_k_path(2), true); + //This straddles a node boundary, so it exercises the multi-byte movement reporting + let mut observed = zipper.path().to_vec(); + assert_eq!(zipper.descend_first_k_path_observed(2, &mut observed), true); assert_eq!(zipper.path(), &[3, 193, 4, 194, 1, 43, 3, 193, 8, 194, 1, 45, 194, 1, 46]); - assert_eq!(zipper.to_next_k_path(2), false); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(2, &mut observed), false); assert_eq!(zipper.path(), &[3, 193, 4, 194, 1, 43, 3, 193, 8, 194, 1, 45, 194]); + assert_eq!(&observed[..], zipper.path()); } pub const K_PATH_TEST6_KEYS: &[&[u8]] = &[ @@ -4636,9 +4831,9 @@ pub(crate) mod zipper_iteration_tests { /// This tests the k_path methods in the context of using them recursively, to shake out /// bugs caused by invalidating the iter token - pub fn k_path_test6<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test6<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { - fn test_loop<'a, Z: ZipperMoving + ZipperIteration, AscendF: Fn(&mut Z, usize), DescendF: Fn(&mut Z, &[u8])>(zipper: &mut Z, descend_f: DescendF, ascend_f: AscendF) { + fn test_loop<'a, Z: ZipperMoving + ZipperIteration + ZipperPath, AscendF: Fn(&mut Z, usize), DescendF: Fn(&mut Z, &[u8])>(zipper: &mut Z, descend_f: DescendF, ascend_f: AscendF) { zipper.reset(); //L0 descent @@ -4720,20 +4915,24 @@ pub(crate) mod zipper_iteration_tests { ]; /// This uses the k_path methods to descend and then re-ascend a trie, one step at a time. - pub fn k_path_test7<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test7<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { let keys = K_PATH_TEST7_KEYS; + //Only k_path calls move this zipper, so one observer tracks the whole descent and re-ascent + let mut observed = Vec::::new(); for i in 0..keys[0].len() { assert_eq!(zipper.path(), &keys[0][..i]); - assert!(zipper.descend_first_k_path(1)); + assert_eq!(&observed[..], zipper.path()); + assert!(zipper.descend_first_k_path_observed(1, &mut observed)); } for i in (0..keys[0].len()).rev() { assert_eq!(zipper.path(), &keys[0][..=i]); + assert_eq!(&observed[..], zipper.path()); if i != 17 { - assert!(!zipper.to_next_k_path(1)); + assert!(!zipper.to_next_k_path_observed(1, &mut observed)); } else { - assert!(zipper.to_next_k_path(1)); - assert!(!zipper.to_next_k_path(1)); + assert!(zipper.to_next_k_path_observed(1, &mut observed)); + assert!(!zipper.to_next_k_path_observed(1, &mut observed)); } } } @@ -4741,15 +4940,18 @@ pub(crate) mod zipper_iteration_tests { pub const K_PATH_TEST8_KEYS: &[&[u8]] = &[b"ABCDEFGHIJKLMNOPQRSTUVWXYZ", b"ab",]; /// Tests `..k_path` after `descend_to_byte` - pub fn k_path_test8<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test8<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { zipper.reset(); zipper.descend_to_byte(b'A'); assert_eq!(zipper.path_exists(), true); - assert_eq!(zipper.descend_first_k_path(1), true); + let mut observed = zipper.path().to_vec(); + assert_eq!(zipper.descend_first_k_path_observed(1, &mut observed), true); assert_eq!(zipper.path(), b"AB"); - assert_eq!(zipper.to_next_k_path(1), false); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(1, &mut observed), false); assert_eq!(zipper.path(), b"A"); + assert_eq!(&observed[..], zipper.path()); } pub const K_PATH_TEST9_KEYS: &[&[u8]] = &[ @@ -4759,13 +4961,16 @@ pub(crate) mod zipper_iteration_tests { ]; /// Tests `..k_path` in a subtrie without attitional branches to descend, when the outer trie does have branches - pub fn k_path_test9<'a, Z: ZipperIteration>(mut zipper: Z) { + pub fn k_path_test9<'a, Z: ZipperIteration + ZipperPath>(mut zipper: Z) { zipper.reset(); - assert_eq!(zipper.descend_first_k_path(1), true); + let mut observed = Vec::::new(); + assert_eq!(zipper.descend_first_k_path_observed(1, &mut observed), true); assert_eq!(zipper.path(), &[1]); - assert_eq!(zipper.to_next_k_path(1), false); + assert_eq!(&observed[..], zipper.path()); + assert_eq!(zipper.to_next_k_path_observed(1, &mut observed), false); assert_eq!(zipper.path(), &[]); + assert_eq!(&observed[..], zipper.path()); } } @@ -5009,9 +5214,12 @@ mod tests { //This tests iteration over an empty map, with no activity at all let mut map = PathMap::::new(); + let mut observed = Vec::::new(); let mut zipper = map.read_zipper(); - assert_eq!(zipper.to_next_val(), false); - assert_eq!(zipper.to_next_val(), false); + assert_eq!(zipper.to_next_val_observed(&mut observed), false); + assert_eq!(zipper.to_next_val_observed(&mut observed), false); + //A `false` return means the zipper came back to its root, so the observer must be empty + assert_eq!(&observed[..], b""); drop(zipper); //Now test some operations that create nodes, but not values @@ -5020,9 +5228,11 @@ mod tests { drop(_wz); drop(map_head); + let mut observed = Vec::::new(); let mut zipper = map.read_zipper(); - assert_eq!(zipper.to_next_val(), false); - assert_eq!(zipper.to_next_val(), false); + assert_eq!(zipper.to_next_val_observed(&mut observed), false); + assert_eq!(zipper.to_next_val_observed(&mut observed), false); + assert_eq!(&observed[..], b""); } /// Tests iterating a subtrie created by a descended WriteZipper @@ -5181,14 +5391,18 @@ mod tests { zipper.descend_to(b"n"); assert_eq!(format!("roma{}", std::str::from_utf8(zipper.path()).unwrap()), "roman"); assert_eq!(std::str::from_utf8(zipper.origin_path()).unwrap(), "roman"); - zipper.to_next_val(); + let mut observed = zipper.path().to_vec(); + zipper.to_next_val_observed(&mut observed); assert_eq!(format!("roma{}", std::str::from_utf8(zipper.path()).unwrap()), "romane"); assert_eq!(std::str::from_utf8(zipper.origin_path()).unwrap(), "romane"); - zipper.to_next_val(); + assert_eq!(&observed[..], zipper.path()); + zipper.to_next_val_observed(&mut observed); assert_eq!(format!("roma{}", std::str::from_utf8(zipper.path()).unwrap()), "romanus"); assert_eq!(std::str::from_utf8(zipper.origin_path()).unwrap(), "romanus"); - zipper.to_next_val(); + assert_eq!(&observed[..], zipper.path()); + zipper.to_next_val_observed(&mut observed); assert_eq!(zipper.path().len(), 0); + assert_eq!(observed.len(), 0); } #[test] @@ -5349,7 +5563,9 @@ mod tests { let btm: PathMap = rs.into_iter().enumerate().map(|(i, k)| (k, i as u64)).collect(); let mut rz = btm.read_zipper(); - assert!(rz.descend_last_path()); + let mut observed = Vec::::new(); + assert!(rz.descend_last_path_observed(&mut observed)); assert_eq!(rz.path(), b"rubicundus"); + assert_eq!(&observed[..], rz.path()); } } diff --git a/src/zipper_tracking.rs b/src/zipper_tracking.rs index ce046f36..884e345f 100644 --- a/src/zipper_tracking.rs +++ b/src/zipper_tracking.rs @@ -135,7 +135,7 @@ impl Conflict { /* at this point zipper is either focued on the given path (when it exists) , or the procedure broke out early, because it was determined that the path does not exist */ { - if zipper.path().len() == path.len() { + if zipper.depth() == path.len() { let mut subtree = zipper.fork_read_zipper(); match subtree.to_next_val() { false => Ok(()), @@ -157,7 +157,7 @@ impl Conflict { let mut zipper = all_paths.read_zipper(); match Conflict::check_for_lock_along_path(path, &mut zipper) { None => { - if zipper.path().len() == path.len() { + if zipper.depth() == path.len() { let mut subtree = zipper.fork_read_zipper(); match subtree.to_next_get_val() { None => Ok(()),