From 2cdf46f50ada1f32ab19946865b10d9f005afc26 Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Wed, 24 Sep 2025 22:32:06 -0500 Subject: [PATCH 01/18] separate zipper path method --- src/experimental.rs | 5 +- src/morphisms.rs | 2 +- src/overlay_zipper.rs | 30 +++-- src/paths_serialization.rs | 2 +- src/prefix_zipper.rs | 24 ++-- src/product_zipper.rs | 63 +++++---- src/tree_serialization.rs | 8 +- src/utils/debug/diff_zipper.rs | 15 ++- src/write_zipper.rs | 73 ++++++++-- src/zipper.rs | 234 ++++++++++++++++++--------------- src/zipper_tracking.rs | 2 +- 11 files changed, 282 insertions(+), 176 deletions(-) diff --git a/src/experimental.rs b/src/experimental.rs index 98b2386c..2d61a075 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -37,7 +37,6 @@ impl ZipperPathBuffer for FullZipper { impl ZipperMoving for FullZipper { fn at_root(&self) -> bool { self.path.len() == 0 } fn reset(&mut self) { self.path.clear() } - fn path(&self) -> &[u8] { &self.path[..] } fn val_count(&self) -> usize { usize::MAX/2 } // usize::MAX is a dangerous default for overflow fn descend_to>(&mut self, k: K) -> bool { self.path.extend_from_slice(k.as_ref()); @@ -82,6 +81,10 @@ impl ZipperMoving for FullZipper { fn to_prev_sibling_byte(&mut self) -> bool { self.to_sibling(false) } } +impl ZipperPath for FullZipper { + fn path(&self) -> &[u8] { &self.path[..] } +} + impl FullZipper { fn to_sibling(&mut self, next: bool) -> bool { if self.path.is_empty() { return false } // right? diff --git a/src/morphisms.rs b/src/morphisms.rs index cb237f7a..5cb8d47a 100644 --- a/src/morphisms.rs +++ b/src/morphisms.rs @@ -628,7 +628,7 @@ where V: 'static + Clone + Send + Sync + Unpin, W: Default, I: IntoIterator, - WZ: ZipperWriting + zipper::ZipperMoving, + WZ: ZipperWriting + zipper::ZipperMoving + zipper::ZipperPath, CoAlgF: Copy + FnMut(W, &[u8]) -> (&'a [u8], ByteMask, I, Option), { let (prefix, bm, ws, mv) = coalg_f(w, wz.path()); diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index 34a212b7..c16f87b8 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -17,7 +17,7 @@ // use crate::utils::{BitMask, ByteMask}; -use crate::zipper::{Zipper, ZipperMoving, ZipperIteration, ZipperValues}; +use crate::zipper::{Zipper, ZipperMoving, ZipperPath, ZipperIteration, ZipperValues}; /// Zipper that traverses a virtual trie formed by fusing the tries of two other zippers pub struct OverlayZipper @@ -68,8 +68,8 @@ impl impl OverlayZipper where - AZipper: ZipperMoving + ZipperValues, - BZipper: ZipperMoving + ZipperValues, + AZipper: ZipperMoving + ZipperValues + ZipperPath, + BZipper: ZipperMoving + ZipperValues + ZipperPath, Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, { fn to_sibling(&mut self, next: bool) -> bool { @@ -130,8 +130,8 @@ impl Zipper impl ZipperMoving for OverlayZipper where - AZipper: ZipperMoving + ZipperValues, - BZipper: ZipperMoving + ZipperValues, + AZipper: ZipperMoving + ZipperValues + ZipperPath, + BZipper: ZipperMoving + ZipperValues + ZipperPath, Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, { fn at_root(&self) -> bool { @@ -143,10 +143,6 @@ impl ZipperMoving self.b.reset(); } - fn path(&self) -> &[u8] { - self.a.path() - } - fn val_count(&self) -> usize { todo!() } @@ -303,11 +299,23 @@ impl ZipperMoving } } +impl ZipperPath + for OverlayZipper + where + AZipper: ZipperMoving + ZipperValues + ZipperPath, + BZipper: ZipperMoving + ZipperValues + ZipperPath, + Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, +{ + fn path(&self) -> &[u8] { + self.a.path() + } +} + impl ZipperIteration for OverlayZipper where - AZipper: ZipperMoving + ZipperValues, - BZipper: ZipperMoving + ZipperValues, + AZipper: ZipperMoving + ZipperValues + ZipperPath, + BZipper: ZipperMoving + ZipperValues + ZipperPath, Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, { } diff --git a/src/paths_serialization.rs b/src/paths_serialization.rs index 088fc500..0b0f4238 100644 --- a/src/paths_serialization.rs +++ b/src/paths_serialization.rs @@ -279,7 +279,7 @@ pub fn for_each_deserialized_path st #[cfg(test)] mod test { - use crate::zipper::{ZipperIteration, ZipperValues, ZipperMoving}; + use crate::zipper::{ZipperIteration, ZipperValues, ZipperPath}; use super::*; #[cfg(not(miri))] // miri really hates the zlib-ng-sys C API diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index f95f78d3..0006f3d9 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -57,7 +57,7 @@ pub struct PrefixZipper<'prefix, Z> { impl<'prefix, Z> PrefixZipper<'prefix, Z> where - Z: ZipperMoving + Z: ZipperMoving + ZipperPath { /// Creates a new `PrefixZipper` wrapping the supplied `source` zipper and prepending the /// supplied `prefix` @@ -244,7 +244,7 @@ impl<'prefix, 'source, Z, V> ZipperReadOnlyConditionalValues<'source, V> } impl<'prefix, Z> ZipperPathBuffer for PrefixZipper<'prefix, Z> - where Z: ZipperMoving + where Z: ZipperMoving + ZipperPath { unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { assert!(self.path.capacity() >= len); @@ -294,7 +294,7 @@ impl<'prefix, Z> Zipper for PrefixZipper<'prefix, Z> impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> where - Z: ZipperMoving + Z: ZipperMoving + ZipperPath { fn at_root(&self) -> bool { match self.position { @@ -311,11 +311,6 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.set_valid(0); } - #[inline] - fn path(&self) -> &[u8] { - &self.path[self.origin_depth..] - } - fn val_count(&self) -> usize { unimplemented!("method will probably get removed") } @@ -453,6 +448,16 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> } } +impl<'prefix, Z> ZipperPath for PrefixZipper<'prefix, Z> + where + Z: ZipperMoving + ZipperPath +{ + #[inline] + fn path(&self) -> &[u8] { + &self.path[self.origin_depth..] + } +} + /// An interface for a [Zipper] to support accessing the full path buffer used to create the zipper impl<'prefix, Z> ZipperAbsolutePath for PrefixZipper<'prefix, Z> where Z: ZipperAbsolutePath @@ -489,8 +494,7 @@ impl<'prefix, Z, V> ZipperForking for PrefixZipper<'prefix, Z> mod tests { use super::PrefixZipper; use crate::trie_map::PathMap; - use crate::zipper::ZipperMoving; - use crate::zipper::ZipperAbsolutePath; + use crate::zipper::{ZipperMoving, ZipperPath, ZipperAbsolutePath}; const PATHS1: &[(&[u8], u64)] = &[ (b"0000", 0), (b"00000", 1), diff --git a/src/product_zipper.rs b/src/product_zipper.rs index cf42a54f..c9b632f9 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -183,10 +183,6 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.factor_paths.clear(); self.z.reset() } - #[inline] - fn path(&self) -> &[u8] { - self.z.path() - } fn val_count(&self) -> usize { //GOAT!!! I think val_count properly belongs in the morphisms module unimplemented!() @@ -285,6 +281,13 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } } +impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ProductZipper<'_, 'trie, V, A> { + #[inline] + fn path(&self) -> &[u8] { + self.z.path() + } +} + impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperIteration for ProductZipper<'_, 'trie, V, A> { } //Use the default impl for all methods impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperValues for ProductZipper<'_, 'trie, V, A> { @@ -366,8 +369,8 @@ pub struct ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving, - SecondaryZ: ZipperMoving, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, { /// Creates a new `ProductZipper` from the provided zippers /// @@ -514,8 +517,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperAbsolutePath for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperAbsolutePath, - SecondaryZ: ZipperMoving, + PrimaryZ: ZipperAbsolutePath + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, { fn origin_path(&self) -> &[u8] { self.primary.origin_path() } fn root_prefix_path(&self) -> &[u8] { self.primary.root_prefix_path() } @@ -525,8 +528,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperConcretePriv for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperConcretePriv, - SecondaryZ: ZipperMoving + ZipperConcretePriv, + PrimaryZ: ZipperMoving + ZipperPath + ZipperConcretePriv, + SecondaryZ: ZipperMoving + ZipperPath + ZipperConcretePriv, { fn shared_node_id(&self) -> Option { if let Some(idx) = self.factor_idx(true) { @@ -541,8 +544,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperConcrete for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperConcrete, - SecondaryZ: ZipperMoving + ZipperConcrete, + PrimaryZ: ZipperMoving + ZipperPath + ZipperConcrete, + SecondaryZ: ZipperMoving + ZipperPath + ZipperConcrete, { fn is_shared(&self) -> bool { if let Some(idx) = self.factor_idx(true) { @@ -569,8 +572,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperValues for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperValues, - SecondaryZ: ZipperMoving + ZipperValues, + PrimaryZ: ZipperMoving + ZipperPath + ZipperValues, + SecondaryZ: ZipperMoving + ZipperPath + ZipperValues, { fn val(&self) -> Option<&V> { if let Some(idx) = self.factor_idx(true) { @@ -585,8 +588,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperReadOnlyValues<'trie, V> for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, + PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, { fn get_val(&self) -> Option<&'trie V> { if let Some(idx) = self.factor_idx(true) { @@ -601,8 +604,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperReadOnlyConditionalValues<'trie, V> for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperReadOnlyValues<'trie, V>, + PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, { type WitnessT = (); fn witness<'w>(&self) -> Self::WitnessT { () } @@ -614,8 +617,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperReadOnlyConditionalValues<'trie, V> impl<'trie, PrimaryZ, SecondaryZ, V> Zipper for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + Zipper, - SecondaryZ: ZipperMoving + Zipper, + PrimaryZ: ZipperMoving + ZipperPath + Zipper, + SecondaryZ: ZipperMoving + ZipperPath + Zipper, { fn path_exists(&self) -> bool { if let Some(idx) = self.factor_idx(true) { @@ -650,8 +653,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> Zipper for ProductZipperG<'trie, PrimaryZ, impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving, - SecondaryZ: ZipperMoving, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, { fn at_root(&self) -> bool { self.path().is_empty() @@ -663,10 +666,6 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim } self.primary.reset(); } - #[inline] - fn path(&self) -> &[u8] { - self.primary.path() - } fn val_count(&self) -> usize { unimplemented!("method will probably get removed") } @@ -774,6 +773,18 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim } } +impl<'trie, PrimaryZ, SecondaryZ, V> ZipperPath for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> + where + V: Clone + Send + Sync, + PrimaryZ: ZipperMoving + ZipperPath, + SecondaryZ: ZipperMoving + ZipperPath, +{ + #[inline] + fn path(&self) -> &[u8] { + self.primary.path() + } +} + impl<'trie, PrimaryZ, SecondaryZ, V> ZipperIteration for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where diff --git a/src/tree_serialization.rs b/src/tree_serialization.rs index 4705e349..eb91896e 100644 --- a/src/tree_serialization.rs +++ b/src/tree_serialization.rs @@ -34,7 +34,13 @@ pub fn serialize_fork, F: FnMut(usize, &[u8] } /// WIP -pub fn deserialize_fork + zipper::ZipperMoving, F: Fn(usize, &[u8]) -> V>(node: usize, wz: &mut WZ, source: &[u8], fv: F) -> std::io::Result { +pub fn deserialize_fork(node: usize, wz: &mut WZ, source: &[u8], fv: F) -> std::io::Result +where + V: TrieValue, + A: Allocator, + WZ: ZipperWriting + zipper::ZipperMoving + zipper::ZipperPath, + F: Fn(usize, &[u8]) -> V, +{ unsafe { // let mut recovered = 0; new_map_from_ana_jumping(wz, node, |n: usize, path: &[u8]| { diff --git a/src/utils/debug/diff_zipper.rs b/src/utils/debug/diff_zipper.rs index 445419b0..b3b568b0 100644 --- a/src/utils/debug/diff_zipper.rs +++ b/src/utils/debug/diff_zipper.rs @@ -60,12 +60,6 @@ impl ZipperMoving for DiffZi println!("DiffZipper: reset") } } - fn path(&self) -> &[u8] { - let a = self.a.path(); - let b = self.b.path(); - assert_eq!(a, b); - a - } fn val_count(&self) -> usize { let a = self.a.val_count(); let b = self.b.val_count(); @@ -194,6 +188,15 @@ impl ZipperMoving for DiffZi } } +impl ZipperPath for DiffZipper { + fn path(&self) -> &[u8] { + let a = self.a.path(); + let b = self.b.path(); + assert_eq!(a, b); + a + } +} + impl ZipperAbsolutePath for DiffZipper { fn origin_path(&self) -> &[u8] { diff --git a/src/write_zipper.rs b/src/write_zipper.rs index 2e63e885..b08bbb84 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -347,7 +347,6 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperSubtries impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving for WriteZipperTracked<'a, 'path, V, A> { fn at_root(&self) -> bool { self.z.at_root() } fn reset(&mut self) { self.z.reset() } - fn path(&self) -> &[u8] { self.z.path() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } @@ -361,7 +360,9 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn ascend_until(&mut self) -> bool { self.z.ascend_until() } fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } } - +impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperTracked<'a, 'path, V, A> { + fn path(&self) -> &[u8] { self.z.path() } +} impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> zipper_priv::ZipperPriv for WriteZipperTracked<'a, 'path, V, A> { type V = V; type A = A; @@ -502,7 +503,6 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperSubtries impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving for WriteZipperUntracked<'a, 'path, V, A> { fn at_root(&self) -> bool { self.z.at_root() } fn reset(&mut self) { self.z.reset() } - fn path(&self) -> &[u8] { self.z.path() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } @@ -516,6 +516,9 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn ascend_until(&mut self) -> bool { self.z.ascend_until() } fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } } +impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperUntracked<'a, 'path, V, A> { + fn path(&self) -> &[u8] { self.z.path() } +} impl<'a, 'k, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> zipper_priv::ZipperPriv for WriteZipperUntracked<'a, 'k, V, A> { type V = V; @@ -680,7 +683,6 @@ impl ZipperSubtries for Writ impl ZipperMoving for WriteZipperOwned { fn at_root(&self) -> bool { self.z.at_root() } fn reset(&mut self) { self.z.reset() } - fn path(&self) -> &[u8] { self.z.path() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } @@ -694,6 +696,9 @@ impl ZipperMoving for WriteZipperO fn ascend_until(&mut self) -> bool { self.z.ascend_until() } fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } } +impl ZipperPath for WriteZipperOwned { + fn path(&self) -> &[u8] { self.z.path() } +} impl zipper_priv::ZipperPriv for WriteZipperOwned { type V = V; @@ -960,14 +965,6 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving self.key.prefix_idx.clear(); } - fn path(&self) -> &[u8] { - if self.key.prefix_buf.len() > 0 { - &self.key.prefix_buf[self.key.origin_path.len()..] - } else { - &[] - } - } - fn val_count(&self) -> usize { let focus = self.get_focus(); if focus.is_none() { @@ -1046,6 +1043,58 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving debug_assert!(self.key.node_key().len() > 0); //We should never finish with a zero-length node-key true } + fn to_next_sibling_byte(&mut self) -> bool { + let cur_byte = match self.path().last() { + Some(byte) => *byte, + None => return false + }; + if !self.ascend_byte() { + return false + } + let mask = self.child_mask(); + match mask.next_bit(cur_byte) { + Some(byte) => { + let descended = self.descend_to_byte(byte); + debug_assert!(descended); + true + }, + None => { + self.descend_to_byte(cur_byte); + false + } + } + } + fn to_prev_sibling_byte(&mut self) -> bool { + let cur_byte = match self.path().last() { + Some(byte) => *byte, + None => return false + }; + if !self.ascend_byte() { + return false + } + let mask = self.child_mask(); + match mask.prev_bit(cur_byte) { + Some(byte) => { + let descended = self.descend_to_byte(byte); + debug_assert!(descended); + true + }, + None => { + let descended = self.descend_to_byte(cur_byte); + debug_assert!(descended); + false + } + } + } +} +impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperCore<'a, 'path, V, A> { + fn path(&self) -> &[u8] { + if self.key.prefix_buf.len() > 0 { + &self.key.prefix_buf[self.key.origin_path.len()..] + } else { + &[] + } + } } impl<'a, 'k, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> zipper_priv::ZipperPriv for WriteZipperCore<'a, 'k, V, A> { diff --git a/src/zipper.rs b/src/zipper.rs index 0a5b9d16..5b56edf1 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -111,9 +111,10 @@ pub trait ZipperSubtries: Zi /// will never change. pub trait ZipperMoving: Zipper { /// Returns `true` if the zipper's focus is at its root, and it cannot ascend further, otherwise returns `false` - fn at_root(&self) -> bool { - self.path().len() == 0 - } + fn at_root(&self) -> bool; + // -> bool { + // self.path().len() == 0 + // } /// Resets the zipper's focus back to its root fn reset(&mut self) { @@ -122,32 +123,12 @@ pub trait ZipperMoving: Zipper { } } - /// Returns the path from the zipper's root to the current focus - fn path(&self) -> &[u8]; - /// Returns the total number of values contained at and below the zipper's focus, including the focus itself /// /// WARNING: This is not a cheap method. It may have an order-N cost //GOAT! This doesn't belong here. Should be a function that uses a non-side-effect catamorphism fn val_count(&self) -> usize; - /// Moves the zipper's focus to a specific location specified by `path`, relative to the zipper's root - /// - /// Returns the number of bytes shared between the old and new location and whether the new location exists in the trie - fn move_to_path>(&mut self, path: K) -> (usize, bool) { - let path = path.as_ref(); - let p = self.path(); - let overlap = find_prefix_overlap(path, p); - let to_ascend = p.len() - overlap; - if overlap == 0 { // This heuristic can be fine-tuned for performance; the behavior of the two branches is equivalent - self.reset(); - (overlap, self.descend_to(path)) - } else { - self.ascend(to_ascend); - (overlap, self.descend_to(&path[overlap..])) - } - } - /// Moves the zipper deeper into the trie, to the `key` specified relative to the current zipper focus /// /// Returns `true` if the zipper points to an existing path within the tree, otherwise `false`. The @@ -291,27 +272,28 @@ pub trait ZipperMoving: Zipper { /// /// This method is equivalent to calling [ZipperMoving::ascend] with `1`, followed by [ZipperMoving::descend_indexed_byte] /// where the index passed is 1 more than the index of the current focus position. - fn to_next_sibling_byte(&mut self) -> bool { - let cur_byte = match self.path().last() { - Some(byte) => *byte, - None => return false - }; - if !self.ascend_byte() { - return false - } - let mask = self.child_mask(); - match mask.next_bit(cur_byte) { - Some(byte) => { - let descended = self.descend_to_byte(byte); - debug_assert!(descended); - true - }, - None => { - self.descend_to_byte(cur_byte); - false - } - } - } + fn to_next_sibling_byte(&mut self) -> bool; + // { + // let cur_byte = match self.path().last() { + // Some(byte) => *byte, + // None => return false + // }; + // if !self.ascend_byte() { + // return false + // } + // let mask = self.child_mask(); + // match mask.next_bit(cur_byte) { + // Some(byte) => { + // let descended = self.descend_to_byte(byte); + // debug_assert!(descended); + // true + // }, + // None => { + // self.descend_to_byte(cur_byte); + // false + // } + // } + // } /// Moves the zipper's focus to the previous sibling byte with the same parent /// @@ -320,28 +302,29 @@ pub trait ZipperMoving: Zipper { /// /// This method is equivalent to calling [Self::ascend] with `1`, followed by [Self::descend_indexed_byte] /// where the index passed is 1 less than the index of the current focus position. - fn to_prev_sibling_byte(&mut self) -> bool { - let cur_byte = match self.path().last() { - Some(byte) => *byte, - None => return false - }; - if !self.ascend_byte() { - return false - } - let mask = self.child_mask(); - match mask.prev_bit(cur_byte) { - Some(byte) => { - let descended = self.descend_to_byte(byte); - debug_assert!(descended); - true - }, - None => { - let descended = self.descend_to_byte(cur_byte); - debug_assert!(descended); - false - } - } - } + fn to_prev_sibling_byte(&mut self) -> bool; + // { + // let cur_byte = match self.path().last() { + // Some(byte) => *byte, + // None => return false + // }; + // if !self.ascend_byte() { + // return false + // } + // let mask = self.child_mask(); + // match mask.prev_bit(cur_byte) { + // Some(byte) => { + // let descended = self.descend_to_byte(byte); + // debug_assert!(descended); + // true + // }, + // None => { + // let descended = self.descend_to_byte(cur_byte); + // debug_assert!(descended); + // false + // } + // } + // } /// Advances the zipper to visit every existing path within the trie in a depth-first order /// @@ -456,7 +439,7 @@ pub trait ZipperReadOnlySubtries<'a, V: Clone + Send + Sync, A: Allocator = Glob /// An interface for advanced [Zipper] movements used for various types of iteration; such as iterating /// every value, or iterating all paths descending from a common root at a certain depth -pub trait ZipperIteration: ZipperMoving { +pub trait ZipperIteration: ZipperMoving + ZipperPath { /// Systematically advances to the next value accessible from the zipper, traversing in a depth-first /// order /// @@ -528,7 +511,7 @@ pub trait ZipperIteration: ZipperMoving { /// The default implementation of both [ZipperIteration::to_next_k_path] and [ZipperIteration::descend_first_k_path] #[inline] -fn k_path_default_internal(z: &mut Z, k: usize, base_idx: usize) -> bool { +fn k_path_default_internal(z: &mut Z, k: usize, base_idx: usize) -> bool { loop { if z.path().len() < base_idx + k { while z.descend_first_byte() { @@ -548,7 +531,7 @@ fn k_path_default_internal(z: &mut Z, k: usize, base_i } /// An interface for a [Zipper] to support accessing the full path buffer used to create the zipper -pub trait ZipperAbsolutePath: ZipperMoving { +pub trait ZipperAbsolutePath: ZipperPath { /// Returns the entire path from the zipper's origin to its current focus /// /// The zipper's origin depends on how the zipper was created. For zippers created directly from a @@ -590,8 +573,33 @@ pub trait ZipperConcrete: zipper_priv::ZipperConcretePriv { fn is_shared(&self) -> bool; } +/// Provides path to the current focus. +/// +/// If a zipper doesn't implement this trait, it is a "blind" zipper, +/// which doesn't track it's own path in the trie. +pub trait ZipperPath: ZipperMoving { + /// Returns the path from the zipper's root to the current focus + fn path(&self) -> &[u8]; + /// Moves the zipper's focus to a specific location specified by `path`, relative to the zipper's root + /// + /// Returns the number of bytes shared between the old and new location and whether the new location exists in the trie + fn move_to_path>(&mut self, path: K) -> (usize, bool) { + let path = path.as_ref(); + let p = self.path(); + let overlap = find_prefix_overlap(path, p); + let to_ascend = p.len() - overlap; + if overlap == 0 { // This heuristic can be fine-tuned for performance; the behavior of the two branches is equivalent + self.reset(); + (overlap, self.descend_to(path)) + } else { + self.ascend(to_ascend); + (overlap, self.descend_to(&path[overlap..])) + } + } +} + /// Provides more direct control over a [ZipperMoving] zipper's path buffer -pub trait ZipperPathBuffer: ZipperMoving { +pub trait ZipperPathBuffer: ZipperPath { /// Internal method to get the path, beyond its length. Panics if `len` > the path's capacity, or /// if the zipper is relative and doesn't have an `origin_path` /// @@ -704,7 +712,6 @@ impl Zipper for &mut Z where Z: Zipper { impl ZipperMoving for &mut Z where Z: ZipperMoving + Zipper { fn at_root(&self) -> bool { (**self).at_root() } fn reset(&mut self) { (**self).reset() } - fn path(&self) -> &[u8] { (**self).path() } fn val_count(&self) -> usize { (**self).val_count() } fn descend_to>(&mut self, k: K) -> bool { (**self).descend_to(k) } fn descend_to_existing>(&mut self, k: K) -> usize { (**self).descend_to_existing(k) } @@ -722,6 +729,10 @@ impl ZipperMoving for &mut Z where Z: ZipperMoving + Zipper { fn to_next_step(&mut self) -> bool { (**self).to_next_step() } } +impl ZipperPath for &mut Z where Z: ZipperPath { + fn path(&self) -> &[u8] { (**self).path() } +} + impl ZipperAbsolutePath for &mut Z where Z: ZipperAbsolutePath { fn origin_path(&self) -> &[u8] { (**self).origin_path() } fn root_prefix_path(&self) -> &[u8] { (**self).root_prefix_path() } @@ -839,8 +850,6 @@ impl ZipperSubtries for Read impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ReadZipperTracked<'trie, '_, V, A> { fn at_root(&self) -> bool { self.z.at_root() } fn reset(&mut self) { self.z.reset() } - #[inline] - fn path(&self) -> &[u8] { self.z.path() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_existing>(&mut self, k: K) -> usize { self.z.descend_to_existing(k) } @@ -858,6 +867,11 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } +impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ReadZipperTracked<'trie, '_, V, A> { + #[inline] + fn path(&self) -> &[u8] { self.z.path() } +} + impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyConditionalValues<'trie, V> for ReadZipperTracked<'trie, '_, V, A> { type WitnessT = ReadZipperWitness; fn witness<'w>(&self) -> ReadZipperWitness { self.z.witness() } @@ -987,8 +1001,6 @@ impl ZipperSubtries for Read impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ReadZipperUntracked<'trie, '_, V, A> { fn at_root(&self) -> bool { self.z.at_root() } fn reset(&mut self) { self.z.reset() } - #[inline] - fn path(&self) -> &[u8] { self.z.path() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_existing>(&mut self, k: K) -> usize { self.z.descend_to_existing(k) } @@ -1006,6 +1018,11 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } +impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ReadZipperUntracked<'trie, '_, V, A> { + #[inline] + fn path(&self) -> &[u8] { self.z.path() } +} + impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyValues<'trie, V> for ReadZipperUntracked<'trie, '_, V, A> { fn get_val(&self) -> Option<&'trie V> { self.z.get_val() } } @@ -1185,8 +1202,6 @@ impl ZipperSubtries for Read impl ZipperMoving for ReadZipperOwned { fn at_root(&self) -> bool { self.z.at_root() } fn reset(&mut self) { self.z.reset() } - #[inline] - fn path(&self) -> &[u8] { self.z.path() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_existing>(&mut self, k: K) -> usize { self.z.descend_to_existing(k) } @@ -1204,6 +1219,11 @@ impl ZipperMoving for ReadZipperOw fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } +impl ZipperPath for ReadZipperOwned { + #[inline] + fn path(&self) -> &[u8] { self.z.path() } +} + impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyConditionalValues<'trie, V> for ReadZipperOwned { type WitnessT = ReadZipperWitness; fn witness<'w>(&self) -> ReadZipperWitness { self.z.witness() } @@ -1505,15 +1525,6 @@ pub(crate) mod read_zipper_core { self.prefix_buf.truncate(self.origin_path.len()); } - #[inline] - fn path(&self) -> &[u8] { - if self.prefix_buf.len() > 0 { - &self.prefix_buf[self.origin_path.len()..] - } else { - &[] - } - } - fn val_count(&self) -> usize { if self.node_key().len() == 0 { val_count_below_root(*self.focus_node) + (self.is_val() as usize) @@ -1833,6 +1844,17 @@ pub(crate) mod read_zipper_core { } } + impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperPath for ReadZipperCore<'trie, '_, V, A> { + #[inline] + fn path(&self) -> &[u8] { + if self.prefix_buf.len() > 0 { + &self.prefix_buf[self.origin_path.len()..] + } else { + &[] + } + } + } + impl zipper_priv::ZipperPriv for ReadZipperCore<'_, '_, V, A> { type V = V; type A = A; @@ -3069,7 +3091,7 @@ pub(crate) mod zipper_moving_tests { /// from https://en.wikipedia.org/wiki/Radix_tree#/media/File:Patricia_trie.svg pub const ZIPPER_MOVING_BASIC_TEST_KEYS: &[&[u8]] = &[b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; - pub fn zipper_moving_basic_test(mut zipper: Z) { + pub fn zipper_moving_basic_test(mut zipper: Z) { fn assert_in_list(val: &[u8], list: &[&[u8]]) { for test_val in list { if *test_val == val { @@ -3115,7 +3137,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_WITH_ROOT_PATH_PATH: &[u8] = b"ro"; /// Tests creating a zipper at a specific key within a map - pub fn zipper_with_root_path(mut zipper: Z) { + pub fn zipper_with_root_path(mut zipper: Z) { //Test `descend_to` and `ascend_until` assert_eq!(zipper.path(), b""); @@ -3163,7 +3185,7 @@ pub(crate) mod zipper_moving_tests { // A wide shallow trie pub const ZIPPER_INDEXED_BYTE_TEST1_KEYS: &[&[u8]] = &[b"0", b"1", b"2", b"3", b"4", b"5", b"6"]; - pub fn zipper_indexed_bytes_test1(mut zip: Z) { + pub fn zipper_indexed_bytes_test1(mut zip: Z) { zip.descend_to("2"); assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 0); @@ -3220,7 +3242,7 @@ pub(crate) mod zipper_moving_tests { // A narrow deeper trie pub const ZIPPER_INDEXED_BYTE_TEST2_KEYS: &[&[u8]] = &[b"000", b"1Z", b"00AAA", b"00AA000", b"00AA00AAA"]; - pub fn zipper_indexed_bytes_test2(mut zip: Z) { + pub fn zipper_indexed_bytes_test2(mut zip: Z) { zip.descend_to("000"); assert_eq!(zip.is_val(), true); assert_eq!(zip.path(), b"000"); @@ -3252,7 +3274,7 @@ pub(crate) mod zipper_moving_tests { // Tests how descend_until treats values along paths pub const ZIPPER_DESCEND_UNTIL_TEST1_KEYS: &[&[u8]] = &[b"a", b"ab", b"abCDEf", b"abCDEfGHi"]; - pub fn zipper_descend_until_test1(mut zip: Z) { + pub fn zipper_descend_until_test1(mut zip: Z) { for key in ZIPPER_DESCEND_UNTIL_TEST1_KEYS { assert!(zip.descend_until()); assert_eq!(zip.path(), *key); @@ -3262,7 +3284,7 @@ pub(crate) mod zipper_moving_tests { // Test a 3-way branch, so we definitely don't have a pair node pub const ZIPPER_ASCEND_UNTIL_TEST1_KEYS: &[&[u8]] = &[b"AAa", b"AAb", b"AAc"]; - pub fn zipper_ascend_until_test1(mut zip: Z) { + pub fn zipper_ascend_until_test1(mut zip: Z) { assert!(!zip.descend_to(b"AAaDDd")); assert_eq!(zip.path(), b"AAaDDd"); assert!(zip.ascend_until()); @@ -3277,7 +3299,7 @@ pub(crate) mod zipper_moving_tests { // Test what's likely to be represented as a pair node pub const ZIPPER_ASCEND_UNTIL_TEST2_KEYS: &[&[u8]] = &[b"AAa", b"AAb"]; - pub fn zipper_ascend_until_test2(mut zip: Z) { + pub fn zipper_ascend_until_test2(mut zip: Z) { assert!(!zip.descend_to(b"AAaDDd")); assert_eq!(zip.path(), b"AAaDDd"); assert!(zip.ascend_until()); @@ -3292,7 +3314,7 @@ pub(crate) mod zipper_moving_tests { /// Test a straight-line trie pub const ZIPPER_ASCEND_UNTIL_TEST3_KEYS: &[&[u8]] = &[b"1", b"12", b"123", b"1234", b"12345"]; - pub fn zipper_ascend_until_test3(mut zip: Z) { + pub fn zipper_ascend_until_test3(mut zip: Z) { //First test that ascend_until stops when transitioning from non-existent path assert_eq!(zip.descend_to(b"123456"), false); @@ -3362,7 +3384,7 @@ pub(crate) mod zipper_moving_tests { /// Some paths encountered will be values only, some will be branches only, and some will be both pub const ZIPPER_ASCEND_UNTIL_TEST4_KEYS: &[&[u8]] = &[b"1", b"123", b"12345", b"1abc", b"1234abc"]; - pub fn zipper_ascend_until_test4(mut zip: Z) { + pub fn zipper_ascend_until_test4(mut zip: Z) { assert!(zip.descend_to(b"12345")); assert_eq!(zip.path(), b"12345"); @@ -3399,7 +3421,7 @@ pub(crate) mod zipper_moving_tests { /// Test ascending over a long key that spans multiple nodes pub const ZIPPER_ASCEND_UNTIL_TEST5_KEYS: &[&[u8]] = &[b"A", b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"]; - pub fn zipper_ascend_until_test5(mut zip: Z) { + pub fn zipper_ascend_until_test5(mut zip: Z) { //Test that ascend_until stops when transitioning from non-existent path assert_eq!(zip.descend_to(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB"), false); @@ -3416,9 +3438,9 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_INDEXED_MOVEMENT_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; - pub fn indexed_zipper_movement1(mut zipper: Z) { + pub fn indexed_zipper_movement1(mut zipper: Z) { //descends a single specific byte using `descend_indexed_byte`. Just for testing. A real user would use `descend_towards` - fn descend_byte(zipper: &mut Z, byte: u8) { + fn descend_byte(zipper: &mut Z, byte: u8) { for i in 0..zipper.child_count() { assert_eq!(zipper.descend_indexed_byte(i), true); if *zipper.path().last().unwrap() == byte { @@ -3521,7 +3543,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_DESCEND_TO_EXISTING_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"roman", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; - pub fn descend_to_existing_test1(mut zipper: Z) { + pub fn descend_to_existing_test1(mut zipper: Z) { assert_eq!(3, zipper.descend_to_existing("bowling")); assert_eq!("bow".as_bytes(), zipper.path()); @@ -3539,7 +3561,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_DESCEND_TO_EXISTING_TEST2_KEYS: &[&[u8]] = &[b"arrow"]; /// Tests a really long path that doesn't exist, to exercise the chunk-descending code - pub fn descend_to_existing_test2(mut zipper: Z) { + pub fn descend_to_existing_test2(mut zipper: Z) { assert_eq!(5, zipper.descend_to_existing("arrow0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")); assert_eq!(zipper.path(), &b"arrow"[..]); @@ -3552,7 +3574,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_DESCEND_TO_EXISTING_TEST3_KEYS: &[&[u8]] = &[b"arrow"]; /// Tests calling the method when the focus is already on a non-existent path - pub fn descend_to_existing_test3(mut zipper: Z) { + pub fn descend_to_existing_test3(mut zipper: Z) { assert_eq!(false, zipper.descend_to("arrow00000")); assert_eq!(zipper.path(), &b"arrow00000"[..]); @@ -3563,7 +3585,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_TO_NEXT_STEP_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"roman", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; - pub fn to_next_step_test1(mut zipper: Z) { + pub fn to_next_step_test1(mut zipper: Z) { let mut i = 0; while zipper.to_next_step() { match i { @@ -3595,7 +3617,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_BYTES_ITER_TEST1_KEYS: &[&[u8]] = &[b"ABCDEFGHIJKLMNOPQRSTUVWXYZ", b"ab",]; - pub fn zipper_byte_iter_test1(mut zipper: Z) { + pub fn zipper_byte_iter_test1(mut zipper: Z) { assert_eq!(zipper.descend_to_byte(b'A'), true); assert_eq!(zipper.descend_first_byte(), true); @@ -3607,7 +3629,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_BYTES_ITER_TEST2_KEYS: &[&[u8]] = &[&[2, 194, 1, 1, 193, 5], &[3, 194, 1, 0, 193, 6, 193, 5], &[3, 193, 4, 193]]; pub const ZIPPER_BYTES_ITER_TEST2_PATH: &[u8] = &[2, 194]; - pub fn zipper_byte_iter_test2(mut zipper: Z) { + pub fn zipper_byte_iter_test2(mut zipper: Z) { assert_eq!(zipper.descend_first_byte(), true); assert_eq!(zipper.path(), &[1]); assert_eq!(zipper.to_next_sibling_byte(), false); @@ -3617,7 +3639,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_BYTES_ITER_TEST3_KEYS: &[&[u8]] = &[&[3, 193, 4, 193, 5, 2, 193, 6, 193, 7], &[3, 193, 4, 193, 5, 2, 193, 6, 255]]; pub const ZIPPER_BYTES_ITER_TEST3_PATH: &[u8] = &[3, 193, 4, 193, 5, 2, 193]; - pub fn zipper_byte_iter_test3(mut zipper: Z) { + pub fn zipper_byte_iter_test3(mut zipper: Z) { assert_eq!(zipper.path(), &[]); assert_eq!(zipper.descend_first_byte(), true); assert_eq!(zipper.path(), &[6]); @@ -3629,7 +3651,7 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_BYTES_ITER_TEST4_KEYS: &[&[u8]] = &[b"ABC", b"ABCDEF", b"ABCdef"]; - pub fn zipper_byte_iter_test4(mut zipper: Z) { + pub fn zipper_byte_iter_test4(mut zipper: Z) { //Check that we end up at the first leaf by depth-first search while zipper.descend_first_byte() {} diff --git a/src/zipper_tracking.rs b/src/zipper_tracking.rs index 8fa2cd0c..0638abec 100644 --- a/src/zipper_tracking.rs +++ b/src/zipper_tracking.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use std::sync::RwLock; use crate::PathMap; -use crate::zipper::{ReadZipperUntracked, Zipper, ZipperAbsolutePath, ZipperForking, ZipperMoving, ZipperReadOnlyValues, ZipperWriting, ZipperIteration, ZipperReadOnlyIteration, }; +use crate::zipper::{ReadZipperUntracked, Zipper, ZipperPath, ZipperAbsolutePath, ZipperForking, ZipperMoving, ZipperReadOnlyValues, ZipperWriting, ZipperIteration, ZipperReadOnlyIteration, }; /// Marker to track an outstanding read zipper pub struct TrackingRead; From dd8d80e29a4895ce7362b176ccef62f351eb01e1 Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 25 Sep 2025 00:39:52 -0500 Subject: [PATCH 02/18] update all the APIs & tests to allow ZipperMoving to track where it goes --- src/experimental.rs | 41 +-- src/morphisms.rs | 19 +- src/overlay_zipper.rs | 82 +++--- src/prefix_zipper.rs | 89 +++---- src/product_zipper.rs | 106 ++++---- src/utils/debug/diff_zipper.rs | 20 +- src/utils/ints.rs | 2 +- src/write_zipper.rs | 167 ++++++------ src/zipper.rs | 448 +++++++++++++++++---------------- src/zipper_head.rs | 30 +-- 10 files changed, 523 insertions(+), 481 deletions(-) diff --git a/src/experimental.rs b/src/experimental.rs index 2d61a075..ff11f484 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -46,39 +46,40 @@ impl ZipperMoving for FullZipper { self.path.push(k); true } - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { assert!(idx < 256); self.path.push(idx as u8); - true + Some(idx as u8) } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.path.push(0); - true + Some(0) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.path.push(0); // not sure? + if let Some(dst) = dst { dst.push(0) } true } - fn ascend(&mut self, steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> Result<(), usize> { if steps > self.path.len() { self.path.clear(); - false + Err(steps - self.path.len()) } else { self.path.truncate(self.path.len() - steps); - true + Ok(()) } } fn ascend_byte(&mut self) -> bool { self.path.pop().is_some() } - fn ascend_until(&mut self) -> bool { - self.path.pop().is_some() // not sure? + fn ascend_until(&mut self) -> Option { + self.path.pop().map(|_| 1) // not sure? } - fn ascend_until_branch(&mut self) -> bool { - self.path.pop().is_some() // not sure? What's the difference with the previous? + fn ascend_until_branch(&mut self) -> Option { + self.path.pop().map(|_| 1) // not sure? What's the difference with the previous? } - fn to_next_sibling_byte(&mut self) -> bool { self.to_sibling(true) } - fn to_prev_sibling_byte(&mut self) -> bool { self.to_sibling(false) } + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling(true) } + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } } impl ZipperPath for FullZipper { @@ -86,16 +87,16 @@ impl ZipperPath for FullZipper { } impl FullZipper { - fn to_sibling(&mut self, next: bool) -> bool { - if self.path.is_empty() { return false } // right? + fn to_sibling(&mut self, next: bool) -> Option { + if self.path.is_empty() { return None } // right? if next { let last = self.path.last_mut().unwrap(); - if *last != 255 { *last = *last + 1; true } - else { false } + if *last != 255 { *last = *last + 1; Some(*last) } + else { None } } else { let first = self.path.first_mut().unwrap(); - if *first != 0 { *first = *first - 1; true } - else { false } + if *first != 0 { *first = *first - 1; Some(*first) } + else { None } } } } diff --git a/src/morphisms.rs b/src/morphisms.rs index 5cb8d47a..6adada17 100644 --- a/src/morphisms.rs +++ b/src/morphisms.rs @@ -406,7 +406,7 @@ fn cata_side_effect_body<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(mut z: z.prepare_buffers(); //Push a stack frame for the root, and start on the first branch off the root stack.push(StackFrame::from(&z)); - if !z.descend_first_byte() { + if !z.descend_first_byte().is_some() { //Empty trie is a special case return alg_f(&ByteMask::EMPTY, &mut [], 0, z.val(), z.origin_path()) } @@ -415,7 +415,7 @@ fn cata_side_effect_body<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(mut z: //Descend to the next forking point, or leaf let mut is_leaf = false; while z.child_count() < 2 { - if !z.descend_until() { + if !z.descend_until(None) { is_leaf = true; break; } @@ -461,7 +461,7 @@ fn cata_side_effect_body<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(mut z: //Position to descend the next child branch let descended = z.descend_indexed_byte(stack[frame_idx].child_idx as usize); - debug_assert!(descended); + debug_assert!(descended.is_some()); } else { //Push a new stack frame for this branch Stack::push_state_raw(&mut stack, &mut frame_idx, &z); @@ -491,7 +491,7 @@ fn ascend_to_fork<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(z: &mut Z, let old_path_len = z.origin_path().len(); let old_val = z.get_val_with_witness(&z_witness); let ascended = z.ascend_until(); - debug_assert!(ascended); + debug_assert!(ascended.is_some()); let origin_path = unsafe{ z.origin_path_assert_len(old_path_len) }; let jump_len = if z.child_count() != 1 || z.is_val() { @@ -641,7 +641,8 @@ where new_map_from_ana_jumping(wz, w, coalg_f); wz.ascend_byte(); } - wz.ascend(prefix_len); + let ascended = wz.ascend(prefix_len); + debug_assert_eq!(ascended, Ok(())); } /// A trait to dictate if and how the value should be cached. @@ -734,7 +735,7 @@ fn into_cata_cached_body<'a, Z, V: 'a, W, E, AlgF, Cache, const JUMPING: bool>( // Descend until leaf or branch let mut is_leaf = false; 'descend: while zipper.child_count() < 2 { - if !zipper.descend_until() { + if !zipper.descend_until(None) { is_leaf = true; break 'descend; } @@ -926,7 +927,8 @@ pub(crate) fn new_map_from_ana_in(w: W, mut alg_f: Alg // Path from a graft, we shouldn't descend WOrNode::Node(node) => { z.core().graft_internal(Some(node)); - z.ascend(child_path_len); + let ascended = z.ascend(child_path_len); + debug_assert_eq!(ascended, Ok(())); } } } else { @@ -934,7 +936,8 @@ pub(crate) fn new_map_from_ana_in(w: W, mut alg_f: Alg if frame_idx == 0 { break } - z.ascend(stack[frame_idx].1); + let ascended = z.ascend(stack[frame_idx].1); + debug_assert_eq!(ascended, Ok(())); stack[frame_idx].0.reset(); frame_idx -= 1; } diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index c16f87b8..0c15bad8 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -72,12 +72,12 @@ impl BZipper: ZipperMoving + ZipperValues + ZipperPath, Mapping: for<'a> Fn(Option<&'a AV>, Option<&'a BV>) -> Option<&'a OutV>, { - fn to_sibling(&mut self, next: bool) -> bool { + fn to_sibling(&mut self, next: bool) -> Option { let path = self.path(); let Some(&last) = path.last() else { - return false; + return None; }; - self.ascend(1); + self.ascend_byte(); let child_mask = self.child_mask(); let maybe_child = if next { child_mask.next_bit(last) @@ -86,9 +86,10 @@ impl }; let Some(child) = maybe_child else { self.descend_to_byte(last); - return false; + return None; }; - self.descend_to_byte(child) + self.descend_to_byte(child); + Some(child) } } @@ -173,7 +174,8 @@ impl ZipperMoving let depth_o = self.b.descend_to_val(path); if depth_a < depth_o { if self.a.is_val() { - self.b.ascend(depth_o - depth_a); + let ascended = self.b.ascend(depth_o - depth_a); + debug_assert_eq!(ascended, Ok(())); depth_a } else { self.a.descend_to(&path[depth_a..depth_o]); @@ -181,7 +183,8 @@ impl ZipperMoving } } else if depth_o < depth_a { if self.b.is_val() { - self.a.ascend(depth_a - depth_o); + let ascended = self.a.ascend(depth_a - depth_o); + debug_assert_eq!(ascended, Ok(())); depth_o } else { self.a.descend_to(&path[depth_o..depth_a]); @@ -196,23 +199,24 @@ impl ZipperMoving self.a.descend_to(&[k]) | self.b.descend_to(&[k]) } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { let child_mask = self.child_mask(); - let Some(byte) = child_mask.indexed_bit::(idx) else { - return false; - }; - self.descend_to_byte(byte) + let byte = child_mask.indexed_bit::(idx)?; + let descended = self.descend_to_byte(byte); + debug_assert!(descended); + Some(byte) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, _dst: Option<&mut Vec>) -> bool { + // TODO: track dst use crate::utils::find_prefix_overlap; let start_depth = self.a.path().len(); - let desc_a = self.a.descend_until(); - let desc_b = self.b.descend_until(); + let desc_a = self.a.descend_until(None); + let desc_b = self.b.descend_until(None); let path_a = &self.a.path()[start_depth..]; let path_b = &self.b.path()[start_depth..]; if !desc_a && !desc_b { @@ -223,7 +227,8 @@ impl ZipperMoving self.a.descend_to(path_b); return true; } else { - self.b.ascend(self.b.path().len() - start_depth); + let ascended = self.b.ascend(self.b.path().len() - start_depth); + debug_assert_eq!(ascended, Ok(())); return false; } } @@ -232,29 +237,35 @@ impl ZipperMoving self.b.descend_to(path_a); return true; } else { - self.a.ascend(self.a.path().len() - start_depth); + let ascended = self.a.ascend(self.a.path().len() - start_depth); + debug_assert_eq!(ascended, Ok(())); return false; } } let overlap = find_prefix_overlap(path_a, path_b); if path_a.len() > overlap { - self.a.ascend(path_a.len() - overlap); + let ascended = self.a.ascend(path_a.len() - overlap); + debug_assert_eq!(ascended, Ok(())); } if path_b.len() > overlap { - self.b.ascend(path_b.len() - overlap); + let ascended = self.b.ascend(path_b.len() - overlap); + debug_assert_eq!(ascended, Ok(())); } overlap > 0 } - fn ascend(&mut self, steps: usize) -> bool { - self.a.ascend(steps) | self.b.ascend(steps) + fn ascend(&mut self, steps: usize) -> Result<(), usize> { + let rv_a = self.a.ascend(steps); + let rv_b = self.b.ascend(steps); + debug_assert_eq!(rv_a, rv_b); + rv_a } fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1).is_ok() } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> Option { debug_assert_eq!(self.a.path(), self.b.path()); // eprintln!("asc_until i {:?} {:?}", self.base.path(), self.overlay.path()); let asc_a = self.a.ascend_until(); @@ -263,38 +274,45 @@ impl ZipperMoving let asc_b = self.b.ascend_until(); let path_b = self.b.path(); let depth_b = path_b.len(); - if !(asc_b || asc_a) { - return false; - } + let min = match (asc_a, asc_b) { + (None, None) => return None, + (Some(a), None) | (None, Some(a)) => a, + (Some(a), Some(b)) => a.min(b), + }; // eprintln!("asc_until {path_a:?} {path_b:?}"); if depth_b > depth_a { self.a.descend_to(&path_b[depth_a..]); } else if depth_a > depth_b { self.b.descend_to(&path_a[depth_b..]); } - true + Some(min) } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> Option { let asc_a = self.a.ascend_until_branch(); let path_a = self.a.path(); let depth_a = path_a.len(); let asc_b = self.b.ascend_until_branch(); let path_b = self.b.path(); let depth_b = path_b.len(); + let min = match (asc_a, asc_b) { + (None, None) => return None, + (Some(a), None) | (None, Some(a)) => a, + (Some(a), Some(b)) => a.min(b), + }; if depth_b > depth_a { self.a.descend_to(&path_b[depth_a..]); } else if depth_a > depth_b { self.b.descend_to(&path_a[depth_b..]); } - asc_a || asc_b + Some(min) } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling(true) } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } } diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index 0006f3d9..e83b8ef7 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -131,7 +131,7 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> // return Ok(()); // }; let len_before = self.source.path().len(); - if self.source.ascend(steps) { + if self.source.ascend(steps).is_ok() { return Ok(()) } let len_after = self.source.path().len(); @@ -164,7 +164,7 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> } else { self.source.ascend_until_branch() }; - if was_good && ((VAL && self.source.is_val()) || self.source.child_count() > 1) { + if was_good.is_some() && ((VAL && self.source.is_val()) || self.source.child_count() > 1) { let len_after = self.source.path().len(); return Some(len_before - len_after); } @@ -362,21 +362,19 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.descend_to([k]) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { let mask = self.child_mask(); - let Some(byte) = mask.indexed_bit::(child_idx) else { - return false; - }; + let byte = mask.indexed_bit::(child_idx)?; debug_assert!(self.descend_to_byte(byte)); - true + Some(byte) } #[inline] - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { if self.position.is_invalid() { return false; } @@ -385,7 +383,7 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.position = PrefixPos::Source; } let len_before = self.source.path().len(); - if !self.source.descend_until() { + if !self.source.descend_until(dst) { return false; } let path = self.source.path(); @@ -394,57 +392,48 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> } #[inline] - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { if !self.position.is_source() { - return false; - } - if !self.source.to_next_sibling_byte() { - return false; + return None; } - let byte = *self.source.path().last().unwrap(); + let byte = self.source.to_next_sibling_byte()?; *self.path.last_mut().unwrap() = byte; - true + Some(byte) } #[inline] - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { if !self.position.is_source() { - return false; - } - if !self.source.to_prev_sibling_byte() { - return false; + return None; } - let byte = *self.source.path().last().unwrap(); + let byte = self.source.to_prev_sibling_byte()?; *self.path.last_mut().unwrap() = byte; - true + Some(byte) } - fn ascend(&mut self, steps: usize) -> bool { - let ascended = match self.ascend_n(steps) { + fn ascend(&mut self, steps: usize) -> Result<(), usize> { + let rv = self.ascend_n(steps); + let ascended = match rv { Err(remaining) => steps - remaining, Ok(()) => steps, }; self.path.truncate(self.path.len() - ascended); - ascended > 0 + rv } #[inline] fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1).is_ok() } #[inline] - fn ascend_until(&mut self) -> bool { - let Some(ascended) = self.ascend_until_n::() else { - return false; - }; + fn ascend_until(&mut self) -> Option { + let ascended = self.ascend_until_n::()?; self.path.truncate(self.path.len() - ascended); - true + Some(ascended) } #[inline] - fn ascend_until_branch(&mut self) -> bool { - let Some(ascended) = self.ascend_until_n::() else { - return false; - }; + fn ascend_until_branch(&mut self) -> Option { + let ascended = self.ascend_until_n::()?; self.path.truncate(self.path.len() - ascended); - true + Some(ascended) } } @@ -514,18 +503,18 @@ mod tests { let mut rz = PrefixZipper::new(b"prefix", map.read_zipper()); rz.set_root_prefix_path(b"pre").unwrap(); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until(), true); + assert_eq!(rz.ascend_until(), Some(1)); assert_eq!(rz.path(), b"fix0000"); assert_eq!(rz.origin_path(), b"prefix0000"); assert_eq!(rz.descend_to_existing(b"0"), 1); - assert_eq!(rz.ascend_until_branch(), true); + assert_eq!(rz.ascend_until_branch(), Some(2)); assert_eq!(rz.path(), b"fix000"); - assert_eq!(rz.ascend_until_branch(), true); + assert_eq!(rz.ascend_until_branch(), Some(3)); assert_eq!(rz.path(), b"fix"); - assert_eq!(rz.ascend_until_branch(), true); + assert_eq!(rz.ascend_until_branch(), Some(3)); assert_eq!(rz.path(), b""); assert_eq!(rz.origin_path(), b"pre"); - assert_eq!(rz.ascend_until_branch(), false); + assert_eq!(rz.ascend_until_branch(), None); } #[test] @@ -534,20 +523,20 @@ mod tests { let mut rz = PrefixZipper::new(b"prefix", map.read_zipper()); rz.set_root_prefix_path(b"pre").unwrap(); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until(), true); + assert_eq!(rz.ascend_until(), Some(2)); assert_eq!(rz.path(), b"fix000"); assert_eq!(rz.origin_path(), b"prefix000"); - assert_eq!(rz.ascend_until(), true); + assert_eq!(rz.ascend_until(), Some(1)); assert_eq!(rz.path(), b"fix00"); - assert_eq!(rz.ascend_until(), true); + assert_eq!(rz.ascend_until(), Some(5)); assert_eq!(rz.path(), b""); - assert_eq!(rz.ascend_until(), false); + assert_eq!(rz.ascend_until(), None); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until_branch(), true); + assert_eq!(rz.ascend_until_branch(), Some(3)); assert_eq!(rz.path(), b"fix00"); - assert_eq!(rz.ascend_until_branch(), true); + assert_eq!(rz.ascend_until_branch(), Some(5)); assert_eq!(rz.path(), b""); assert_eq!(rz.origin_path(), b"pre"); - assert_eq!(rz.ascend_until_branch(), false); + assert_eq!(rz.ascend_until_branch(), None); } } \ No newline at end of file diff --git a/src/product_zipper.rs b/src/product_zipper.rs index c9b632f9..f4c4650f 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -230,22 +230,22 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } else { false } } else { true } } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { let result = self.z.descend_indexed_byte(child_idx); self.ensure_descend_next_factor(); result } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { let result = self.z.descend_first_byte(); self.ensure_descend_next_factor(); result } - fn descend_until(&mut self) -> bool { - let result = self.z.descend_until(); + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { + let result = self.z.descend_until(dst); self.ensure_descend_next_factor(); result } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { if self.factor_paths.last().cloned().unwrap_or(0) == self.path().len() { self.factor_paths.pop(); } @@ -253,7 +253,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.ensure_descend_next_factor(); moved } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { if self.factor_paths.last().cloned().unwrap_or(0) == self.path().len() { self.factor_paths.pop(); } @@ -261,20 +261,20 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.ensure_descend_next_factor(); moved } - fn ascend(&mut self, steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> Result<(), usize> { let ascended = self.z.ascend(steps); self.fix_after_ascend(); ascended } fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1).is_ok() } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> Option { let ascended = self.z.ascend_until(); self.fix_after_ascend(); ascended } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> Option { let ascended = self.z.ascend_until_branch(); self.fix_after_ascend(); ascended @@ -464,7 +464,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) -> bool { + fn ascend_cond(&mut self, allow_stop_on_val: bool) -> Option { let mut plen = self.path().len(); loop { while self.factor_paths.last() == Some(&plen) { @@ -480,9 +480,10 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, }; let delta = before - zipper.path().len(); plen -= delta; - self.primary.ascend(delta); - if rv { - return true; + let ascended = self.primary.ascend(delta); + debug_assert_eq!(ascended, Ok(())); + if rv.is_some() { + return rv; } } else { return if allow_stop_on_val { @@ -495,11 +496,11 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, } /// a combination between `to_next_sibling` and `to_prev_sibling` - fn to_sibling_byte(&mut self, next: bool) -> bool { + fn to_sibling_byte(&mut self, next: bool) -> Option { let Some(&byte) = self.path().last() else { - return false; + return None; }; - assert!(self.ascend(1), "must ascend"); + assert!(self.ascend(1).is_ok(), "must ascend"); let child_mask = self.child_mask(); let Some(sibling_byte) = (if next { child_mask.next_bit(byte) @@ -507,9 +508,10 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, child_mask.prev_bit(byte) }) else { self.descend_to_byte(byte); - return false; + return None; }; - self.descend_to_byte(sibling_byte) + self.descend_to_byte(sibling_byte); + Some(sibling_byte) } } @@ -708,67 +710,71 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim fn descend_to_byte(&mut self, k: u8) -> bool { self.descend_to([k]) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { let mask = self.child_mask(); let Some(byte) = mask.indexed_bit::(child_idx) else { - return false; + return None; }; - self.descend_to_byte(byte) + let descended = self.descend_to_byte(byte); + debug_assert!(descended); + Some(byte) } #[inline] - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.enter_factors(); let rv = if let Some(idx) = self.factor_idx(false) { let zipper = &mut self.secondary[idx]; let before = zipper.path().len(); - let rv = zipper.descend_until(); + let rv = zipper.descend_until(dst); let path = zipper.path(); if path.len() > before { self.primary.descend_to(&path[before..]); } rv } else { - self.primary.descend_until() + self.primary.descend_until(dst) }; self.enter_factors(); rv } #[inline] - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling_byte(true) } #[inline] - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling_byte(false) } - fn ascend(&mut self, mut steps: usize) -> bool { + fn ascend(&mut self, mut steps: usize) -> Result<(), usize> { while steps > 0 { self.exit_factors(); if let Some(idx) = self.factor_idx(false) { let len = self.path().len() - self.factor_paths[idx]; let delta = len.min(steps); - self.secondary[idx].ascend(delta); - self.primary.ascend(delta); + let ascended = self.secondary[idx].ascend(delta); + debug_assert_eq!(ascended, Ok(())); + let ascended = self.primary.ascend(delta); + debug_assert_eq!(ascended, Ok(())); steps -= delta; } else { return self.primary.ascend(steps); } } - true + Ok(()) } #[inline] fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1).is_ok() } #[inline] - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> Option { self.ascend_cond(true) } #[inline] - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> Option { self.ascend_cond(false) } } @@ -876,19 +882,19 @@ mod tests { assert_eq!(pz.child_count(), 0); //Make sure we can ascend out of a secondary factor; in this sub-test we'll hit the path middles - assert!(pz.ascend(1)); + assert_eq!(pz.ascend(1), Ok(())); assert_eq!(pz.get_val(), None); assert_eq!(pz.path(), b"AAaDDdG"); assert_eq!(pz.child_count(), 0); - assert!(pz.ascend(3)); + assert_eq!(pz.ascend(3), Ok(())); assert_eq!(pz.path(), b"AAaD"); assert_eq!(pz.get_val(), None); assert_eq!(pz.child_count(), 1); - assert!(pz.ascend(2)); + assert_eq!(pz.ascend(2), Ok(())); assert_eq!(pz.path(), b"AA"); assert_eq!(pz.get_val(), None); assert_eq!(pz.child_count(), 3); - assert!(!pz.ascend(3)); + assert_eq!(pz.ascend(3), Err(1)); assert_eq!(pz.path(), b""); assert_eq!(pz.get_val(), None); assert_eq!(pz.child_count(), 1); @@ -900,15 +906,15 @@ mod tests { assert_eq!(pz.child_count(), 0); //Now try to hit the path transition points - assert!(pz.ascend(2)); + assert_eq!(pz.ascend(2), Ok(())); assert_eq!(pz.path(), b"AAaDDd"); assert_eq!(pz.get_val(), Some(&1000)); assert_eq!(pz.child_count(), 0); - assert!(pz.ascend(3)); + assert_eq!(pz.ascend(3), Ok(())); assert_eq!(pz.path(), b"AAa"); assert_eq!(pz.get_val(), Some(&0)); assert_eq!(pz.child_count(), 3); - assert!(pz.ascend(3)); + assert_eq!(pz.ascend(3), Ok(())); assert_eq!(pz.path(), b""); assert_eq!(pz.get_val(), None); assert_eq!(pz.child_count(), 1); @@ -1035,7 +1041,7 @@ mod tests { let mut p = $ProductZipper::new(l.read_zipper(), [r.read_zipper(), e.read_zipper()]); assert!(p.descend_to("abcdefghijklmnopqrstuvwxyzbowfo")); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbowfo"); - assert!(p.descend_first_byte()); + assert_eq!(p.descend_first_byte(), Some(b'o')); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbowfoo"); } { @@ -1061,9 +1067,9 @@ mod tests { assert!(p.descend_to_byte(b'o')); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbowpho"); assert!(p.is_val()); - assert!(p.ascend_until()); + assert_eq!(p.ascend_until(), Some(3)); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbow"); - assert!(p.ascend(3)); + assert_eq!(p.ascend(3), Ok(())); assert_eq!(vec![b'A', b'a', b'b'], p.child_mask().iter().collect::>()); assert!(p.descend_to("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); assert_eq!(vec![b'f', b'p'], p.child_mask().iter().collect::>()) @@ -1086,7 +1092,7 @@ mod tests { let mut p = $ProductZipper::new(l.read_zipper(), [r.read_zipper(), e.read_zipper()]); assert!(!p.descend_to("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); // println!("p {}", std::str::from_utf8(p.path()).unwrap()); - assert!(!p.ascend(27)); + assert_eq!(p.ascend(27), Err(1)); } } @@ -1122,7 +1128,7 @@ mod tests { // Validate that I can back up, and re-descend { - p2.ascend(20); + p2.ascend(20).unwrap(); assert_eq!(p2.path(), b"arr"); assert_eq!(p2.path_exists(), true); assert!(p2.is_val()); @@ -1147,7 +1153,7 @@ mod tests { assert_eq!(p2.path(), b"arrowbowclub"); assert_eq!(p2.path_exists(), false); - p2.ascend(9); + p2.ascend(9).unwrap(); assert_eq!(p2.path(), b"arr"); assert_eq!(p2.path_exists(), true); assert!(p2.is_val()); @@ -1167,7 +1173,7 @@ mod tests { assert_eq!(p2.path(), b"arrowheadbowclub"); assert_eq!(p2.path_exists(), false); - p2.ascend(5); + p2.ascend(5).unwrap(); assert_eq!(p2.path(), b"arrowheadbo"); assert_eq!(p2.path_exists(), true); assert!(p2.is_val()); @@ -1199,7 +1205,7 @@ mod tests { // println!("{}", String::from_utf8_lossy(path)); let overlap = crate::utils::find_prefix_overlap(path, moving_pz.path()); if overlap < moving_pz.path().len() { - moving_pz.ascend(moving_pz.path().len() - overlap); + moving_pz.ascend(moving_pz.path().len() - overlap).unwrap(); } if moving_pz.path().len() < path.len() { assert!(moving_pz.descend_to(&path[moving_pz.path().len()..])); diff --git a/src/utils/debug/diff_zipper.rs b/src/utils/debug/diff_zipper.rs index b3b568b0..1164f9ab 100644 --- a/src/utils/debug/diff_zipper.rs +++ b/src/utils/debug/diff_zipper.rs @@ -105,7 +105,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { let a = self.a.descend_indexed_byte(idx); let b = self.b.descend_indexed_byte(idx); if self.log_moves { @@ -114,7 +114,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { let a = self.a.descend_first_byte(); let b = self.b.descend_first_byte(); if self.log_moves { @@ -123,16 +123,16 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn descend_until(&mut self) -> bool { - let a = self.a.descend_until(); - let b = self.b.descend_until(); + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { + let a = self.a.descend_until(dst); + let b = self.b.descend_until(None); if self.log_moves { println!("DiffZipper: descend_until") } assert_eq!(a, b); a } - fn ascend(&mut self, steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> Result<(), usize> { let a = self.a.ascend(steps); let b = self.b.ascend(steps); if self.log_moves { @@ -150,7 +150,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> Option { let a = self.a.ascend_until(); let b = self.b.ascend_until(); if self.log_moves { @@ -159,7 +159,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> Option { let a = self.a.ascend_until_branch(); let b = self.b.ascend_until_branch(); if self.log_moves { @@ -168,7 +168,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { let a = self.a.to_next_sibling_byte(); let b = self.b.to_next_sibling_byte(); if self.log_moves { @@ -177,7 +177,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { let a = self.a.to_prev_sibling_byte(); let b = self.b.to_prev_sibling_byte(); if self.log_moves { diff --git a/src/utils/ints.rs b/src/utils/ints.rs index eb16fb7d..2230e060 100644 --- a/src/utils/ints.rs +++ b/src/utils/ints.rs @@ -302,7 +302,7 @@ fn int_range_generator_5() { drop(buildz); let mut z = zh.read_zipper_at_path(&[0]).unwrap(); - z.descend_until(); + z.descend_until(None); z.descend_first_byte(); let _z2 = zh.read_zipper_at_path(z.origin_path()).unwrap(); diff --git a/src/write_zipper.rs b/src/write_zipper.rs index b08bbb84..60128c5a 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -350,15 +350,15 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { self.z.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut self) -> bool { self.z.descend_first_byte() } - fn descend_until(&mut self) -> bool { self.z.descend_until() } - fn to_next_sibling_byte(&mut self) -> bool { self.z.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> bool { self.z.ascend(steps) } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> bool { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> Option { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } } impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperTracked<'a, 'path, V, A> { fn path(&self) -> &[u8] { self.z.path() } @@ -506,15 +506,15 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { self.z.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut self) -> bool { self.z.descend_first_byte() } - fn descend_until(&mut self) -> bool { self.z.descend_until() } - fn to_next_sibling_byte(&mut self) -> bool { self.z.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> bool { self.z.ascend(steps) } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> bool { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> Option { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } } impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperUntracked<'a, 'path, V, A> { fn path(&self) -> &[u8] { self.z.path() } @@ -686,15 +686,15 @@ impl ZipperMoving for WriteZipperO fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) -> bool { self.z.descend_to(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { self.z.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut self) -> bool { self.z.descend_first_byte() } - fn descend_until(&mut self) -> bool { self.z.descend_until() } - fn to_next_sibling_byte(&mut self) -> bool { self.z.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> bool { self.z.ascend(steps) } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> bool { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> Option { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } } impl ZipperPath for WriteZipperOwned { fn path(&self) -> &[u8] { self.z.path() } @@ -986,16 +986,16 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } - fn ascend(&mut self, mut steps: usize) -> bool { + fn ascend(&mut self, mut steps: usize) -> Result<(), usize> { loop { if self.key.node_key().len() == 0 { self.ascend_across_nodes(); } if steps == 0 { - return true + return Ok(()); } if self.at_root() { - return false + return Err(steps); } debug_assert!(self.key.node_key().len() > 0); let cur_jump = steps.min(self.key.excess_key_len()); @@ -1004,14 +1004,15 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> Option { if self.at_root() { - return false; + return None; } + let mut ascended = 0; loop { - self.ascend_within_node(); + ascended += self.ascend_within_node(); if self.at_root() { - return true; + return Some(ascended); } if self.key.node_key().len() == 0 { self.ascend_across_nodes(); @@ -1021,17 +1022,18 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } debug_assert!(self.key.node_key().len() > 0); //We should never finish with a zero-length node-key - true + Some(ascended) } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> Option { if self.at_root() { - return false; + return None; } + let mut ascended = 0; loop { - self.ascend_within_node(); + ascended += self.ascend_within_node(); if self.at_root() { - return true; + return Some(ascended); } if self.key.node_key().len() == 0 { self.ascend_across_nodes(); @@ -1041,48 +1043,48 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } debug_assert!(self.key.node_key().len() > 0); //We should never finish with a zero-length node-key - true + Some(ascended) } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { let cur_byte = match self.path().last() { Some(byte) => *byte, - None => return false + None => return None, }; if !self.ascend_byte() { - return false + return None; } let mask = self.child_mask(); match mask.next_bit(cur_byte) { Some(byte) => { let descended = self.descend_to_byte(byte); debug_assert!(descended); - true + Some(byte) }, None => { self.descend_to_byte(cur_byte); - false + None } } } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { let cur_byte = match self.path().last() { Some(byte) => *byte, - None => return false + None => return None, }; if !self.ascend_byte() { - return false + return None; } let mask = self.child_mask(); match mask.prev_bit(cur_byte) { Some(byte) => { let descended = self.descend_to_byte(byte); debug_assert!(descended); - true + Some(byte) }, None => { let descended = self.descend_to_byte(cur_byte); debug_assert!(descended); - false + None } } } @@ -1608,7 +1610,7 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC let fully_ascended = self.ascend(n); self.graft_internal(downstream_node); - fully_ascended + fully_ascended.is_ok() } /// See [ZipperWriting::meet_into] pub fn meet_into>(&mut self, read_zipper: &Z, prune: bool) -> AlgebraicStatus where V: Lattice { @@ -1873,7 +1875,8 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC /// See [ZipperWriting::prune_ascend] fn prune_ascend(&mut self) -> usize { let bytes = self.prune_path(); - self.ascend(bytes); + let ascended = self.ascend(bytes); + debug_assert_eq!(ascended, Ok(())); bytes } @@ -2137,10 +2140,12 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC } /// Internal method used to impement `ascend_until` when ascending within a node #[inline] - fn ascend_within_node(&mut self) { + fn ascend_within_node(&mut self) -> usize { let branch_key = self.focus_stack.top().unwrap().prior_branch_key(self.key.node_key()); let new_len = self.key.origin_path.len().max(self.key.node_key_start() + branch_key.len()); + let old_len = self.key.prefix_buf.len(); self.key.prefix_buf.truncate(new_len); + old_len - new_len } } @@ -2857,29 +2862,29 @@ mod tests { // and then clean them up wz.descend_to([0, 0, 0, 0]); wz.set_val(()); - wz.ascend(4); + wz.ascend(4).unwrap(); wz.descend_to([1, 0, 0, 1]); wz.set_val(()); - wz.ascend(4); + wz.ascend(4).unwrap(); wz.descend_to([2, 0, 0, 2]); wz.set_val(()); - wz.ascend(4); + wz.ascend(4).unwrap(); wz.descend_to([0, 0, 0, 0]); wz.remove_val(true); - wz.ascend(4); + wz.ascend(4).unwrap(); wz.descend_to([1, 0, 0, 1]); wz.remove_val(true); - wz.ascend(4); + wz.ascend(4).unwrap(); wz.descend_to([2, 0, 0, 2]); wz.remove_val(true); - wz.ascend(4); + wz.ascend(4).unwrap(); wz.meet_into(&rz, true); assert_eq!(wz.val_count(), 2); assert!(wz.descend_to([194, 7, 162])); assert!(wz.val().is_some()); - assert!(wz.ascend(3)); + assert_eq!(wz.ascend(3), Ok(())); assert!(wz.descend_to([194, 7, 163])); assert!(wz.val().is_some()); } @@ -2900,26 +2905,26 @@ mod tests { assert!(wz.descend_to(b"mulus")); assert_eq!(wz.path(), b"mulus"); assert_eq!(wz.child_count(), 0); - assert!(wz.ascend_until()); + assert_eq!(wz.ascend_until(), Some(4)); assert_eq!(wz.path(), b"m"); assert_eq!(wz.child_count(), 3); //Make sure we can't ascend above the zipper's root with ascend_until - assert!(wz.ascend_until()); + assert_eq!(wz.ascend_until(), Some(1)); assert_eq!(wz.path(), b""); - assert!(!wz.ascend_until()); + assert_eq!(wz.ascend_until(), None); //Test step-wise `ascend` wz.descend_to(b"manus"); assert_eq!(wz.path(), b"manus"); - assert_eq!(wz.ascend(1), true); + assert_eq!(wz.ascend(1), Ok(())); assert_eq!(wz.path(), b"manu"); - assert_eq!(wz.ascend(5), false); + assert_eq!(wz.ascend(5), Err(1)); assert_eq!(wz.path(), b""); assert_eq!(wz.at_root(), true); wz.descend_to(b"mane"); assert_eq!(wz.path(), b"mane"); - assert_eq!(wz.ascend(3), true); + assert_eq!(wz.ascend(3), Ok(())); assert_eq!(wz.path(), b"m"); assert_eq!(wz.child_count(), 3); } @@ -3575,7 +3580,7 @@ mod tests { assert_eq!(wz.origin_path(), b"This path can take you anywhere. Just close your eyes... and open your heart."); // Test forking a zipper from a WriteZipper and make sure it inherits the origin_path - wz.ascend(6); + wz.ascend(6).unwrap(); assert_eq!(wz.is_val(), false); let mut rz = wz.fork_read_zipper(); assert_eq!(rz.path(), b""); @@ -3594,7 +3599,7 @@ mod tests { assert_eq!(rz.is_val(), true); assert_eq!(rz.path(), b" and open your heart."); assert_eq!(rz.origin_path(), b"This path can take you anywhere. Just close your eyes... and open your heart."); - rz.ascend(6); + rz.ascend(6).unwrap(); assert_eq!(rz.path(), b" and open your "); assert_eq!(rz.origin_path(), b"This path can take you anywhere. Just close your eyes... and open your "); assert_eq!(rz.is_val(), false); @@ -3692,9 +3697,9 @@ mod tests { assert_eq!(wz.path(), &[0, 0, 1, 0, 0, 2, 3, 4]); assert_eq!(wz.prune_path(), 6); //Prune back to the value at [0, 0, 0] assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(4), true); + assert_eq!(wz.ascend(4), Ok(())); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(2), true); + assert_eq!(wz.ascend(2), Ok(())); assert_eq!(wz.path_exists(), true); assert_eq!(wz.path(), &[0, 0]); @@ -3745,16 +3750,16 @@ mod tests { assert_eq!(wz.prune_path(), 0); //And try pruning above the end - assert_eq!(wz.ascend(2), true); + assert_eq!(wz.ascend(2), Ok(())); assert_eq!(wz.prune_path(), 0); - assert_eq!(wz.descend_first_byte(), true); + assert_eq!(wz.descend_first_byte(), Some(4)); //Now validate that prune goes all the way to the root assert_eq!(wz.path(), &[0, 0, 0, 1, 2, 3, 4]); assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 7); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(7), true); + assert_eq!(wz.ascend(7), Ok(())); assert_eq!(wz.path(), &[]); assert_eq!(wz.path_exists(), true); assert_eq!(wz.child_count(), 0); @@ -3806,9 +3811,9 @@ mod tests { assert_eq!(wz.path(), &[1, 0, 3, 4, 5, 6]); assert_eq!(wz.prune_path(), 4); //Prune back to the value at [1, 0, 0] assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), true); + assert_eq!(wz.ascend(3), Ok(())); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(1), true); + assert_eq!(wz.ascend(1), Ok(())); assert_eq!(wz.path_exists(), true); assert_eq!(wz.path(), &[1, 0]); assert_eq!(wz.child_count(), 3); @@ -3839,7 +3844,7 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 50); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(49), true); + assert_eq!(wz.ascend(49), Ok(())); assert_eq!(wz.path_exists(), false); assert_eq!(wz.ascend_byte(), true); assert_eq!(wz.path_exists(), true); @@ -3861,7 +3866,7 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 3); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), true); + assert_eq!(wz.ascend(3), Ok(())); assert_eq!(wz.child_count(), 2); } @@ -3888,16 +3893,16 @@ mod tests { assert_eq!(wz.prune_path(), 3); assert_eq!(wz.path(), &[0, 0, 1, 0, 0]); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), true); + assert_eq!(wz.ascend(3), Ok(())); assert_eq!(wz.path_exists(), true); //Recreate some new paths, remove one and try re-extending it assert_eq!(wz.descend_to([0, 0, 0, 0]), false); assert_eq!(wz.set_val(()), None); - assert_eq!(wz.ascend(4), true); + assert_eq!(wz.ascend(4), Ok(())); assert_eq!(wz.descend_to([0, 0, 1, 0]), false); assert_eq!(wz.set_val(()), None); - assert_eq!(wz.ascend(2), true); + assert_eq!(wz.ascend(2), Ok(())); assert_eq!(wz.descend_to_byte(0), true); assert_eq!(wz.remove_branches(false), true); assert_eq!(wz.path_exists(), true); @@ -3953,7 +3958,7 @@ mod tests { assert_eq!(src_z.prune_path(), 3); assert_eq!(src_z.path(), &[0, 0, 1, 0, 0]); assert_eq!(src_z.path_exists(), false); - assert_eq!(src_z.ascend(3), true); + assert_eq!(src_z.ascend(3), Ok(())); assert_eq!(src_z.path_exists(), true); //Test removing from a node boundary @@ -4027,11 +4032,11 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.create_path(), false); - assert_eq!(wz.descend_first_byte(), true); + assert_eq!(wz.descend_first_byte(), Some(0)); assert_eq!(wz.path_exists(), true); assert_eq!(wz.create_path(), false); assert_eq!(wz.val(), Some(&())); - assert_eq!(wz.descend_first_byte(), false); + assert_eq!(wz.descend_first_byte(), None); assert_eq!(wz.descend_to_byte(0), false); assert_eq!(wz.path_exists(), false); assert_eq!(wz.create_path(), true); @@ -4055,7 +4060,7 @@ mod tests { assert_eq!(wz.ascend_byte(), true); assert_eq!(wz.descend_to([3, 0, 0, 0]), false); assert_eq!(wz.create_path(), true); - assert_eq!(wz.ascend(4), true); + assert_eq!(wz.ascend(4), Ok(())); assert_eq!(wz.child_count(), 4); } diff --git a/src/zipper.rs b/src/zipper.rs index 5b56edf1..e3e9812f 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -197,22 +197,22 @@ pub trait ZipperMoving: Zipper { /// WARNING: The branch represented by a given index is not guaranteed to be stable across modifications /// to the trie. This method should only be used as part of a directed traversal operation, but /// index-based paths may not be stored as locations within the trie. - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { let mask = self.child_mask(); let child_byte = match mask.indexed_bit::(idx) { Some(byte) => byte, None => { - return false + return None; } }; let descended = self.descend_to_byte(child_byte); debug_assert!(descended); - true + Some(child_byte) } /// A deprecated alias for [ZipperMoving::descend_indexed_byte] #[deprecated] //GOAT-old-names - fn descend_indexed_branch(&mut self, idx: usize) -> bool { + fn descend_indexed_branch(&mut self, idx: usize) -> Option { self.descend_indexed_byte(idx) } @@ -220,7 +220,7 @@ pub trait ZipperMoving: Zipper { /// /// NOTE: This method should have identical behavior to passing `0` to [descend_indexed_byte](ZipperMoving::descend_indexed_byte), /// although with less overhead - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } @@ -229,11 +229,13 @@ pub trait ZipperMoving: Zipper { /// /// If there is a value at the focus, the zipper will descend to the next value or branch, however the /// zipper will not descend further if this method is called with the focus already on a branch. - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, mut dst: Option<&mut Vec>) -> bool { let mut descended = false; while self.child_count() == 1 { descended = true; - self.descend_first_byte(); + if let (Some(dst), Some(byte)) = (&mut dst, self.descend_first_byte()) { + dst.push(byte); + } if self.is_val() { break; } @@ -241,29 +243,29 @@ pub trait ZipperMoving: Zipper { descended } - /// Ascends the zipper `steps` steps. Returns `true` if the zipper sucessfully moved `steps` + /// Ascends the zipper `steps` steps. Returns `Ok(()))` if the zipper sucessfully moved `steps` /// /// If the root is fewer than `n` steps from the zipper's position, then this method will stop at - /// the root and return `false` - fn ascend(&mut self, steps: usize) -> bool; + /// the root and return the number of remaining steps. + fn ascend(&mut self, steps: usize) -> Result<(), usize>; /// Ascends the zipper up a single byte. Equivalent to passing `1` to [ascend](Self::ascend) fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1).is_ok() } - /// Ascends the zipper to the nearest upstream branch point or value. Returns `true` if the zipper - /// focus moved upwards, otherwise returns `false` if the zipper was already at the root + /// Ascends the zipper to the nearest upstream branch point or value. Returns `Some(steps)` if the zipper + /// focus moved upwards, otherwise returns `None` if the zipper was already at the root /// /// NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. - fn ascend_until(&mut self) -> bool; + fn ascend_until(&mut self) -> Option; /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. Returns - /// `true` if the zipper focus moved upwards, otherwise returns `false` if the zipper was already at the + /// `Some(steps)` if the zipper focus moved upwards, otherwise returns `None` if the zipper was already at the /// root /// /// NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. - fn ascend_until_branch(&mut self) -> bool; + fn ascend_until_branch(&mut self) -> Option; /// Moves the zipper's focus to the next sibling byte with the same parent /// @@ -272,7 +274,7 @@ pub trait ZipperMoving: Zipper { /// /// This method is equivalent to calling [ZipperMoving::ascend] with `1`, followed by [ZipperMoving::descend_indexed_byte] /// where the index passed is 1 more than the index of the current focus position. - fn to_next_sibling_byte(&mut self) -> bool; + fn to_next_sibling_byte(&mut self) -> Option; // { // let cur_byte = match self.path().last() { // Some(byte) => *byte, @@ -302,7 +304,7 @@ pub trait ZipperMoving: Zipper { /// /// This method is equivalent to calling [Self::ascend] with `1`, followed by [Self::descend_indexed_byte] /// where the index passed is 1 less than the index of the current focus position. - fn to_prev_sibling_byte(&mut self) -> bool; + fn to_prev_sibling_byte(&mut self) -> Option; // { // let cur_byte = match self.path().last() { // Some(byte) => *byte, @@ -335,13 +337,13 @@ pub trait ZipperMoving: Zipper { //If we're at a leaf ascend until we're not and jump to the next sibling if self.child_count() == 0 { //We can stop ascending when we succeed in moving to a sibling - while !self.to_next_sibling_byte() { + while !self.to_next_sibling_byte().is_some() { if !self.ascend_byte() { return false; } } } else { - return self.descend_first_byte() + return self.descend_first_byte().is_some() } true } @@ -447,18 +449,18 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { /// encountered the root. fn to_next_val(&mut self) -> bool { loop { - if self.descend_first_byte() { + if self.descend_first_byte().is_some() { if self.is_val() { return true } - if self.descend_until() { + if self.descend_until(None) { if self.is_val() { return true } } } else { 'ascending: loop { - if self.to_next_sibling_byte() { + if self.to_next_sibling_byte().is_some() { if self.is_val() { return true } @@ -514,18 +516,18 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { fn k_path_default_internal(z: &mut Z, k: usize, base_idx: usize) -> bool { loop { if z.path().len() < base_idx + k { - while z.descend_first_byte() { + while z.descend_first_byte().is_some() { if z.path().len() == base_idx + k { return true } } } - if z.to_next_sibling_byte() { + if z.to_next_sibling_byte().is_some() { if z.path().len() == base_idx + k { return true } continue } while z.path().len() > base_idx { z.ascend_byte(); if z.path().len() == base_idx { return false } - if z.to_next_sibling_byte() { break } + if z.to_next_sibling_byte().is_some() { break } } } } @@ -592,7 +594,8 @@ pub trait ZipperPath: ZipperMoving { self.reset(); (overlap, self.descend_to(path)) } else { - self.ascend(to_ascend); + let ascended = self.ascend(to_ascend); + debug_assert_eq!(ascended, Ok(())); (overlap, self.descend_to(&path[overlap..])) } } @@ -717,15 +720,15 @@ impl ZipperMoving for &mut Z where Z: ZipperMoving + Zipper { fn descend_to_existing>(&mut self, k: K) -> usize { (**self).descend_to_existing(k) } fn descend_to_val>(&mut self, k: K) -> usize { (**self).descend_to_val(k) } fn descend_to_byte(&mut self, k: u8) -> bool { (**self).descend_to_byte(k) } - fn descend_indexed_byte(&mut self, idx: usize) -> bool { (**self).descend_indexed_byte(idx) } - fn descend_first_byte(&mut self) -> bool { (**self).descend_first_byte() } - fn descend_until(&mut self) -> bool { (**self).descend_until() } - fn ascend(&mut self, steps: usize) -> bool { (**self).ascend(steps) } + fn descend_indexed_byte(&mut self, idx: usize) -> Option { (**self).descend_indexed_byte(idx) } + fn descend_first_byte(&mut self) -> Option { (**self).descend_first_byte() } + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { (**self).descend_until(dst) } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { (**self).ascend(steps) } fn ascend_byte(&mut self) -> bool { (**self).ascend_byte() } - fn ascend_until(&mut self) -> bool { (**self).ascend_until() } - fn ascend_until_branch(&mut self) -> bool { (**self).ascend_until_branch() } - fn to_next_sibling_byte(&mut self) -> bool { (**self).to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { (**self).to_prev_sibling_byte() } + fn ascend_until(&mut self) -> Option { (**self).ascend_until() } + fn ascend_until_branch(&mut self) -> Option { (**self).ascend_until_branch() } + fn to_next_sibling_byte(&mut self) -> Option { (**self).to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { (**self).to_prev_sibling_byte() } fn to_next_step(&mut self) -> bool { (**self).to_next_step() } } @@ -855,15 +858,15 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn descend_to_existing>(&mut self, k: K) -> usize { self.z.descend_to_existing(k) } fn descend_to_val>(&mut self, k: K) -> usize { self.z.descend_to_val(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { self.z.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut self) -> bool { self.z.descend_first_byte() } - fn descend_until(&mut self) -> bool { self.z.descend_until() } - fn to_next_sibling_byte(&mut self) -> bool { self.z.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> bool { self.z.ascend(steps) } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> bool { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> Option { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } @@ -1006,15 +1009,15 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn descend_to_existing>(&mut self, k: K) -> usize { self.z.descend_to_existing(k) } fn descend_to_val>(&mut self, k: K) -> usize { self.z.descend_to_val(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { self.z.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut self) -> bool { self.z.descend_first_byte() } - fn descend_until(&mut self) -> bool { self.z.descend_until() } - fn to_next_sibling_byte(&mut self) -> bool { self.z.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> bool { self.z.ascend(steps) } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> bool { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> Option { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } @@ -1207,15 +1210,15 @@ impl ZipperMoving for ReadZipperOw fn descend_to_existing>(&mut self, k: K) -> usize { self.z.descend_to_existing(k) } fn descend_to_val>(&mut self, k: K) -> usize { self.z.descend_to_val(k) } fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { self.z.descend_indexed_byte(child_idx) } - fn descend_first_byte(&mut self) -> bool { self.z.descend_first_byte() } - fn descend_until(&mut self) -> bool { self.z.descend_until() } - fn to_next_sibling_byte(&mut self) -> bool { self.z.to_next_sibling_byte() } - fn to_prev_sibling_byte(&mut self) -> bool { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> bool { self.z.ascend(steps) } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } + fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } + fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> bool { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> bool { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> Option { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } @@ -1570,7 +1573,7 @@ pub(crate) mod read_zipper_core { self.focus_node.node_contains_partial_key(self.node_key()) } - fn descend_indexed_byte(&mut self, child_idx: usize) -> bool { + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.prepare_buffers(); debug_assert!(self.is_regularized()); @@ -1580,18 +1583,18 @@ pub(crate) mod read_zipper_core { self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; - true + Some(prefix) }, (Some(prefix), None) => { self.prefix_buf.push(prefix); self.focus_iter_token = NODE_ITER_INVALID; - true + Some(prefix) }, - (None, _) => false + (None, _) => None } } - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.prepare_buffers(); debug_assert!(self.is_regularized()); let cur_tok = self.focus_node.iter_token_for_path(self.node_key()); @@ -1603,7 +1606,7 @@ pub(crate) mod read_zipper_core { let byte_idx = self.node_key().len(); if byte_idx >= key_bytes.len() { debug_assert!(self.is_regularized()); - return false; //We can't go any deeper down this path + return None; //We can't go any deeper down this path } self.focus_iter_token = new_tok; self.prefix_buf.push(key_bytes[byte_idx]); @@ -1619,20 +1622,25 @@ pub(crate) mod read_zipper_core { } } debug_assert!(self.is_regularized()); - true + Some(key_bytes[byte_idx]) } else { self.focus_iter_token = new_tok; debug_assert!(self.is_regularized()); - false + None } } - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, mut dst: Option<&mut Vec>) -> bool { debug_assert!(self.is_regularized()); let mut moved = false; while self.child_count() == 1 { moved = true; - self.descend_first(); + // ??? Rust pls ??? + let dst: Option<&mut Vec> = match &mut dst { + Some(x) => Some(x), + None => None, + }; + self.descend_first(dst); if self.is_val_internal() { break; } @@ -1705,10 +1713,10 @@ pub(crate) mod read_zipper_core { // self.focus_node.node_first_val_depth_along_key(); // } - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.prepare_buffers(); if self.prefix_buf.len() == 0 { - return false + return None; } debug_assert!(self.is_regularized()); self.deregularize(); @@ -1719,7 +1727,7 @@ pub(crate) mod read_zipper_core { if self.focus_iter_token == NODE_ITER_FINISHED { self.regularize(); - return false + return None; } let (mut new_tok, mut key_bytes, mut child_node, mut _value) = self.focus_node.next_items(self.focus_iter_token); @@ -1728,16 +1736,17 @@ pub(crate) mod read_zipper_core { let node_key = self.node_key(); if node_key.len() == 0 { self.focus_iter_token = NODE_ITER_INVALID; - return false; + return None; } let fixed_len = node_key.len() - 1; if fixed_len >= key_bytes.len() || key_bytes[..fixed_len] != node_key[..fixed_len] { self.regularize(); - return false; + return None; } if key_bytes[fixed_len] > node_key[fixed_len] { - *self.prefix_buf.last_mut().unwrap() = key_bytes[node_key.len()-1]; + let byte = key_bytes[node_key.len()-1]; + *self.prefix_buf.last_mut().unwrap() = byte; self.focus_iter_token = new_tok; //If this operation landed us at the end of the path within the node, then we @@ -1754,7 +1763,7 @@ pub(crate) mod read_zipper_core { } debug_assert!(self.is_regularized()); - return true + return Some(byte); } (new_tok, key_bytes, child_node, _value) = self.focus_node.next_items(new_tok); @@ -1762,14 +1771,14 @@ pub(crate) mod read_zipper_core { self.focus_iter_token = NODE_ITER_FINISHED; self.regularize(); - false + None } - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } - fn ascend(&mut self, mut steps: usize) -> bool { + fn ascend(&mut self, mut steps: usize) -> Result<(), usize> { debug_assert!(self.is_regularized()); while steps > 0 { if self.excess_key_len() == 0 { @@ -1780,7 +1789,7 @@ pub(crate) mod read_zipper_core { }, None => { debug_assert!(self.is_regularized()); - return false + return Err(steps) } }; } @@ -1789,7 +1798,7 @@ pub(crate) mod read_zipper_core { steps -= cur_jump; } debug_assert!(self.is_regularized()); - true + Ok(()) } fn ascend_byte(&mut self) -> bool { @@ -1811,34 +1820,36 @@ pub(crate) mod read_zipper_core { true } - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> Option { debug_assert!(self.is_regularized()); if self.at_root() { - return false; + return None; } + let mut ascended = 0; loop { if self.node_key().len() == 0 { self.ascend_across_nodes(); } - self.ascend_within_node(); + ascended += self.ascend_within_node(); if self.child_count() > 1 || self.is_val() || self.at_root() { - return true; + return Some(ascended); } } } - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> Option { debug_assert!(self.is_regularized()); if self.at_root() { - return false; + return None; } + let mut ascended = 0; loop { if self.node_key().len() == 0 { self.ascend_across_nodes(); } - self.ascend_within_node(); + ascended += self.ascend_within_node(); if self.child_count() > 1 || self.at_root() { - return true; + return Some(ascended); } } } @@ -2421,7 +2432,7 @@ pub(crate) mod read_zipper_core { /// performs about as well as the `to_next_sibling_byte` that is there, but doesn't /// update the zipper's iter tokens #[inline] - fn to_sibling(&mut self, next: bool) -> bool { + fn to_sibling(&mut self, next: bool) -> Option { self.prepare_buffers(); debug_assert!(self.is_regularized()); if self.node_key().len() != 0 { @@ -2431,33 +2442,33 @@ pub(crate) mod read_zipper_core { self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; - true + Some(prefix) }, (Some(prefix), None) => { *self.prefix_buf.last_mut().unwrap() = prefix; - true + Some(prefix) }, - (None, _) => false + (None, _) => None } } else { let mut should_pop = false; let result = match self.ancestors.last() { - None => { false } + None => { None } Some((parent, _iter_tok, _prefix_offset)) => { match parent.get_sibling_of_child(self.parent_key(), next) { (Some(prefix), Some(child_node)) => { *self.prefix_buf.last_mut().unwrap() = prefix; *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; - true + Some(prefix) }, (Some(prefix), None) => { *self.prefix_buf.last_mut().unwrap() = prefix; should_pop = true; - true + Some(prefix) }, (None, _) => { - false + None } } } @@ -2617,24 +2628,30 @@ pub(crate) mod read_zipper_core { /// Internal method implementing part of [Self::descend_until], but doesn't pay attention to to [Self::child_count] #[inline] - fn descend_first(&mut self) { + fn descend_first(&mut self, mut dst: Option<&mut Vec>) { self.prepare_buffers(); match self.focus_node.first_child_from_key(self.node_key()) { (Some(prefix), Some(child_node)) => { //Step to a new node self.prefix_buf.extend(prefix); + if let Some(ref mut dst) = dst { + dst.extend_from_slice(prefix); + } self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; //If we're at the root of the new node, descend to the first child if prefix.len() == 0 { - self.descend_first() + self.descend_first(dst) } }, (Some(prefix), None) => { //Stay within the same node self.prefix_buf.extend(prefix); + if let Some(ref mut dst) = dst { + dst.extend_from_slice(prefix); + } }, (None, _) => unreachable!() } @@ -2722,10 +2739,12 @@ pub(crate) mod read_zipper_core { } /// Internal method used to impement `ascend_until` when ascending within a node #[inline] - fn ascend_within_node(&mut self) { + fn ascend_within_node(&mut self) -> usize { let branch_key = self.focus_node.prior_branch_key(self.node_key()); let new_len = self.origin_path.len().max(self.node_key_start() + branch_key.len()); + let old_len = self.prefix_buf.len(); self.prefix_buf.truncate(new_len); + old_len - new_len } /// Push a new node-path pair onto the zipper. This is used in the internal implementation of /// the [crate::zipper::ProductZipper] @@ -3103,32 +3122,32 @@ pub(crate) mod zipper_moving_tests { zipper.descend_to(&[b'r']); zipper.descend_to(&[b'o']); zipper.descend_to(&[b'm']); // focus = rom assert!(zipper.descend_to(&[b'\''])); // focus = rom' (' is the lowest byte) - assert!(zipper.to_next_sibling_byte()); // focus = roma (a is the second byte), but we can't actually guarantee whether we land on 'a' or 'u' + assert!(zipper.to_next_sibling_byte().is_some()); // focus = roma (a is the second byte), but we can't actually guarantee whether we land on 'a' or 'u' assert_in_list(zipper.path(), &[b"roma", b"romu"]); assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'n']); // both follow-ups romane and romanus have n following a - assert!(zipper.to_next_sibling_byte()); // focus = romu (u is the third byte) + assert!(zipper.to_next_sibling_byte().is_some()); // focus = romu (u is the third byte) assert_in_list(zipper.path(), &[b"roma", b"romu"]); assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'l']); // and romu is followed by lus - assert!(!zipper.to_next_sibling_byte()); // fails because there were only 3 children ['\'', 'a', 'u'] - assert!(zipper.to_prev_sibling_byte()); // focus = roma or romu (we stepped back) + assert_eq!(zipper.to_next_sibling_byte(), None); // fails because there were only 3 children ['\'', 'a', 'u'] + assert!(zipper.to_prev_sibling_byte().is_some()); // focus = roma or romu (we stepped back) assert_in_list(zipper.path(), &[b"roma", b"romu"]); - assert!(zipper.to_prev_sibling_byte()); // focus = rom' (we stepped back to where we began) + assert_eq!(zipper.to_prev_sibling_byte(), Some(39)); // focus = rom' (we stepped back to where we began) assert_eq!(zipper.path(), b"rom'"); assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'i']); - assert!(zipper.ascend(1)); // focus = rom + assert_eq!(zipper.ascend(1), Ok(())); // focus = rom assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'\'', b'a', b'u']); // all three options we visited - assert!(zipper.descend_indexed_byte(0)); // focus = rom' + assert_eq!(zipper.descend_indexed_byte(0), Some(39)); // focus = rom' assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'i']); - assert!(zipper.ascend(1)); // focus = rom - assert!(zipper.descend_indexed_byte(1)); // focus = roma + assert_eq!(zipper.ascend(1), Ok(())); // focus = rom + assert_eq!(zipper.descend_indexed_byte(1), Some(b'a')); // focus = roma assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'n']); - assert!(zipper.ascend(1)); - assert!(zipper.descend_indexed_byte(2)); // focus = romu + assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.descend_indexed_byte(2), Some(b'u')); // focus = romu assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'l']); - assert!(zipper.ascend(1)); - assert!(zipper.descend_indexed_byte(1)); // focus = roma + assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.descend_indexed_byte(1), Some(b'a')); // focus = roma assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'n']); - assert!(zipper.ascend(1)); + assert_eq!(zipper.ascend(1), Ok(())); // ' < a < u // 39 105 117 } @@ -3151,34 +3170,35 @@ pub(crate) mod zipper_moving_tests { zipper.descend_to(b"e"); assert_eq!(zipper.path(), b"mane"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend_until(), true); + assert_eq!(zipper.ascend_until(), Some(1)); zipper.descend_to(b"us"); assert_eq!(zipper.path(), b"manus"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend_until(), true); + assert_eq!(zipper.ascend_until(), Some(2)); assert_eq!(zipper.path(), b"man"); assert_eq!(zipper.child_count(), 2); - assert_eq!(zipper.ascend_until(), true); + assert_eq!(zipper.ascend_until(), Some(2)); assert_eq!(zipper.path(), b"m"); assert_eq!(zipper.child_count(), 3); - assert_eq!(zipper.ascend_until(), true); + assert_eq!(zipper.ascend_until(), Some(1)); assert_eq!(zipper.path(), b""); assert_eq!(zipper.child_count(), 1); assert_eq!(zipper.at_root(), true); - assert_eq!(zipper.ascend_until(), false); + assert_eq!(zipper.ascend_until(), None); //Test `ascend` zipper.descend_to(b"manus"); assert_eq!(zipper.path(), b"manus"); - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), Ok(())); assert_eq!(zipper.path(), b"manu"); - assert_eq!(zipper.ascend(5), false); + assert_eq!(zipper.ascend(5), Err(1)); assert_eq!(zipper.path(), b""); assert_eq!(zipper.at_root(), true); zipper.descend_to(b"mane"); assert_eq!(zipper.path(), b"mane"); - assert_eq!(zipper.ascend(3), true); + assert_eq!(zipper.ascend(3), Ok(())); assert_eq!(zipper.path(), b"m"); + eprintln!("child_mask = {:?}", zipper.child_mask()); assert_eq!(zipper.child_count(), 3); } @@ -3189,19 +3209,19 @@ pub(crate) mod zipper_moving_tests { zip.descend_to("2"); assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 0); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.path(), b"2"); zip.reset(); - assert!(zip.descend_indexed_byte(2)); + assert_eq!(zip.descend_indexed_byte(2), Some(b'2')); assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 0); assert_eq!(zip.path(), b"2"); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.path(), b"2"); zip.reset(); - assert!(!zip.descend_indexed_byte(7)); + assert_eq!(zip.descend_indexed_byte(7), None); assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 7); assert_eq!(zip.path(), b""); @@ -3215,26 +3235,26 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zip.val(), Some(&())); assert_eq!(zip.path(), b"000"); assert_eq!(zip.child_count(), 0); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.path(), b"000"); zip.reset(); - assert!(!zip.descend_indexed_byte(2)); + assert_eq!(zip.descend_indexed_byte(2), None); assert_eq!(zip.child_count(), 2); - assert!(zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), Some(b'1')); assert_eq!(zip.path(), b"1"); assert_eq!(zip.val(), None); assert_eq!(zip.child_count(), 1); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.val(), None); assert_eq!(zip.path(), b"1"); zip.reset(); - assert!(zip.descend_indexed_byte(0)); + assert_eq!(zip.descend_indexed_byte(0), Some(b'0')); assert_eq!(zip.path(), b"0"); assert_eq!(zip.val(), None); assert_eq!(zip.child_count(), 1); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.val(), None); assert_eq!(zip.path(), b"0"); } @@ -3247,26 +3267,26 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zip.is_val(), true); assert_eq!(zip.path(), b"000"); assert_eq!(zip.child_count(), 0); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.path(), b"000"); zip.reset(); - assert!(!zip.descend_indexed_byte(2)); + assert_eq!(zip.descend_indexed_byte(2), None); assert_eq!(zip.child_count(), 2); - assert!(zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), Some(b'1')); assert_eq!(zip.path(), b"1"); assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 1); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.is_val(), false); assert_eq!(zip.path(), b"1"); zip.reset(); - assert!(zip.descend_indexed_byte(0)); + assert_eq!(zip.descend_indexed_byte(0), Some(b'0')); assert_eq!(zip.path(), b"0"); assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 1); - assert!(!zip.descend_indexed_byte(1)); + assert_eq!(zip.descend_indexed_byte(1), None); assert_eq!(zip.is_val(), false); assert_eq!(zip.path(), b"0"); } @@ -3276,7 +3296,7 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_descend_until_test1(mut zip: Z) { for key in ZIPPER_DESCEND_UNTIL_TEST1_KEYS { - assert!(zip.descend_until()); + assert!(zip.descend_until(None)); assert_eq!(zip.path(), *key); } } @@ -3287,13 +3307,13 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_ascend_until_test1(mut zip: Z) { assert!(!zip.descend_to(b"AAaDDd")); assert_eq!(zip.path(), b"AAaDDd"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(3)); assert_eq!(zip.path(), b"AAa"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"AA"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(2)); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), None); } // Test what's likely to be represented as a pair node @@ -3302,13 +3322,13 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_ascend_until_test2(mut zip: Z) { assert!(!zip.descend_to(b"AAaDDd")); assert_eq!(zip.path(), b"AAaDDd"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(3)); assert_eq!(zip.path(), b"AAa"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"AA"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(2)); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), None); } /// Test a straight-line trie @@ -3318,27 +3338,27 @@ pub(crate) mod zipper_moving_tests { //First test that ascend_until stops when transitioning from non-existent path assert_eq!(zip.descend_to(b"123456"), false); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"12345"); //Test that ascend_until stops at each value - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"1234"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"123"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"12"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"1"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), None); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values assert!(zip.descend_to(b"12345")); assert_eq!(zip.path(), b"12345"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), Some(5)); assert_eq!(zip.path(), b""); assert!(zip.at_root()); @@ -3350,33 +3370,33 @@ pub(crate) mod zipper_moving_tests { assert!(zip.descend_to(b"12345")); assert_eq!(zip.path(), b"12345"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"1234"); // "1234" is a branch only assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 2); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"123"); // "123" is a value only assert_eq!(zip.child_count(), 1); assert_eq!(zip.is_val(), true); - assert!(zip.ascend_until()); // Jump over "12" because it's neither a branch nor a value + assert_eq!(zip.ascend_until(), Some(2)); // Jump over "12" because it's neither a branch nor a value assert_eq!(zip.path(), b"1"); // "1" is both a branch and a value assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 2); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b""); assert_eq!(zip.child_count(), 1); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), None); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values assert!(zip.descend_to(b"12345")); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), Some(1)); assert_eq!(zip.path(), b"1234"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), Some(3)); assert_eq!(zip.path(), b"1"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), Some(1)); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), None); assert!(zip.at_root()); } @@ -3388,33 +3408,33 @@ pub(crate) mod zipper_moving_tests { assert!(zip.descend_to(b"12345")); assert_eq!(zip.path(), b"12345"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"1234"); // "1234" is a branch only assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 2); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"123"); // "123" is a value only assert_eq!(zip.child_count(), 1); assert_eq!(zip.is_val(), true); - assert!(zip.ascend_until()); // Jump over "12" because it's neither a branch nor a value + assert_eq!(zip.ascend_until(), Some(2)); // Jump over "12" because it's neither a branch nor a value assert_eq!(zip.path(), b"1"); // "1" is both a branch and a value assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 2); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b""); assert_eq!(zip.child_count(), 1); - assert!(!zip.ascend_until()); + assert_eq!(zip.ascend_until(), None); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values assert!(zip.descend_to(b"12345")); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), Some(1)); assert_eq!(zip.path(), b"1234"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), Some(3)); assert_eq!(zip.path(), b"1"); - assert!(zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), Some(1)); assert_eq!(zip.path(), b""); - assert!(!zip.ascend_until_branch()); + assert_eq!(zip.ascend_until_branch(), None); assert!(zip.at_root()); } @@ -3425,15 +3445,15 @@ pub(crate) mod zipper_moving_tests { //Test that ascend_until stops when transitioning from non-existent path assert_eq!(zip.descend_to(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB"), false); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); //Test that jump all the way back to where we want to be - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(126)); assert_eq!(zip.path(), b"A"); - assert!(zip.ascend_until()); + assert_eq!(zip.ascend_until(), Some(1)); assert_eq!(zip.path(), b""); - assert_eq!(zip.ascend_until(), false); + assert_eq!(zip.ascend_until(), None); } pub const ZIPPER_INDEXED_MOVEMENT_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; @@ -3442,11 +3462,11 @@ pub(crate) mod zipper_moving_tests { //descends a single specific byte using `descend_indexed_byte`. Just for testing. A real user would use `descend_towards` fn descend_byte(zipper: &mut Z, byte: u8) { for i in 0..zipper.child_count() { - assert_eq!(zipper.descend_indexed_byte(i), true); + assert!(zipper.descend_indexed_byte(i).is_some()); if *zipper.path().last().unwrap() == byte { break } else { - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), Ok(())); } } } @@ -3456,24 +3476,24 @@ pub(crate) mod zipper_moving_tests { descend_byte(&mut zipper, b'r'); assert_eq!(zipper.path(), b"r"); assert_eq!(zipper.child_count(), 2); - assert_eq!(zipper.descend_until(), false); + assert_eq!(zipper.descend_until(None), false); descend_byte(&mut zipper, b'o'); assert_eq!(zipper.path(), b"ro"); assert_eq!(zipper.child_count(), 1); - assert_eq!(zipper.descend_until(), true); + assert_eq!(zipper.descend_until(None), true); assert_eq!(zipper.path(), b"rom"); assert_eq!(zipper.child_count(), 3); zipper.reset(); - assert_eq!(zipper.descend_until(), false); + assert_eq!(zipper.descend_until(None), false); descend_byte(&mut zipper, b'a'); assert_eq!(zipper.path(), b"a"); assert_eq!(zipper.child_count(), 1); - assert_eq!(zipper.descend_until(), true); + assert_eq!(zipper.descend_until(None), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend(3), true); + assert_eq!(zipper.ascend(3), Ok(())); assert_eq!(zipper.path(), b"ar"); assert_eq!(zipper.child_count(), 1); } @@ -3492,10 +3512,10 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.is_val(), true); zipper.descend_to(b"e"); assert_eq!(zipper.is_val(), true); - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), Ok(())); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); - zipper.descend_until(); + zipper.descend_until(None); assert_eq!(zipper.is_val(), true); } @@ -3620,9 +3640,9 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_byte_iter_test1(mut zipper: Z) { assert_eq!(zipper.descend_to_byte(b'A'), true); - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(b'B')); assert_eq!(zipper.path(), b"AB"); - assert_eq!(zipper.to_next_sibling_byte(), false); + assert_eq!(zipper.to_next_sibling_byte(), None); assert_eq!(zipper.path(), b"AB"); } @@ -3630,9 +3650,9 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_BYTES_ITER_TEST2_PATH: &[u8] = &[2, 194]; pub fn zipper_byte_iter_test2(mut zipper: Z) { - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(1)); assert_eq!(zipper.path(), &[1]); - assert_eq!(zipper.to_next_sibling_byte(), false); + assert_eq!(zipper.to_next_sibling_byte(), None); assert_eq!(zipper.path(), &[1]); } @@ -3641,11 +3661,11 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_byte_iter_test3(mut zipper: Z) { assert_eq!(zipper.path(), &[]); - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(6)); assert_eq!(zipper.path(), &[6]); - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(193)); assert_eq!(zipper.path(), &[6, 193]); - assert_eq!(zipper.descend_first_byte(), true); + assert_eq!(zipper.descend_first_byte(), Some(7)); assert_eq!(zipper.path(), &[6, 193, 7]); } @@ -3654,20 +3674,20 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_byte_iter_test4(mut zipper: Z) { //Check that we end up at the first leaf by depth-first search - while zipper.descend_first_byte() {} + while zipper.descend_first_byte().is_some() {} assert_eq!(zipper.path(), b"ABCDEF"); //Try taking a different branch zipper.reset(); assert!(zipper.descend_to(b"ABC")); assert_eq!(zipper.path(), b"ABC"); - assert!(zipper.descend_indexed_byte(1)); + assert_eq!(zipper.descend_indexed_byte(1), Some(b'd')); assert_eq!(zipper.path(), b"ABCd"); - assert!(zipper.descend_first_byte()); + assert_eq!(zipper.descend_first_byte(), Some(b'e')); assert_eq!(zipper.path(), b"ABCde"); - assert!(zipper.descend_first_byte()); + assert_eq!(zipper.descend_first_byte(), Some(b'f')); assert_eq!(zipper.path(), b"ABCdef"); - assert!(!zipper.descend_first_byte()); + assert_eq!(zipper.descend_first_byte(), None); } pub const ZIPPER_BYTES_ITER_TEST5_KEYS: &[&[u8]] = &[ @@ -3683,16 +3703,16 @@ pub(crate) mod zipper_moving_tests { zipper.reset(); zipper.descend_to(&keys[0][..i]); if i != 18 && i != 5 { - assert_eq!(zipper.to_next_sibling_byte(), false); + assert_eq!(zipper.to_next_sibling_byte(), None); } } zipper.reset(); zipper.descend_to([2, 197, 97, 120, 105, 111, 109, 3, 193, 61, 4, 193, 97, 192, 192, 3, 193, 75]); - assert_eq!(zipper.to_next_sibling_byte(), true); + assert_eq!(zipper.to_next_sibling_byte(), Some(84)); zipper.reset(); zipper.descend_to([2, 197, 97, 120, 105]); - assert_eq!(zipper.to_next_sibling_byte(), true); + assert_eq!(zipper.to_next_sibling_byte(), Some(255)); } } @@ -3845,12 +3865,12 @@ pub(crate) mod zipper_iteration_tests { pub fn k_path_test1<'a, Z: ZipperIteration>(mut zipper: Z) { //This is a cheesy way to encode lengths, but it's is more readable than unprintable chars - assert!(zipper.descend_indexed_byte(0)); + assert_eq!(zipper.descend_indexed_byte(0), Some(b'5')); let sym_len = usize::from_str_radix(std::str::from_utf8(&[zipper.path()[0]]).unwrap(), 10).unwrap(); assert_eq!(sym_len, 5); //Step over the ':' character - assert!(zipper.descend_indexed_byte(0)); + assert_eq!(zipper.descend_indexed_byte(0), Some(b':')); assert_eq!(zipper.child_count(), 6); //Start iterating over all the symbols of length=sym_len @@ -3956,7 +3976,7 @@ pub(crate) mod zipper_iteration_tests { assert!(zipper.descend_to(b"1")); assert_eq!(zipper.descend_first_k_path(1), true); assert_eq!(zipper.path(), b"1a"); - assert_eq!(zipper.descend_indexed_byte(0), true); + assert_eq!(zipper.descend_indexed_byte(0), Some(b'1')); assert_eq!(zipper.path(), b"1a1"); assert_eq!(zipper.descend_first_k_path(1), true); assert_eq!(zipper.path(), b"1a1A"); @@ -3966,7 +3986,7 @@ pub(crate) mod zipper_iteration_tests { assert_eq!(zipper.path(), b"1a1C"); assert_eq!(zipper.to_next_k_path(1), false); assert_eq!(zipper.path(), b"1a1"); - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), Ok(())); assert_eq!(zipper.path(), b"1a"); assert_eq!(zipper.to_next_k_path(1), true); assert_eq!(zipper.path(), b"1b"); @@ -4106,7 +4126,7 @@ pub(crate) mod zipper_iteration_tests { //Try with a `descend_to` & `ascend` test_loop(&mut zipper, |zipper, path| assert!(zipper.descend_to(path)), - |zipper, steps| assert!(zipper.ascend(steps)), + |zipper, steps| assert!(zipper.ascend(steps).is_ok()), ); //Try with a `descend_to_byte` & `ascend_byte` @@ -4126,8 +4146,8 @@ pub(crate) mod zipper_iteration_tests { //Try with a `descend_first_byte` & `ascend_byte` test_loop(&mut zipper, |zipper, path| { - for _ in 0..path.len() { - assert!(zipper.descend_first_byte()); + for ii in 0..path.len() { + assert_eq!(zipper.descend_first_byte(), Some(path[ii])); } }, |zipper, steps| { @@ -4258,11 +4278,11 @@ mod tests { zipper.descend_to(b"e"); assert_eq!(zipper.is_val(), true); assert_eq!(zipper.get_val(), Some(&"romane")); - assert_eq!(zipper.ascend(1), true); + assert_eq!(zipper.ascend(1), Ok(())); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); assert_eq!(zipper.get_val(), None); - zipper.descend_until(); + zipper.descend_until(None); assert_eq!(zipper.is_val(), true); assert_eq!(zipper.get_val(), Some(&"romanus")); } @@ -4396,15 +4416,15 @@ mod tests { let mut r0 = map.read_zipper(); assert_eq!(r0.descend_to_byte(0), true); let mut r1 = r0.fork_read_zipper(); - assert_eq!(r1.to_next_sibling_byte(), false); + assert_eq!(r1.to_next_sibling_byte(), None); assert_eq!(r1.child_mask().0[0], (1<<3) | (1<<4) | (1<<5)); assert_eq!(r1.descend_to_byte(3), true); assert_eq!(r1.child_mask().0[0], 0); - assert_eq!(r1.to_next_sibling_byte(), true); + assert_eq!(r1.to_next_sibling_byte(), Some(4)); assert_eq!(r1.origin_path(), &[0, 4]); assert_eq!(r1.path(), &[4]); - assert_eq!(r1.to_next_sibling_byte(), true); - assert_eq!(r1.to_next_sibling_byte(), false); + assert_eq!(r1.to_next_sibling_byte(), Some(5)); + assert_eq!(r1.to_next_sibling_byte(), None); } #[test] diff --git a/src/zipper_head.rs b/src/zipper_head.rs index e66d97a8..96d6e595 100644 --- a/src/zipper_head.rs +++ b/src/zipper_head.rs @@ -820,7 +820,7 @@ mod tests { let mut wz = zh.write_zipper_at_exclusive_path(&[3, 194, 22]).unwrap(); assert_eq!(rz.val(), None); - assert!(rz.descend_first_byte()); + assert_eq!(rz.descend_first_byte(), Some(133)); assert_eq!(rz.val(), Some(&())); assert_eq!(wz.val(), None); @@ -828,7 +828,7 @@ mod tests { rz.reset(); assert_eq!(rz.val(), None); - assert!(rz.descend_first_byte()); + assert_eq!(rz.descend_first_byte(), Some(133)); assert_eq!(rz.val(), Some(&())); drop(wz); @@ -850,7 +850,7 @@ mod tests { let mut wz = zh.write_zipper_at_exclusive_path(&[3, 194, 22]).unwrap(); assert_eq!(rz.val(), None); - assert!(rz.descend_first_byte()); + assert_eq!(rz.descend_first_byte(), Some(133)); assert_eq!(rz.val(), Some(&1004)); assert_eq!(rz2.val(), Some(&1003)); @@ -1009,7 +1009,7 @@ mod tests { assert_eq!(z.child_count(), 0); assert_eq!(z.child_mask(), ByteMask::EMPTY); assert_eq!(z.path_exists(), true); - assert_eq!(z.to_next_sibling_byte(), false); + assert_eq!(z.to_next_sibling_byte(), None); assert_eq!(z.ascend_byte(), false); // Test creating a zipper at a path that doesn't exist @@ -1017,7 +1017,7 @@ mod tests { assert_eq!(z2.val(), None); assert_eq!(z2.child_count(), 0); assert_eq!(z2.child_mask(), ByteMask::EMPTY); - assert_eq!(z2.to_next_sibling_byte(), false); + assert_eq!(z2.to_next_sibling_byte(), None); assert_eq!(z2.ascend_byte(), false); //Conceptually this should be `false`, but the act of creating the ReadZipper currently creates @@ -1037,28 +1037,28 @@ mod tests { // Create a zipper and test to make sure it behaves properly let mut z = zh.read_zipper_at_path(b"A").unwrap(); assert_eq!(z.val(), Some(&24)); - assert_eq!(z.to_next_sibling_byte(), false); - z.descend_until(); + assert_eq!(z.to_next_sibling_byte(), None); + z.descend_until(None); assert_eq!(z.path(), b"BCDEFG"); assert_eq!(z.origin_path(), b"ABCDEFG"); assert_eq!(z.val(), Some(&42)); - assert_eq!(z.to_next_sibling_byte(), false); + assert_eq!(z.to_next_sibling_byte(), None); // Create a second zipper and ensure it's valid let mut z2 = zh.read_zipper_at_path(z.origin_path()).unwrap(); assert_eq!(z2.path(), b""); assert_eq!(z2.origin_path(), b"ABCDEFG"); assert_eq!(z2.val(), Some(&42)); - assert_eq!(z2.to_next_sibling_byte(), false); + assert_eq!(z2.to_next_sibling_byte(), None); // Test the original zipper assert_eq!(z.val(), Some(&42)); - assert_eq!(z.to_next_sibling_byte(), false); - assert_eq!(z.ascend_until(), true); + assert_eq!(z.to_next_sibling_byte(), None); + assert_eq!(z.ascend_until(), Some(6)); assert_eq!(z.path(), b""); assert_eq!(z.origin_path(), b"A"); assert_eq!(z.val(), Some(&24)); - assert_eq!(z.to_next_sibling_byte(), false); + assert_eq!(z.to_next_sibling_byte(), None); } /// Similar test to zipper_headc, but here we are testing to make sure there are no issues when @@ -1135,7 +1135,7 @@ mod tests { //Try pre-creating trie in the parent that will be visited by the child zipper b_zipper.descend_to(b"-children-0+metadata"); b_zipper.set_val(-3); - b_zipper.ascend(10); + b_zipper.ascend(10).unwrap(); //Make a ZipperHead on the WriteZipper, and make two more parallel zippers let b_head = b_zipper.zipper_head(); @@ -1406,7 +1406,7 @@ mod tests { //Make sure we cleaned up the dangling path, but nothing else let mut rz = zh.read_zipper_at_borrowed_path(b"a_path_").unwrap(); - assert!(rz.descend_until()); + assert!(rz.descend_until(None)); assert_eq!(rz.path(), b"to_somewhere"); drop(rz); @@ -1415,7 +1415,7 @@ mod tests { zh.cleanup_write_zipper(wz); let mut rz = zh.read_zipper_at_borrowed_path(b"a_path_").unwrap(); assert_eq!(rz.path(), b""); - assert!(rz.descend_until()); + assert!(rz.descend_until(None)); assert_eq!(rz.path(), b"to_somewhere"); drop(rz); From 98e06fb23ea649352d8b36a93d2dcc363549959c Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 25 Sep 2025 14:51:55 -0500 Subject: [PATCH 03/18] added wrapper to support tracking paths on blind zippers --- src/lib.rs | 3 + src/track_path.rs | 200 ++++++++++++++++++++++++++++++++++++++++++++++ src/zipper.rs | 2 + 3 files changed, 205 insertions(+) create mode 100644 src/track_path.rs diff --git a/src/lib.rs b/src/lib.rs index 41d269ba..86e26570 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,6 +90,9 @@ pub mod utils; /// Extensions to the API that may or may not become permanant pub mod experimental; +/// Wrapper to allow tracking path for a blind zipper +pub mod track_path; + /// Compact representation of the trie #[cfg(feature = "arena_compact")] pub mod arena_compact; diff --git a/src/track_path.rs b/src/track_path.rs new file mode 100644 index 00000000..7bd64896 --- /dev/null +++ b/src/track_path.rs @@ -0,0 +1,200 @@ +use crate::{ + utils::ByteMask, + zipper::{ + Zipper, ZipperAbsolutePath, ZipperMoving, ZipperIteration, + ZipperPath, ZipperPathBuffer, ZipperValues, + ZipperReadOnlyValues, ZipperReadOnlyConditionalValues, + }, +}; + +/// Wrapper for blind zippers that allows tracking path +/// +/// This allows having nested virtual zippers that don't maintain their +/// own path buffer, such that they don't repeat the work of copying paths. +pub struct TrackPath { + zipper: Z, + path: Vec, + origin_len: usize, +} + +impl TrackPath { + pub fn new(mut zipper: Z) -> Self { + zipper.reset(); + Self { + zipper, + path: Vec::new(), + origin_len: 0, + } + } + pub fn with_origin(mut zipper: Z, origin: &[u8]) -> Self { + zipper.reset(); + Self { + zipper, + path: origin.to_vec(), + origin_len: origin.len(), + } + } +} + +impl Zipper for TrackPath { + #[inline] fn path_exists(&self) -> bool { self.zipper.path_exists() } + #[inline] fn is_val(&self) -> bool { self.zipper.is_val() } + #[inline] fn child_count(&self) -> usize { self.zipper.child_count() } + #[inline] fn child_mask(&self) -> ByteMask { self.zipper.child_mask() } +} +impl ZipperMoving for TrackPath { + #[inline] fn at_root(&self) -> bool { self.zipper.at_root() } + fn reset(&mut self) { + self.zipper.reset(); + self.path.truncate(self.origin_len); + } + fn val_count(&self) -> usize { todo!() } + fn descend_to>(&mut self, path: K) -> bool { + let path = path.as_ref(); + self.path.extend_from_slice(path); + self.zipper.descend_to(path) + } + fn descend_to_existing>(&mut self, path: K) -> usize { + let path = path.as_ref(); + let descended = self.zipper.descend_to_existing(path); + self.path.extend_from_slice(&path[..descended]); + descended + } + fn descend_to_val>(&mut self, path: K) -> usize { + let path = path.as_ref(); + let descended = self.zipper.descend_to_val(path); + self.path.extend_from_slice(&path[..descended]); + descended + } + fn descend_to_byte(&mut self, k: u8) -> bool { + self.path.push(k); + self.zipper.descend_to_byte(k) + } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { + let rv = self.zipper.ascend(steps); + let ascended = match rv { + Ok(()) => steps, + Err(remaining) => steps - remaining, + }; + let orig_len = self.path.len(); + self.path.truncate(orig_len - ascended); + rv + } + fn ascend_byte(&mut self) -> bool { + if !self.zipper.ascend_byte() { + return false; + } + self.path.pop(); + true + } + fn ascend_until(&mut self) -> Option { + let ascended = self.zipper.ascend_until()?; + let orig_len = self.path.len(); + self.path.truncate(orig_len - ascended); + Some(ascended) + } + fn ascend_until_branch(&mut self) -> Option { + let ascended = self.zipper.ascend_until_branch()?; + let orig_len = self.path.len(); + self.path.truncate(orig_len - ascended); + Some(ascended) + } + fn to_next_sibling_byte(&mut self) -> Option { + let byte = self.zipper.to_next_sibling_byte()?; + let last = self.path.last_mut().expect("path must not be empty"); + *last = byte; + Some(byte) + } + fn to_prev_sibling_byte(&mut self) -> Option { + let byte = self.zipper.to_prev_sibling_byte()?; + let last = self.path.last_mut().expect("path must not be empty"); + *last = byte; + Some(byte) + } + fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { + let byte = self.zipper.descend_indexed_byte(child_idx)?; + self.path.push(byte); + Some(byte) + } + fn descend_first_byte(&mut self) -> Option { + let byte = self.zipper.descend_first_byte()?; + self.path.push(byte); + Some(byte) + } + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { + let orig_len = self.path.len(); + let descended = self.zipper.descend_until(Some(&mut self.path)); + if let Some(dst) = dst { + dst.extend_from_slice(&self.path[orig_len..]); + } + descended + } + // TODO: using default impl. re-using zipper's own `to_next_step` implementation + // would require changing the API such that path can be updated. + // fn to_next_step(&mut self) -> bool; +} + +impl ZipperIteration for TrackPath { } + +impl ZipperPath for TrackPath { + fn path(&self) -> &[u8] { &self.path[self.origin_len..] } +} + +impl ZipperAbsolutePath for TrackPath { + fn origin_path(&self) -> &[u8] { &self.path } + fn root_prefix_path(&self) -> &[u8] { &self.path[..self.origin_len] } +} + +impl, V> ZipperValues for TrackPath { + fn val(&self) -> Option<&V> { self.zipper.val() } +} + +impl<'a, Z: ZipperReadOnlyValues<'a, V>, V> ZipperReadOnlyValues<'a, V> for TrackPath { + fn get_val(&self) -> Option<&'a V> { self.zipper.get_val() } +} + +impl<'a, Z: ZipperReadOnlyConditionalValues<'a, V>, V> ZipperReadOnlyConditionalValues<'a, V> for TrackPath { + type WitnessT = Z::WitnessT; + fn witness<'w>(&self) -> Self::WitnessT { self.zipper.witness() } + fn get_val_with_witness<'w>(&self, witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { + self.zipper.get_val_with_witness(witness) + } +} + +impl ZipperPathBuffer for TrackPath { + unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { + let ptr = self.path.as_ptr(); + unsafe { core::slice::from_raw_parts(ptr, len) } + } + fn prepare_buffers(&mut self) { } + fn reserve_buffers(&mut self, path_len: usize, _stack: usize) { + self.path.reserve(path_len); + } +} + +#[cfg(test)] +mod tests { + use super::{TrackPath}; + use crate::{ + PathMap, + zipper::{zipper_iteration_tests, zipper_moving_tests}, + }; + + zipper_moving_tests::zipper_moving_tests!(track_path, + |keys: &[&[u8]]| { + keys.into_iter().map(|k| (k, ())).collect::>() + }, + |trie: &mut PathMap<()>, path: &[u8]| { + TrackPath::with_origin(trie.read_zipper_at_path(path), path) + } + ); + + zipper_iteration_tests::zipper_iteration_tests!(track_path, + |keys: &[&[u8]]| { + keys.into_iter().map(|k| (k, ())).collect::>() + }, + |trie: &mut PathMap<()>, path: &[u8]| { + TrackPath::with_origin(trie.read_zipper_at_path(path), path) + } + ); +} diff --git a/src/zipper.rs b/src/zipper.rs index e3e9812f..c9ec8e12 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -19,6 +19,7 @@ pub use crate::zipper_head::*; pub use crate::product_zipper::{ProductZipper, ProductZipperG}; pub use crate::overlay_zipper::{OverlayZipper}; pub use crate::prefix_zipper::{PrefixZipper}; +pub use crate::track_path::{TrackPath}; use crate::zipper_tracking::*; @@ -3942,6 +3943,7 @@ pub(crate) mod zipper_iteration_tests { //Scan over the nested second symbols in the path (upper case letters) zipper.reset(); assert!(zipper.descend_to(b"1a1")); + assert_eq!(zipper.path(), b"1a1"); assert_eq!(zipper.descend_first_k_path(1), true); assert_eq!(zipper.path(), b"1a1A"); assert_eq!(zipper.to_next_k_path(1), true); From 7b5e75c193d4287a6e9ce11ab9267ee4acdbf49e Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Tue, 7 Oct 2025 22:25:02 -0600 Subject: [PATCH 04/18] Reverting to main-line gxhash, now that all the changes we need have landed upstream --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 939eeef8..33f90dc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ act_counters = ["arena_compact"] # LP: Question: Why isn't this code enabled by viz = [] [target.'cfg(not(any(miri,target_arch="riscv64")))'.dependencies] -gxhash = {version="3.5", git="https://github.com/ogxd/gxhash"} # for dag_serialization +gxhash = {version="3.5"} # for dag_serialization, merkleization, and caching catamorphism [target.'cfg(any(miri,target_arch="riscv64"))'.dependencies] xxhash-rust = { version = "0.8.15", features = ["xxh64", "xxh3", "const_xxh3"] } # Replacement for gxhash running under miri From 519341f88e3b4eeac31d30a53654d4b086c39067 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Tue, 7 Oct 2025 22:49:38 -0600 Subject: [PATCH 05/18] Merging main in, which involved updating PolyZipper and EmptyZipper types to the "Blind" zipper API Also filling in missing impls in PolyZipper b/c the default impls are sooooo much less efficient. They're mainly there to model correct behavior --- Cargo.toml | 18 +- .../diagram_src/1.03.00_exclusivity_rules.mmd | 53 ++ .../diagram_src/1.04.00_product_zipper.mmd | 65 +++ pathmap-book/src/1.00.00_intro.md | 9 +- pathmap-book/src/1.00.01_basics.md | 71 +++ pathmap-book/src/1.01.00_algebraic_ops.md | 4 +- pathmap-book/src/1.02.00_zippers.md | 10 +- pathmap-book/src/1.02.02_zipper_values.md | 3 +- pathmap-book/src/1.02.05_zipper_iter.md | 4 +- pathmap-book/src/1.03.00_multi_zipper.md | 43 +- pathmap-book/src/1.03.01_multithreading.md | 100 ++++ pathmap-book/src/1.04.00_abstract_zippers.md | 105 +++- .../src/1.06.00_serialize_deserilize.md | 2 + pathmap-book/src/A.0001_map_root_values.md | 4 +- pathmap-book/src/A.0002_smart_ptr_upgrade.md | 14 +- pathmap-book/src/SUMMARY.md | 9 +- .../src/images/1.03.00_exclusivity_rules.svg | 1 + .../src/images/1.04.00_product_zipper.svg | 1 + pathmap-derive/Cargo.toml | 12 + pathmap-derive/src/lib.rs | 531 ++++++++++++++++++ src/arena_compact.rs | 12 +- src/bridge_node.rs | 3 +- src/dense_byte_node.rs | 62 +- src/empty_zipper.rs | 135 +++++ src/lib.rs | 13 +- src/line_list_node.rs | 52 +- src/morphisms.rs | 2 +- src/poly_zipper.rs | 124 ++++ src/prefix_zipper.rs | 16 +- src/product_zipper.rs | 117 ++-- src/ring.rs | 511 ++++++++--------- src/tiny_node.rs | 10 +- src/trie_map.rs | 164 ++++-- src/trie_ref.rs | 29 +- src/utils/mod.rs | 8 + src/write_zipper.rs | 276 +++++++-- src/zipper.rs | 359 +++++++----- 37 files changed, 2244 insertions(+), 708 deletions(-) create mode 100644 pathmap-book/diagram_src/1.03.00_exclusivity_rules.mmd create mode 100644 pathmap-book/diagram_src/1.04.00_product_zipper.mmd create mode 100644 pathmap-book/src/1.00.01_basics.md create mode 100644 pathmap-book/src/1.03.01_multithreading.md create mode 100644 pathmap-book/src/1.06.00_serialize_deserilize.md create mode 100644 pathmap-book/src/images/1.03.00_exclusivity_rules.svg create mode 100644 pathmap-book/src/images/1.04.00_product_zipper.svg create mode 100644 pathmap-derive/Cargo.toml create mode 100644 pathmap-derive/src/lib.rs create mode 100644 src/empty_zipper.rs create mode 100644 src/poly_zipper.rs diff --git a/Cargo.toml b/Cargo.toml index 33f90dc9..6978d819 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,12 +3,22 @@ name = "pathmap" version = "0.1.0" edition = "2024" rust-version = "1.88" +license = "MIT" description = "A key-value store with prefix compression, structural sharing, and powerful algebraic operations" -exclude = ["benches/", "pathmap-book/", ".*"] +repository = "https://github.com/adam-Vandervorst/pathMap/" +keywords = ["trie", "MORK", "path", "algebra"] +categories = ["algorithms", "data-structures", "database-implementations"] +readme = "README.md" +exclude = ["target/", "benches/", "pathmap-book/", ".*"] + +[lib] +name = "pathmap" +path = "src/lib.rs" [dependencies] +pathmap-derive = { path = "./pathmap-derive" } maybe-dangling = "0.1.1" -dyn-clone = "1.0.17" +dyn-clone = "1.0.17" # NOTE, we can eliminate this when we eliminate `!slim_ptrs` local-or-heap = "0.1.0" reusing-vec = {version = "0.2.0", features = ["smallvec"]} smallvec = {version = "1.13.2", features = ["union"]} @@ -99,3 +109,7 @@ harness = false [[bench]] name = "product_zipper" harness = false + +[workspace] +members = ["pathmap-derive"] +resolver = "2" diff --git a/pathmap-book/diagram_src/1.03.00_exclusivity_rules.mmd b/pathmap-book/diagram_src/1.03.00_exclusivity_rules.mmd new file mode 100644 index 00000000..9b1509b2 --- /dev/null +++ b/pathmap-book/diagram_src/1.03.00_exclusivity_rules.mmd @@ -0,0 +1,53 @@ +flowchart LR +g1153027057754177728@{ shape: cylinder, label: "PathMap"} +g1153027057754177728 --""int""--> g576566305450754560 +g576566305450754560@{ shape: circle, label: "."} +style g576566305450754560 fill:grey,stroke:none,color:transparent,font-size:0px +g576566305450754560 --""e""--> g576566305450754496 +g576566305450754496@{ shape: circle, label: "."} +style g576566305450754496 fill:grey,stroke:none,color:transparent,font-size:0px +g576566305450754496 --""g""--> g1153027057754178176 +g1153027057754178176@{ shape: circle, label: "."} +style g1153027057754178176 fill:yellow,stroke:black,color:transparent,font-size:0px +g1153027057754178176 --""er""--> v1055531473312481153027057754178176 +v1055531473312481153027057754178176@{ shape: circle, label: "."} +style v1055531473312481153027057754178176 fill:yellow,stroke:black,color:transparent,font-size:0px +g1153027057754178176 --""r""--> g1153027057754178240 +g1153027057754178240@{ shape: circle, label: "."} +style g1153027057754178240 fill:yellow,stroke:black,color:transparent,font-size:0px +g1153027057754178240 --""a""--> g1153027057754177792 +g1153027057754177792@{ shape: circle, label: "."} +style g1153027057754177792 fill:yellow,stroke:black,color:transparent,font-size:0px +g1153027057754177792 --""l""--> v1055531473308641153027057754177792 +v1055531473308641153027057754177792@{ shape: circle, label: "."} +style v1055531473308641153027057754177792 fill:yellow,stroke:black,color:transparent,font-size:0px +g1153027057754177792 --""tion""--> v1055531473308721153027057754177792 +v1055531473308721153027057754177792@{ shape: circle, label: "."} +style v1055531473308721153027057754177792 fill:yellow,stroke:black,color:transparent,font-size:0px +g1153027057754178240 --""ity""--> v1055531473313201153027057754178240 +v1055531473313201153027057754178240@{ shape: circle, label: "."} +style v1055531473313201153027057754178240 fill:yellow,stroke:black,color:transparent,font-size:0px +g576566305450754496 --""r""--> g1153027057754177920 +g1153027057754177920@{ shape: circle, label: "."} +style g1153027057754177920 fill:grey,stroke:none,color:transparent,font-size:0px +g1153027057754177920 --""n""--> g1153027057754177856 +g1153027057754177856@{ shape: circle, label: "."} +style g1153027057754177856 fill:red,stroke:black,color:transparent,font-size:0px +g1153027057754177856 --""al""--> v1055531473309281153027057754177856 +v1055531473309281153027057754177856@{ shape: circle, label: "."} +style v1055531473309281153027057754177856 fill:red,stroke:black,color:transparent,font-size:0px +g1153027057754177856 --""et""--> v1055531473309361153027057754177856 +v1055531473309361153027057754177856@{ shape: circle, label: "."} +style v1055531473309361153027057754177856 fill:red,stroke:black,color:transparent,font-size:0px +g1153027057754177920 --""val""--> v1055531473310001153027057754177920 +v1055531473310001153027057754177920@{ shape: circle, label: "."} +style v1055531473310001153027057754177920 fill:blue,stroke:none,color:transparent,font-size:0px +g576566305450754560 --""o""--> g1153027057754178112 +g1153027057754178112@{ shape: circle, label: "."} +style g1153027057754178112 fill:blue,stroke:none,color:transparent,font-size:0px +g1153027057754178112 --""lerable""--> v1055531232133371153027057754178112 +v1055531232133371153027057754178112@{ shape: circle, label: "."} +style v1055531232133371153027057754178112 fill:blue,stroke:none,color:transparent,font-size:0px +g1153027057754178112 --""ne""--> v1055531473311841153027057754178112 +v1055531473311841153027057754178112@{ shape: circle, label: "."} +style v1055531473311841153027057754178112 fill:blue,stroke:none,color:transparent,font-size:0px diff --git a/pathmap-book/diagram_src/1.04.00_product_zipper.mmd b/pathmap-book/diagram_src/1.04.00_product_zipper.mmd new file mode 100644 index 00000000..869c443c --- /dev/null +++ b/pathmap-book/diagram_src/1.04.00_product_zipper.mmd @@ -0,0 +1,65 @@ +flowchart LR +g1153027057747149312@{ shape: cylinder, label: "root"} +g1153027057747149312 --""base-""--> g14583419917035086263 +g14583419917035086263@{ shape: circle, label: "."} +style g14583419917035086263 fill:black,stroke:none,color:transparent,font-size:0px +g14583419917035086263 --> v10555314030270414583419917035086263 +v10555314030270414583419917035086263@{ shape: circle, label: "."} +style v10555314030270414583419917035086263 fill:black,stroke:none,color:transparent,font-size:0px +g14583419917035086263 --""next""--> g576566305443726400 +g576566305443726400@{ shape: circle, label: "."} +style g576566305443726400 fill:blue,stroke:none,color:transparent,font-size:0px +g576566305443726400 --""A-""--> g5319652995283220654 +g5319652995283220654@{ shape: circle, label: "."} +style g5319652995283220654 fill:black,stroke:none,color:transparent,font-size:0px +g5319652995283220654 --> v1055531403028325319652995283220654 +v1055531403028325319652995283220654@{ shape: circle, label: "."} +style v1055531403028325319652995283220654 fill:black,stroke:none,color:transparent,font-size:0px +g5319652995283220654 --""next""--> g576566305443726656 +g576566305443726656@{ shape: circle, label: "."} +style g576566305443726656 fill:blue,stroke:none,color:transparent,font-size:0px +g576566305443726656 --""A-""--> v105553140303088576566305443726656 +v105553140303088576566305443726656@{ shape: circle, label: "."} +style v105553140303088576566305443726656 fill:black,stroke:none,color:transparent,font-size:0px +g576566305443726656 --""B-""--> v105553140303152576566305443726656 +v105553140303152576566305443726656@{ shape: circle, label: "."} +style v105553140303152576566305443726656 fill:black,stroke:none,color:transparent,font-size:0px +g576566305443726656 --""C-""--> v105553140303280576566305443726656 +v105553140303280576566305443726656@{ shape: circle, label: "."} +style v105553140303280576566305443726656 fill:black,stroke:none,color:transparent,font-size:0px +g576566305443726400 --""B-""--> g4140095658675917873 +g4140095658675917873@{ shape: circle, label: "."} +style g4140095658675917873 fill:black,stroke:none,color:transparent,font-size:0px +g4140095658675917873 --> v1055531403028964140095658675917873 +v1055531403028964140095658675917873@{ shape: circle, label: "."} +style v1055531403028964140095658675917873 fill:black,stroke:none,color:transparent,font-size:0px +g4140095658675917873 --""next""--> g576566305443726912 +g576566305443726912@{ shape: circle, label: "."} +style g576566305443726912 fill:blue,stroke:none,color:transparent,font-size:0px +g576566305443726912 --""A-""--> v105553140303344576566305443726912 +v105553140303344576566305443726912@{ shape: circle, label: "."} +style v105553140303344576566305443726912 fill:black,stroke:none,color:transparent,font-size:0px +g576566305443726912 --""B-""--> v105553140303408576566305443726912 +v105553140303408576566305443726912@{ shape: circle, label: "."} +style v105553140303408576566305443726912 fill:black,stroke:none,color:transparent,font-size:0px +g576566305443726912 --""C-""--> v105553140303536576566305443726912 +v105553140303536576566305443726912@{ shape: circle, label: "."} +style v105553140303536576566305443726912 fill:black,stroke:none,color:transparent,font-size:0px +g576566305443726400 --""C-""--> g12715251498481534381 +g12715251498481534381@{ shape: circle, label: "."} +style g12715251498481534381 fill:black,stroke:none,color:transparent,font-size:0px +g12715251498481534381 --> v10555314030302412715251498481534381 +v10555314030302412715251498481534381@{ shape: circle, label: "."} +style v10555314030302412715251498481534381 fill:black,stroke:none,color:transparent,font-size:0px +g12715251498481534381 --""next""--> g576566305443727168 +g576566305443727168@{ shape: circle, label: "."} +style g576566305443727168 fill:blue,stroke:none,color:transparent,font-size:0px +g576566305443727168 --""A-""--> v105553140303600576566305443727168 +v105553140303600576566305443727168@{ shape: circle, label: "."} +style v105553140303600576566305443727168 fill:black,stroke:none,color:transparent,font-size:0px +g576566305443727168 --""B-""--> v105553140303664576566305443727168 +v105553140303664576566305443727168@{ shape: circle, label: "."} +style v105553140303664576566305443727168 fill:black,stroke:none,color:transparent,font-size:0px +g576566305443727168 --""C-""--> v105553140303792576566305443727168 +v105553140303792576566305443727168@{ shape: circle, label: "."} +style v105553140303792576566305443727168 fill:black,stroke:none,color:transparent,font-size:0px diff --git a/pathmap-book/src/1.00.00_intro.md b/pathmap-book/src/1.00.00_intro.md index 6047a44b..74b04898 100644 --- a/pathmap-book/src/1.00.00_intro.md +++ b/pathmap-book/src/1.00.00_intro.md @@ -4,7 +4,9 @@ A simplistic way to begin describing [PathMap] is as a collection type, with an Therefore `PathMap` has a superset of the functionality offered by `HashMap, V>`. ```rust -let mut map = PathMap::new::(); +# extern crate pathmap; +# use pathmap::PathMap; +let mut map = PathMap::::new(); map.insert(b"arrow", 0); map.insert(b"bow", 1); map.insert(b"cannon", 2); @@ -20,6 +22,8 @@ Unlike hashable keys, paths have the property that they are fractal. That is, o This property allows for composition, where for example, one map can be embedded, aka [`grafted`](ZipperWriting::graft) within another, or a subtree from a map can be removed or copied to a stand-alone map with a single operation. ```rust +# extern crate pathmap; +# use pathmap::{PathMap, zipper::*}; let mut sub_map = PathMap::new(); sub_map.insert(b"arrow", ()); sub_map.insert(b"bow", ()); @@ -37,9 +41,6 @@ wz.graft_map(sub_map);

- - ## Structural Sharing Storage inside `PathMap` makes use of references that can point to data within the same map or across multiple maps. This means that it is possible to construct a map with far more values than could fit in memory if they were all stored individually. diff --git a/pathmap-book/src/1.00.01_basics.md b/pathmap-book/src/1.00.01_basics.md new file mode 100644 index 00000000..b06c0f64 --- /dev/null +++ b/pathmap-book/src/1.00.01_basics.md @@ -0,0 +1,71 @@ + +# PathMap Structure +The intro chapter described [`PathMap`] as a collection of values, but crucially it is actually a tree of paths. In other words, paths can exist by themselves in the absence of values. + +A structure of paths is required to host values, but values are entirely optional. A path that terminates without any value or downstream children is still a valid path, and is known as a "dangling" path. + +## Creating and Removing Paths +PathMap provides several methods to manage path structure independently of values. + +### Creating Paths +[`create_path`] creates a dangling path (without a value) in the trie. This is useful when you need to establish path structure before adding values, or when you want to mark certain paths as existing without storing data at those locations. + +```rust +# extern crate pathmap; +use pathmap::PathMap; + +let mut map = PathMap::::new(); +assert!(!map.path_exists_at(b"path/to/data")); + +// Create the path structure without a value +map.create_path(b"path/to/data"); +assert!(map.path_exists_at(b"path/to/data")); +assert_eq!(map.get_val_at(b"path/to/data"), None); // No value stored +``` + +### Checking Path Existence +[`path_exists_at`] checks whether a specific path exists in the trie, regardless of whether it has an associated value. + +```rust +# extern crate pathmap; +use pathmap::PathMap; + +let mut map = PathMap::new(); +map.insert(b"existing/path", 42); + +assert!(map.path_exists_at(b"existing/path")); +assert!(map.path_exists_at(b"existing")); // Parent path also exists +assert!(!map.path_exists_at(b"nonexistent")); +``` + +### Removing Path Structure +[`remove_branches_at`] removes all child paths below the specified path. The `prune` parameter controls whether the path itself should be removed if it becomes dangling. + +```rust +# extern crate pathmap; +use pathmap::PathMap; + +let mut map = PathMap::new(); +map.insert(b"base/branch1/leaf", 1); +map.insert(b"base/branch2/leaf", 2); + +// Remove all branches under "base", keeping "base" as dangling path +map.remove_branches_at(b"base", false); +assert!(map.path_exists_at(b"base")); +assert!(!map.path_exists_at(b"base/branch1")); +``` + +[`prune_path`] removes dangling path segments, working upward from the specified path until it reaches a path with a value or multiple children. This is useful for cleaning up empty path structure. + +```rust +# extern crate pathmap; +use pathmap::PathMap; + +let mut map = PathMap::<()>::new(); +map.create_path(b"long/dangling/path/chain"); + +// Prune the dangling path - removes the entire chain since no values exist +let removed_bytes = map.prune_path(b"long/dangling/path/chain"); +assert_eq!(removed_bytes, 24); // Length of the entire path +assert!(!map.path_exists_at(b"long")); +``` diff --git a/pathmap-book/src/1.01.00_algebraic_ops.md b/pathmap-book/src/1.01.00_algebraic_ops.md index ee43689e..afe84caa 100644 --- a/pathmap-book/src/1.01.00_algebraic_ops.md +++ b/pathmap-book/src/1.01.00_algebraic_ops.md @@ -110,7 +110,7 @@ movies:sci-fi: ``` result: -``` +```txt books:fiction:don_quixote books:fiction:great_gatsby,the books:fiction:moby_dick @@ -129,7 +129,7 @@ books:moby_dick ``` result of `join_k_path(6)` (dropping the first 6 chars: `books:`): -``` +```txt don_quixote great_gatsby,the moby_dick diff --git a/pathmap-book/src/1.02.00_zippers.md b/pathmap-book/src/1.02.00_zippers.md index a36e923f..81888fe1 100644 --- a/pathmap-book/src/1.02.00_zippers.md +++ b/pathmap-book/src/1.02.00_zippers.md @@ -1,4 +1,4 @@ -# A Zipper is a Cursor in a trie +# A Zipper is a Cursor in a Trie Zippers are persistent pointers that refer to a location within a trie, called the zipper's `focus`. When an operation is applied through a zipper, it accesses and /or acts on the focus. The advantage of zippers is that they don't require re-traversal of the in-memory data structure for each operation. A zipper is most often used to point to a location within a [PathMap], however a zipper may also operate within other kinds of tries. One example is the [ACT] on-disk file format, which allows a trie to be explored without requiring the entire data structure to be loaded into memory. Another example is a virtualized trie, such as an infinite trie that is defined parametrically. @@ -27,12 +27,12 @@ Zipper capabilities are defined across a number of traits. There are many diffe ## Zipper and Zipper-like Objects The `pathmap` crate provides a number of zipper and zipper-like objects with different capabilities and performance characteristics. It is also permissible to implement most (but not all) of the zipper traits on outside objects. Therefore this is not a complete set of zippers, but includes the core types that work directly on [`PathMap`] tries. -| Object | Lifetime | Writeable | Movable | Cost to Create | Primary Use Case | +| Object | Lifetime | Writing | Moving | Cost to Create | Primary Use Case | |--------|----------|-----------|---------|----------------|------------------| -| [`PathMap`] | 'static | ✓ | ✗ | Medium | Root data structure, stand-alone trie | +| [`PathMap`] | 'static | ✓ | ✗ | Med | Root data structure, stand-alone trie | | [`TrieRefBorrowed`] | Borrowed | ✗ | ✗ | Ultra Low | Quick focus access, no navigation | | [`TrieRefOwned`] | 'static | ✗ | ✗ | Low | Focus access without lifetime, no navigation | -| [`ReadZipper`] | Borrowed | ✗ | ✓ | Medium | Reading, navigation and iteration | +| [`ReadZipper`] | Borrowed | ✗ | ✓ | Med | Reading, navigation and iteration | | [`ReadZipperOwned`] | 'static | ✗ | ✓ | High | Reading, navigation and iteration, send across threads | -| [`WriteZipper`] | Borrowed | ✓ | ✓ | Medium | Trie modifications | +| [`WriteZipper`] | Borrowed | ✓ | ✓ | Med | Trie modifications | | [`WriteZipperOwned`] | 'static | ✓ | ✓ | High | Trie modifications, send across threads | diff --git a/pathmap-book/src/1.02.02_zipper_values.md b/pathmap-book/src/1.02.02_zipper_values.md index ff7f3386..820b64e6 100644 --- a/pathmap-book/src/1.02.02_zipper_values.md +++ b/pathmap-book/src/1.02.02_zipper_values.md @@ -28,7 +28,8 @@ This design allows the zipper to be moved while ensuring that any borrowed value ### Example Usage ```rust -use pathmap::PathMap; +# extern crate pathmap; +use pathmap::{PathMap, zipper::*}; // Create a PathMap and populate it let mut map = PathMap::new(); diff --git a/pathmap-book/src/1.02.05_zipper_iter.md b/pathmap-book/src/1.02.05_zipper_iter.md index 210453c1..c618e75a 100644 --- a/pathmap-book/src/1.02.05_zipper_iter.md +++ b/pathmap-book/src/1.02.05_zipper_iter.md @@ -21,8 +21,8 @@ The `k_path` methods allow iterating over children at depth `k` relative to the #### Example: Iterating Fixed-Length Records ```rust -use pathmap::PathMap; -use crate::zipper::ZipperIteration; +# extern crate pathmap; +use pathmap::{PathMap, zipper::*}; // Create a map with various 4-byte keys let mut map = PathMap::new(); diff --git a/pathmap-book/src/1.03.00_multi_zipper.md b/pathmap-book/src/1.03.00_multi_zipper.md index c2bb2618..2b911998 100644 --- a/pathmap-book/src/1.03.00_multi_zipper.md +++ b/pathmap-book/src/1.03.00_multi_zipper.md @@ -9,12 +9,36 @@ Creating multiple zippers in the same map is possible and very handy. That's wh ## Making Multiple Zippers The normal [`PathMap`] methods to create write-enabled zippers, ie. [`write_zipper`](PathMap::write_zipper) and [`write_zipper_at_path`](PathMap::write_zipper_at_path) require an `&mut` borrow of the map. This prevents a write-enabled zipper from being created while any other zipper simultaneously exists in that map (for reading or writing). This can be very obnoxious, especially when you are working within a single large map. -[`ZipperHead`] provides an alternative API to create zippers, that allows the exclusivity rules to be checked at runtime, rather than statically by the compiler. +[`ZipperHead`] provides an alternative API to create zippers, that allows the exclusivity rules to be checked at runtime, rather than statically by the Rust borrow checker. + +NOTE: Using [`ZipperHead`] methods comes with a high runtime cost relative to using the [`PathMap`] methods. So don't use a `ZipperHead` unless you need to. + +WARNING: Creating a write zipper from a `ZipperHead` has the side effect of creating the path up to the zipper's root. Then dropping the write zipper will leave a dangling path if no further trie structure was added by the write zipper. If this is unacceptable, call [`cleanup_write_zipper`]. + +### Zipper Exclusivity Rules +A zipper is a cursor to read and/or write into a location in a trie. By extension it is also a permission to perform that reading / writing. Analogous to the borrow checker's rules, zippers under a `ZipperHead` must obey the following: + +> For any reachable path, there can either be one write-enabled zipper, or many read-only zippers, but never both at the same time. + +An example trie containing the paths: `["internal", "internet", "interval", "integer", "integral", "integration", "integrity", "intolerable", "intone"]` looks like the diagram below. + +Let's create a [`ReadZipper`] at the path: `"integ"` and a [`WriteZipper`] at the path `"intern"`. Notice in the diagram that paths in the trie accessible to the `ReadZipper` are **yellow**. Additional `ReadZipper`s may be created on any of these nodes. + +Paths accessible to the `WriteZipper` are **red**, and they are reserved strictly for this zipper, while it exists. + +The paths in **blue** are free for a zipper of any type to be created. + +The paths in **grey** are not a accessible to any zippers. This is because any zipper created at those paths could descend to the exclusive **red** paths held by the `WriteZipper`. + +

+ +

### Example: Creating Multiple Zippers in a PathMap ```rust -use pathmap::PathMap; +# extern crate pathmap; +use pathmap::{PathMap, zipper::*}; // Create and populate a map let mut map = PathMap::new(); @@ -45,18 +69,3 @@ drop(zh); assert_eq!(map.get_val_at(b"data:0000:result"), Some(&200)); assert_eq!(map.get_val_at(b"data:0001:result"), Some(&400)); ``` - -NOTE: Using [`ZipperHead`] methods comes with a high runtime cost relative to using the [`PathMap`] methods. So don't use a `ZipperHead` unless you need to. - -## Zipper Exclusivity Rules - - -A zipper is a cursor to read and/or write into a location in a trie. By extension it is also a permission to perform that reading / writing. - -GOAT more to say in this section - -## Multi-threading Patterns - -Many zipper types implement [`Send`] and/or [`Sync`] meaning multiple threads may be accessing the same underlying trie. - -GOAT move to a new section \ No newline at end of file diff --git a/pathmap-book/src/1.03.01_multithreading.md b/pathmap-book/src/1.03.01_multithreading.md new file mode 100644 index 00000000..87a0c44c --- /dev/null +++ b/pathmap-book/src/1.03.01_multithreading.md @@ -0,0 +1,100 @@ +# Multi-threading Patterns + +Many zipper types implement [`Send`] and/or [`Sync`] meaning it is legal to send zippers across channels to distribute work to multiple threads, working in the same underlying trie. The zipper exclusivity rules uphold the guarantees that prevent data races. + +NOTE: Rust's default allocator tends to become the bottleneck when creating and editing lots of trie paths in parallel. Experimentally we have found that `jemalloc` alleviates this issue, and the `jemalloc` crate feature can be enabled to switch the default allocator for the process. + +## Parallel Example + +The code below will spawn parallel threads, and make a deep copy of every item in the `"in` subtrie into the `"out"` subtrie. This just illustrates one possible approach to distributing a workload across multiple threads and many other patterns are possible. + +```rust +# extern crate pathmap; +use pathmap::{PathMap, zipper::*}; + +let elements = 65535; +let thread_cnt: usize = 4; +let elements_per_thread = elements / thread_cnt; + +//Pre-initialize the data in the `PathMap`, for demonstration purposes +let mut map = PathMap::::new(); +let mut zipper = map.write_zipper_at_path(b"in"); +for n in 0..thread_cnt { + for i in (n * elements_per_thread)..((n+1) * elements_per_thread) { + zipper.descend_to_byte(n as u8); + zipper.descend_to(i.to_be_bytes()); + zipper.set_val(i); + zipper.reset(); + } +} +drop(zipper); + +let zipper_head = map.zipper_head(); + +std::thread::scope(|scope| { + + //Allocate channels to send the zippers + let mut zipper_senders: Vec, WriteZipperTracked<'_, '_, usize>)>> = Vec::with_capacity(thread_cnt); + let mut signal_receivers: Vec> = Vec::with_capacity(thread_cnt); + + //Spawn all the threads + for _thread_idx in 0..thread_cnt { + let (zipper_tx, zipper_rx) = std::sync::mpsc::channel(); + zipper_senders.push(zipper_tx); + let (signal_tx, signal_rx) = std::sync::mpsc::channel::(); + signal_receivers.push(signal_rx); + + //===================================================================================== + // Worker Thread Body + //===================================================================================== + scope.spawn(move || { + loop { + + //The thread will block here waiting for the zippers to be sent + match zipper_rx.recv() { + 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) { + writer_z.descend_to(reader_z.path()); + writer_z.set_val(*val); + writer_z.reset(); + } + + //Tell the main thread we're done + signal_tx.send(true).unwrap(); + }, + Err(_) => { + //The zipper_sender channel is closed, meaning it's time to shut down + break; + } + } + } + }); + //===================================================================================== + // End of Worker Thread Body + //===================================================================================== + } + + let mut writer_z = zipper_head.write_zipper_at_exclusive_path(b"out").unwrap(); + writer_z.remove_branches(true); + drop(writer_z); + + //Make a ReadZipper and a WriteZipper for each thread, and send them through the channel + for n in 0..thread_cnt { + let path = vec![b'o', b'u', b't', n as u8]; + let writer_z = zipper_head.write_zipper_at_exclusive_path(path).unwrap(); + let path = vec![b'i', b'n', n as u8]; + let reader_z = zipper_head.read_zipper_at_path(path).unwrap(); + + zipper_senders[n].send((reader_z, writer_z)).unwrap(); + }; + + //Wait for the threads to all be done + for n in 0..thread_cnt { + assert_eq!(signal_receivers[n].recv().unwrap(), true); + }; +}); +drop(zipper_head); +``` diff --git a/pathmap-book/src/1.04.00_abstract_zippers.md b/pathmap-book/src/1.04.00_abstract_zippers.md index 05a720b2..ab6bd4d7 100644 --- a/pathmap-book/src/1.04.00_abstract_zippers.md +++ b/pathmap-book/src/1.04.00_abstract_zippers.md @@ -1,6 +1,101 @@ -# Zippers over Abstract Tries +# Zipper Combinators and Abstract Tries -GOAT!!, this gets its own major section on composable zipper types -[`ProductZipper`] -[`PrefixZipper`] -Also to include are the "overlay zipper" and the "enum zipper" +In the prior [Zipper section](./1.02.00_zippers.md), we introduced zipper types that move over a concrete trie in memory. However zippers may also reference (and often move within) tries that are defined parametrically. These are called "abstract" or "virtual" tries. + +Many abstract zipper types work using other zippers as parameters. This allows zippers to be combined to create virtual tries with the desired characteristics. + +NOTE: The current set of abstract zippers reflects what we needed to build MORK, as opposed to a deliberate process to create a complete API. If you have ideas or needs that aren't addressed, please consider adding other abstract zippers, or reach out and discuss your use case. + +### PrefixZipper +[`PrefixZipper`] wraps another zipper and prepends an arbitrary path prefix to the wrapped zipper's space. This allows creating a virtual trie that appears to be rooted at a different location. + +```rust +# extern crate pathmap; +use pathmap::{PathMap, zipper::*}; + +let map: PathMap<()> = [(b"A", ()), (b"B", ())].into_iter().collect(); +let mut rz = PrefixZipper::new(b"origin.prefix.", map.read_zipper()); +rz.set_root_prefix_path(b"origin.").unwrap(); + +rz.descend_to(b"prefix.A"); +assert_eq!(rz.path_exists(), true); +assert_eq!(rz.origin_path(), b"origin.prefix.A"); +assert_eq!(rz.path(), b"prefix.A"); +``` + +### OverlayZipper +[`OverlayZipper`] traverses a virtual trie formed by fusing two other zippers. It combines the path structures of both source zippers, and has configurable value mapping to reconcile the values in the final virtual trie. + +```rust +# extern crate pathmap; +use pathmap::{PathMap, zipper::*}; + +let map_a: PathMap<&str> = [(b"shared", "from_a"), (b"only_a", "a_value")].into_iter().collect(); +let map_b: PathMap<&str> = [(b"shared", "from_b"), (b"only_b", "b_value")].into_iter().collect(); + +// Default mapping prefers values from the first zipper +let mut overlay = OverlayZipper::new(map_a.read_zipper(), map_b.read_zipper()); + +// Access value that exists in both - first zipper takes precedence +overlay.descend_to(b"shared"); +assert_eq!(overlay.val(), Some(&"from_a")); + +// Access value that exists only in first zipper +overlay.reset(); +overlay.descend_to(b"only_a"); +assert_eq!(overlay.val(), Some(&"a_value")); + +// Access value that exists only in second zipper +overlay.reset(); +overlay.descend_to(b"only_b"); +assert_eq!(overlay.val(), Some(&"b_value")); +``` + +### PolyZipper +[`PolyZipper`] is a derive macro that enables creating polymorphic zippers - enum-based zippers that can represent different underlying zipper types and dispatch to the appropriate implementation at runtime. This is conceptually similar to using `&dyn` trait objects, but with enum-based dispatch. + +The macro automatically generates implementations for most zipper traits ([`Zipper`], [`ZipperMoving`], [`ZipperIteration`], etc.) by dispatching each method call to the appropriate variant. Note that [`ZipperForking`] is intentionally not implemented, as the mapping between child zipper types and output types is not always straightforward and should be implemented manually when needed. + +```rust +# extern crate pathmap; +use pathmap::{PathMap, zipper::*}; + +#[derive(PolyZipper)] +enum MyPolyZipper<'trie, V: Clone + Send + Sync + Unpin = ()> { + Tracked(ReadZipperTracked<'trie, 'trie, V>), + Untracked(ReadZipperUntracked<'trie, 'trie, V>), +} + +let map: PathMap<&str> = [(b"foo", "value1"), (b"bar", "value2")].into_iter().collect(); + +// Create a PolyZipper from either variant +let mut poly_z = MyPolyZipper::from(map.read_zipper()); + +// Use like any other zipper +poly_z.descend_to(b"foo"); +assert_eq!(poly_z.val(), Some(&"value1")); +``` + +### ProductZipper +[`ProductZipper`] creates a Cartesian product trie by extending each path in the primary trie with the root of the next secondary trie, recursively for all secondary zippers. + +```rust +# extern crate pathmap; +use pathmap::{PathMap, zipper::*}; + +let primary: PathMap<()> = [(b"base-", ())].into_iter().collect(); +let secondary: PathMap<()> = [(b"nextA-", ()), (b"nextB-", ()), (b"nextC-", ())].into_iter().collect(); + +let mut pz = ProductZipper::new(primary.read_zipper(), [secondary.read_zipper(), secondary.read_zipper()]); + +// Navigate to value in the third factor +pz.descend_to(b"base-nextB-nextA-"); +assert_eq!(pz.focus_factor(), 2); +assert!(pz.is_val()); +``` + +#### ProductZipper Virtual Trie from Example Above + +

+ +

diff --git a/pathmap-book/src/1.06.00_serialize_deserilize.md b/pathmap-book/src/1.06.00_serialize_deserilize.md new file mode 100644 index 00000000..e06ec377 --- /dev/null +++ b/pathmap-book/src/1.06.00_serialize_deserilize.md @@ -0,0 +1,2 @@ +GOAT TODO + diff --git a/pathmap-book/src/A.0001_map_root_values.md b/pathmap-book/src/A.0001_map_root_values.md index 10eee196..dc0be763 100644 --- a/pathmap-book/src/A.0001_map_root_values.md +++ b/pathmap-book/src/A.0001_map_root_values.md @@ -34,7 +34,7 @@ The node contract expressed throught the [`TrieNode`] trait dictates that a node I think my preferred fix would be to change the trait methods to reflect the implications of values associated with node roots. For example, a ByteNode currently is defined (conceptually) as: -```rust +```rust, ignore pub struct ByteNode { mask: [u64; 4], values: Vec>, @@ -46,7 +46,7 @@ For example, a ByteNode currently is defined (conceptually) as: ``` Under the new proposal, it'd be defined (conceptually) as: -```rust +```rust, ignore pub struct ByteNode { root: Option, mask: [u64; 4], diff --git a/pathmap-book/src/A.0002_smart_ptr_upgrade.md b/pathmap-book/src/A.0002_smart_ptr_upgrade.md index 506d1a83..09f529b9 100644 --- a/pathmap-book/src/A.0002_smart_ptr_upgrade.md +++ b/pathmap-book/src/A.0002_smart_ptr_upgrade.md @@ -126,7 +126,7 @@ The alternative is to have a garbage collector of some sort. Let's look at the following tree: -``` +```txt a - b - c \ d - e ``` @@ -134,7 +134,7 @@ a - b - c if you have references to `c,d`, `b,e`, and graft `d` onto `c`, then `b` onto `e`, this will create the following graph: -``` +```txt a - b - c \ X \ d - e @@ -146,14 +146,14 @@ which has a circular path `abedc(b)`. It seems to me that detecting this loop is > graft `d` onto `c` This leads to: -``` +```txt a - b - c \ / \ d - e ``` > then `b` onto `e` Will cause a copy-on-write of `d` and `e`, leading to" -``` +```txt a - b - c - d - e \ ^------<. \ d' - e' -^ @@ -194,7 +194,7 @@ The new TrieNodeODRc would be a 64-bit type. In that we want to encode the foll * node_id: 7 bits. An id of a node, used to interpret the node's location within a block. Usually corresponds to a node block chunk index. * spare: 10 bits. Currently unused, by may permit tries that span multiple computers in the future. -``` +```txt ┌07 ┌0F ┌17 ┌1F ┌27 ┌2F ┌37 ┌3F ---------------------------------------------------------------- @@ -276,7 +276,7 @@ A `ByteSaturatedHead` chunk begins life as a `ByteUnsaturated` chunk, and has al Allocation can be skipped for `ByteSaturatedBody` chunks that have no set values. Given the fact that encoding algorithms often cluster values together (i.e. integers, printable ascii, etc.) it has been observed to be common to have large runs of unused children even for nodes that have 60+ children. Mapping from a set bit in the `child_mask` to a location of a child node or value is acomplished with the following algorithm: -```rust +```rust, ignore if (byte & 0xF) != 0xF { let body_node = get_node_from_payload_ref(self.payloads[byte >> 4]); let (body_node, idx) = (body_node, byte & 0xF); @@ -362,7 +362,7 @@ QUESTION: We could represent the bit_string in a depth-first encoding, rather th ### Examples of the LOUDS Chunk Format Example 1: Pair -``` +```txt (h) |-----+ (e) (o) diff --git a/pathmap-book/src/SUMMARY.md b/pathmap-book/src/SUMMARY.md index ff5d0eba..3797dafd 100644 --- a/pathmap-book/src/SUMMARY.md +++ b/pathmap-book/src/SUMMARY.md @@ -3,6 +3,7 @@ # Concepts and Basic Usage - [PathMap Intro](./1.00.00_intro.md) + - [Basic Structure](./1.00.01_basics.md) - [Algebraic Operations](./1.01.00_algebraic_ops.md) - [Traits and Values](./1.01.01_algebraic_traits.md) - [Zippers](./1.02.00_zippers.md) @@ -11,11 +12,13 @@ - [Paths and Absolute Paths](1.02.03_zipper_paths.md) - [Focus Moving](1.02.04_zipper_moving.md) - [Iteration](./1.02.05_zipper_iter.md) - - [Modifying the Space](./1.02.06_zipper_writing.md) - - [Algebra on Subspaces](./1.02.07_zipper_algebra.md) + - [Modifying the Trie](./1.02.06_zipper_writing.md) + - [Algebra on Subtries](./1.02.07_zipper_algebra.md) - [Concurrent Zippers](./1.03.00_multi_zipper.md) -- [Abstract Spaces](./1.04.00_abstract_zippers.md) + - [Multi-threading](./1.03.01_multithreading.md) +- [Abstract Zippers and Virtual Tries](./1.04.00_abstract_zippers.md) - [Morphisms](./1.05.00_morphisms.md) +- [Serialization and Deserialization](./1.06.00_serialize_deserilize.md) # Building a Database on Pathmap diff --git a/pathmap-book/src/images/1.03.00_exclusivity_rules.svg b/pathmap-book/src/images/1.03.00_exclusivity_rules.svg new file mode 100644 index 00000000..4e332e94 --- /dev/null +++ b/pathmap-book/src/images/1.03.00_exclusivity_rules.svg @@ -0,0 +1 @@ +

int

e

g

er

r

a

l

tion

ity

r

n

al

et

val

o

lerable

ne

PathMap

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

\ No newline at end of file diff --git a/pathmap-book/src/images/1.04.00_product_zipper.svg b/pathmap-book/src/images/1.04.00_product_zipper.svg new file mode 100644 index 00000000..5819b662 --- /dev/null +++ b/pathmap-book/src/images/1.04.00_product_zipper.svg @@ -0,0 +1 @@ +

base-

next

A-

next

A-

B-

C-

B-

next

A-

B-

C-

C-

next

A-

B-

C-

root

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

.

\ No newline at end of file diff --git a/pathmap-derive/Cargo.toml b/pathmap-derive/Cargo.toml new file mode 100644 index 00000000..62ef59ac --- /dev/null +++ b/pathmap-derive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "pathmap-derive" +version = "0.1.0" +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +syn = { version = "2.0", features = ["full"] } +quote = "1.0" +proc-macro2 = "1.0" diff --git a/pathmap-derive/src/lib.rs b/pathmap-derive/src/lib.rs new file mode 100644 index 00000000..94e27b54 --- /dev/null +++ b/pathmap-derive/src/lib.rs @@ -0,0 +1,531 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput, Data, Fields}; + +// See the docs for `PolyZipper` in `pathmap::zipper::PolyZipper` +#[proc_macro_derive(PolyZipper)] +pub fn derive_poly_zipper(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let enum_name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + // Extract enum variants + let variants = match &input.data { + Data::Enum(data_enum) => &data_enum.variants, + _ => panic!("PolyZipper can only be derived for enums"), + }; + + // Generate From and TryFrom impls for each variant + let from_impls = variants.iter().map(|variant| { + let variant_name = &variant.ident; + + // Get the inner type (assuming single unnamed field) + let inner_type = match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + &fields.unnamed[0].ty + } + _ => panic!("Each variant must have exactly one unnamed field"), + }; + + quote! { + impl #impl_generics From<#inner_type> for #enum_name #ty_generics #where_clause { + fn from(value: #inner_type) -> Self { + #enum_name::#variant_name(value) + } + } + + impl #impl_generics core::convert::TryFrom<#enum_name #ty_generics> for #inner_type #where_clause { + type Error = (); + + fn try_from(value: #enum_name #ty_generics) -> Result { + match value { + #enum_name::#variant_name(inner) => Ok(inner), + _ => Err(()), + } + } + } + } + }); + + let variant_arms: Vec<_> = variants.iter().map(|variant| { + let variant_name = &variant.ident; + quote! { Self::#variant_name(inner) } + }).collect(); + + let inner_types: Vec<_> = variants.iter().map(|variant| { + match &variant.fields { + Fields::Unnamed(fields) if fields.unnamed.len() == 1 => { + &fields.unnamed[0].ty + } + _ => panic!("Each variant must have exactly one unnamed field"), + } + }).collect(); + + // Generate Zipper trait implementation + let zipper_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::Zipper for #enum_name #ty_generics #where_clause { + fn path_exists(&self) -> bool { + match self { + #(#variant_arms => inner.path_exists(),)* + } + } + + fn is_val(&self) -> bool { + match self { + #(#variant_arms => inner.is_val(),)* + } + } + + fn child_count(&self) -> usize { + match self { + #(#variant_arms => inner.child_count(),)* + } + } + + fn child_mask(&self) -> pathmap::utils::ByteMask { + match self { + #(#variant_arms => inner.child_mask(),)* + } + } + } + } + }; + + // Generate ZipperValues trait implementation + let zipper_values_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperValues for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperValues,)* + #where_clause + { + fn val(&self) -> Option<&V> { + match self { + #(#variant_arms => inner.val(),)* + } + } + } + } + }; + + // Generate ZipperReadOnlyValues trait implementation + let zipper_read_only_values_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperReadOnlyValues<'trie, V> for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperReadOnlyValues<'trie, V>,)* + #where_clause + { + fn get_val(&self) -> Option<&'trie V> { + match self { + #(#variant_arms => inner.get_val(),)* + } + } + } + } + }; + + // Generate witness enum name and variant names for conditional traits + let witness_enum_name = syn::Ident::new(&format!("{}Witness", enum_name), enum_name.span()); + let variant_names: Vec<_> = variants.iter().map(|variant| &variant.ident).collect(); + + // Generate ZipperReadOnlyConditionalValues trait implementation with witness enum + let zipper_read_only_conditional_values_impl = { + quote! { + //GOAT TODO: I think we could get into trouble if the witness types have fewer generics than + // the outer PolyZipper enum. So we probably should add a phantom case too. + pub enum #witness_enum_name #impl_generics + where + #(#inner_types: pathmap::zipper::ZipperReadOnlyConditionalValues<'trie, V>,)* + #where_clause + { + #(#variant_names(<#inner_types as pathmap::zipper::ZipperReadOnlyConditionalValues<'trie, V>>::WitnessT),)* + } + + impl #impl_generics pathmap::zipper::ZipperReadOnlyConditionalValues<'trie, V> for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperReadOnlyConditionalValues<'trie, V>,)* + #where_clause + { + type WitnessT = #witness_enum_name #ty_generics; + + fn witness<'w>(&self) -> Self::WitnessT { + match self { + #(Self::#variant_names(inner) => #witness_enum_name::#variant_names(inner.witness()),)* + } + } + + fn get_val_with_witness<'w>(&self, witness: &'w Self::WitnessT) -> Option<&'w V> where 'trie: 'w { + match (self, witness) { + #((Self::#variant_names(inner), #witness_enum_name::#variant_names(w)) => inner.get_val_with_witness(w),)* + _ => { + debug_assert!(false, "Witness variant must match zipper + variant"); + None + }, + } + } + } + } + }; + + //GOAT, this is probably dead code given the decision (described in the PolyZipper docs) + // not to support a `ZipperForking` impl + // + //I can't seem to figure out how to align the `'a` and the `'read_z` lifetimes without changing the trait definition, + // and ideally we'd want to return a new enum-based zipper. The elegant way to do that would be to actually invoke + // the PolyZipper macro recursively to get maximum support on the new zipper, but there are a bunch of obnoxious details + // to make that work. - like what to do about the recursion so we don't have infinite recursion in the macro, and how + // to map the lifetimes required by the trait onto the lifetimes of the new zippers. + // + // // Generate ZipperForking trait implementation + // let zipper_forking_impl = { + // let variant_arms = &variant_arms; + // let first_inner_type = &inner_types[0]; + // let other_inner_types = &inner_types[1..]; + + // // Create modified generics with additional lifetime + // let mut forking_generics = generics.clone(); + // forking_generics.params.insert(0, syn::parse_quote!('read_z)); + // let (forking_impl_generics, _, _) = forking_generics.split_for_impl(); + + // quote! { + // impl #forking_impl_generics pathmap::zipper::ZipperForking for #enum_name #ty_generics + // where + // #(#inner_types: pathmap::zipper::ZipperForking,)* + // #(#other_inner_types: pathmap::zipper::ZipperForking = <#first_inner_type as pathmap::zipper::ZipperForking>::ReadZipperT<'read_z>>,)* + // Self: 'read_z, + // #where_clause + // { + // type ReadZipperT<'a> = <#first_inner_type as pathmap::zipper::ZipperForking>::ReadZipperT<'a> where Self: 'a; + + // fn fork_read_zipper<'a>(&'a self) -> Self::ReadZipperT<'a> { + // match self { + // #(#variant_arms => inner.fork_read_zipper(),)* + // } + // } + // } + // } + // }; + + // Generate ZipperPath trait implementation + let zipper_path_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperPath for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperPath,)* + #where_clause + { + fn path(&self) -> &[u8] { + match self { + #(#variant_arms => inner.path(),)* + } + } + } + } + }; + + // Generate ZipperMoving trait implementation + let zipper_moving_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperMoving for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperMoving,)* + #where_clause + { + fn at_root(&self) -> bool { + match self { + #(#variant_arms => inner.at_root(),)* + } + } + + fn reset(&mut self) { + match self { + #(#variant_arms => inner.reset(),)* + } + } + + fn val_count(&self) -> usize { + match self { + #(#variant_arms => inner.val_count(),)* + } + } + + fn descend_to>(&mut self, k: K) -> bool { + match self { + #(#variant_arms => inner.descend_to(k),)* + } + } + + fn descend_to_existing>(&mut self, k: K) -> usize { + match self { + #(#variant_arms => inner.descend_to_existing(k),)* + } + } + + fn descend_to_val>(&mut self, k: K) -> usize { + match self { + #(#variant_arms => inner.descend_to_val(k),)* + } + } + + fn descend_to_byte(&mut self, k: u8) -> bool { + match self { + #(#variant_arms => inner.descend_to_byte(k),)* + } + } + + fn descend_indexed_byte(&mut self, idx: usize) -> Option { + match self { + #(#variant_arms => inner.descend_indexed_byte(idx),)* + } + } + + fn descend_first_byte(&mut self) -> Option { + match self { + #(#variant_arms => inner.descend_first_byte(),)* + } + } + + fn descend_last_byte(&mut self) -> Option { + match self { + #(#variant_arms => inner.descend_last_byte(),)* + } + } + + fn descend_until(&mut self, mut dst_path: Option<&mut Vec>) -> bool { + match self { + #(#variant_arms => inner.descend_until(dst_path),)* + } + } + + fn ascend(&mut self, steps: usize) -> Result<(), usize> { + match self { + #(#variant_arms => inner.ascend(steps),)* + } + } + + fn ascend_byte(&mut self) -> bool { + match self { + #(#variant_arms => inner.ascend_byte(),)* + } + } + + fn ascend_until(&mut self) -> Option { + match self { + #(#variant_arms => inner.ascend_until(),)* + } + } + + fn ascend_until_branch(&mut self) -> Option { + match self { + #(#variant_arms => inner.ascend_until_branch(),)* + } + } + + fn to_next_sibling_byte(&mut self) -> Option { + match self { + #(#variant_arms => inner.to_next_sibling_byte(),)* + } + } + + fn to_prev_sibling_byte(&mut self) -> Option { + match self { + #(#variant_arms => inner.to_prev_sibling_byte(),)* + } + } + + fn to_next_step(&mut self) -> bool { + match self { + #(#variant_arms => inner.to_next_step(),)* + } + } + } + } + }; + + // Generate ZipperConcrete trait implementation + let zipper_concrete_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperConcrete for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperConcrete,)* + #where_clause + { + fn shared_node_id(&self) -> Option { + match self { + #(#variant_arms => inner.shared_node_id(),)* + } + } + fn is_shared(&self) -> bool { + match self { + #(#variant_arms => inner.is_shared(),)* + } + } + } + } + }; + + // Generate ZipperAbsolutePath trait implementation + let zipper_absolute_path_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperAbsolutePath for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperAbsolutePath,)* + #where_clause + { + fn origin_path(&self) -> &[u8] { + match self { + #(#variant_arms => inner.origin_path(),)* + } + } + + fn root_prefix_path(&self) -> &[u8] { + match self { + #(#variant_arms => inner.root_prefix_path(),)* + } + } + } + } + }; + + // Generate ZipperPathBuffer trait implementation + let zipper_path_buffer_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperPathBuffer for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperPathBuffer,)* + #where_clause + { + unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { + match self { + #(#variant_arms => unsafe { inner.origin_path_assert_len(len) },)* + } + } + + fn prepare_buffers(&mut self) { + match self { + #(#variant_arms => inner.prepare_buffers(),)* + } + } + + fn reserve_buffers(&mut self, path_len: usize, stack_depth: usize) { + match self { + #(#variant_arms => inner.reserve_buffers(path_len, stack_depth),)* + } + } + } + } + }; + + // Generate ZipperIteration trait implementation + let zipper_iteration_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperIteration for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperIteration,)* + #where_clause + { + fn to_next_val(&mut self) -> bool { + match self { + #(#variant_arms => inner.to_next_val(),)* + } + } + + fn descend_last_path(&mut self) -> bool { + match self { + #(#variant_arms => inner.descend_last_path(),)* + } + } + + fn descend_first_k_path(&mut self, k: usize) -> bool { + match self { + #(#variant_arms => inner.descend_first_k_path(k),)* + } + } + + fn to_next_k_path(&mut self, k: usize) -> bool { + match self { + #(#variant_arms => inner.to_next_k_path(k),)* + } + } + } + } + }; + + // Generate ZipperReadOnlyIteration trait implementation + let zipper_read_only_iteration_impl = { + let variant_arms = &variant_arms; + quote! { + impl #impl_generics pathmap::zipper::ZipperReadOnlyIteration<'trie, V> for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperReadOnlyIteration<'trie, V>,)* + #where_clause + { + fn to_next_get_val(&mut self) -> Option<&'trie V> { + match self { + #(#variant_arms => inner.to_next_get_val(),)* + } + } + + #[deprecated] + fn to_next_get_value(&mut self) -> Option<&'trie V> { + match self { + #(#variant_arms => inner.to_next_get_value(),)* + } + } + } + } + }; + + // Generate ZipperReadOnlyConditionalIteration trait implementation + let zipper_read_only_conditional_iteration_impl = { + quote! { + impl #impl_generics pathmap::zipper::ZipperReadOnlyConditionalIteration<'trie, V> for #enum_name #ty_generics + where + #(#inner_types: pathmap::zipper::ZipperReadOnlyConditionalIteration<'trie, V>,)* + #where_clause + { + fn to_next_get_val_with_witness<'w>(&mut self, witness: &'w Self::WitnessT) -> 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),)* + _ => { + debug_assert!(false, "Witness variant must match zipper variant"); + None + }, + } + } + } + } + }; + + let expanded = quote! { + #(#from_impls)* + #zipper_impl + #zipper_values_impl + #zipper_read_only_values_impl + #zipper_read_only_conditional_values_impl + // #zipper_forking_impl + #zipper_path_impl + #zipper_moving_impl + #zipper_concrete_impl + #zipper_absolute_path_impl + #zipper_path_buffer_impl + #zipper_iteration_impl + #zipper_read_only_iteration_impl + #zipper_read_only_conditional_iteration_impl + }; + + TokenStream::from(expanded) +} diff --git a/src/arena_compact.rs b/src/arena_compact.rs index 50e4bd82..3d5bd03f 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -85,7 +85,7 @@ use crate::{ zipper::{ Zipper, ZipperValues, ZipperForking, ZipperAbsolutePath, ZipperIteration, ZipperMoving, ZipperPathBuffer, ZipperReadOnlyValues, - ZipperConcretePriv, ZipperConcrete, ZipperReadOnlyConditionalValues, + ZipperConcrete, ZipperReadOnlyConditionalValues, }, }; @@ -715,7 +715,7 @@ where Storage: AsRef<[u8]> match cur_node { Node::Line(line) => { let lpath = self.get_line(line.path); - if !path.starts_with(lpath) { + if !starts_with(path, lpath) { return None; } path = &path[lpath.len()..]; @@ -1068,6 +1068,7 @@ fn build_arena_tree(zipper: Z, map_val: F) -> ArenaCompactTree> use std::io::{BufWriter, Seek, SeekFrom}; use std::fs::{File, OpenOptions}; +use crate::utils::starts_with; pub struct FileDumper { buf_writer: BufWriter, @@ -1701,18 +1702,13 @@ where Storage: AsRef<[u8]> } } -impl<'tree, Storage, Value> ZipperConcretePriv for ACTZipper<'tree, Storage, Value> +impl<'tree, Storage, Value> ZipperConcrete for ACTZipper<'tree, Storage, Value> where Storage: AsRef<[u8]> { fn shared_node_id(&self) -> Option { // TODO: no way to detect now None } -} - -impl<'tree, Storage, Value> ZipperConcrete for ACTZipper<'tree, Storage, Value> -where Storage: AsRef<[u8]> -{ fn is_shared(&self) -> bool { // TODO: no way to detect now false diff --git a/src/bridge_node.rs b/src/bridge_node.rs index d97fb0eb..56135a68 100644 --- a/src/bridge_node.rs +++ b/src/bridge_node.rs @@ -7,6 +7,7 @@ use crate::trie_node::*; use crate::ring::*; use crate::dense_byte_node::{DenseByteNode, CellByteNode, test_bit_in_mask}; use crate::tiny_node::TinyRefNode; +use crate::utils::starts_with; /// A node type that only has a single value or onward link pub struct BridgeNode { @@ -555,7 +556,7 @@ impl TrieNode for BridgeNode { fn take_node_at_key(&mut self, key: &[u8]) -> Option> { debug_assert!(!self.is_empty()); let self_key = self.key(); - if self_key.starts_with(key) { + if starts_with(self_key, key) { if self_key.len() == key.len() { if self.is_child_ptr() { let self_payload = self.take_payload(); diff --git a/src/dense_byte_node.rs b/src/dense_byte_node.rs index 1198bb4b..90c3b152 100644 --- a/src/dense_byte_node.rs +++ b/src/dense_byte_node.rs @@ -1746,9 +1746,10 @@ impl, Other let val = self.val().pmeet(&other.val()); self.combine_algebraic_results(other, rec, val) } - fn join_all(_xs: &[&Self]) -> Self where Self: Sized { - unreachable!() //Currently not used - } + //GOAT, HeteroLattice will totally disappear when we do the policy refactor + // fn join_all(_xs: &[&Self]) -> Self where Self: Sized { + // unreachable!() //Currently not used + // } fn convert(other: OtherCf) -> Self { Self::from_cf(other) } @@ -2103,40 +2104,41 @@ impl, Other } } - fn join_all(xs: &[&Self]) -> Self { - let alloc = xs[0].alloc.clone(); - let mut jm: ByteMask = ByteMask::EMPTY; - for x in xs.iter() { - jm |= x.mask; - } + //GOAT, kept for now, for reference, but this code is unreachable using the current Lattice interface + // fn join_all(xs: &[&Self]) -> Self { + // let alloc = xs[0].alloc.clone(); + // let mut jm: ByteMask = ByteMask::EMPTY; + // for x in xs.iter() { + // jm |= x.mask; + // } - let jmc = [jm.0[0].count_ones(), jm.0[1].count_ones(), jm.0[2].count_ones(), jm.0[3].count_ones()]; + // let jmc = [jm.0[0].count_ones(), jm.0[1].count_ones(), jm.0[2].count_ones(), jm.0[3].count_ones()]; - let len = (jmc[0] + jmc[1] + jmc[2] + jmc[3]) as usize; - let mut v = ValuesVec::with_capacity_in(len, alloc.clone()); - let new_v = v.v.spare_capacity_mut(); + // let len = (jmc[0] + jmc[1] + jmc[2] + jmc[3]) as usize; + // let mut v = ValuesVec::with_capacity_in(len, alloc.clone()); + // let new_v = v.v.spare_capacity_mut(); - let mut c = 0; + // let mut c = 0; - for i in 0..4 { - let mut lm = jm.0[i]; - while lm != 0 { - // this body runs at most 256 times, in the case there is 100% overlap between full nodes - let index = lm.trailing_zeros(); + // for i in 0..4 { + // let mut lm = jm.0[i]; + // while lm != 0 { + // // this body runs at most 256 times, in the case there is 100% overlap between full nodes + // let index = lm.trailing_zeros(); - //GOAT, allocating a temp buffer likely undoes the gains from join_all - let to_join: Vec<&Cf> = xs.iter().enumerate().filter_map(|(i, x)| x.get(i as u8)).collect(); - let joined = HeteroLattice::::join_all(&to_join[..]); - unsafe { new_v.get_unchecked_mut(c).write(joined) }; + // //GOAT, allocating a temp buffer likely undoes the gains from join_all + // let to_join: Vec<&Cf> = xs.iter().enumerate().filter_map(|(i, x)| x.get(i as u8)).collect(); + // let joined = HeteroLattice::::join_all(&to_join[..]); + // unsafe { new_v.get_unchecked_mut(c).write(joined) }; - lm ^= 1u64 << index; - c += 1; - } - } + // lm ^= 1u64 << index; + // c += 1; + // } + // } - unsafe{ v.v.set_len(c); } - return Self::new_with_fields_in(jm, v, alloc); - } + // unsafe{ v.v.set_len(c); } + // return Self::new_with_fields_in(jm, v, alloc); + // } fn convert(other: ByteNode) -> Self { let mut values = ValuesVec::with_capacity_in(other.values.len(), other.alloc.clone()); for other_cf in other.values { diff --git a/src/empty_zipper.rs b/src/empty_zipper.rs new file mode 100644 index 00000000..63d735c9 --- /dev/null +++ b/src/empty_zipper.rs @@ -0,0 +1,135 @@ + +use crate::zipper::*; +use crate::utils::ByteMask; + +/// A [`Zipper`] type that moves over a completely empty trie +#[derive(Clone, Default)] +pub struct EmptyZipper { + path_start_idx: usize, + path: Vec //NOTE: we could init this lazily and take a borrowed path, but I can't see a use case where that would matter +} + +impl EmptyZipper { + /// Returns a new `EmptyZipper` starting at the root + pub fn new() -> Self { + Self::default() + } + /// Returns a new `EmptyZipper` with the provided [`root_prefix_path`](ZipperAbsolutePath::root_prefix_path) + pub fn new_at_path>(path: K) -> Self { + let path = path.as_ref(); + Self { + path_start_idx: path.len(), + path: path.to_vec(), + } + } +} + +impl Zipper for EmptyZipper { + fn path_exists(&self) -> bool { false } + fn is_val(&self) -> bool { false } + fn child_count(&self) -> usize { 0 } + fn child_mask(&self) -> ByteMask { ByteMask::EMPTY } +} + +impl ZipperMoving for EmptyZipper { + fn at_root(&self) -> bool { self.path.len() == self.path_start_idx } + fn reset(&mut self) { self.path.truncate(self.path_start_idx) } + fn val_count(&self) -> usize { 0 } + fn descend_to>(&mut self, k: K) -> bool { + self.path.extend_from_slice(k.as_ref()); + false + } + fn descend_to_byte(&mut self, k: u8) -> bool { + self.path.push(k); + false + } + fn descend_indexed_byte(&mut self, _idx: usize) -> Option { None } + fn descend_first_byte(&mut self) -> Option { None } + fn descend_until(&mut self, _dst_path: Option<&mut Vec>) -> bool { false } + fn ascend(&mut self, steps: usize) -> Result<(), usize> { + if steps > self.path.len() - self.path_start_idx { + self.reset(); + Err(steps - (self.path.len() - self.path_start_idx)) + } else { + self.path.truncate(self.path.len() - self.path_start_idx - steps); + Ok(()) + } + } + fn ascend_byte(&mut self) -> bool { + if self.path.len() > self.path_start_idx { + self.path.pop(); + true + } else { + false + } + } + fn ascend_until(&mut self) -> Option { + if self.at_root() { + None + } else { + let old_path_len = self.path.len() - self.path_start_idx; + self.reset(); + Some(old_path_len) + } + } + fn ascend_until_branch(&mut self) -> Option { + self.ascend_until() + } + fn to_next_sibling_byte(&mut self) -> Option { None } + fn to_prev_sibling_byte(&mut self) -> Option { None } +} + +impl ZipperPath for EmptyZipper { + fn path(&self) -> &[u8] { &self.path[self.path_start_idx..] } +} + +impl ZipperAbsolutePath for EmptyZipper { + fn origin_path(&self) -> &[u8] { &self.path } + fn root_prefix_path(&self) -> &[u8] { &self.path[..self.path_start_idx] } +} + +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 } +} + +impl ZipperValues for EmptyZipper { + fn val(&self) -> Option<&V> { None } +} + +impl ZipperForking for EmptyZipper { + type ReadZipperT<'a> = EmptyZipper; + fn fork_read_zipper<'a>(&'a self) -> Self::ReadZipperT<'a> { Self::new_at_path(self.origin_path()) } +} + +impl<'a, V: Clone + Send + Sync> ZipperReadOnlyValues<'a, V> for EmptyZipper { + fn get_val(&self) -> Option<&'a V> { None } +} + +impl<'a, V: Clone + Send + Sync> ZipperReadOnlyConditionalValues<'a, V> for EmptyZipper { + type WitnessT = (); + fn witness<'w>(&self) -> Self::WitnessT { () } + fn get_val_with_witness<'w>(&self, _witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { None } +} + +impl<'a, V: Clone + Send + Sync> ZipperReadOnlyIteration<'a, V> for EmptyZipper { + fn to_next_get_val(&mut self) -> 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 } +} + +impl ZipperPathBuffer for EmptyZipper { + unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { + assert!(len <= self.path.capacity()); + unsafe{ core::slice::from_raw_parts(self.path.as_ptr(), len) } + } + fn prepare_buffers(&mut self) { + self.reserve_buffers(EXPECTED_PATH_LEN, 0) + } + fn reserve_buffers(&mut self, path_len: usize, _stack_depth: usize) { + self.path.reserve(path_len) + } +} diff --git a/src/lib.rs b/src/lib.rs index 86e26570..a1b8a285 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,8 +71,7 @@ static GLOBAL: Jemalloc = Jemalloc; pub mod ring; /// A collection indexed by paths of bytes, supporting [algebraic](crate::ring) operations -//GOAT-old-names, this mod shouldn't be pub, because it only contains one public object which is re-exported here -pub mod trie_map; +mod trie_map; pub use trie_map::PathMap; /// Cursors that can move over a trie, to inspect and modify contained elements or entire branches @@ -105,6 +104,9 @@ pub mod zipper_tracking; #[cfg(not(feature = "zipper_tracking"))] mod zipper_tracking; +/// Only includes PolyZipper tests. The real implementation is in the `pathmap-derive` crate +mod poly_zipper; + /// Used to create multiple simultaneous zippers from the same parent mod zipper_head; @@ -130,6 +132,7 @@ pub mod tree_serialization; mod trie_node; mod write_zipper; mod product_zipper; +mod empty_zipper; mod prefix_zipper; mod overlay_zipper; mod trie_ref; @@ -598,7 +601,11 @@ mod tests { // test_key_len(16777216); //2^24 bytes // test_key_len(67108864); //2^26 bytes // test_key_len(268435456); //2^28 bytes - // test_key_len(1073741824); //2^30 bytes //Still no failure at 1GB keys + // test_key_len(1073741824); //2^30 bytes - 1GB keys + // test_key_len(2147483648); //2^31 bytes - 2GB keys + // test_key_len(4294967296); //2^32 bytes - 4GB keys + // test_key_len(8589934592); //2^33 bytes - 8GB keys + // test_key_len(17179869184); //2^34 bytes - Still no failure at 16GB keys } } diff --git a/src/line_list_node.rs b/src/line_list_node.rs index e4341148..9ffa3e23 100644 --- a/src/line_list_node.rs +++ b/src/line_list_node.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use local_or_heap::LocalOrHeap; -use crate::utils::{BitMask, ByteMask, find_prefix_overlap}; +use crate::utils::{BitMask, ByteMask, find_prefix_overlap, starts_with}; use crate::alloc::Allocator; use crate::trie_node::*; use crate::ring::*; @@ -521,7 +521,7 @@ impl LineListNode { if self.is_used::<0>() && self.is_child_ptr::<0>() { if unsafe { &self.val_or_child0.child }.is_empty() { let node_key_0 = unsafe{ self.key_unchecked::<0>() }; - if key.starts_with(node_key_0) { + if starts_with(key, node_key_0) { self.take_payload::<0>(); } } @@ -529,7 +529,7 @@ impl LineListNode { if self.is_used::<1>() && self.is_child_ptr::<1>() { if unsafe { &self.val_or_child1.child }.is_empty() { let node_key_1 = unsafe{ self.key_unchecked::<1>() }; - if key.starts_with(node_key_1) { + if starts_with(key, node_key_1) { self.take_payload::<1>(); } } @@ -1655,7 +1655,7 @@ impl TrieNode for LineListNode #[inline] fn node_contains_partial_key(&self, key: &[u8]) -> bool { let (key0, key1) = self.get_both_keys(); - if key0.starts_with(key) || key1.starts_with(key) { + if starts_with(key0, key) || starts_with(key1, key) { return true; } false @@ -1700,7 +1700,7 @@ impl TrieNode for LineListNode debug_assert!(results.len() >= keys.len()); for ((key, expect_val), (result_key_len, payload_ref)) in keys.into_iter().zip(results.iter_mut()) { if self.is_used::<0>() { - if key.starts_with(node_key_0) { + if starts_with(key, node_key_0) { let node_key_len = node_key_0.len(); if self.is_child_ptr::<0>() { if !*expect_val || node_key_len < key.len() { @@ -1718,7 +1718,7 @@ impl TrieNode for LineListNode } } if self.is_used::<1>() { - if key.starts_with(node_key_1) { + if starts_with(key, node_key_1) { let node_key_len = node_key_1.len(); if self.is_child_ptr::<1>() { if !*expect_val || node_key_len < key.len() { @@ -1841,9 +1841,9 @@ impl TrieNode for LineListNode fn node_remove_all_branches(&mut self, key: &[u8], prune: bool) -> bool { let key_len = key.len(); let (key0, key1) = self.get_both_keys(); - let key0_starts_with = key0.starts_with(key); + let key0_starts_with = starts_with(key0, key); let remove_0 = key0_starts_with && (key0.len() > key_len || self.is_child_ptr::<0>()); - let remove_1 = key1.starts_with(key) && (key1.len() > key_len || self.is_child_ptr::<1>()); + let remove_1 = starts_with(key1, key) && (key1.len() > key_len || self.is_child_ptr::<1>()); self.remove_subtries(remove_0, remove_1, key0_starts_with, prune, key.len()); remove_0 || remove_1 } @@ -1853,7 +1853,7 @@ impl TrieNode for LineListNode let (key0, key1) = self.get_both_keys(); let mut remove_0 = false; let mut remove_1 = false; - let key0_starts_with = key0.starts_with(key); + let key0_starts_with = starts_with(key0, key); if key0_starts_with { if key0.len() > key_len { remove_0 = !mask.test_bit(key0[key_len]); @@ -1863,7 +1863,7 @@ impl TrieNode for LineListNode debug_assert!(!self.is_child_ptr::<0>()); } } - if key1.starts_with(key) { + if starts_with(key1, key) { if key1.len() > key_len { remove_1 = !mask.test_bit(key1[key_len]); } else { @@ -1984,10 +1984,10 @@ impl TrieNode for LineListNode fn node_first_val_depth_along_key(&self, key: &[u8]) -> Option { debug_assert!(key.len() > 0); let (key0, key1) = self.get_both_keys(); - if self.is_used_value_0() && key.starts_with(key0) { + if self.is_used_value_0() && starts_with(key, key0) { return Some(key0.len() - 1) } - if self.is_used_value_1() && key.starts_with(key1) { + if self.is_used_value_1() && starts_with(key, key1) { return Some(key1.len() - 1) } None @@ -2000,7 +2000,7 @@ impl TrieNode for LineListNode match n { 0 => { let (key0, key1) = self.get_both_keys(); - if key0.starts_with(key) && key0.len() > key.len() { + if starts_with(key0, key) && key0.len() > key.len() { if key0 != key1 { if key.len() + 1 == key0.len() && self.is_child_ptr::<0>() { return (Some(key0[key.len()]), unsafe{ Some(self.child_in_slot::<0>().as_tagged()) }) @@ -2009,7 +2009,7 @@ impl TrieNode for LineListNode } } } - if key1.starts_with(key) && key1.len() > key.len() { + if starts_with(key1, key) && key1.len() > key.len() { if key.len() + 1 == key1.len() && self.is_child_ptr::<1>() { return (Some(key1[key.len()]), unsafe{ Some(self.child_in_slot::<1>().as_tagged()) }) } else { @@ -2104,7 +2104,7 @@ impl TrieNode for LineListNode } //Case 4 - if key0.starts_with(key) { + if starts_with(key0, key) { let remaining_key = remaining_key(key0, key.len()); if self.is_child_ptr::<0>() { return (Some(remaining_key), unsafe{ Some(self.child_in_slot::<0>().as_tagged()) }) @@ -2114,7 +2114,7 @@ impl TrieNode for LineListNode } //Case 5 - if key1.starts_with(key) { + if starts_with(key1, key) { let remaining_key = remaining_key(key1, key.len()); if self.is_child_ptr::<1>() { return (Some(remaining_key), unsafe{ Some(self.child_in_slot::<1>().as_tagged()) }) @@ -2140,12 +2140,12 @@ impl TrieNode for LineListNode // k0="ahoy", k1="howdy", key="h", result = 1 // k0="ahoy", k1="howdy", key="", result = 2 - let c0 = if key0.len() > key_len && key0.starts_with(key) { + let c0 = if key0.len() > key_len && starts_with(key0, key) { Some(key0[key_len]) } else { None }; - let c1 = if key1.len() > key_len && key1.starts_with(key) { + let c1 = if key1.len() > key_len && starts_with(key1, key) { Some(key1[key_len]) } else { None @@ -2169,11 +2169,11 @@ impl TrieNode for LineListNode let (key0, key1) = self.get_both_keys(); let mut m = [0u64; 4]; - if key0.len() > key.len() && key0.starts_with(key) { + if key0.len() > key.len() && starts_with(key0, key) { let k = key0[key.len()]; m[((k & 0b11000000) >> 6) as usize] |= 1u64 << (k & 0b00111111); } - if key1.len() > key.len() && key1.starts_with(key) { + if key1.len() > key.len() && starts_with(key1, key) { let k = key1[key.len()]; m[((k & 0b11000000) >> 6) as usize] |= 1u64 << (k & 0b00111111); } @@ -2222,7 +2222,7 @@ impl TrieNode for LineListNode let (key0, key1) = self.get_both_keys(); match next { true => { - if key0.starts_with(key) && key1.starts_with(common_key) { + if starts_with(key0, key) && starts_with(key1, common_key) { let key1_last_byte = match key1.get(last_key_byte_idx) { Some(byte) => byte, None => return (None, None) @@ -2244,7 +2244,7 @@ impl TrieNode for LineListNode } }, false => { - if key1.starts_with(key) && key0.starts_with(common_key) { + if starts_with(key1, key) && starts_with(key0, common_key) { let key0_last_byte = match key0.get(last_key_byte_idx) { Some(byte) => byte, None => return (None, None) @@ -2289,7 +2289,7 @@ impl TrieNode for LineListNode } //Otherwise check to see if we need to make a sub-node. If we do, // We know the new node will have only 1 slot filled - if key0.len() > key.len() && key0.starts_with(key) { + if key0.len() > key.len() && starts_with(key0, key) { let new_key = &key0[key.len()..]; //If the new node's key is 7 Bytes or fewer, we can make a TinyRefNode if new_key.len() <= 7 { @@ -2303,7 +2303,7 @@ impl TrieNode for LineListNode return AbstractNodeRef::OwnedRc(TrieNodeODRc::new_in(new_node, self.alloc.clone())); } } - if key1.len() > key.len() && key1.starts_with(key) { + if key1.len() > key.len() && starts_with(key1, key) { let new_key = &key1[key.len()..]; //If the new node's key is 7 Bytes or fewer, we can make a TinyRefNode if new_key.len() <= 7 { @@ -2346,7 +2346,7 @@ impl TrieNode for LineListNode //Otherwise check to see if we need to make a sub-node. If we do, // We know the new node will have only 1 slot filled - if key0.len() > key.len() && key0.starts_with(key) { + if key0.len() > key.len() && starts_with(key0, key) { let mut new_node = Self::new_in(self.alloc.clone()); unsafe{ new_node.set_payload_0(&key0[key.len()..], self.is_child_ptr::<0>(), ValOrChildUnion{ _unused: () }) } new_node.val_or_child0 = if prune { @@ -2358,7 +2358,7 @@ impl TrieNode for LineListNode debug_assert!(validate_node(&new_node)); return Some(TrieNodeODRc::new_in(new_node, self.alloc.clone())); } - if key1.len() > key.len() && key1.starts_with(key) { + if key1.len() > key.len() && starts_with(key1, key) { let mut new_node = Self::new_in(self.alloc.clone()); unsafe{ new_node.set_payload_0(&key1[key.len()..], self.is_child_ptr::<1>(), ValOrChildUnion{ _unused: () }) } new_node.val_or_child0 = if prune { diff --git a/src/morphisms.rs b/src/morphisms.rs index 6adada17..b290ba27 100644 --- a/src/morphisms.rs +++ b/src/morphisms.rs @@ -1765,7 +1765,7 @@ mod tests { } invocations += 1; }); - assert_eq!(map.val_count(), 5); + assert_eq!(map.val_count(), 6); assert_eq!(invocations, 6); // Generate all 3-lenght 'L' | 'R' permutations diff --git a/src/poly_zipper.rs b/src/poly_zipper.rs new file mode 100644 index 00000000..491eefe8 --- /dev/null +++ b/src/poly_zipper.rs @@ -0,0 +1,124 @@ + +#[cfg(doc)] +use crate::zipper::*; + +/// Derive macro to implement *most* zipper traits on an enum designed to act as a polymorphic zipper +/// +/// A polymorphic zipper is a zipper that can represent different underlying zipper kinds, and dispatch +/// to the appropriate type at runtime. Similar in concept to an `&dyn` reference. +/// +/// The `PolyZipper` macro implements the following traits, provided they are implemented on each of the enum variants: +/// * [`Zipper`] +/// * [`ZipperPath`] +/// * [`ZipperAbsolutePath`] +/// * [`ZipperConcrete`] +/// * [`ZipperIteration`] +/// * [`ZipperMoving`] +/// * [`ZipperPathBuffer`] +/// * [`ZipperReadOnlyConditionalIteration`] +/// * [`ZipperReadOnlyConditionalValues`] +/// * [`ZipperReadOnlyIteration`] +/// * [`ZipperReadOnlyValues`] +/// * [`ZipperValues`] +/// +/// NOTE: This macro does not derive an impl for [`ZipperForking`] +/// because the mapping between child zipper types and the output type is not always straightforward. +/// Therefore it is recommended to implement `ZipperForking` yourself. +/// +/// [`ZipperWriting`] and other write zipper trait are also not supported currently. That decision is +/// not fundamental and additional impls could be added in the future. +/// +/// ## USAGE: +/// The generic parameter names: `'trie`, `'path`, `V`, and `A` have special meaning to +/// the traits that require them. `V` must be specified as a generic type paremeter, even if +/// you intend to specify a default type. +/// +/// ``` +/// use pathmap::zipper::{PolyZipper, ReadZipperTracked, ReadZipperUntracked}; +/// +/// #[derive(PolyZipper)] +/// enum MyPolyZipper<'trie, 'path, V: Clone + Send + Sync + Unpin = ()> { +/// Tracked(ReadZipperTracked<'trie, 'path, V>), +/// Untracked(ReadZipperUntracked<'trie, 'path, V>), +/// } +/// ``` +pub use pathmap_derive::PolyZipper; + +#[cfg(test)] +mod tests { + use crate as pathmap; + use crate::PathMap; + use crate::zipper::*; + + #[cfg(feature = "arena_compact")] + use crate::arena_compact::{ACTMmapZipper, ACTVecZipper, ACTVec}; + + #[cfg(not(feature = "arena_compact"))] + #[derive(PolyZipper)] + pub enum TestPolyZipper<'trie, V: Clone + Send + Sync + Unpin = ()> { + /// Tracked PathMap read zipper + PathMap(ReadZipperTracked<'trie, 'trie, V>), + /// Unracked PathMap read zipper. + /// The reason this exists is to allow forking the zipper. + /// Forking a Tracked read zipper return an Untracked read zipper. + PathMapU(ReadZipperUntracked<'trie, 'trie, V>), + } + + #[cfg(feature = "arena_compact")] + #[derive(PolyZipper)] + pub enum TestPolyZipper<'trie, V: Clone + Send + Sync + Unpin = ()> { + /// Tracked PathMap read zipper + PathMap(ReadZipperTracked<'trie, 'trie, V>), + /// Unracked PathMap read zipper. + /// The reason this exists is to allow forking the zipper. + /// Forking a Tracked read zipper return an Untracked read zipper. + PathMapU(ReadZipperUntracked<'trie, 'trie, V>), + /// Memory-mapped ACT. + /// Prefix is necessary here such that MORK can see ACT under a specific path + ACTMmapPrefix(PrefixZipper<'trie, ACTMmapZipper<'trie, V>>), + /// `Vec` based ACT + /// Prefix is necessary here such that MORK can see ACT under a specific path + ACTVecPrefix(PrefixZipper<'trie, ACTVecZipper<'trie, V>>), + } + + + crate::zipper::zipper_moving_tests::zipper_moving_tests!(poly_zipper_pm, + |keys: &[&[u8]]| { + keys.iter().map(|k| (k, ())).collect::>() + }, + |btm: &mut PathMap<()>, path: &[u8]| -> _ { + TestPolyZipper::PathMapU(btm.read_zipper_at_path(path)) + } + ); + + crate::zipper::zipper_iteration_tests::zipper_iteration_tests!(poly_zipper_pm, + |keys: &[&[u8]]| { + keys.iter().map(|k| (k, ())).collect::>() + }, + |btm: &mut PathMap<()>, path: &[u8]| -> _ { + TestPolyZipper::PathMapU(btm.read_zipper_at_path(path)) + } + ); + + #[cfg(feature = "arena_compact")] + crate::zipper::zipper_moving_tests::zipper_moving_tests!(poly_zipper_act, + |keys: &[&[u8]]| { + let btm = keys.iter().map(|k| (k, ())).collect::>(); + ACTVec::from_zipper(btm.read_zipper(), |()| 0) + }, + |act: &mut ACTVec, path: &[u8]| -> _ { + TestPolyZipper::ACTVecPrefix(PrefixZipper::new(&[], act.read_zipper_at_path(path))) + } + ); + + #[cfg(feature = "arena_compact")] + crate::zipper::zipper_iteration_tests::zipper_iteration_tests!(poly_zipper_act, + |keys: &[&[u8]]| { + let btm = keys.iter().map(|k| (k, ())).collect::>(); + ACTVec::from_zipper(btm.read_zipper(), |()| 0) + }, + |act: &mut ACTVec, path: &[u8]| -> _ { + TestPolyZipper::ACTVecPrefix(PrefixZipper::new(&[], act.read_zipper_at_path(path))) + } + ); +} diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index e83b8ef7..2cd49867 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -1,5 +1,5 @@ use std::borrow::Cow; -use crate::utils::ByteMask; +use crate::utils::{starts_with, ByteMask}; use crate::zipper::*; enum PrefixPos { @@ -81,7 +81,7 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> } pub fn with_origin(mut self, origin: &[u8]) -> Result { - if !self.prefix.starts_with(origin) { + if !starts_with(&*self.prefix, origin) { return Err("set_origin must be called within prefix"); } self.origin_depth = origin.len(); @@ -94,7 +94,7 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> /// The remaining portion of the `prefix` will be part of the [`path`](ZipperMoving::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 !self.prefix.starts_with(root_prefix_path) { + if !starts_with(&*self.prefix, root_prefix_path) { return Err("zipper's prefix must begin with root_prefix_path"); } self.origin_depth = root_prefix_path.len(); @@ -179,9 +179,9 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> } } -impl<'prefix, Z> ZipperConcretePriv for PrefixZipper<'prefix, Z> +impl<'prefix, Z> ZipperConcrete for PrefixZipper<'prefix, Z> where - Z: ZipperConcretePriv + Z: ZipperConcrete { fn shared_node_id(&self) -> Option { match self.position { @@ -189,12 +189,6 @@ impl<'prefix, Z> ZipperConcretePriv for PrefixZipper<'prefix, Z> _ => None, } } -} - -impl<'prefix, Z> ZipperConcrete for PrefixZipper<'prefix, Z> - where - Z: ZipperConcrete -{ fn is_shared(&self) -> bool { match self.position { PrefixPos::Source => self.source.is_shared(), diff --git a/src/product_zipper.rs b/src/product_zipper.rs index f4c4650f..23eb7583 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -5,9 +5,8 @@ use crate::trie_node::*; use crate::zipper::*; use zipper_priv::*; -/// A [Zipper] type that moves through a Cartesian product space created by extending each value at the -/// end of a path in a primary space with the root of a secondardary space, and doing it recursively for -/// as many spaces as needed +/// A [Zipper] type that moves through a Cartesian product trie created by extending each path in a primary +/// trie with the root of the next secondardary trie, doing it recursively for all provided tries pub struct ProductZipper<'factor_z, 'trie, V: Clone + Send + Sync, A: Allocator = GlobalAlloc> { z: read_zipper_core::ReadZipperCore<'trie, 'static, V, A>, /// All of the seconday factors beyond the primary factor @@ -292,18 +291,29 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperValues for ProductZipper<'_, 'trie, V, A> { fn val(&self) -> Option<&V> { - self.z.get_val() + unsafe{ self.z.get_val() } } } -impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyValues<'trie, V> for ProductZipper<'factor_z, 'trie, V, A> { - fn get_val(&self) -> Option<&'trie V> { self.z.get_val() } -} +/// A [`witness`](ZipperReadOnlyConditionalValues::witness) type used by [`ProductZipper`] +pub struct ProductZipperWitness((ReadZipperWitness, Vec>)); impl<'factor_z, 'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyConditionalValues<'trie, V> for ProductZipper<'factor_z, 'trie, V, A> { - type WitnessT = (); - fn witness<'w>(&self) -> Self::WitnessT { () } - fn get_val_with_witness<'w>(&self, _witness: &'w Self::WitnessT) -> Option<&'w V> where 'trie: 'w { self.get_val() } + type WitnessT = ProductZipperWitness; + fn witness<'w>(&self) -> Self::WitnessT { + let primary_witness = self.z.witness(); + let secondary_witnesses = self.secondaries.iter().filter_map(|trie_ref| { + match trie_ref { + TrieRef::Owned(trie_ref) => Some(trie_ref.clone()), + TrieRef::Borrowed(_) => None + } + }).collect(); + ProductZipperWitness((primary_witness, secondary_witnesses)) + } + fn get_val_with_witness<'w>(&self, _witness: &'w Self::WitnessT) -> Option<&'w V> where 'trie: 'w { + //SAFETY: We know the witnesses are keeping the nodes we're borrowing from alive + unsafe{ self.z.get_val() } + } } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper for ProductZipper<'_, 'trie, V, A> { @@ -322,11 +332,8 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } impl ZipperConcrete for ProductZipper<'_, '_, V, A> { - fn is_shared(&self) -> bool { self.z.is_shared() } -} - -impl ZipperConcretePriv for ProductZipper<'_, '_, V, A> { fn shared_node_id(&self) -> Option { self.z.shared_node_id() } + fn is_shared(&self) -> bool { self.z.is_shared() } } impl zipper_priv::ZipperPriv for ProductZipper<'_, '_, V, A> { @@ -347,12 +354,12 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn root_prefix_path(&self) -> &[u8] { self.z.root_prefix_path() } } -/// A [Zipper] type that moves through a Cartesian product space created by extending each value at the -/// end of a path in a primary space with the root of a secondardary space, and doing it recursively for -/// as many spaces as needed +/// A [Zipper] type that moves through a Cartesian product trie created by extending each path in a primary +/// trie with the root of the next secondardary trie, doing it recursively for all provided tries /// /// Compared to [ProductZipper], this is a generic virtual zipper that works without -/// inspecting the inner workings of primary and secondary zippers. +/// inspecting the inner workings of primary and secondary zippers. `ProductZipperG` is more general, +/// while `ProductZipper` is faster in situations where it can be used. /// /// NOTE: In the future, this generic type will be renamed to `ProductZipper`, and the existing /// [ProductZipper] will be renamed something else or removed entirely. @@ -373,9 +380,6 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, SecondaryZ: ZipperMoving + ZipperPath, { /// Creates a new `ProductZipper` from the provided zippers - /// - /// WARNING: passing `other_zippers` that are not at node roots may lead to a panic. This is - /// an implementation issue, but would be very difficult to fix and may not be worth fixing. pub fn new(primary: PrimaryZ, other_zippers: ZipperList) -> Self where ZipperList: IntoIterator, @@ -526,12 +530,12 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperAbsolutePath fn root_prefix_path(&self) -> &[u8] { self.primary.root_prefix_path() } } -impl<'trie, PrimaryZ, SecondaryZ, V> ZipperConcretePriv +impl<'trie, PrimaryZ, SecondaryZ, V> ZipperConcrete for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperPath + ZipperConcretePriv, - SecondaryZ: ZipperMoving + ZipperPath + ZipperConcretePriv, + PrimaryZ: ZipperMoving + ZipperPath + ZipperConcrete, + SecondaryZ: ZipperMoving + ZipperPath + ZipperConcrete, { fn shared_node_id(&self) -> Option { if let Some(idx) = self.factor_idx(true) { @@ -540,15 +544,6 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperConcretePriv self.primary.shared_node_id() } } -} - -impl<'trie, PrimaryZ, SecondaryZ, V> ZipperConcrete - for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> - where - V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperPath + ZipperConcrete, - SecondaryZ: ZipperMoving + ZipperPath + ZipperConcrete, -{ fn is_shared(&self) -> bool { if let Some(idx) = self.factor_idx(true) { self.secondary[idx].is_shared() @@ -606,13 +601,21 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperReadOnlyConditionalValues<'trie, V> for ProductZipperG<'trie, PrimaryZ, SecondaryZ, V> where V: Clone + Send + Sync, - PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, - SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyValues<'trie, V>, + PrimaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyConditionalValues<'trie, V>, + SecondaryZ: ZipperMoving + ZipperPath + ZipperReadOnlyConditionalValues<'trie, V>, { - type WitnessT = (); - fn witness<'w>(&self) -> Self::WitnessT { () } - fn get_val_with_witness<'w>(&self, _witness: &'w Self::WitnessT) -> Option<&'w V> where 'trie: 'w { - self.get_val() + type WitnessT = (PrimaryZ::WitnessT, Vec); + fn witness<'w>(&self) -> Self::WitnessT { + let primary_witness = self.primary.witness(); + let secondary_witnesses = self.secondary.iter().map(|secondary| secondary.witness()).collect(); + (primary_witness, secondary_witnesses) + } + fn get_val_with_witness<'w>(&self, witness: &'w Self::WitnessT) -> Option<&'w V> where 'trie: 'w { + if let Some(idx) = self.factor_idx(true) { + self.secondary[idx].get_val_with_witness(&witness.1[idx]) + } else { + self.primary.get_val_with_witness(&witness.0) + } } } @@ -830,25 +833,25 @@ mod tests { //Descend within the first factor assert!(pz.descend_to(b"AA")); assert_eq!(pz.path(), b"AA"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 3); assert!(pz.descend_to(b"a")); assert_eq!(pz.path(), b"AAa"); - assert_eq!(pz.get_val(), Some(&0)); + assert_eq!(pz.val(), Some(&0)); assert_eq!(pz.child_count(), 3); //Step to the next factor assert!(pz.descend_to(b"DD")); assert_eq!(pz.path(), b"AAaDD"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); assert!(pz.descend_to(b"d")); assert_eq!(pz.path(), b"AAaDDd"); - assert_eq!(pz.get_val(), Some(&1000)); + assert_eq!(pz.val(), Some(&1000)); assert_eq!(pz.child_count(), 0); assert!(!pz.descend_to(b"GGg")); assert_eq!(pz.path(), b"AAaDDdGGg"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 0); //Test Reset, if the zipper was in another factor @@ -856,67 +859,67 @@ mod tests { assert_eq!(pz.path(), b""); assert!(pz.descend_to(b"AA")); assert_eq!(pz.path(), b"AA"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 3); //Try to descend to a non-existent path that would be within the first factor assert!(!pz.descend_to(b"aBBb")); assert_eq!(pz.path(), b"AAaBBb"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 0); //Now descend to the second factor in one jump pz.reset(); assert!(pz.descend_to(b"AAaDD")); assert_eq!(pz.path(), b"AAaDD"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); pz.reset(); assert!(pz.descend_to(b"AAaDDd")); assert_eq!(pz.path(), b"AAaDDd"); - assert_eq!(pz.get_val(), Some(&1000)); + assert_eq!(pz.val(), Some(&1000)); assert_eq!(pz.child_count(), 0); assert!(!pz.descend_to(b"GG")); assert_eq!(pz.path(), b"AAaDDdGG"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 0); //Make sure we can ascend out of a secondary factor; in this sub-test we'll hit the path middles assert_eq!(pz.ascend(1), Ok(())); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.path(), b"AAaDDdG"); assert_eq!(pz.child_count(), 0); assert_eq!(pz.ascend(3), Ok(())); assert_eq!(pz.path(), b"AAaD"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); assert_eq!(pz.ascend(2), Ok(())); assert_eq!(pz.path(), b"AA"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 3); assert_eq!(pz.ascend(3), Err(1)); assert_eq!(pz.path(), b""); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); assert!(pz.at_root()); assert!(!pz.descend_to(b"AAaDDdGG")); assert_eq!(pz.path(), b"AAaDDdGG"); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 0); //Now try to hit the path transition points assert_eq!(pz.ascend(2), Ok(())); assert_eq!(pz.path(), b"AAaDDd"); - assert_eq!(pz.get_val(), Some(&1000)); + assert_eq!(pz.val(), Some(&1000)); assert_eq!(pz.child_count(), 0); assert_eq!(pz.ascend(3), Ok(())); assert_eq!(pz.path(), b"AAa"); - assert_eq!(pz.get_val(), Some(&0)); + assert_eq!(pz.val(), Some(&0)); assert_eq!(pz.child_count(), 3); assert_eq!(pz.ascend(3), Ok(())); assert_eq!(pz.path(), b""); - assert_eq!(pz.get_val(), None); + assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); assert!(pz.at_root()); } diff --git a/src/ring.rs b/src/ring.rs index 47356aea..cb3370ae 100644 --- a/src/ring.rs +++ b/src/ring.rs @@ -640,7 +640,7 @@ pub(crate) trait HeteroLattice { in_place_default_impl(result, self, other, |_s| {}, |e| Self::convert(e)) } fn pmeet(&self, other: &OtherT) -> AlgebraicResult where Self: Sized; - fn join_all(xs: &[&Self]) -> Self where Self: Sized; + // fn join_all(xs: &[&Self]) -> Self where Self: Sized; //HeteroLattice will entirely disappear with the policy refactor, so it's not worth worying about this anymore fn convert(other: OtherT) -> Self; } @@ -960,48 +960,48 @@ pub trait SetLattice { #[macro_export] macro_rules! set_lattice { ( $type_ident:ident $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? ) => { - impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? Lattice for $type_ident $(< $( $lt ),+ >)? where Self: SetLattice, ::V: Lattice { - fn pjoin(&self, other: &Self) -> AlgebraicResult { - let self_len = SetLattice::len(self); - let other_len = SetLattice::len(other); - let mut result = ::with_capacity(self_len.max(other_len)); + impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $crate::ring::Lattice for $type_ident $(< $( $lt ),+ >)? where Self: $crate::ring::SetLattice, ::V: $crate::ring::Lattice { + fn pjoin(&self, other: &Self) -> $crate::ring::AlgebraicResult { + let self_len = $crate::ring::SetLattice::len(self); + let other_len = $crate::ring::SetLattice::len(other); + let mut result = ::with_capacity(self_len.max(other_len)); let mut is_ident = self_len >= other_len; let mut is_counter_ident = self_len <= other_len; - for (key, self_val) in SetLattice::iter(self) { - if let Some(other_val) = SetLattice::get(other, key) { + for (key, self_val) in $crate::ring::SetLattice::iter(self) { + if let Some(other_val) = $crate::ring::SetLattice::get(other, key) { // A key in both sets let inner_result = self_val.pjoin(other_val); - set_lattice_update_ident_flags_with_result( + $crate::ring::set_lattice_update_ident_flags_with_result( &mut result, inner_result, key, self_val, other_val, &mut is_ident, &mut is_counter_ident ); } else { // A key in self, but not in other - SetLattice::insert(&mut result, key.clone(), self_val.clone()); + $crate::ring::SetLattice::insert(&mut result, key.clone(), self_val.clone()); is_counter_ident = false; } } for (key, value) in SetLattice::iter(other) { - if !SetLattice::contains_key(self, key) { + if !$crate::ring::SetLattice::contains_key(self, key) { // A key in other, but not in self - SetLattice::insert(&mut result, key.clone(), value.clone()); + $crate::ring::SetLattice::insert(&mut result, key.clone(), value.clone()); is_ident = false; } } - set_lattice_integrate_into_result(result, is_ident, is_counter_ident, self_len, other_len) + $crate::ring::set_lattice_integrate_into_result(result, is_ident, is_counter_ident, self_len, other_len) } - fn pmeet(&self, other: &Self) -> AlgebraicResult { - let mut result = ::with_capacity(0); + fn pmeet(&self, other: &Self) -> $crate::ring::AlgebraicResult { + let mut result = ::with_capacity(0); let mut is_ident = true; let mut is_counter_ident = true; - let (smaller, larger, switch) = if SetLattice::len(self) < SetLattice::len(other) { + let (smaller, larger, switch) = if $crate::ring::SetLattice::len(self) < $crate::ring::SetLattice::len(other) { (self, other, false) } else { (other, self, true) }; - for (key, self_val) in SetLattice::iter(smaller) { - if let Some(other_val) = SetLattice::get(larger, key) { + for (key, self_val) in $crate::ring::SetLattice::iter(smaller) { + if let Some(other_val) = $crate::ring::SetLattice::get(larger, key) { let inner_result = self_val.pmeet(other_val); - set_lattice_update_ident_flags_with_result( + $crate::ring::set_lattice_update_ident_flags_with_result( &mut result, inner_result, key, self_val, other_val, &mut is_ident, &mut is_counter_ident ); } else { @@ -1011,7 +1011,7 @@ macro_rules! set_lattice { if switch { core::mem::swap(&mut is_ident, &mut is_counter_ident); } - set_lattice_integrate_into_result(result, is_ident, is_counter_ident, self.len(), other.len()) + $crate::ring::set_lattice_integrate_into_result(result, is_ident, is_counter_ident, self.len(), other.len()) } } } @@ -1019,7 +1019,8 @@ macro_rules! set_lattice { /// Internal function to integrate an `AlgebraicResult` from an element in a set into the set's own overall result #[inline] -fn set_lattice_update_ident_flags_with_result( +#[doc(hidden)] +pub fn set_lattice_update_ident_flags_with_result( result_set: &mut S, result: AlgebraicResult, key: &S::K, @@ -1057,7 +1058,8 @@ fn set_lattice_update_ident_flags_with_result( /// Internal function to make an `AlgebraicResult` from a new result set and flags #[inline] -fn set_lattice_integrate_into_result( +#[doc(hidden)] +pub fn set_lattice_integrate_into_result( result_set: S, is_ident: bool, is_counter_ident: bool, @@ -1087,31 +1089,31 @@ fn set_lattice_integrate_into_result( #[macro_export] macro_rules! set_dist_lattice { ( $type_ident:ident $(< $( $lt:tt $( : $clt:tt $(+ $dlt:tt )* )? ),+ >)? ) => { - impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? DistributiveLattice for $type_ident $(< $( $lt ),+ >)? where Self: SetLattice + Clone, ::V: DistributiveLattice { - fn psubtract(&self, other: &Self) -> AlgebraicResult { + impl $(< $( $lt $( : $clt $(+ $dlt )* )? ),+ >)? $crate::ring::DistributiveLattice for $type_ident $(< $( $lt ),+ >)? where Self: $crate::ring::SetLattice + Clone, ::V: $crate::ring::DistributiveLattice { + fn psubtract(&self, other: &Self) -> $crate::ring::AlgebraicResult { let mut is_ident = true; let mut result = self.clone(); //Two code paths, so that we only iterate over the smaller set - if SetLattice::len(self) > SetLattice::len(other) { - for (key, other_val) in SetLattice::iter(other) { - if let Some(self_val) = SetLattice::get(self, key) { + if $crate::ring::SetLattice::len(self) > $crate::ring::SetLattice::len(other) { + for (key, other_val) in $crate::ring::SetLattice::iter(other) { + if let Some(self_val) = $crate::ring::SetLattice::get(self, key) { set_lattice_subtract_element(&mut result, key, self_val, other_val, &mut is_ident) } } } else { - for (key, self_val) in SetLattice::iter(self) { - if let Some(other_val) = SetLattice::get(other, key) { + for (key, self_val) in $crate::ring::SetLattice::iter(self) { + if let Some(other_val) = $crate::ring::SetLattice::get(other, key) { set_lattice_subtract_element(&mut result, key, self_val, other_val, &mut is_ident) } } } - if SetLattice::len(&result) == 0 { - AlgebraicResult::None + if $crate::ring::SetLattice::len(&result) == 0 { + $crate::ring::AlgebraicResult::None } else if is_ident { - AlgebraicResult::Identity(SELF_IDENT) + $crate::ring::AlgebraicResult::Identity(SELF_IDENT) } else { - SetLattice::shrink_to_fit(&mut result); - AlgebraicResult::Element(result) + $crate::ring::SetLattice::shrink_to_fit(&mut result); + $crate::ring::AlgebraicResult::Element(result) } } } @@ -1189,226 +1191,233 @@ impl<'a, K> Iterator for HashSetIterWrapper<'a, K> { set_lattice!(HashSet); set_dist_lattice!(HashSet); -#[test] -fn set_lattice_join_test1() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - //Test None result - let joined_result = a.pjoin(&b); - assert_eq!(joined_result, AlgebraicResult::None); - - //Straightforward join - a.insert("A"); - b.insert("B"); - let joined_result = a.pjoin(&b); - assert!(joined_result.is_element()); - let joined = joined_result.unwrap([&a, &b]); - assert_eq!(joined.len(), 2); - assert!(joined.get("A").is_some()); - assert!(joined.get("B").is_some()); - - //Make "self" contain more entries - a.insert("C"); - let joined_result = a.pjoin(&b); - assert!(joined_result.is_element()); - let joined = joined_result.unwrap([&a, &b]); - assert_eq!(joined.len(), 3); - - //Make "other" contain more entries - b.insert("D"); - b.insert("F"); - b.insert("H"); - let joined_result = a.pjoin(&b); - assert!(joined_result.is_element()); - let joined = joined_result.unwrap([&a, &b]); - assert_eq!(joined.len(), 6); - - //Test identity with self arg - let joined_result = joined.pjoin(&b); - assert_eq!(joined_result, AlgebraicResult::Identity(SELF_IDENT)); - - //Test identity with other arg - let joined_result = b.pjoin(&joined); - assert_eq!(joined_result, AlgebraicResult::Identity(COUNTER_IDENT)); - - //Test mutual identity - let joined_result = joined.pjoin(&joined); - assert_eq!(joined_result, AlgebraicResult::Identity(SELF_IDENT | COUNTER_IDENT)); -} - -#[test] -fn set_lattice_meet_test1() { - let mut a = HashSet::new(); - let mut b = HashSet::new(); - - //Test disjoint result - a.insert("A"); - b.insert("B"); - let meet_result = a.pmeet(&b); - assert_eq!(meet_result, AlgebraicResult::None); - - //Straightforward meet - a.insert("A"); - a.insert("C"); - b.insert("B"); - b.insert("C"); - let meet_result = a.pmeet(&b); - assert!(meet_result.is_element()); - let meet = meet_result.unwrap([&a, &b]); - assert_eq!(meet.len(), 1); - assert!(meet.get("A").is_none()); - assert!(meet.get("B").is_none()); - assert!(meet.get("C").is_some()); - - //Make "self" contain more entries - a.insert("D"); - let meet_result = a.pmeet(&b); - assert!(meet_result.is_element()); - let meet = meet_result.unwrap([&a, &b]); - assert_eq!(meet.len(), 1); - - //Make "other" contain more entries - b.insert("D"); - b.insert("E"); - b.insert("F"); - let meet_result = a.pmeet(&b); - assert!(meet_result.is_element()); - let meet = meet_result.unwrap([&a, &b]); - assert_eq!(meet.len(), 2); - - //Test identity with self arg - let meet_result = meet.pmeet(&b); - assert_eq!(meet_result, AlgebraicResult::Identity(SELF_IDENT)); - - //Test identity with other arg - let meet_result = b.pmeet(&meet); - assert_eq!(meet_result, AlgebraicResult::Identity(COUNTER_IDENT)); - - //Test mutual identity - let meet_result = meet.pmeet(&meet); - assert_eq!(meet_result, AlgebraicResult::Identity(SELF_IDENT | COUNTER_IDENT)); -} +#[cfg(test)] +mod tests { + use std::collections::{HashSet, HashMap}; + use crate::ring::Lattice; + use super::{AlgebraicResult, SetLattice, SELF_IDENT, COUNTER_IDENT}; + + #[test] + fn set_lattice_join_test1() { + let mut a = HashSet::new(); + let mut b = HashSet::new(); + + //Test None result + let joined_result = a.pjoin(&b); + assert_eq!(joined_result, AlgebraicResult::None); + + //Straightforward join + a.insert("A"); + b.insert("B"); + let joined_result = a.pjoin(&b); + assert!(joined_result.is_element()); + let joined = joined_result.unwrap([&a, &b]); + assert_eq!(joined.len(), 2); + assert!(joined.get("A").is_some()); + assert!(joined.get("B").is_some()); + + //Make "self" contain more entries + a.insert("C"); + let joined_result = a.pjoin(&b); + assert!(joined_result.is_element()); + let joined = joined_result.unwrap([&a, &b]); + assert_eq!(joined.len(), 3); + + //Make "other" contain more entries + b.insert("D"); + b.insert("F"); + b.insert("H"); + let joined_result = a.pjoin(&b); + assert!(joined_result.is_element()); + let joined = joined_result.unwrap([&a, &b]); + assert_eq!(joined.len(), 6); + + //Test identity with self arg + let joined_result = joined.pjoin(&b); + assert_eq!(joined_result, AlgebraicResult::Identity(SELF_IDENT)); + + //Test identity with other arg + let joined_result = b.pjoin(&joined); + assert_eq!(joined_result, AlgebraicResult::Identity(COUNTER_IDENT)); + + //Test mutual identity + let joined_result = joined.pjoin(&joined); + assert_eq!(joined_result, AlgebraicResult::Identity(SELF_IDENT | COUNTER_IDENT)); + } -/// Used in [set_lattice_join_test2] and [set_lattice_meet_test2] -#[derive(Clone, Debug)] -struct Map<'a>(HashMap::<&'a str, HashMap<&'a str, ()>>);// TODO, should be struct Map<'a>(HashMap::<&'a str, Map<'a>>); see comment above about chalk -impl<'a> SetLattice for Map<'a> { - type K = &'a str; - type V = HashMap<&'a str, ()>; //Option>>; TODO, see comment above about chalk - type Iter<'it> = std::collections::hash_map::Iter<'it, Self::K, Self::V> where Self: 'it, Self::K: 'it, Self::V: 'it; - fn with_capacity(capacity: usize) -> Self { Map(HashMap::with_capacity(capacity)) } - fn len(&self) -> usize { self.0.len() } - fn is_empty(&self) -> bool { self.0.is_empty() } - fn contains_key(&self, key: &Self::K) -> bool { self.0.contains_key(key) } - fn insert(&mut self, key: Self::K, val: Self::V) { self.0.insert(key, val); } - fn get(&self, key: &Self::K) -> Option<&Self::V> { self.0.get(key) } - fn replace(&mut self, key: &Self::K, val: Self::V) { self.0.replace(key, val) } - fn remove(&mut self, key: &Self::K) { self.0.remove(key); } - fn iter<'it>(&'it self) -> Self::Iter<'it> { self.0.iter() } - fn shrink_to_fit(&mut self) { self.0.shrink_to_fit(); } -} -set_lattice!(Map<'a>); + #[test] + fn set_lattice_meet_test1() { + let mut a = HashSet::new(); + let mut b = HashSet::new(); + + //Test disjoint result + a.insert("A"); + b.insert("B"); + let meet_result = a.pmeet(&b); + assert_eq!(meet_result, AlgebraicResult::None); + + //Straightforward meet + a.insert("A"); + a.insert("C"); + b.insert("B"); + b.insert("C"); + let meet_result = a.pmeet(&b); + assert!(meet_result.is_element()); + let meet = meet_result.unwrap([&a, &b]); + assert_eq!(meet.len(), 1); + assert!(meet.get("A").is_none()); + assert!(meet.get("B").is_none()); + assert!(meet.get("C").is_some()); + + //Make "self" contain more entries + a.insert("D"); + let meet_result = a.pmeet(&b); + assert!(meet_result.is_element()); + let meet = meet_result.unwrap([&a, &b]); + assert_eq!(meet.len(), 1); + + //Make "other" contain more entries + b.insert("D"); + b.insert("E"); + b.insert("F"); + let meet_result = a.pmeet(&b); + assert!(meet_result.is_element()); + let meet = meet_result.unwrap([&a, &b]); + assert_eq!(meet.len(), 2); + + //Test identity with self arg + let meet_result = meet.pmeet(&b); + assert_eq!(meet_result, AlgebraicResult::Identity(SELF_IDENT)); + + //Test identity with other arg + let meet_result = b.pmeet(&meet); + assert_eq!(meet_result, AlgebraicResult::Identity(COUNTER_IDENT)); + + //Test mutual identity + let meet_result = meet.pmeet(&meet); + assert_eq!(meet_result, AlgebraicResult::Identity(SELF_IDENT | COUNTER_IDENT)); + } -#[test] -/// Tests a HashMap containing more HashMaps -//TODO: When the [chalk trait solver](https://github.com/rust-lang/chalk) lands in stable rust, it would be nice -// to promote this test to sample code and implement an arbitrarily deep recursive structure. But currently -// that's not worth the complexity due to limits in the stable rust trait sovler. -fn set_lattice_join_test2() { - let mut a = Map::with_capacity(1); - let mut b = Map::with_capacity(1); - - // Top level join - let mut inner_map_1 = HashMap::with_capacity(1); - inner_map_1.insert("1", ()); - a.0.insert("A", inner_map_1.clone()); - b.0.insert("B", inner_map_1); - // b.0.insert("C", HashMap::new()); TODO: We might want to test collapse of empty items using the is_bottom() method - let joined_result = a.pjoin(&b); - assert!(joined_result.is_element()); - let joined = joined_result.unwrap([&a, &b]); - assert_eq!(joined.len(), 2); - assert!(joined.get(&"A").is_some()); - assert!(joined.get(&"B").is_some()); - assert!(joined.get(&"C").is_none()); //Empty sub-sets should not be merged - - // Two level join, results should be Element even though the key existed in both args, because the values joined - let mut inner_map_2 = HashMap::with_capacity(1); - inner_map_2.insert("2", ()); - b.0.remove("B"); - b.0.insert("A", inner_map_2); - let joined_result = a.pjoin(&b); - assert!(joined_result.is_element()); - let joined = joined_result.unwrap([&a, &b]); - assert_eq!(joined.len(), 1); - let joined_inner = joined.get(&"A").unwrap(); - assert_eq!(joined_inner.len(), 2); - assert!(joined_inner.get(&"1").is_some()); - assert!(joined_inner.get(&"2").is_some()); - - // Redoing the join should yield Identity - let joined_result = joined.pjoin(&a); - assert_eq!(joined_result.identity_mask().unwrap(), SELF_IDENT); - let joined_result = b.pjoin(&joined); - assert_eq!(joined_result.identity_mask().unwrap(), COUNTER_IDENT); -} + /// Used in [set_lattice_join_test2] and [set_lattice_meet_test2] + #[derive(Clone, Debug)] + struct Map<'a>(HashMap::<&'a str, HashMap<&'a str, ()>>);// TODO, should be struct Map<'a>(HashMap::<&'a str, Map<'a>>); see comment above about chalk + impl<'a> SetLattice for Map<'a> { + type K = &'a str; + type V = HashMap<&'a str, ()>; //Option>>; TODO, see comment above about chalk + type Iter<'it> = std::collections::hash_map::Iter<'it, Self::K, Self::V> where Self: 'it, Self::K: 'it, Self::V: 'it; + fn with_capacity(capacity: usize) -> Self { Map(HashMap::with_capacity(capacity)) } + fn len(&self) -> usize { self.0.len() } + fn is_empty(&self) -> bool { self.0.is_empty() } + fn contains_key(&self, key: &Self::K) -> bool { self.0.contains_key(key) } + fn insert(&mut self, key: Self::K, val: Self::V) { self.0.insert(key, val); } + fn get(&self, key: &Self::K) -> Option<&Self::V> { self.0.get(key) } + fn replace(&mut self, key: &Self::K, val: Self::V) { self.0.replace(key, val) } + fn remove(&mut self, key: &Self::K) { self.0.remove(key); } + fn iter<'it>(&'it self) -> Self::Iter<'it> { self.0.iter() } + fn shrink_to_fit(&mut self) { self.0.shrink_to_fit(); } + } + set_lattice!(Map<'a>); + + #[test] + /// Tests a HashMap containing more HashMaps + //TODO: When the [chalk trait solver](https://github.com/rust-lang/chalk) lands in stable rust, it would be nice + // to promote this test to sample code and implement an arbitrarily deep recursive structure. But currently + // that's not worth the complexity due to limits in the stable rust trait sovler. + fn set_lattice_join_test2() { + let mut a = Map::with_capacity(1); + let mut b = Map::with_capacity(1); + + // Top level join + let mut inner_map_1 = HashMap::with_capacity(1); + inner_map_1.insert("1", ()); + a.0.insert("A", inner_map_1.clone()); + b.0.insert("B", inner_map_1); + // b.0.insert("C", HashMap::new()); TODO: We might want to test collapse of empty items using the is_bottom() method + let joined_result = a.pjoin(&b); + assert!(joined_result.is_element()); + let joined = joined_result.unwrap([&a, &b]); + assert_eq!(joined.len(), 2); + assert!(joined.get(&"A").is_some()); + assert!(joined.get(&"B").is_some()); + assert!(joined.get(&"C").is_none()); //Empty sub-sets should not be merged + + // Two level join, results should be Element even though the key existed in both args, because the values joined + let mut inner_map_2 = HashMap::with_capacity(1); + inner_map_2.insert("2", ()); + b.0.remove("B"); + b.0.insert("A", inner_map_2); + let joined_result = a.pjoin(&b); + assert!(joined_result.is_element()); + let joined = joined_result.unwrap([&a, &b]); + assert_eq!(joined.len(), 1); + let joined_inner = joined.get(&"A").unwrap(); + assert_eq!(joined_inner.len(), 2); + assert!(joined_inner.get(&"1").is_some()); + assert!(joined_inner.get(&"2").is_some()); + + // Redoing the join should yield Identity + let joined_result = joined.pjoin(&a); + assert_eq!(joined_result.identity_mask().unwrap(), SELF_IDENT); + let joined_result = b.pjoin(&joined); + assert_eq!(joined_result.identity_mask().unwrap(), COUNTER_IDENT); + } -#[test] -/// Tests a HashMap containing more HashMaps. See comments on [set_lattice_join_test2] -fn set_lattice_meet_test2() { - let mut a = Map::with_capacity(1); - let mut b = Map::with_capacity(1); - - let mut inner_map_a = HashMap::new(); - inner_map_a.insert("a", ()); - let mut inner_map_b = HashMap::new(); - inner_map_b.insert("b", ()); - let mut inner_map_c = HashMap::new(); - inner_map_c.insert("c", ()); - - // One level meet - a.0.insert("A", inner_map_a.clone()); - a.0.insert("C", inner_map_c.clone()); - b.0.insert("B", inner_map_b.clone()); - b.0.insert("C", inner_map_c.clone()); - let meet_result = a.pmeet(&b); - assert!(meet_result.is_element()); - let meet = meet_result.unwrap([&a, &b]); - assert_eq!(meet.len(), 1); - assert!(meet.get(&"A").is_none()); - assert!(meet.get(&"B").is_none()); - assert!(meet.get(&"C").is_some()); - - // Two level meet, results should be None even though the key existed in both args, because the inner values don't overlap - let mut inner_map_1 = HashMap::with_capacity(1); - inner_map_1.insert("1", ()); - a.0.insert("A", inner_map_1); - let mut inner_map_2 = HashMap::with_capacity(1); - inner_map_2.insert("2", ()); - b.0.remove("B"); - b.0.remove("C"); - b.0.insert("A", inner_map_2.clone()); - let meet_result = a.pmeet(&b); - assert!(meet_result.is_none()); - - // Two level meet, now should return Element, because the values have some overlap - inner_map_2.insert("1", ()); - b.0.insert("A", inner_map_2); - let meet_result = a.pmeet(&b); - assert!(meet_result.is_element()); - let meet = meet_result.unwrap([&a, &b]); - assert_eq!(meet.len(), 1); - let meet_inner = meet.get(&"A").unwrap(); - assert_eq!(meet_inner.len(), 1); - assert!(meet_inner.get(&"1").is_some()); - assert!(meet_inner.get(&"2").is_none()); - - // Redoing the meet should yield Identity - let meet_result = meet.pmeet(&a); - assert_eq!(meet_result.identity_mask().unwrap(), SELF_IDENT); - let meet_result = b.pmeet(&meet); - assert_eq!(meet_result.identity_mask().unwrap(), COUNTER_IDENT); + #[test] + /// Tests a HashMap containing more HashMaps. See comments on [set_lattice_join_test2] + fn set_lattice_meet_test2() { + let mut a = Map::with_capacity(1); + let mut b = Map::with_capacity(1); + + let mut inner_map_a = HashMap::new(); + inner_map_a.insert("a", ()); + let mut inner_map_b = HashMap::new(); + inner_map_b.insert("b", ()); + let mut inner_map_c = HashMap::new(); + inner_map_c.insert("c", ()); + + // One level meet + a.0.insert("A", inner_map_a.clone()); + a.0.insert("C", inner_map_c.clone()); + b.0.insert("B", inner_map_b.clone()); + b.0.insert("C", inner_map_c.clone()); + let meet_result = a.pmeet(&b); + assert!(meet_result.is_element()); + let meet = meet_result.unwrap([&a, &b]); + assert_eq!(meet.len(), 1); + assert!(meet.get(&"A").is_none()); + assert!(meet.get(&"B").is_none()); + assert!(meet.get(&"C").is_some()); + + // Two level meet, results should be None even though the key existed in both args, because the inner values don't overlap + let mut inner_map_1 = HashMap::with_capacity(1); + inner_map_1.insert("1", ()); + a.0.insert("A", inner_map_1); + let mut inner_map_2 = HashMap::with_capacity(1); + inner_map_2.insert("2", ()); + b.0.remove("B"); + b.0.remove("C"); + b.0.insert("A", inner_map_2.clone()); + let meet_result = a.pmeet(&b); + assert!(meet_result.is_none()); + + // Two level meet, now should return Element, because the values have some overlap + inner_map_2.insert("1", ()); + b.0.insert("A", inner_map_2); + let meet_result = a.pmeet(&b); + assert!(meet_result.is_element()); + let meet = meet_result.unwrap([&a, &b]); + assert_eq!(meet.len(), 1); + let meet_inner = meet.get(&"A").unwrap(); + assert_eq!(meet_inner.len(), 1); + assert!(meet_inner.get(&"1").is_some()); + assert!(meet_inner.get(&"2").is_none()); + + // Redoing the meet should yield Identity + let meet_result = meet.pmeet(&a); + assert_eq!(meet_result.identity_mask().unwrap(), SELF_IDENT); + let meet_result = b.pmeet(&meet); + assert_eq!(meet_result.identity_mask().unwrap(), COUNTER_IDENT); + } } //GOAT, do a test for the HashMap impl of psubtract diff --git a/src/tiny_node.rs b/src/tiny_node.rs index 05aebca5..d95c5721 100644 --- a/src/tiny_node.rs +++ b/src/tiny_node.rs @@ -11,7 +11,7 @@ use core::mem::MaybeUninit; use core::fmt::{Debug, Formatter}; use std::collections::HashMap; -use crate::utils::{ByteMask, find_prefix_overlap}; +use crate::utils::{ByteMask, find_prefix_overlap, starts_with}; use crate::alloc::Allocator; use crate::trie_node::*; use crate::ring::*; @@ -127,7 +127,7 @@ impl<'a, V: Clone + Send + Sync, A: Allocator> TrieNode for TinyRefNode<'a find_prefix_overlap(self.key(), key) } fn node_contains_partial_key(&self, key: &[u8]) -> bool { - if self.key().starts_with(key) { + if starts_with(self.key(), key) { true } else { false @@ -156,7 +156,7 @@ impl<'a, V: Clone + Send + Sync, A: Allocator> TrieNode for TinyRefNode<'a let self_key = self.key(); debug_assert!(results.len() >= keys.len()); for ((key, expect_val), (result_key_len, payload_ref)) in keys.into_iter().zip(results.into_iter()) { - if key.starts_with(self_key) { + if starts_with(key, self_key) { let self_key_len = self_key.len(); if self.is_child_ptr() { if !*expect_val || self_key_len < key.len() { @@ -227,7 +227,7 @@ impl<'a, V: Clone + Send + Sync, A: Allocator> TrieNode for TinyRefNode<'a fn node_first_val_depth_along_key(&self, key: &[u8]) -> Option { debug_assert!(key.len() > 0); let node_key = self.key(); - if self.is_used_val() && key.starts_with(node_key) { + if self.is_used_val() && starts_with(key, node_key) { Some(node_key.len() - 1) } else { None @@ -267,7 +267,7 @@ impl<'a, V: Clone + Send + Sync, A: Allocator> TrieNode for TinyRefNode<'a } //Otherwise check to see if we need to make a sub-node. - if node_key.len() > key.len() && node_key.starts_with(key) { + if node_key.len() > key.len() && starts_with(node_key, key) { let new_key = &node_key[key.len()..]; let ref_node = TinyRefNode::new_in(self.is_child_ptr(), new_key, self.payload, self.alloc.clone()); return AbstractNodeRef::BorrowedTiny(ref_node) diff --git a/src/trie_map.rs b/src/trie_map.rs index d03d1a85..865e5f0d 100644 --- a/src/trie_map.rs +++ b/src/trie_map.rs @@ -9,11 +9,7 @@ use crate::ring::{AlgebraicResult, AlgebraicStatus, COUNTER_IDENT, SELF_IDENT, L use crate::gxhash::{self, gxhash128}; -//GOAT-old-names -#[deprecated] -pub type BytesTrieMap = PathMap; - -/// A map type that uses byte slices `&[u8]` as keys +/// A map type that uses a trie based on byte slices (`&[u8]`) known as "paths" /// /// This type is implemented using some of the approaches explained in the /// ["Bitwise trie with bitmap" Wikipedia article](https://en.wikipedia.org/wiki/Bitwise_trie_with_bitmap). @@ -89,23 +85,6 @@ impl PathMap { { Self::new_from_ana_in(w, alg_f, global_alloc()) } - - /// Optimize the `PathMap` by factoring shared subtries using a temporary [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) - pub fn merkleize(&mut self) -> MerkleizeResult - where V: core::hash::Hash - { - let Some(root) = self.root() else { - return MerkleizeResult::default(); - }; - let mut result = MerkleizeResult::default(); - let mut memo = gxhash::HashMap::default(); - let (hash, new_root) = merkleize_impl(&mut result, &mut memo, root, self.root_val()); - result.hash = hash; - if let Some(new_root) = new_root { - *self.root.get_mut() = Some(new_root); - } - result - } } impl PathMap { @@ -324,13 +303,19 @@ impl PathMap { zipper.is_val() } - /// Returns `true` if a path is contained within the map, or `false` otherwise - pub fn contains_path>(&self, k: K) -> bool { - let k = k.as_ref(); - let zipper = self.read_zipper_at_borrowed_path(k); + /// Returns `true` if `path` is contained within the map, or `false` otherwise + pub fn path_exists_at>(&self, path: K) -> bool { + let path = path.as_ref(); + let zipper = self.read_zipper_at_borrowed_path(path); zipper.path_exists() } + /// Deprecated alias for [`PathMap::path_exists_at`] + #[deprecated] + pub fn contains_path>(&self, k: K) -> bool { + self.path_exists_at(k) + } + /// Inserts `v` into the map at `path`. Panics if `path` has a zero length /// /// Returns `Some(replaced_val)` if an existing value was replaced, otherwise returns `None` if @@ -445,6 +430,21 @@ impl PathMap { self.get_val_or_set_mut_with_at(path, || default) } + /// Removes all downstream branches below `path`. Does not affect a value at `path` + /// + /// Returns `true` if at least one branch was removed. + /// + /// If `prune` is `true`, the path will be pruned, otherwise it will be left dangling. + pub fn remove_branches_at>(&mut self, path: K, prune: bool) -> bool { + let path = path.as_ref(); + //NOTE: we're descending the zipper rather than creating it at the path so it will be allowed to + // prune the branches. A WriteZipper can't move above its root, so it couldn't prune otherwise + //GOAT, come back and redo this withoug a temporary WZ + let mut zipper = self.write_zipper(); + zipper.descend_to(path); + zipper.remove_branches(prune) + } + /// Returns `true` if the map is empty, otherwise returns `false` pub fn is_empty(&self) -> bool { (match self.root() { @@ -477,14 +477,17 @@ impl PathMap { /// /// WARNING: This is not a cheap method. It may have an order-N cost pub fn val_count(&self) -> usize { + let root_val = unsafe{ &*self.root_val.get() }.is_some() as usize; match self.root() { - Some(root) => val_count_below_root(root.as_tagged()), - None => 0 + Some(root) => val_count_below_root(root.as_tagged()) + root_val, + None => root_val } } - const INVIS_HASH: u128 = 0b00001110010011001111100111000110011110101111001101110110011100001011010011010011001000100111101000001100011111110100001000000111; - /// Hash the logical `PathMap` and all its values with the provided hash function (which can return INVIS_HASH to ignore values). + pub const INVIS_HASH: u128 = 0b00001110010011001111100111000110011110101111001101110110011100001011010011010011001000100111101000001100011111110100001000000111; + + /// Hash the logical `PathMap` and all its values with the provided hash function (which can return [PathMap::INVIS_HASH] to ignore values). + //GOAT, do we need to do anything to make sure Merkleization and this hash method are in harmony? pub fn hash u128>(&self, vhash: VHash) -> u128 { unsafe { self.read_zipper().into_cata_cached(|bm, hs, mv, _| { @@ -557,6 +560,23 @@ impl PathMap { Self::new_with_root_in(subtracted_root_node, subtracted_root_val, self.alloc.clone()) } + + /// Optimize the `PathMap` by factoring shared subtries using a temporary [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) + pub fn merkleize(&mut self) -> MerkleizeResult + where V: core::hash::Hash + { + let Some(root) = self.root() else { + return MerkleizeResult::default(); + }; + let mut result = MerkleizeResult::default(); + let mut memo = gxhash::HashMap::default(); + let (hash, new_root) = merkleize_impl(&mut result, &mut memo, root, self.root_val()); + result.hash = hash; + if let Some(new_root) = new_root { + *self.root.get_mut() = Some(new_root); + } + result + } } @@ -589,6 +609,26 @@ impl> FromIterator<(K, V)> for Pa } } +impl<'a, V: Clone + Send + Sync + Unpin, K: AsRef<[u8]>> FromIterator<&'a (K, V)> for PathMap { + fn from_iter>(iter: I) -> Self { + let mut map = Self::new(); + for (key, val) in iter { + map.set_val_at(key, val.clone()); + } + map + } +} + +impl<'a> FromIterator<&'a [u8]> for PathMap<()> { + fn from_iter>(iter: I) -> Self { + let mut map = Self::new(); + for key in iter { + map.set_val_at(key, ()); + } + map + } +} + impl> From<(K, V)> for PathMap { fn from(pair: (K, V)) -> Self { let mut map = Self::new(); @@ -858,10 +898,10 @@ mod tests { let rs = ["arrow", "bow", "cannon", "roman", "romane", "romanus", "romulus", "rubens", "ruber", "rubicon", "rubicundus", "rom'i"]; rs.iter().enumerate().for_each(|(i, r)| { btm.set_val_at(r.as_bytes(), i); }); - assert_eq!(btm.contains_path(b"can"), true); - assert_eq!(btm.contains_path(b"cannon"), true); - assert_eq!(btm.contains_path(b"cannonade"), false); - assert_eq!(btm.contains_path(b""), true); + assert_eq!(btm.path_exists_at(b"can"), true); + assert_eq!(btm.path_exists_at(b"cannon"), true); + assert_eq!(btm.path_exists_at(b"cannonade"), false); + assert_eq!(btm.path_exists_at(b""), true); } #[test] @@ -1334,6 +1374,62 @@ mod tests { assert_eq!(map.remove_val_at("how do you do", true), Some("how do you do".to_string())); assert_eq!(map.remove_val_at("hello", true), Some("hello".to_string())); } + + #[test] + fn val_count_root_value() { + let mut map = PathMap::new(); + map.insert(b"", ()); + map.insert(b"a", ()); + assert_eq!(map.val_count(), 2); + } + + /// Validates that alg ops on whole maps do the right thing WRT the existence of the root value + #[test] + fn map_root_val_test1() { + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + + //Validate subtract of identity clears the root val + let ident_map = map.clone(); + let result_map = map.subtract(&ident_map); + assert_eq!(result_map.iter().count(), 0); + + //Validate subtract of empty keeps the root val + let empty_map = PathMap::new(); + let result_map = map.subtract(&empty_map); + assert_eq!(result_map.iter().count(), 3); + + //Validate subtract of just_root clears it + let mut just_root_map = PathMap::new(); + just_root_map.insert(b"", ()); + let result_map = map.subtract(&just_root_map); + assert_eq!(result_map.iter().count(), 2); + + //Validate subtract of all_but_root keeps it + let mut all_but_root_map = PathMap::new(); + all_but_root_map.insert(b"b", ()); + all_but_root_map.insert(b"a", ()); + let result_map = map.subtract(&all_but_root_map); + assert_eq!(result_map.iter().count(), 1); + + //Validate meet with empty clears the root val + let result_map = map.meet(&empty_map); + assert_eq!(result_map.iter().count(), 0); + + //Validate meet with identity leaves the root val + let result_map = map.meet(&ident_map); + assert_eq!(result_map.iter().count(), 3); + + //Validate meet with just_root keeps it + let result_map = map.meet(&just_root_map); + assert_eq!(result_map.iter().count(), 1); + + //Validate meet with all_but_root removes it + let result_map = map.meet(&all_but_root_map); + assert_eq!(result_map.iter().count(), 2); + } } //GOAT, Consider refactor of zipper traits. `WriteZipper` -> `PathWriter`. Zipper is split into the zipper diff --git a/src/trie_ref.rs b/src/trie_ref.rs index 77ccc297..33fcbe3e 100644 --- a/src/trie_ref.rs +++ b/src/trie_ref.rs @@ -330,6 +330,9 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyS } impl ZipperConcrete for TrieRefBorrowed<'_, V, A> { + fn shared_node_id(&self) -> Option { + read_zipper_core::read_zipper_shared_node_id(self) + } fn is_shared(&self) -> bool { false //We don't have enough info in the TrieRef to get back to the parent node. This will change in the future when we move values at zero-length paths into the nodes themselves } @@ -344,10 +347,6 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperReadOnlyPriv<' } } -impl ZipperConcretePriv for TrieRefBorrowed<'_, V, A> { - fn shared_node_id(&self) -> Option { read_zipper_core::read_zipper_shared_node_id(self) } -} - /// An owned read-only reference to a location in a trie. See [TrieRef] /// #[derive(Clone)] @@ -656,6 +655,9 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyS } impl ZipperConcrete for TrieRefOwned { + fn shared_node_id(&self) -> Option { + read_zipper_core::read_zipper_shared_node_id(self) + } fn is_shared(&self) -> bool { false //We don't have enough info in the TrieRef to get back to the parent node. This will change in the future when we move values at zero-length paths into the nodes themselves } @@ -671,10 +673,6 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperReadOnlyPriv<' } } -impl ZipperConcretePriv for TrieRefOwned { - fn shared_node_id(&self) -> Option { read_zipper_core::read_zipper_shared_node_id(self) } -} - /// An abstract wrapper type over [TrieRefBorrowed] and [TrieRefOwned], with capabilities that are the intersection /// of the two #[derive(Clone)] @@ -795,6 +793,12 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyS } impl ZipperConcrete for TrieRef<'_, V, A> { + fn shared_node_id(&self) -> Option { + match self { + TrieRef::Borrowed(trie_ref) => trie_ref.shared_node_id(), + TrieRef::Owned(trie_ref) => trie_ref.shared_node_id(), + } + } fn is_shared(&self) -> bool { match self { TrieRef::Borrowed(trie_ref) => trie_ref.is_shared(), @@ -815,15 +819,6 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperReadOnlyPriv<' } } -impl ZipperConcretePriv for TrieRef<'_, V, A> { - fn shared_node_id(&self) -> Option { - match self { - TrieRef::Borrowed(trie_ref) => trie_ref.shared_node_id(), - TrieRef::Owned(trie_ref) => trie_ref.shared_node_id(), - } - } -} - #[cfg(test)] mod tests { use crate::{trie_node::AbstractNodeRef, utils::ByteMask, zipper::{zipper_priv::ZipperPriv, *}, PathMap}; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 27aa3adf..514c1976 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -868,3 +868,11 @@ fn find_prefix_overlap_test() { assert_eq!(overlap, test.2); } } + +#[inline(always)] +pub fn starts_with(x: &[u8], y: &[u8]) -> bool { + if y.len() == 0 { return true } + if x.len() == 0 { return false } + if y.len() > x.len() { return false } + find_prefix_overlap(x, y) == y.len() +} diff --git a/src/write_zipper.rs b/src/write_zipper.rs index 60128c5a..5441c0e0 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -966,11 +966,12 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } fn val_count(&self) -> usize { + let root_val = self.is_val() as usize; let focus = self.get_focus(); if focus.is_none() { - 0 + root_val } else { - val_count_below_root(focus.as_tagged()) + (self.is_val() as usize) + val_count_below_root(focus.as_tagged()) + root_val } } fn descend_to>(&mut self, k: K) -> bool { @@ -1494,7 +1495,6 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC /// See [ZipperWriting::join_map_into] pub fn join_map_into(&mut self, map: PathMap) -> AlgebraicStatus where V: Lattice { let (src_root_node, src_root_val) = map.into_root(); - #[cfg(not(feature = "graft_root_vals"))] let _ = src_root_val; #[cfg(feature = "graft_root_vals")] @@ -1614,41 +1614,70 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC } /// See [ZipperWriting::meet_into] pub fn meet_into>(&mut self, read_zipper: &Z, prune: bool) -> AlgebraicStatus where V: Lattice { - match self.get_focus().try_as_tagged() { + let src_root_val = read_zipper.val(); + #[cfg(not(feature = "graft_root_vals"))] + let _ = src_root_val; + #[cfg(feature = "graft_root_vals")] + let (val_status, val_was_none) = match (self.get_val_mut(), src_root_val) { + (Some(self_val), Some(src_val)) => { + let new_status = match self_val.pmeet(src_val) { + AlgebraicResult::Element(new_val) => {self.set_val(new_val); AlgebraicStatus::Element }, + AlgebraicResult::None => {self.remove_val(prune); AlgebraicStatus::None }, + AlgebraicResult::Identity(_) => { AlgebraicStatus::Identity } + }; + (new_status, false) + }, + (None, Some(_)) => { (AlgebraicStatus::None, true) }, + (Some(_), None) => { self.remove_val(prune); (AlgebraicStatus::None, false) }, + (None, None) => { (AlgebraicStatus::None, true) }, + }; + + let node_was_none; + let node_status = match self.get_focus().try_as_tagged() { Some(self_node) => { + node_was_none = false; let src = read_zipper.get_focus(); if src.is_none() { self.graft_internal(None); if prune { self.prune_path(); } - return AlgebraicStatus::None - } - match self_node.pmeet_dyn(src.as_tagged()) { - AlgebraicResult::Element(intersection) => { - self.graft_internal(Some(intersection)); - AlgebraicStatus::Element - }, - AlgebraicResult::None => { - self.graft_internal(None); - if prune { - self.prune_path(); - } - AlgebraicStatus::None - }, - AlgebraicResult::Identity(mask) => { - if mask & SELF_IDENT > 0 { - AlgebraicStatus::Identity - } else { - debug_assert_eq!(mask, COUNTER_IDENT); //It's gotta be self or other - self.graft_internal(Some(src.into_option().unwrap())); + AlgebraicStatus::None + } else { + match self_node.pmeet_dyn(src.as_tagged()) { + AlgebraicResult::Element(intersection) => { + self.graft_internal(Some(intersection)); AlgebraicStatus::Element - } - }, + }, + AlgebraicResult::None => { + self.graft_internal(None); + if prune { + self.prune_path(); + } + AlgebraicStatus::None + }, + AlgebraicResult::Identity(mask) => { + if mask & SELF_IDENT > 0 { + AlgebraicStatus::Identity + } else { + debug_assert_eq!(mask, COUNTER_IDENT); //It's gotta be self or other + self.graft_internal(Some(src.into_option().unwrap())); + AlgebraicStatus::Element + } + }, + } } }, - None => AlgebraicStatus::None - } + None => { + node_was_none = true; + AlgebraicStatus::None + } + }; + + #[cfg(not(feature = "graft_root_vals"))] + return node_status; + #[cfg(feature = "graft_root_vals")] + return node_status.merge(val_status, node_was_none, val_was_none) } /// See [WriteZipper::meet_2] pub fn meet_2, ZB: ZipperSubtries>(&mut self, rz_a: &ZA, rz_b: &ZB) -> AlgebraicStatus where V: Lattice { @@ -1691,37 +1720,68 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC } /// See [ZipperWriting::subtract_into] pub fn subtract_into>(&mut self, read_zipper: &Z, prune: bool) -> AlgebraicStatus where V: DistributiveLattice { + let src_root_val = read_zipper.val(); + #[cfg(not(feature = "graft_root_vals"))] + let _ = src_root_val; + #[cfg(feature = "graft_root_vals")] + let (val_status, val_was_none) = match (self.get_val_mut(), src_root_val) { + (Some(self_val), Some(src_val)) => { + let new_status = match self_val.psubtract(src_val) { + AlgebraicResult::Element(new_val) => {self.set_val(new_val); AlgebraicStatus::Element }, + AlgebraicResult::None => {self.remove_val(prune); AlgebraicStatus::None }, + AlgebraicResult::Identity(_) => { AlgebraicStatus::Identity } + }; + (new_status, false) + }, + (None, Some(_)) => { (AlgebraicStatus::None, true) }, + (Some(_), None) => { (AlgebraicStatus::Identity, false) }, + (None, None) => { (AlgebraicStatus::None, true) }, + }; + + let node_was_none; let src = read_zipper.get_focus(); let self_focus = self.get_focus(); - if src.is_none() { + let node_status = if src.is_none() { if self_focus.is_none() { - return AlgebraicStatus::None + node_was_none = true; + AlgebraicStatus::None } else { - return AlgebraicStatus::Identity + node_was_none = false; + AlgebraicStatus::Identity } - } - match self_focus.try_as_tagged() { - Some(self_node) => { - match self_node.psubtract_dyn(src.as_tagged()) { - AlgebraicResult::Element(diff) => { - self.graft_internal(Some(diff)); - AlgebraicStatus::Element - }, - AlgebraicResult::None => { - self.graft_internal(None); - if prune { - self.prune_path(); - } - AlgebraicStatus::None - }, - AlgebraicResult::Identity(mask) => { - debug_assert_eq!(mask, SELF_IDENT); //subtract is non-commutative - AlgebraicStatus::Identity - }, + } else { + match self_focus.try_as_tagged() { + Some(self_node) => { + node_was_none = false; + match self_node.psubtract_dyn(src.as_tagged()) { + AlgebraicResult::Element(diff) => { + self.graft_internal(Some(diff)); + AlgebraicStatus::Element + }, + AlgebraicResult::None => { + self.graft_internal(None); + if prune { + self.prune_path(); + } + AlgebraicStatus::None + }, + AlgebraicResult::Identity(mask) => { + debug_assert_eq!(mask, SELF_IDENT); //subtract is non-commutative + AlgebraicStatus::Identity + }, + } + }, + None => { + node_was_none = true; + AlgebraicStatus::None } - }, - None => AlgebraicStatus::None - } + } + }; + + #[cfg(not(feature = "graft_root_vals"))] + return node_status; + #[cfg(feature = "graft_root_vals")] + return node_status.merge(val_status, node_was_none, val_was_none) } /// See [WriteZipper::restrict] pub fn restrict>(&mut self, read_zipper: &Z) -> AlgebraicStatus { @@ -2678,7 +2738,7 @@ mod tests { } #[test] - fn write_zipper_join_test() { + fn write_zipper_join_into_test() { let a_keys = ["arrow", "bow", "cannon", "roman", "romane", "romanus", "romulus", "rubens", "ruber", "rubicon", "rubicundus", "rom'i"]; let mut a: PathMap = a_keys.iter().enumerate().map(|(i, k)| (k, i as u64)).collect(); assert_eq!(a.val_count(), 12); @@ -2785,7 +2845,7 @@ mod tests { } #[test] - fn write_zipper_meet_test1() { + fn write_zipper_meet_into_test1() { let a_keys = ["12345", "1aaaa", "1bbbb", "1cccc", "1dddd"]; let b_keys = ["12345", "1zzzz"]; let a: PathMap<()> = a_keys.iter().map(|k| (k, ())).collect(); @@ -2828,7 +2888,7 @@ mod tests { /// designed to shake out bugs in the abstract-meet function, such as the bug where the `ListNode` `self` /// was a perfect subset of the `DenseNode` `other`, but the `COUNTER_IDENT` flag was set in error. #[test] - fn write_zipper_meet_test2() { + fn write_zipper_meet_into_test2() { let a_keys = [ vec![193, 11], vec![194, 1, 0], @@ -2889,6 +2949,92 @@ mod tests { assert!(wz.val().is_some()); } + /// Tests whether the [WriteZipper::meet_into] operation will do the right thing with the root value + #[test] + fn write_zipper_meet_into_test3() { + //Validate meet with empty clears the root val + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + let empty_map = PathMap::new(); + assert_eq!(map.write_zipper().meet_into(&empty_map.read_zipper(), true), AlgebraicStatus::None); + assert_eq!(map.iter().count(), 0); + + //Validate meet with identity leaves the root val alone + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + let ident_map = map.clone(); + assert_eq!(map.write_zipper().meet_into(&ident_map.read_zipper(), true), AlgebraicStatus::Identity); + assert_eq!(map.iter().count(), 3); + + //Validate meet with just_root keeps the root val and removes the rest + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + let mut just_root_map = PathMap::new(); + just_root_map.insert(b"", ()); + assert_eq!(map.write_zipper().meet_into(&just_root_map.read_zipper(), true), AlgebraicStatus::Element); + assert_eq!(map.iter().count(), 1); + + //Validate meet with all_but_root removes the root + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + let mut all_but_root_map = PathMap::new(); + all_but_root_map.insert(b"b", ()); + all_but_root_map.insert(b"a", ()); + assert_eq!(map.write_zipper().meet_into(&all_but_root_map.read_zipper(), true), AlgebraicStatus::Element); + assert_eq!(map.iter().count(), 2); + } + + /// Tests whether the [WriteZipper::subtract_into] operation will do the right thing with the root value + #[test] + fn write_zipper_subtract_into_test1() { + //Validate subtract of identity clears the root val + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + let ident_map = map.clone(); + assert_eq!(map.write_zipper().subtract_into(&ident_map.read_zipper(), true), AlgebraicStatus::None); + assert_eq!(map.iter().count(), 0); + + //Validate subtract of empty keeps the root val + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + let empty_map = PathMap::new(); + assert_eq!(map.write_zipper().subtract_into(&empty_map.read_zipper(), true), AlgebraicStatus::Identity); + assert_eq!(map.iter().count(), 3); + + //Validate subtract of just_root clears the root val + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + let mut just_root_map = PathMap::new(); + just_root_map.insert(b"", ()); + assert_eq!(map.write_zipper().subtract_into(&just_root_map.read_zipper(), true), AlgebraicStatus::Element); + assert_eq!(map.iter().count(), 2); + + //Validate subtract of all_but_root keeps it + let mut map = PathMap::new(); + map.insert(b"b", ()); + map.insert(b"a", ()); + map.insert(b"", ()); + let mut all_but_root_map = PathMap::new(); + all_but_root_map.insert(b"b", ()); + all_but_root_map.insert(b"a", ()); + assert_eq!(map.write_zipper().subtract_into(&all_but_root_map.read_zipper(), true), AlgebraicStatus::Element); + assert_eq!(map.iter().count(), 1); + } + #[test] fn write_zipper_movement_test() { let keys = ["romane", "romanus", "romulus", "rubens", "ruber", "rubicon", "rubicundus", "rom'i"]; @@ -2988,8 +3134,8 @@ mod tests { assert_eq!(wz.path(), b"abcdefghijklmnopq"); drop(wz); - assert!(!map.contains_path(b"abcdefghijklmnopq")); - assert!(!map.contains_path(b"abc")); + assert!(!map.path_exists_at(b"abcdefghijklmnopq")); + assert!(!map.path_exists_at(b"abc")); } #[test] @@ -4108,6 +4254,20 @@ mod tests { assert!(matches!(wz.get_focus(), AbstractNodeRef::BorrowedRc(_))); //Make sure we get the ODRc } + /// Tests the zipper `val_count` method, including with root values + const ZIPPER_VAL_COUNT_TEST1_KEYS: &[&[u8]] = &[b"", b"arrow"]; + #[test] + fn write_zipper_val_count_test1() { + let mut map: PathMap<()> = ZIPPER_VAL_COUNT_TEST1_KEYS.into_iter().cloned().collect(); + let mut zipper = map.write_zipper(); + + assert_eq!(zipper.path(), b""); + assert_eq!(zipper.val_count(), 2); + assert_eq!(zipper.descend_until(None), true); + assert_eq!(zipper.path(), b"arrow"); + assert_eq!(zipper.val_count(), 1); + } + crate::zipper::zipper_moving_tests::zipper_moving_tests!(write_zipper, |keys: &[&[u8]]| { let mut btm = PathMap::new(); diff --git a/src/zipper.rs b/src/zipper.rs index c9ec8e12..56f49822 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -20,7 +20,8 @@ pub use crate::product_zipper::{ProductZipper, ProductZipperG}; pub use crate::overlay_zipper::{OverlayZipper}; pub use crate::prefix_zipper::{PrefixZipper}; pub use crate::track_path::{TrackPath}; - +pub use crate::empty_zipper::{EmptyZipper}; +pub use crate::poly_zipper::PolyZipper; use crate::zipper_tracking::*; /// The most fundamantal interface for a zipper, compatible with all zipper types @@ -113,6 +114,7 @@ pub trait ZipperSubtries: Zi pub trait ZipperMoving: Zipper { /// Returns `true` if the zipper's focus is at its root, and it cannot ascend further, otherwise returns `false` fn at_root(&self) -> bool; + //GOAT, This default impl isn't compatible with the blind ZipperMoving anymore. But there is value in expressing this equivalence... Not sure where to do it. Maybe the docs for `path()`? // -> bool { // self.path().len() == 0 // } @@ -225,17 +227,29 @@ pub trait ZipperMoving: Zipper { self.descend_indexed_byte(0) } + /// Descends the zipper's focus one step into the last child branch + /// + /// NOTE: This method should have identical behavior to passing `child_count() - 1` to [descend_indexed_byte](ZipperMoving::descend_indexed_byte), + /// although with less overhead + fn descend_last_byte(&mut self) -> Option { + let cc = self.child_count(); + if cc == 0 { None } + else { self.descend_indexed_byte( cc- 1) } + } + /// Descends the zipper's focus until a branch or a value is encountered. Returns `true` if the focus /// moved otherwise returns `false` /// /// If there is a value at the focus, the zipper will descend to the next value or branch, however the /// zipper will not descend further if this method is called with the focus already on a branch. - fn descend_until(&mut self, mut dst: Option<&mut Vec>) -> bool { + /// + /// If the `dst_path` argument is non-None, the descended path bytes will be pushed onto provided `Vec`. + fn descend_until(&mut self, mut dst_path: Option<&mut Vec>) -> bool { let mut descended = false; while self.child_count() == 1 { descended = true; - if let (Some(dst), Some(byte)) = (&mut dst, self.descend_first_byte()) { - dst.push(byte); + if let (Some(dst_path), Some(byte)) = (&mut dst_path, self.descend_first_byte()) { + dst_path.push(byte); } if self.is_val() { break; @@ -276,6 +290,7 @@ pub trait ZipperMoving: Zipper { /// This method is equivalent to calling [ZipperMoving::ascend] with `1`, followed by [ZipperMoving::descend_indexed_byte] /// where the index passed is 1 more than the index of the current focus position. fn to_next_sibling_byte(&mut self) -> Option; + //GOAT, this default impl is incompatible with the blind zippers. :-( That argues that perhaps it belongs in another trait, e.g. ZipperIteration // { // let cur_byte = match self.path().last() { // Some(byte) => *byte, @@ -306,6 +321,7 @@ pub trait ZipperMoving: Zipper { /// This method is equivalent to calling [Self::ascend] with `1`, followed by [Self::descend_indexed_byte] /// where the index passed is 1 less than the index of the current focus position. fn to_prev_sibling_byte(&mut self) -> Option; + //GOAT, this default impl is incompatible with the blind zippers. :-( That argues that perhaps it belongs in another trait, e.g. ZipperIteration // { // let cur_byte = match self.path().last() { // Some(byte) => *byte, @@ -477,7 +493,24 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { } } - /// Descends the zipper's focus `k`` bytes, following the first child at each branch, and continuing + /// Descends the zipper to the end of the last path (last by sort order) reachable by descent + /// from the current focus. + /// + /// 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 { + let mut any = false; + while self.descend_last_byte().is_some() { + any = true; + self.descend_until(None); + } + any + } + + /// 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 /// /// Returns `true` if the zipper has sucessfully descended `k` steps, or `false` otherwise. If this @@ -554,9 +587,21 @@ pub trait ZipperAbsolutePath: ZipperPath { /// Implemented on zippers that traverse in-memory trie structures, as opposed to virtual /// spaces or abstract projections. /// -/// Sometimes `ZipperConcrete` is implemented on projection zippers, such as [ProductZipper], +/// In some cases `ZipperConcrete` can be implemented on projection zippers, such as [ProductZipper], /// because it is composed of concrete tries -pub trait ZipperConcrete: zipper_priv::ZipperConcretePriv { +pub trait ZipperConcrete { + /// Get an identifier unique to the node at the zipper's focus, if the zipper's focus is at the + /// root of a node in memory. When zipper's focus is inside of a node, returns `None`. + /// + /// NOTE: The returned value is not a logical hash of the contents, but is based on + /// the node's memory address. Therefore it is not stable across runs and can't be + /// used to infer logical or structural equality. Furthermore, it is subject to + /// change when the content of the node is modified. + /// + /// However when returned identifiers are equal it means the zipper has arrived at the same node + /// in memory, even if it got there via different parent paths through the trie. + fn shared_node_id(&self) -> Option; + /// Returns `true` if the zipper's focus is at a location that may be accessed via two or /// more distinct paths /// @@ -688,23 +733,8 @@ pub(crate) mod zipper_priv { /// pollute the API, given we already have [ZipperReadOnly] and [ZipperMoving]) fn take_core(&mut self) -> Option>; } - - pub trait ZipperConcretePriv { - /// Get an identifier unique to the node at the zipper's focus, if the zipper's focus is at the - /// root of a node in memory. When zipper's focus is inside of a node, returns `None`. - /// - /// NOTE: The returned value is not a logical hash of the contents, but is based on - /// the node's memory address. Therefore it is not stable across runs and can't be - /// used to infer logical or structural equality. Furthermore, it is subject to - /// change when the content of the node is modified. - /// - /// However when returned identifiers are equal it means the zipper has arrived at the same node - /// in memory, even if it got there via different parent paths through the trie. - fn shared_node_id(&self) -> Option; - } } use zipper_priv::*; -pub use zipper_priv::ZipperConcretePriv; impl Zipper for &mut Z where Z: Zipper { fn path_exists(&self) -> bool { (**self).path_exists() } @@ -784,7 +814,10 @@ impl<'a, V: Clone + Send + Sync + 'a, Z, A: Allocator + 'a> ZipperReadOnlySubtri fn trie_ref_at_path>(&self, path: K) -> Self::TrieRefT { (**self).trie_ref_at_path(path) } } -impl ZipperConcrete for &mut Z where Z: ZipperConcrete, Self: ZipperConcretePriv { +impl ZipperConcrete for &mut Z where Z: ZipperConcrete { + #[inline] + fn shared_node_id(&self) -> Option { (**self).shared_node_id() } + #[inline] fn is_shared(&self) -> bool { (**self).is_shared() } } @@ -806,11 +839,6 @@ impl<'a, V: Clone + Send + Sync, Z, A: Allocator> ZipperReadOnlyPriv<'a, V, A> f fn take_core(&mut self) -> Option> { (**self).take_core() } } -impl ZipperConcretePriv for &mut Z where Z: ZipperConcretePriv { - #[inline] - fn shared_node_id(&self) -> Option { (**self).shared_node_id() } -} - // ***---***---***---***---***---***---***---***---***---***---***---***---***---***---***---***---***---***--- // ReadZipperTracked // ***---***---***---***---***---***---***---***---***---***---***---***---***---***---***---***---***---***--- @@ -891,6 +919,9 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyS } impl ZipperConcrete for ReadZipperTracked<'_, '_, V, A> { + #[inline] + fn shared_node_id(&self) -> Option { self.z.shared_node_id() } + #[inline] fn is_shared(&self) -> bool { self.z.is_shared() } } @@ -903,11 +934,6 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyP } } -impl ZipperConcretePriv for ReadZipperTracked<'_, '_, V, A> { - #[inline] - fn shared_node_id(&self) -> Option { self.z.shared_node_id() } -} - impl zipper_priv::ZipperPriv for ReadZipperTracked<'_, '_, V, A> { type V = V; type A = A; @@ -1028,7 +1054,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyValues<'trie, V> for ReadZipperUntracked<'trie, '_, V, A> { - fn get_val(&self) -> Option<&'trie V> { self.z.get_val() } + fn get_val(&self) -> Option<&'trie V> { unsafe{ self.z.get_val() } } } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyConditionalValues<'trie, V> for ReadZipperUntracked<'trie, '_, V, A> { @@ -1046,6 +1072,9 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyS } impl ZipperConcrete for ReadZipperUntracked<'_, '_, V, A> { + #[inline] + fn shared_node_id(&self) -> Option { self.z.shared_node_id() } + #[inline] fn is_shared(&self) -> bool { self.z.is_shared() } } @@ -1058,11 +1087,6 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyP } } -impl ZipperConcretePriv for ReadZipperUntracked<'_, '_, V, A> { - #[inline] - fn shared_node_id(&self) -> Option { self.z.shared_node_id() } -} - impl zipper_priv::ZipperPriv for ReadZipperUntracked<'_, '_, V, A> { type V = V; type A = A; @@ -1083,7 +1107,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper } 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> { self.z.to_next_get_val() } + fn to_next_get_val(&mut self) -> Option<&'trie V> { unsafe{ self.z.to_next_get_val() } } } impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyConditionalIteration<'trie, V> for ReadZipperUntracked<'trie, '_, V, A> { } @@ -1188,7 +1212,7 @@ impl Zipper for ReadZipperOwned ZipperValues for ReadZipperOwned { - fn val(&self) -> Option<&V> { self.z.get_val() } + fn val(&self) -> Option<&V> { unsafe{ self.z.get_val() } } } impl ZipperForking for ReadZipperOwned { @@ -1243,6 +1267,9 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator> ZipperReadOnlySubtries<'a } impl ZipperConcrete for ReadZipperOwned { + #[inline] + fn shared_node_id(&self) -> Option { self.z.shared_node_id() } + #[inline] fn is_shared(&self) -> bool { self.z.is_shared() } } @@ -1255,11 +1282,6 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator> ZipperReadOnlyPriv<'a, V, } } -impl ZipperConcretePriv for ReadZipperOwned { - #[inline] - fn shared_node_id(&self) -> Option { self.z.shared_node_id() } -} - impl zipper_priv::ZipperPriv for ReadZipperOwned { type V = V; type A = A; @@ -1483,13 +1505,13 @@ pub(crate) mod read_zipper_core { } impl ZipperValues for ReadZipperCore<'_, '_, V, A> { - fn val(&self) -> Option<&V> { self.get_val() } + fn val(&self) -> Option<&V> { unsafe{ self.get_val() } } } impl ZipperForking for ReadZipperCore<'_, '_, V, A> { type ReadZipperT<'a> = ReadZipperCore<'a, 'a, V, A> where Self: 'a; fn fork_read_zipper<'a>(&'a self) -> Self::ReadZipperT<'a> { - let new_root_val = self.get_val(); + let new_root_val = self.val(); let new_root_path = self.origin_path(); let new_root_key_start = new_root_path.len() - self.node_key().len(); Self::ReadZipperT::new_with_node_and_path_internal_in(OwnedOrBorrowed::Borrowed(self.focus_parent()), new_root_path, new_root_key_start, new_root_val, self.alloc.clone()) @@ -1501,7 +1523,7 @@ pub(crate) mod read_zipper_core { #[cfg(not(feature = "graft_root_vals"))] let root_val = None; #[cfg(feature = "graft_root_vals")] - let root_val = self.get_val().cloned(); + let root_val = self.val().cloned(); let root_node = self.get_focus().into_option(); if root_node.is_some() || root_val.is_some() { @@ -1530,14 +1552,15 @@ pub(crate) mod read_zipper_core { } fn val_count(&self) -> usize { + let root_val = self.is_val() as usize; if self.node_key().len() == 0 { - val_count_below_root(*self.focus_node) + (self.is_val() as usize) + val_count_below_root(*self.focus_node) + root_val } else { let focus = self.get_focus(); if focus.is_none() { - 0 + root_val } else { - val_count_below_root(focus.as_tagged()) + (self.is_val() as usize) + val_count_below_root(focus.as_tagged()) + root_val } } } @@ -1980,32 +2003,13 @@ pub(crate) mod read_zipper_core { } } - impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperReadOnlyValues<'trie, V> for ReadZipperCore<'trie, '_, V, A> { - fn get_val(&self) -> Option<&'trie V> { - let key = self.node_key(); - if key.len() > 0 { - self.focus_node.node_get_val(key) - } else { - if let Some((parent, _iter_tok, _prefix_offset)) = self.ancestors.last() { - parent.node_get_val(self.parent_key()) - } else { - //NOTE: It's true that we shouldn't have ZipperReadOnlyValues implemented on a type that comes from a ZipperHead, but - // we currently share the same implementation between `val()` and `get_val()` because the only difference is the return - // lifetime, and the current ZipperHead implementation is actually ok with referencing the value in the root of the ZipperHead. - // debug_assert!(self.root_node.is_borrowed()); - self.root_val - } - } - } - } - impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperReadOnlyPriv<'a, V, A> for ReadZipperCore<'a, '_, V, A> { - fn borrow_raw_parts<'z>(&'z self) -> (TaggedNodeRef<'a, V, A>, &'z [u8], Option<&'a V>) { + fn borrow_raw_parts(&self) -> (TaggedNodeRef<'_, V, A>, &[u8], Option<&V>) { let node_key = self.node_key(); if node_key.len() > 0 { (*self.focus_node, node_key, None) } else { - (*self.focus_node, &[], self.get_val()) + (*self.focus_node, &[], self.val()) } } fn take_core(&mut self) -> Option> { @@ -2014,6 +2018,10 @@ pub(crate) mod read_zipper_core { } impl ZipperConcrete for ReadZipperCore<'_, '_, V, A> { + #[inline] + fn shared_node_id(&self) -> Option { + read_zipper_shared_node_id(self) + } #[inline] fn is_shared(&self) -> bool { let key = self.node_key(); @@ -2030,13 +2038,6 @@ pub(crate) mod read_zipper_core { } } - impl ZipperConcretePriv for ReadZipperCore<'_, '_, V, A> { - #[inline] - fn shared_node_id(&self) -> Option { - read_zipper_shared_node_id(self) - } - } - #[inline] pub(crate) fn read_zipper_shared_node_id<'a, V: Clone + Send + Sync + 'a, A: Allocator + 'a, Z: Zipper + ZipperReadOnlyPriv<'a, V, A> + ZipperConcrete>(zipper: &Z) -> Option { let (node, key, value) = zipper.borrow_raw_parts(); @@ -2061,7 +2062,7 @@ pub(crate) mod read_zipper_core { impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperIteration for ReadZipperCore<'trie, '_, V, A> { fn to_next_val(&mut self) -> bool { - self.to_next_get_val().is_some() + unsafe{ self.to_next_get_val() }.is_some() } fn descend_first_k_path(&mut self, k: usize) -> bool { self.prepare_buffers(); @@ -2085,72 +2086,6 @@ pub(crate) mod read_zipper_core { } } - impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> ZipperReadOnlyIteration<'a, V> for ReadZipperCore<'a, '_, V, A> { - fn to_next_get_val(&mut self) -> Option<&'a V> { - self.prepare_buffers(); - loop { - if self.focus_iter_token == NODE_ITER_INVALID { - let cur_tok = self.focus_node.iter_token_for_path(self.node_key()); - self.focus_iter_token = cur_tok; - } - - let (new_tok, key_bytes, child_node, value) = if self.focus_iter_token != NODE_ITER_FINISHED { - self.focus_node.next_items(self.focus_iter_token) - } else { - (NODE_ITER_FINISHED, &[][..] as &[u8], None, None) - }; - - if new_tok != NODE_ITER_FINISHED { - self.focus_iter_token = new_tok; - - let key_start = self.node_key_start(); - - //Make sure we don't move to a branch that forks above our zipper root - let origin_path_len = self.origin_path.len(); - if key_start < origin_path_len { - debug_assert_eq!(self.ancestors.len(), 0); - - 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 { - self.prefix_buf.truncate(origin_path_len); - return None - } - } - - self.prefix_buf.truncate(key_start); - self.prefix_buf.extend(key_bytes); - - match child_node { - None => {}, - Some(rec) => { - self.ancestors.push((*self.focus_node.clone(), new_tok, self.prefix_buf.len())); - *self.focus_node = rec.as_tagged(); - self.focus_iter_token = self.focus_node.new_iter_token(); - }, - } - - match value { - Some(v) => return Some(v), - None => {} - } - } else { - //Ascend - if let Some((focus_node, iter_tok, prefix_offset)) = self.ancestors.pop() { - *self.focus_node = focus_node; - self.focus_iter_token = iter_tok; - self.prefix_buf.truncate(prefix_offset); - } else { - let new_len = self.origin_path.len(); - self.focus_iter_token = NODE_ITER_INVALID; - self.prefix_buf.truncate(new_len); - return None - } - } - } - } - } - impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperAbsolutePath for ReadZipperCore<'trie, '_, V, A> { fn origin_path(&self) -> &[u8] { if self.prefix_buf.capacity() > 0 { @@ -2330,6 +2265,91 @@ pub(crate) mod read_zipper_core { } } + /// Internal impl is marked `unsafe` because ReadZipperCore::root_node might be `Owned`, meaning + /// the returned value might outlive the zipper. We need to only expose this through methods + /// that have tighter lifetime bounds, or on types that guarantee the root won't be owned + pub(crate) unsafe fn get_val(&self) -> Option<&'a V> { + let key = self.node_key(); + if key.len() > 0 { + self.focus_node.node_get_val(key) + } else { + if let Some((parent, _iter_tok, _prefix_offset)) = self.ancestors.last() { + parent.node_get_val(self.parent_key()) + } else { + //NOTE: It's true that we shouldn't have ZipperReadOnlyValues implemented on a type that comes from a ZipperHead, but + // we currently share the same implementation between `val()` and `get_val()` because the only difference is the return + // lifetime, and the current ZipperHead implementation is actually ok with referencing the value in the root of the ZipperHead. + // debug_assert!(self.root_node.is_borrowed()); + self.root_val + } + } + } + + /// See [ReadZipperCore::get_val] for explanation as to why this is unsafe + pub(crate) unsafe fn to_next_get_val(&mut self) -> Option<&'a V> { + self.prepare_buffers(); + loop { + if self.focus_iter_token == NODE_ITER_INVALID { + let cur_tok = self.focus_node.iter_token_for_path(self.node_key()); + self.focus_iter_token = cur_tok; + } + + let (new_tok, key_bytes, child_node, value) = if self.focus_iter_token != NODE_ITER_FINISHED { + self.focus_node.next_items(self.focus_iter_token) + } else { + (NODE_ITER_FINISHED, &[][..] as &[u8], None, None) + }; + + if new_tok != NODE_ITER_FINISHED { + self.focus_iter_token = new_tok; + + let key_start = self.node_key_start(); + + //Make sure we don't move to a branch that forks above our zipper root + let origin_path_len = self.origin_path.len(); + if key_start < origin_path_len { + debug_assert_eq!(self.ancestors.len(), 0); + + 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 { + self.prefix_buf.truncate(origin_path_len); + return None + } + } + + self.prefix_buf.truncate(key_start); + self.prefix_buf.extend(key_bytes); + + match child_node { + None => {}, + Some(rec) => { + self.ancestors.push((*self.focus_node.clone(), new_tok, self.prefix_buf.len())); + *self.focus_node = rec.as_tagged(); + self.focus_iter_token = self.focus_node.new_iter_token(); + }, + } + + match value { + Some(v) => return Some(v), + None => {} + } + } else { + //Ascend + if let Some((focus_node, iter_tok, prefix_offset)) = self.ancestors.pop() { + *self.focus_node = focus_node; + self.focus_iter_token = iter_tok; + self.prefix_buf.truncate(prefix_offset); + } else { + let new_len = self.origin_path.len(); + self.focus_iter_token = NODE_ITER_INVALID; + self.prefix_buf.truncate(new_len); + return None + } + } + } + } + /// Returns the parent and path from which the top of the focus_stack can be re-acquired #[inline] pub(crate) fn focus_parent(&self) -> &TrieNodeODRc { @@ -2840,13 +2860,15 @@ impl<'a, V: Clone + Send + Sync + Unpin + 'a, A: Allocator + 'a> Iterator for Re if !self.started { self.started = true; if let Some(zipper) = &mut self.zipper { - if let Some(val) = zipper.get_val() { + //SAFETY: we only allow ReadZipperUntracked to become a `ReadZipperIter` + if let Some(val) = unsafe{ zipper.get_val() } { return Some((zipper.path().to_vec(), val)) } } } if let Some(zipper) = &mut self.zipper { - match zipper.to_next_get_val() { + //SAFETY: we only allow ReadZipperUntracked to become a `ReadZipperIter` + match unsafe{ zipper.to_next_get_val() } { Some(val) => return Some((zipper.path().to_vec(), val)), None => self.zipper = None } @@ -4271,22 +4293,22 @@ mod tests { assert_eq!(zipper.is_val(), false); zipper.descend_to(b"mulus"); assert_eq!(zipper.is_val(), true); - assert_eq!(zipper.get_val(), Some(&"romulus")); + assert_eq!(zipper.val(), Some(&"romulus")); let root_key = b"roman"; let mut zipper = ReadZipperCore::new_with_node_and_path_in(btm.root().unwrap(), false, root_key, root_key.len(), 0, None, global_alloc()); assert_eq!(zipper.is_val(), true); - assert_eq!(zipper.get_val(), Some(&"roman")); + assert_eq!(zipper.val(), Some(&"roman")); zipper.descend_to(b"e"); assert_eq!(zipper.is_val(), true); - assert_eq!(zipper.get_val(), Some(&"romane")); + assert_eq!(zipper.val(), Some(&"romane")); assert_eq!(zipper.ascend(1), Ok(())); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); - assert_eq!(zipper.get_val(), None); + assert_eq!(zipper.val(), None); zipper.descend_until(None); assert_eq!(zipper.is_val(), true); - assert_eq!(zipper.get_val(), Some(&"romanus")); + assert_eq!(zipper.val(), Some(&"romanus")); } /// Tests that a zipper forked at a subtrie will iterate correctly within that subtrie, also tests ReadZipper::IntoIterator impl @@ -4451,11 +4473,11 @@ mod tests { let mut parent_zipper = family.read_zipper_at_path(parent_path.as_bytes()); - assert!(family.contains_path(parent_path)); + assert!(family.path_exists_at(parent_path)); let mut full_parent_path = parent_path.as_bytes().to_vec(); full_parent_path.extend(parent_zipper.path()); - assert!(family.contains_path(&full_parent_path)); + assert!(family.path_exists_at(&full_parent_path)); while parent_zipper.to_next_val() { let mut full_parent_path = parent_path.as_bytes().to_vec(); @@ -4608,4 +4630,29 @@ mod tests { assert_eq!(node.as_tagged().node_branches_mask(&[]), expected_mask); assert!(matches!(rz.get_focus(), AbstractNodeRef::BorrowedRc(_))); //Make sure we get the ODRc } + + /// Tests the zipper `val_count` method, including with root values + /// NOTE: This test isn't in the generic suite because not all zipper types have real implementations of val_count + const ZIPPER_VAL_COUNT_TEST1_KEYS: &[&[u8]] = &[b"", b"arrow"]; + #[test] + fn read_zipper_val_count_test1() { + let map: PathMap<()> = ZIPPER_VAL_COUNT_TEST1_KEYS.into_iter().cloned().collect(); + let mut zipper = map.read_zipper(); + + assert_eq!(zipper.path(), b""); + assert_eq!(zipper.val_count(), 2); + assert_eq!(zipper.descend_until(None), true); + assert_eq!(zipper.path(), b"arrow"); + assert_eq!(zipper.val_count(), 1); + } + + #[test] + fn descend_last_path() { + let rs = ["arrow", "bow", "cannon", "roman", "romane", "romanus", "romulus", "rubens", "ruber", "rubicon", "rubicundus", "rom'i"]; + 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()); + assert_eq!(rz.path(), b"rubicundus"); + } } From 6b095df2c925801da535edeaceda5e85bb87a47a Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Tue, 7 Oct 2025 22:58:36 -0600 Subject: [PATCH 06/18] Fixing build for `fuzzer` feature --- src/fuzzer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fuzzer.rs b/src/fuzzer.rs index f4279678..5f39cdae 100644 --- a/src/fuzzer.rs +++ b/src/fuzzer.rs @@ -7,7 +7,7 @@ use std::ptr::null; use rand::distr::{Iter, Uniform}; use crate::TrieValue; use crate::utils::{BitMask, ByteMask}; -use crate::zipper::{ReadZipperUntracked, Zipper, ZipperReadOnlyIteration, ZipperMoving, ZipperReadOnlyValues}; +use crate::zipper::*; use crate::gxhash::{HashMap, HashMapExt}; @@ -577,7 +577,7 @@ mod tests { assert_eq!(rz.get_val(), trie.get_val_at(&path[..])); path_fuzzer.clone().sample_iter(rng_.clone()).take(N_DESCENDS).for_each(|path| { rz.descend_to(&path[..]); - rz.ascend(path.len()); + let _ = rz.ascend(path.len()); }); assert_eq!(rz.path(), &path[..]); assert_eq!(rz.get_val(), trie.get_val_at(&path[..])); From d0feb3b6ddf173835f8dfd26f833fa0ebdcc4a9d Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 9 Oct 2025 20:48:56 -0500 Subject: [PATCH 07/18] refactor ascend functions --- pathmap-derive/src/lib.rs | 6 +- src/empty_zipper.rs | 14 +-- src/experimental.rs | 14 +-- src/morphisms.rs | 11 +- src/overlay_zipper.rs | 48 +++++---- src/prefix_zipper.rs | 97 +++++++++-------- src/product_zipper.rs | 71 ++++++------ src/track_path.rs | 24 ++--- src/utils/debug/diff_zipper.rs | 6 +- src/write_zipper.rs | 107 +++++++++--------- src/zipper.rs | 192 +++++++++++++++++---------------- src/zipper_head.rs | 4 +- 12 files changed, 302 insertions(+), 292 deletions(-) diff --git a/pathmap-derive/src/lib.rs b/pathmap-derive/src/lib.rs index 94e27b54..c1e0bc87 100644 --- a/pathmap-derive/src/lib.rs +++ b/pathmap-derive/src/lib.rs @@ -306,7 +306,7 @@ pub fn derive_poly_zipper(input: TokenStream) -> TokenStream { } } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { + fn ascend(&mut self, steps: usize) -> usize { match self { #(#variant_arms => inner.ascend(steps),)* } @@ -318,13 +318,13 @@ pub fn derive_poly_zipper(input: TokenStream) -> TokenStream { } } - fn ascend_until(&mut self) -> Option { + fn ascend_until(&mut self) -> usize { match self { #(#variant_arms => inner.ascend_until(),)* } } - fn ascend_until_branch(&mut self) -> Option { + fn ascend_until_branch(&mut self) -> usize { match self { #(#variant_arms => inner.ascend_until_branch(),)* } diff --git a/src/empty_zipper.rs b/src/empty_zipper.rs index 63d735c9..4294c51f 100644 --- a/src/empty_zipper.rs +++ b/src/empty_zipper.rs @@ -46,13 +46,13 @@ impl ZipperMoving for EmptyZipper { fn descend_indexed_byte(&mut self, _idx: usize) -> Option { None } fn descend_first_byte(&mut self) -> Option { None } fn descend_until(&mut self, _dst_path: Option<&mut Vec>) -> bool { false } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { + fn ascend(&mut self, steps: usize) -> usize { if steps > self.path.len() - self.path_start_idx { self.reset(); - Err(steps - (self.path.len() - self.path_start_idx)) + self.path.len() - self.path_start_idx } else { self.path.truncate(self.path.len() - self.path_start_idx - steps); - Ok(()) + steps } } fn ascend_byte(&mut self) -> bool { @@ -63,16 +63,16 @@ impl ZipperMoving for EmptyZipper { false } } - fn ascend_until(&mut self) -> Option { + fn ascend_until(&mut self) -> usize { if self.at_root() { - None + 0 } else { let old_path_len = self.path.len() - self.path_start_idx; self.reset(); - Some(old_path_len) + old_path_len } } - fn ascend_until_branch(&mut self) -> Option { + fn ascend_until_branch(&mut self) -> usize { self.ascend_until() } fn to_next_sibling_byte(&mut self) -> Option { None } diff --git a/src/experimental.rs b/src/experimental.rs index ff11f484..856e57c2 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -60,23 +60,23 @@ impl ZipperMoving for FullZipper { if let Some(dst) = dst { dst.push(0) } true } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { + fn ascend(&mut self, steps: usize) -> usize { if steps > self.path.len() { self.path.clear(); - Err(steps - self.path.len()) + self.path.len() } else { self.path.truncate(self.path.len() - steps); - Ok(()) + steps } } fn ascend_byte(&mut self) -> bool { self.path.pop().is_some() } - fn ascend_until(&mut self) -> Option { - self.path.pop().map(|_| 1) // not sure? + fn ascend_until(&mut self) -> usize { + self.path.pop().is_some() as usize // not sure? } - fn ascend_until_branch(&mut self) -> Option { - self.path.pop().map(|_| 1) // not sure? What's the difference with the previous? + fn ascend_until_branch(&mut self) -> usize { + self.path.pop().is_some() as usize // not sure? What's the difference with the previous? } fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling(true) } fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } diff --git a/src/morphisms.rs b/src/morphisms.rs index b290ba27..a37dba3a 100644 --- a/src/morphisms.rs +++ b/src/morphisms.rs @@ -491,7 +491,7 @@ fn ascend_to_fork<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(z: &mut Z, let old_path_len = z.origin_path().len(); let old_val = z.get_val_with_witness(&z_witness); let ascended = z.ascend_until(); - debug_assert!(ascended.is_some()); + debug_assert!(ascended > 0); let origin_path = unsafe{ z.origin_path_assert_len(old_path_len) }; let jump_len = if z.child_count() != 1 || z.is_val() { @@ -642,7 +642,7 @@ where wz.ascend_byte(); } let ascended = wz.ascend(prefix_len); - debug_assert_eq!(ascended, Ok(())); + debug_assert_eq!(ascended, prefix_len); } /// A trait to dictate if and how the value should be cached. @@ -928,7 +928,7 @@ pub(crate) fn new_map_from_ana_in(w: W, mut alg_f: Alg WOrNode::Node(node) => { z.core().graft_internal(Some(node)); let ascended = z.ascend(child_path_len); - debug_assert_eq!(ascended, Ok(())); + debug_assert_eq!(ascended, child_path_len); } } } else { @@ -936,8 +936,9 @@ pub(crate) fn new_map_from_ana_in(w: W, mut alg_f: Alg if frame_idx == 0 { break } - let ascended = z.ascend(stack[frame_idx].1); - debug_assert_eq!(ascended, Ok(())); + let to_ascend = stack[frame_idx].1; + let ascended = z.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); stack[frame_idx].0.reset(); frame_idx -= 1; } diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index d156887d..26d8e1d9 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -176,7 +176,7 @@ impl ZipperMoving if depth_a < depth_o { if self.a.is_val() { let ascended = self.b.ascend(depth_o - depth_a); - debug_assert_eq!(ascended, Ok(())); + debug_assert_eq!(ascended, depth_o - depth_a); depth_a } else { self.a.descend_to(&path[depth_a..depth_o]); @@ -185,7 +185,7 @@ impl ZipperMoving } else if depth_o < depth_a { if self.b.is_val() { let ascended = self.a.ascend(depth_a - depth_o); - debug_assert_eq!(ascended, Ok(())); + debug_assert_eq!(ascended, depth_a - depth_o); depth_o } else { self.a.descend_to(&path[depth_o..depth_a]); @@ -227,8 +227,9 @@ impl ZipperMoving self.a.descend_to(path_b); return true; } else { - let ascended = self.b.ascend(self.b.path().len() - start_depth); - debug_assert_eq!(ascended, Ok(())); + let to_ascend = self.b.path().len() - start_depth; + let ascended = self.b.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); return false; } } @@ -237,24 +238,27 @@ impl ZipperMoving self.b.descend_to(path_a); return true; } else { - let ascended = self.a.ascend(self.a.path().len() - start_depth); - debug_assert_eq!(ascended, Ok(())); + let to_ascend = self.a.path().len() - start_depth; + let ascended = self.a.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); return false; } } let overlap = find_prefix_overlap(path_a, path_b); if path_a.len() > overlap { - let ascended = self.a.ascend(path_a.len() - overlap); - debug_assert_eq!(ascended, Ok(())); + let to_ascend = path_a.len() - overlap; + let ascended = self.a.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); } if path_b.len() > overlap { - let ascended = self.b.ascend(path_b.len() - overlap); - debug_assert_eq!(ascended, Ok(())); + let to_ascend = path_b.len() - overlap; + let ascended = self.b.ascend(to_ascend); + debug_assert_eq!(ascended, to_ascend); } overlap > 0 } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { + fn ascend(&mut self, steps: usize) -> usize { let rv_a = self.a.ascend(steps); let rv_b = self.b.ascend(steps); debug_assert_eq!(rv_a, rv_b); @@ -262,10 +266,10 @@ impl ZipperMoving } fn ascend_byte(&mut self) -> bool { - self.ascend(1).is_ok() + self.ascend(1) == 1 } - fn ascend_until(&mut self) -> Option { + fn ascend_until(&mut self) -> usize { debug_assert_eq!(self.a.path(), self.b.path()); // eprintln!("asc_until i {:?} {:?}", self.base.path(), self.overlay.path()); let asc_a = self.a.ascend_until(); @@ -275,9 +279,9 @@ impl ZipperMoving let path_b = self.b.path(); let depth_b = path_b.len(); let min = match (asc_a, asc_b) { - (None, None) => return None, - (Some(a), None) | (None, Some(a)) => a, - (Some(a), Some(b)) => a.min(b), + (0, 0) => return 0, + (a, 0) | (0, a) => a, + (a, b) => a.min(b), }; // eprintln!("asc_until {path_a:?} {path_b:?}"); if depth_b > depth_a { @@ -285,10 +289,10 @@ impl ZipperMoving } else if depth_a > depth_b { self.b.descend_to(&path_a[depth_b..]); } - Some(min) + min } - fn ascend_until_branch(&mut self) -> Option { + fn ascend_until_branch(&mut self) -> usize { let asc_a = self.a.ascend_until_branch(); let path_a = self.a.path(); let depth_a = path_a.len(); @@ -296,16 +300,16 @@ impl ZipperMoving let path_b = self.b.path(); let depth_b = path_b.len(); let min = match (asc_a, asc_b) { - (None, None) => return None, - (Some(a), None) | (None, Some(a)) => a, - (Some(a), Some(b)) => a.min(b), + (0, 0) => return 0, + (a, 0) | (0, a) => a, + (a, b) => a.min(b), }; if depth_b > depth_a { self.a.descend_to(&path_b[depth_a..]); } else if depth_a > depth_b { self.b.descend_to(&path_a[depth_b..]); } - Some(min) + min } fn to_next_sibling_byte(&mut self) -> Option { diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index c131bad8..fc8a2042 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -112,47 +112,48 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> }; } - fn ascend_n(&mut self, mut steps: usize) -> Result<(), usize> { + fn ascend_n(&mut self, steps: usize) -> usize { + let mut remaining = steps; if let PrefixPos::PrefixOff { valid, mut invalid } = self.position { - if invalid > steps { - invalid -= steps; + if invalid > remaining { + invalid -= remaining; self.position = PrefixPos::PrefixOff { valid, invalid }; - return Ok(()); + return steps; } - steps -= invalid; - self.set_valid(valid.saturating_sub(steps)); - return if let Some(remaining) = steps.checked_sub(valid) { - Err(remaining) + remaining -= invalid; + self.set_valid(valid.saturating_sub(remaining)); + return if let Some(remaining) = remaining.checked_sub(valid) { + steps - remaining } else { - Ok(()) + steps }; } if self.position.is_source() { - // let Err(remaining) = self.source.ascend(steps) else { + // let Err(remaining) = self.source.ascend(remaining) else { // return Ok(()); // }; let len_before = self.source.path().len(); - if self.source.ascend(steps).is_ok() { - return Ok(()) + if self.source.ascend(remaining) == remaining { + return steps } let len_after = self.source.path().len(); - steps -= len_before - len_after; + remaining -= len_before - len_after; self.position = PrefixPos::Prefix { valid: self.prefix.len() - self.origin_depth }; // Intermediate state: self.position points one off } if let PrefixPos::Prefix { valid } = self.position { - self.set_valid(valid.saturating_sub(steps)); - return if let Some(remaining) = steps.checked_sub(valid) { - Err(remaining) + self.set_valid(valid.saturating_sub(remaining)); + return if let Some(remaining) = remaining.checked_sub(valid) { + steps - remaining } else { - Ok(()) + steps }; } - Err(steps) + steps - remaining } - fn ascend_until_n(&mut self) -> Option { + fn ascend_until_n(&mut self) -> usize { if self.at_root() { - return None; + return 0; } let mut ascended = 0; if self.position.is_source() { @@ -165,9 +166,9 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> } else { self.source.ascend_until_branch() }; - if was_good.is_some() && ((VAL && self.source.is_val()) || self.source.child_count() > 1) { + if was_good > 0 && ((VAL && self.source.is_val()) || self.source.child_count() > 1) { let len_after = self.source.path().len(); - return Some(len_before - len_after); + return len_before - len_after; } ascended += len_before; let valid = self.prefix.len() - self.origin_depth; @@ -176,7 +177,7 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> ascended += self.position.prefixed_depth() .expect("we should no longer pointe at source at this point"); self.set_valid(0); - Some(ascended) + ascended } } @@ -405,30 +406,28 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> *self.path.last_mut().unwrap() = byte; Some(byte) } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { - let rv = self.ascend_n(steps); - let ascended = match rv { - Err(remaining) => steps - remaining, - Ok(()) => steps, - }; + fn ascend(&mut self, steps: usize) -> usize { + let ascended = self.ascend_n(steps); self.path.truncate(self.path.len() - ascended); - rv + ascended } #[inline] fn ascend_byte(&mut self) -> bool { - self.ascend(1).is_ok() + self.ascend(1) == 1 } #[inline] - fn ascend_until(&mut self) -> Option { - let ascended = self.ascend_until_n::()?; + fn ascend_until(&mut self) -> usize { + let ascended = self.ascend_until_n::(); + if ascended == 0 { return 0; } self.path.truncate(self.path.len() - ascended); - Some(ascended) + ascended } #[inline] - fn ascend_until_branch(&mut self) -> Option { - let ascended = self.ascend_until_n::()?; + fn ascend_until_branch(&mut self) -> usize { + let ascended = self.ascend_until_n::(); + if ascended == 0 { return 0; } self.path.truncate(self.path.len() - ascended); - Some(ascended) + ascended } } @@ -498,18 +497,18 @@ mod tests { let mut rz = PrefixZipper::new(b"prefix", map.read_zipper()); rz.set_root_prefix_path(b"pre").unwrap(); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until(), Some(1)); + assert_eq!(rz.ascend_until(), 1); assert_eq!(rz.path(), b"fix0000"); assert_eq!(rz.origin_path(), b"prefix0000"); assert_eq!(rz.descend_to_existing(b"0"), 1); - assert_eq!(rz.ascend_until_branch(), Some(2)); + assert_eq!(rz.ascend_until_branch(), 2); assert_eq!(rz.path(), b"fix000"); - assert_eq!(rz.ascend_until_branch(), Some(3)); + assert_eq!(rz.ascend_until_branch(), 3); assert_eq!(rz.path(), b"fix"); - assert_eq!(rz.ascend_until_branch(), Some(3)); + assert_eq!(rz.ascend_until_branch(), 3); assert_eq!(rz.path(), b""); assert_eq!(rz.origin_path(), b"pre"); - assert_eq!(rz.ascend_until_branch(), None); + assert_eq!(rz.ascend_until_branch(), 0); } #[test] @@ -518,20 +517,20 @@ mod tests { let mut rz = PrefixZipper::new(b"prefix", map.read_zipper()); rz.set_root_prefix_path(b"pre").unwrap(); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until(), Some(2)); + assert_eq!(rz.ascend_until(), 2); assert_eq!(rz.path(), b"fix000"); assert_eq!(rz.origin_path(), b"prefix000"); - assert_eq!(rz.ascend_until(), Some(1)); + assert_eq!(rz.ascend_until(), 1); assert_eq!(rz.path(), b"fix00"); - assert_eq!(rz.ascend_until(), Some(5)); + assert_eq!(rz.ascend_until(), 5); assert_eq!(rz.path(), b""); - assert_eq!(rz.ascend_until(), None); + assert_eq!(rz.ascend_until(), 0); assert_eq!(rz.descend_to_existing(b"fix00000"), 8); - assert_eq!(rz.ascend_until_branch(), Some(3)); + assert_eq!(rz.ascend_until_branch(), 3); assert_eq!(rz.path(), b"fix00"); - assert_eq!(rz.ascend_until_branch(), Some(5)); + assert_eq!(rz.ascend_until_branch(), 5); assert_eq!(rz.path(), b""); assert_eq!(rz.origin_path(), b"pre"); - assert_eq!(rz.ascend_until_branch(), None); + assert_eq!(rz.ascend_until_branch(), 0); } } \ No newline at end of file diff --git a/src/product_zipper.rs b/src/product_zipper.rs index cdd7445d..12e884f5 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -260,20 +260,20 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.ensure_descend_next_factor(); moved } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { + fn ascend(&mut self, steps: usize) -> usize { let ascended = self.z.ascend(steps); self.fix_after_ascend(); ascended } fn ascend_byte(&mut self) -> bool { - self.ascend(1).is_ok() + self.ascend(1) == 1 } - fn ascend_until(&mut self) -> Option { + fn ascend_until(&mut self) -> usize { let ascended = self.z.ascend_until(); self.fix_after_ascend(); ascended } - fn ascend_until_branch(&mut self) -> Option { + fn ascend_until_branch(&mut self) -> usize { let ascended = self.z.ascend_until_branch(); self.fix_after_ascend(); ascended @@ -468,7 +468,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) -> Option { + fn ascend_cond(&mut self, allow_stop_on_val: bool) -> usize { let mut plen = self.path().len(); loop { while self.factor_paths.last() == Some(&plen) { @@ -485,8 +485,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, let delta = before - zipper.path().len(); plen -= delta; let ascended = self.primary.ascend(delta); - debug_assert_eq!(ascended, Ok(())); - if rv.is_some() { + debug_assert_eq!(ascended, delta); + if rv > 0 { return rv; } } else { @@ -504,7 +504,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, let Some(&byte) = self.path().last() else { return None; }; - assert!(self.ascend(1).is_ok(), "must ascend"); + assert!(self.ascend(1) == 1, "must ascend"); let child_mask = self.child_mask(); let Some(sibling_byte) = (if next { child_mask.next_bit(byte) @@ -751,33 +751,35 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling_byte(false) } - fn ascend(&mut self, mut steps: usize) -> Result<(), usize> { - while steps > 0 { + fn ascend(&mut self, steps: usize) -> usize { + let mut remaining = steps; + while remaining > 0 { self.exit_factors(); if let Some(idx) = self.factor_idx(false) { let len = self.path().len() - self.factor_paths[idx]; - let delta = len.min(steps); + let delta = len.min(remaining); let ascended = self.secondary[idx].ascend(delta); - debug_assert_eq!(ascended, Ok(())); + debug_assert_eq!(ascended, delta); let ascended = self.primary.ascend(delta); - debug_assert_eq!(ascended, Ok(())); - steps -= delta; + debug_assert_eq!(ascended, delta); + remaining -= delta; } else { - return self.primary.ascend(steps); + let ascended = steps - remaining; + return ascended + self.primary.ascend(remaining); } } - Ok(()) + steps } #[inline] fn ascend_byte(&mut self) -> bool { - self.ascend(1).is_ok() + self.ascend(1) == 1 } #[inline] - fn ascend_until(&mut self) -> Option { + fn ascend_until(&mut self) -> usize { self.ascend_cond(true) } #[inline] - fn ascend_until_branch(&mut self) -> Option { + fn ascend_until_branch(&mut self) -> usize { self.ascend_cond(false) } } @@ -886,19 +888,19 @@ mod tests { assert_eq!(pz.child_count(), 0); //Make sure we can ascend out of a secondary factor; in this sub-test we'll hit the path middles - assert_eq!(pz.ascend(1), Ok(())); + assert_eq!(pz.ascend(1), 1); assert_eq!(pz.val(), None); assert_eq!(pz.path(), b"AAaDDdG"); assert_eq!(pz.child_count(), 0); - assert_eq!(pz.ascend(3), Ok(())); + assert_eq!(pz.ascend(3), 3); assert_eq!(pz.path(), b"AAaD"); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); - assert_eq!(pz.ascend(2), Ok(())); + assert_eq!(pz.ascend(2), 2); assert_eq!(pz.path(), b"AA"); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 3); - assert_eq!(pz.ascend(3), Err(1)); + assert_eq!(pz.ascend(3), 2); assert_eq!(pz.path(), b""); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); @@ -908,17 +910,16 @@ mod tests { assert_eq!(pz.path(), b"AAaDDdGG"); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 0); - //Now try to hit the path transition points - assert_eq!(pz.ascend(2), Ok(())); + assert_eq!(pz.ascend(2), 2); assert_eq!(pz.path(), b"AAaDDd"); assert_eq!(pz.val(), Some(&1000)); assert_eq!(pz.child_count(), 0); - assert_eq!(pz.ascend(3), Ok(())); + assert_eq!(pz.ascend(3), 3); assert_eq!(pz.path(), b"AAa"); assert_eq!(pz.val(), Some(&0)); assert_eq!(pz.child_count(), 3); - assert_eq!(pz.ascend(3), Ok(())); + assert_eq!(pz.ascend(3), 3); assert_eq!(pz.path(), b""); assert_eq!(pz.val(), None); assert_eq!(pz.child_count(), 1); @@ -1071,9 +1072,9 @@ mod tests { assert!(p.descend_to_byte(b'o')); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbowpho"); assert!(p.is_val()); - assert_eq!(p.ascend_until(), Some(3)); + assert_eq!(p.ascend_until(), 3); assert_eq!(p.path(), b"abcdefghijklmnopqrstuvwxyzbow"); - assert_eq!(p.ascend(3), Ok(())); + assert_eq!(p.ascend(3), 3); assert_eq!(vec![b'A', b'a', b'b'], p.child_mask().iter().collect::>()); assert!(p.descend_to("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); assert_eq!(vec![b'f', b'p'], p.child_mask().iter().collect::>()) @@ -1096,7 +1097,7 @@ mod tests { let mut p = $ProductZipper::new(l.read_zipper(), [r.read_zipper(), e.read_zipper()]); assert!(!p.descend_to("ABCDEFGHIJKLMNOPQRSTUVWXYZ")); // println!("p {}", std::str::from_utf8(p.path()).unwrap()); - assert_eq!(p.ascend(27), Err(1)); + assert_eq!(p.ascend(27), 26); } } @@ -1132,7 +1133,7 @@ mod tests { // Validate that I can back up, and re-descend { - p2.ascend(20).unwrap(); + assert_eq!(p2.ascend(20), 20); assert_eq!(p2.path(), b"arr"); assert_eq!(p2.path_exists(), true); assert!(p2.is_val()); @@ -1157,7 +1158,7 @@ mod tests { assert_eq!(p2.path(), b"arrowbowclub"); assert_eq!(p2.path_exists(), false); - p2.ascend(9).unwrap(); + assert_eq!(p2.ascend(9), 9); assert_eq!(p2.path(), b"arr"); assert_eq!(p2.path_exists(), true); assert!(p2.is_val()); @@ -1177,7 +1178,7 @@ mod tests { assert_eq!(p2.path(), b"arrowheadbowclub"); assert_eq!(p2.path_exists(), false); - p2.ascend(5).unwrap(); + assert_eq!(p2.ascend(5), 5); assert_eq!(p2.path(), b"arrowheadbo"); assert_eq!(p2.path_exists(), true); assert!(p2.is_val()); @@ -1209,7 +1210,9 @@ mod tests { // println!("{}", String::from_utf8_lossy(path)); let overlap = find_prefix_overlap(path, moving_pz.path()); if overlap < moving_pz.path().len() { - moving_pz.ascend(moving_pz.path().len() - overlap).unwrap(); + let to_ascend = moving_pz.path().len() - overlap; + let ascended = moving_pz.ascend(to_ascend); + assert_eq!(ascended, to_ascend); } if moving_pz.path().len() < path.len() { assert!(moving_pz.descend_to(&path[moving_pz.path().len()..])); diff --git a/src/track_path.rs b/src/track_path.rs index 7bd64896..90aa2c0a 100644 --- a/src/track_path.rs +++ b/src/track_path.rs @@ -70,15 +70,11 @@ impl ZipperMoving for TrackPath { self.path.push(k); self.zipper.descend_to_byte(k) } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { - let rv = self.zipper.ascend(steps); - let ascended = match rv { - Ok(()) => steps, - Err(remaining) => steps - remaining, - }; + fn ascend(&mut self, steps: usize) -> usize { + let ascended = self.zipper.ascend(steps); let orig_len = self.path.len(); self.path.truncate(orig_len - ascended); - rv + ascended } fn ascend_byte(&mut self) -> bool { if !self.zipper.ascend_byte() { @@ -87,17 +83,19 @@ impl ZipperMoving for TrackPath { self.path.pop(); true } - fn ascend_until(&mut self) -> Option { - let ascended = self.zipper.ascend_until()?; + fn ascend_until(&mut self) -> usize { + let ascended = self.zipper.ascend_until(); + if ascended == 0 { return 0; } let orig_len = self.path.len(); self.path.truncate(orig_len - ascended); - Some(ascended) + ascended } - fn ascend_until_branch(&mut self) -> Option { - let ascended = self.zipper.ascend_until_branch()?; + fn ascend_until_branch(&mut self) -> usize { + let ascended = self.zipper.ascend_until_branch(); + if ascended == 0 { return 0; } let orig_len = self.path.len(); self.path.truncate(orig_len - ascended); - Some(ascended) + ascended } fn to_next_sibling_byte(&mut self) -> Option { let byte = self.zipper.to_next_sibling_byte()?; diff --git a/src/utils/debug/diff_zipper.rs b/src/utils/debug/diff_zipper.rs index 1164f9ab..ee8b5643 100644 --- a/src/utils/debug/diff_zipper.rs +++ b/src/utils/debug/diff_zipper.rs @@ -132,7 +132,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { + fn ascend(&mut self, steps: usize) -> usize { let a = self.a.ascend(steps); let b = self.b.ascend(steps); if self.log_moves { @@ -150,7 +150,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn ascend_until(&mut self) -> Option { + fn ascend_until(&mut self) -> usize { let a = self.a.ascend_until(); let b = self.b.ascend_until(); if self.log_moves { @@ -159,7 +159,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn ascend_until_branch(&mut self) -> Option { + fn ascend_until_branch(&mut self) -> usize { let a = self.a.ascend_until_branch(); let b = self.b.ascend_until_branch(); if self.log_moves { diff --git a/src/write_zipper.rs b/src/write_zipper.rs index 5441c0e0..eba0f2e6 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -355,10 +355,10 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } + fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> Option { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> usize { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> usize { self.z.ascend_until_branch() } } impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperTracked<'a, 'path, V, A> { fn path(&self) -> &[u8] { self.z.path() } @@ -511,10 +511,10 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } + fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> Option { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> usize { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> usize { self.z.ascend_until_branch() } } impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperPath for WriteZipperUntracked<'a, 'path, V, A> { fn path(&self) -> &[u8] { self.z.path() } @@ -691,10 +691,10 @@ impl ZipperMoving for WriteZipperO fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } + fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> Option { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> usize { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> usize { self.z.ascend_until_branch() } } impl ZipperPath for WriteZipperOwned { fn path(&self) -> &[u8] { self.z.path() } @@ -987,33 +987,34 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } - fn ascend(&mut self, mut steps: usize) -> Result<(), usize> { + fn ascend(&mut self, steps: usize) -> usize { + let mut remaining = steps; loop { if self.key.node_key().len() == 0 { self.ascend_across_nodes(); } - if steps == 0 { - return Ok(()); + if remaining == 0 { + return steps; } if self.at_root() { - return Err(steps); + return steps - remaining; } debug_assert!(self.key.node_key().len() > 0); - let cur_jump = steps.min(self.key.excess_key_len()); + let cur_jump = remaining.min(self.key.excess_key_len()); self.key.prefix_buf.truncate(self.key.prefix_buf.len() - cur_jump); - steps -= cur_jump; + remaining -= cur_jump; } } - fn ascend_until(&mut self) -> Option { + fn ascend_until(&mut self) -> usize { if self.at_root() { - return None; + return 0; } let mut ascended = 0; loop { ascended += self.ascend_within_node(); if self.at_root() { - return Some(ascended); + return ascended; } if self.key.node_key().len() == 0 { self.ascend_across_nodes(); @@ -1023,18 +1024,18 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } debug_assert!(self.key.node_key().len() > 0); //We should never finish with a zero-length node-key - Some(ascended) + ascended } - fn ascend_until_branch(&mut self) -> Option { + fn ascend_until_branch(&mut self) -> usize { if self.at_root() { - return None; + return 0; } let mut ascended = 0; loop { ascended += self.ascend_within_node(); if self.at_root() { - return Some(ascended); + return ascended; } if self.key.node_key().len() == 0 { self.ascend_across_nodes(); @@ -1044,7 +1045,7 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving } } debug_assert!(self.key.node_key().len() > 0); //We should never finish with a zero-length node-key - Some(ascended) + ascended } fn to_next_sibling_byte(&mut self) -> Option { let cur_byte = match self.path().last() { @@ -1610,7 +1611,7 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC let fully_ascended = self.ascend(n); self.graft_internal(downstream_node); - fully_ascended.is_ok() + fully_ascended == n } /// See [ZipperWriting::meet_into] pub fn meet_into>(&mut self, read_zipper: &Z, prune: bool) -> AlgebraicStatus where V: Lattice { @@ -1936,7 +1937,7 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC fn prune_ascend(&mut self) -> usize { let bytes = self.prune_path(); let ascended = self.ascend(bytes); - debug_assert_eq!(ascended, Ok(())); + debug_assert_eq!(ascended, bytes); bytes } @@ -2922,29 +2923,29 @@ mod tests { // and then clean them up wz.descend_to([0, 0, 0, 0]); wz.set_val(()); - wz.ascend(4).unwrap(); + assert_eq!(wz.ascend(4), 4); wz.descend_to([1, 0, 0, 1]); wz.set_val(()); - wz.ascend(4).unwrap(); + assert_eq!(wz.ascend(4), 4); wz.descend_to([2, 0, 0, 2]); wz.set_val(()); - wz.ascend(4).unwrap(); + assert_eq!(wz.ascend(4), 4); wz.descend_to([0, 0, 0, 0]); wz.remove_val(true); - wz.ascend(4).unwrap(); + assert_eq!(wz.ascend(4), 4); wz.descend_to([1, 0, 0, 1]); wz.remove_val(true); - wz.ascend(4).unwrap(); + assert_eq!(wz.ascend(4), 4); wz.descend_to([2, 0, 0, 2]); wz.remove_val(true); - wz.ascend(4).unwrap(); + assert_eq!(wz.ascend(4), 4); wz.meet_into(&rz, true); assert_eq!(wz.val_count(), 2); assert!(wz.descend_to([194, 7, 162])); assert!(wz.val().is_some()); - assert_eq!(wz.ascend(3), Ok(())); + assert_eq!(wz.ascend(3), 3); assert!(wz.descend_to([194, 7, 163])); assert!(wz.val().is_some()); } @@ -3051,26 +3052,26 @@ mod tests { assert!(wz.descend_to(b"mulus")); assert_eq!(wz.path(), b"mulus"); assert_eq!(wz.child_count(), 0); - assert_eq!(wz.ascend_until(), Some(4)); + assert_eq!(wz.ascend_until(), 4); assert_eq!(wz.path(), b"m"); assert_eq!(wz.child_count(), 3); //Make sure we can't ascend above the zipper's root with ascend_until - assert_eq!(wz.ascend_until(), Some(1)); + assert_eq!(wz.ascend_until(), 1); assert_eq!(wz.path(), b""); - assert_eq!(wz.ascend_until(), None); + assert_eq!(wz.ascend_until(), 0); //Test step-wise `ascend` wz.descend_to(b"manus"); assert_eq!(wz.path(), b"manus"); - assert_eq!(wz.ascend(1), Ok(())); + assert_eq!(wz.ascend(1), 1); assert_eq!(wz.path(), b"manu"); - assert_eq!(wz.ascend(5), Err(1)); + assert_eq!(wz.ascend(5), 4); assert_eq!(wz.path(), b""); assert_eq!(wz.at_root(), true); wz.descend_to(b"mane"); assert_eq!(wz.path(), b"mane"); - assert_eq!(wz.ascend(3), Ok(())); + assert_eq!(wz.ascend(3), 3); assert_eq!(wz.path(), b"m"); assert_eq!(wz.child_count(), 3); } @@ -3726,7 +3727,7 @@ mod tests { assert_eq!(wz.origin_path(), b"This path can take you anywhere. Just close your eyes... and open your heart."); // Test forking a zipper from a WriteZipper and make sure it inherits the origin_path - wz.ascend(6).unwrap(); + assert_eq!(wz.ascend(6), 6); assert_eq!(wz.is_val(), false); let mut rz = wz.fork_read_zipper(); assert_eq!(rz.path(), b""); @@ -3745,7 +3746,7 @@ mod tests { assert_eq!(rz.is_val(), true); assert_eq!(rz.path(), b" and open your heart."); assert_eq!(rz.origin_path(), b"This path can take you anywhere. Just close your eyes... and open your heart."); - rz.ascend(6).unwrap(); + assert_eq!(rz.ascend(6), 6); assert_eq!(rz.path(), b" and open your "); assert_eq!(rz.origin_path(), b"This path can take you anywhere. Just close your eyes... and open your "); assert_eq!(rz.is_val(), false); @@ -3843,9 +3844,9 @@ mod tests { assert_eq!(wz.path(), &[0, 0, 1, 0, 0, 2, 3, 4]); assert_eq!(wz.prune_path(), 6); //Prune back to the value at [0, 0, 0] assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(4), Ok(())); + assert_eq!(wz.ascend(4), 4); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(2), Ok(())); + assert_eq!(wz.ascend(2), 2); assert_eq!(wz.path_exists(), true); assert_eq!(wz.path(), &[0, 0]); @@ -3896,7 +3897,7 @@ mod tests { assert_eq!(wz.prune_path(), 0); //And try pruning above the end - assert_eq!(wz.ascend(2), Ok(())); + assert_eq!(wz.ascend(2), 2); assert_eq!(wz.prune_path(), 0); assert_eq!(wz.descend_first_byte(), Some(4)); @@ -3905,7 +3906,7 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 7); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(7), Ok(())); + assert_eq!(wz.ascend(7), 7); assert_eq!(wz.path(), &[]); assert_eq!(wz.path_exists(), true); assert_eq!(wz.child_count(), 0); @@ -3957,9 +3958,9 @@ mod tests { assert_eq!(wz.path(), &[1, 0, 3, 4, 5, 6]); assert_eq!(wz.prune_path(), 4); //Prune back to the value at [1, 0, 0] assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), Ok(())); + assert_eq!(wz.ascend(3), 3); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(1), Ok(())); + assert_eq!(wz.ascend(1), 1); assert_eq!(wz.path_exists(), true); assert_eq!(wz.path(), &[1, 0]); assert_eq!(wz.child_count(), 3); @@ -3990,7 +3991,7 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 50); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(49), Ok(())); + assert_eq!(wz.ascend(49), 49); assert_eq!(wz.path_exists(), false); assert_eq!(wz.ascend_byte(), true); assert_eq!(wz.path_exists(), true); @@ -4012,7 +4013,7 @@ mod tests { assert_eq!(wz.path_exists(), true); assert_eq!(wz.prune_path(), 3); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), Ok(())); + assert_eq!(wz.ascend(3), 3); assert_eq!(wz.child_count(), 2); } @@ -4039,16 +4040,16 @@ mod tests { assert_eq!(wz.prune_path(), 3); assert_eq!(wz.path(), &[0, 0, 1, 0, 0]); assert_eq!(wz.path_exists(), false); - assert_eq!(wz.ascend(3), Ok(())); + assert_eq!(wz.ascend(3), 3); assert_eq!(wz.path_exists(), true); //Recreate some new paths, remove one and try re-extending it assert_eq!(wz.descend_to([0, 0, 0, 0]), false); assert_eq!(wz.set_val(()), None); - assert_eq!(wz.ascend(4), Ok(())); + assert_eq!(wz.ascend(4), 4); assert_eq!(wz.descend_to([0, 0, 1, 0]), false); assert_eq!(wz.set_val(()), None); - assert_eq!(wz.ascend(2), Ok(())); + assert_eq!(wz.ascend(2), 2); assert_eq!(wz.descend_to_byte(0), true); assert_eq!(wz.remove_branches(false), true); assert_eq!(wz.path_exists(), true); @@ -4104,7 +4105,7 @@ mod tests { assert_eq!(src_z.prune_path(), 3); assert_eq!(src_z.path(), &[0, 0, 1, 0, 0]); assert_eq!(src_z.path_exists(), false); - assert_eq!(src_z.ascend(3), Ok(())); + assert_eq!(src_z.ascend(3), 3); assert_eq!(src_z.path_exists(), true); //Test removing from a node boundary @@ -4206,7 +4207,7 @@ mod tests { assert_eq!(wz.ascend_byte(), true); assert_eq!(wz.descend_to([3, 0, 0, 0]), false); assert_eq!(wz.create_path(), true); - assert_eq!(wz.ascend(4), Ok(())); + assert_eq!(wz.ascend(4), 4); assert_eq!(wz.child_count(), 4); } diff --git a/src/zipper.rs b/src/zipper.rs index 7f9da8f6..543ddd85 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -281,29 +281,32 @@ pub trait ZipperMoving: Zipper { descended } - /// Ascends the zipper `steps` steps. Returns `Ok(()))` if the zipper sucessfully moved `steps` + /// Ascends the zipper `steps` steps. /// - /// If the root is fewer than `n` steps from the zipper's position, then this method will stop at - /// the root and return the number of remaining steps. - fn ascend(&mut self, steps: usize) -> Result<(), usize>; + /// Returns the number of ascended steps. + /// + /// If the root is fewer than `steps` steps from the zipper's position, then this method will stop at + /// the root and return the number of ascended steps. + fn ascend(&mut self, steps: usize) -> usize; /// Ascends the zipper up a single byte. Equivalent to passing `1` to [ascend](Self::ascend) fn ascend_byte(&mut self) -> bool { - self.ascend(1).is_ok() + self.ascend(1) == 1 } - /// Ascends the zipper to the nearest upstream branch point or value. Returns `Some(steps)` if the zipper - /// focus moved upwards, otherwise returns `None` if the zipper was already at the root + /// Ascends the zipper to the nearest upstream branch point or value. + /// + /// Returns the number of ascended steps. If at root, doesn't move and returns 0. /// /// NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. - fn ascend_until(&mut self) -> Option; + fn ascend_until(&mut self) -> usize; - /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. Returns - /// `Some(steps)` if the zipper focus moved upwards, otherwise returns `None` if the zipper was already at the - /// root + /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. + /// + /// Returns the number of ascended steps. If at root, doesn't move and returns 0. /// /// NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. - fn ascend_until_branch(&mut self) -> Option; + fn ascend_until_branch(&mut self) -> usize; /// Moves the zipper's focus to the next sibling byte with the same parent /// @@ -664,7 +667,7 @@ pub trait ZipperPath: ZipperMoving { (overlap, self.descend_to(path)) } else { let ascended = self.ascend(to_ascend); - debug_assert_eq!(ascended, Ok(())); + debug_assert_eq!(ascended, to_ascend); (overlap, self.descend_to(&path[overlap..])) } } @@ -777,10 +780,10 @@ impl ZipperMoving for &mut Z where Z: ZipperMoving + Zipper { fn descend_indexed_byte(&mut self, idx: usize) -> Option { (**self).descend_indexed_byte(idx) } fn descend_first_byte(&mut self) -> Option { (**self).descend_first_byte() } fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { (**self).descend_until(dst) } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { (**self).ascend(steps) } + fn ascend(&mut self, steps: usize) -> usize { (**self).ascend(steps) } fn ascend_byte(&mut self) -> bool { (**self).ascend_byte() } - fn ascend_until(&mut self) -> Option { (**self).ascend_until() } - fn ascend_until_branch(&mut self) -> Option { (**self).ascend_until_branch() } + fn ascend_until(&mut self) -> usize { (**self).ascend_until() } + fn ascend_until_branch(&mut self) -> usize { (**self).ascend_until_branch() } fn to_next_sibling_byte(&mut self) -> Option { (**self).to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { (**self).to_prev_sibling_byte() } fn to_next_step(&mut self) -> bool { (**self).to_next_step() } @@ -915,10 +918,10 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } + fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> Option { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> usize { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> usize { self.z.ascend_until_branch() } fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } @@ -1064,10 +1067,10 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } + fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> Option { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> usize { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> usize { self.z.ascend_until_branch() } fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } @@ -1263,10 +1266,10 @@ impl ZipperMoving for ReadZipperOw fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } - fn ascend(&mut self, steps: usize) -> Result<(), usize> { self.z.ascend(steps) } + fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } fn ascend_byte(&mut self) -> bool { self.z.ascend_byte() } - fn ascend_until(&mut self) -> Option { self.z.ascend_until() } - fn ascend_until_branch(&mut self) -> Option { self.z.ascend_until_branch() } + fn ascend_until(&mut self) -> usize { self.z.ascend_until() } + fn ascend_until_branch(&mut self) -> usize { self.z.ascend_until_branch() } fn to_next_step(&mut self) -> bool { self.z.to_next_step() } } @@ -1825,9 +1828,10 @@ pub(crate) mod read_zipper_core { self.to_sibling(false) } - fn ascend(&mut self, mut steps: usize) -> Result<(), usize> { + fn ascend(&mut self, steps: usize) -> usize { + let mut remaining = steps; debug_assert!(self.is_regularized()); - while steps > 0 { + while remaining > 0 { if self.excess_key_len() == 0 { match self.ancestors.pop() { Some((node, iter_tok, _prefix_offset)) => { @@ -1836,16 +1840,16 @@ pub(crate) mod read_zipper_core { }, None => { debug_assert!(self.is_regularized()); - return Err(steps) + return steps - remaining; } }; } - let cur_jump = steps.min(self.excess_key_len()); + let cur_jump = remaining.min(self.excess_key_len()); self.prefix_buf.truncate(self.prefix_buf.len() - cur_jump); - steps -= cur_jump; + remaining -= cur_jump; } debug_assert!(self.is_regularized()); - Ok(()) + steps } fn ascend_byte(&mut self) -> bool { @@ -1867,10 +1871,10 @@ pub(crate) mod read_zipper_core { true } - fn ascend_until(&mut self) -> Option { + fn ascend_until(&mut self) -> usize { debug_assert!(self.is_regularized()); if self.at_root() { - return None; + return 0; } let mut ascended = 0; loop { @@ -1879,15 +1883,15 @@ pub(crate) mod read_zipper_core { } ascended += self.ascend_within_node(); if self.child_count() > 1 || self.is_val() || self.at_root() { - return Some(ascended); + return ascended; } } } - fn ascend_until_branch(&mut self) -> Option { + fn ascend_until_branch(&mut self) -> usize { debug_assert!(self.is_regularized()); if self.at_root() { - return None; + return 0; } let mut ascended = 0; loop { @@ -1896,7 +1900,7 @@ pub(crate) mod read_zipper_core { } ascended += self.ascend_within_node(); if self.child_count() > 1 || self.at_root() { - return Some(ascended); + return ascended; } } } @@ -3180,20 +3184,20 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.to_prev_sibling_byte(), Some(39)); // focus = rom' (we stepped back to where we began) assert_eq!(zipper.path(), b"rom'"); assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'i']); - assert_eq!(zipper.ascend(1), Ok(())); // focus = rom + assert_eq!(zipper.ascend(1), 1); // focus = rom assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'\'', b'a', b'u']); // all three options we visited assert_eq!(zipper.descend_indexed_byte(0), Some(39)); // focus = rom' assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'i']); - assert_eq!(zipper.ascend(1), Ok(())); // focus = rom + assert_eq!(zipper.ascend(1), 1); // focus = rom assert_eq!(zipper.descend_indexed_byte(1), Some(b'a')); // focus = roma assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'n']); - assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.ascend(1), 1); assert_eq!(zipper.descend_indexed_byte(2), Some(b'u')); // focus = romu assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'l']); - assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.ascend(1), 1); assert_eq!(zipper.descend_indexed_byte(1), Some(b'a')); // focus = roma assert_eq!(zipper.child_mask().byte_mask_iter().collect::>(), vec![b'n']); - assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.ascend(1), 1); // ' < a < u // 39 105 117 } @@ -3216,33 +3220,33 @@ pub(crate) mod zipper_moving_tests { zipper.descend_to(b"e"); assert_eq!(zipper.path(), b"mane"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend_until(), Some(1)); + assert_eq!(zipper.ascend_until(), 1); zipper.descend_to(b"us"); assert_eq!(zipper.path(), b"manus"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend_until(), Some(2)); + assert_eq!(zipper.ascend_until(), 2); assert_eq!(zipper.path(), b"man"); assert_eq!(zipper.child_count(), 2); - assert_eq!(zipper.ascend_until(), Some(2)); + assert_eq!(zipper.ascend_until(), 2); assert_eq!(zipper.path(), b"m"); assert_eq!(zipper.child_count(), 3); - assert_eq!(zipper.ascend_until(), Some(1)); + assert_eq!(zipper.ascend_until(), 1); assert_eq!(zipper.path(), b""); assert_eq!(zipper.child_count(), 1); assert_eq!(zipper.at_root(), true); - assert_eq!(zipper.ascend_until(), None); + assert_eq!(zipper.ascend_until(), 0); //Test `ascend` zipper.descend_to(b"manus"); assert_eq!(zipper.path(), b"manus"); - assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.ascend(1), 1); assert_eq!(zipper.path(), b"manu"); - assert_eq!(zipper.ascend(5), Err(1)); + assert_eq!(zipper.ascend(5), 4); assert_eq!(zipper.path(), b""); assert_eq!(zipper.at_root(), true); zipper.descend_to(b"mane"); assert_eq!(zipper.path(), b"mane"); - assert_eq!(zipper.ascend(3), Ok(())); + assert_eq!(zipper.ascend(3), 3); assert_eq!(zipper.path(), b"m"); eprintln!("child_mask = {:?}", zipper.child_mask()); assert_eq!(zipper.child_count(), 3); @@ -3353,13 +3357,13 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_ascend_until_test1(mut zip: Z) { assert!(!zip.descend_to(b"AAaDDd")); assert_eq!(zip.path(), b"AAaDDd"); - assert_eq!(zip.ascend_until(), Some(3)); + assert_eq!(zip.ascend_until(), 3); assert_eq!(zip.path(), b"AAa"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"AA"); - assert_eq!(zip.ascend_until(), Some(2)); + assert_eq!(zip.ascend_until(), 2); assert_eq!(zip.path(), b""); - assert_eq!(zip.ascend_until(), None); + assert_eq!(zip.ascend_until(), 0); } // Test what's likely to be represented as a pair node @@ -3368,13 +3372,13 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_ascend_until_test2(mut zip: Z) { assert!(!zip.descend_to(b"AAaDDd")); assert_eq!(zip.path(), b"AAaDDd"); - assert_eq!(zip.ascend_until(), Some(3)); + assert_eq!(zip.ascend_until(), 3); assert_eq!(zip.path(), b"AAa"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"AA"); - assert_eq!(zip.ascend_until(), Some(2)); + assert_eq!(zip.ascend_until(), 2); assert_eq!(zip.path(), b""); - assert_eq!(zip.ascend_until(), None); + assert_eq!(zip.ascend_until(), 0); } /// Test a straight-line trie @@ -3384,27 +3388,27 @@ pub(crate) mod zipper_moving_tests { //First test that ascend_until stops when transitioning from non-existent path assert_eq!(zip.descend_to(b"123456"), false); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"12345"); //Test that ascend_until stops at each value - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"1234"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"123"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"12"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"1"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b""); - assert_eq!(zip.ascend_until(), None); + assert_eq!(zip.ascend_until(), 0); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values assert!(zip.descend_to(b"12345")); assert_eq!(zip.path(), b"12345"); - assert_eq!(zip.ascend_until_branch(), Some(5)); + assert_eq!(zip.ascend_until_branch(), 5); assert_eq!(zip.path(), b""); assert!(zip.at_root()); @@ -3416,33 +3420,33 @@ pub(crate) mod zipper_moving_tests { assert!(zip.descend_to(b"12345")); assert_eq!(zip.path(), b"12345"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"1234"); // "1234" is a branch only assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 2); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"123"); // "123" is a value only assert_eq!(zip.child_count(), 1); assert_eq!(zip.is_val(), true); - assert_eq!(zip.ascend_until(), Some(2)); // Jump over "12" because it's neither a branch nor a value + assert_eq!(zip.ascend_until(), 2); // Jump over "12" because it's neither a branch nor a value assert_eq!(zip.path(), b"1"); // "1" is both a branch and a value assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 2); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b""); assert_eq!(zip.child_count(), 1); - assert_eq!(zip.ascend_until(), None); + assert_eq!(zip.ascend_until(), 0); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values assert!(zip.descend_to(b"12345")); - assert_eq!(zip.ascend_until_branch(), Some(1)); + assert_eq!(zip.ascend_until_branch(), 1); assert_eq!(zip.path(), b"1234"); - assert_eq!(zip.ascend_until_branch(), Some(3)); + assert_eq!(zip.ascend_until_branch(), 3); assert_eq!(zip.path(), b"1"); - assert_eq!(zip.ascend_until_branch(), Some(1)); + assert_eq!(zip.ascend_until_branch(), 1); assert_eq!(zip.path(), b""); - assert_eq!(zip.ascend_until_branch(), None); + assert_eq!(zip.ascend_until_branch(), 0); assert!(zip.at_root()); } @@ -3454,33 +3458,33 @@ pub(crate) mod zipper_moving_tests { assert!(zip.descend_to(b"12345")); assert_eq!(zip.path(), b"12345"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"1234"); // "1234" is a branch only assert_eq!(zip.is_val(), false); assert_eq!(zip.child_count(), 2); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"123"); // "123" is a value only assert_eq!(zip.child_count(), 1); assert_eq!(zip.is_val(), true); - assert_eq!(zip.ascend_until(), Some(2)); // Jump over "12" because it's neither a branch nor a value + assert_eq!(zip.ascend_until(), 2); // Jump over "12" because it's neither a branch nor a value assert_eq!(zip.path(), b"1"); // "1" is both a branch and a value assert_eq!(zip.is_val(), true); assert_eq!(zip.child_count(), 2); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b""); assert_eq!(zip.child_count(), 1); - assert_eq!(zip.ascend_until(), None); + assert_eq!(zip.ascend_until(), 0); assert!(zip.at_root()); //Test that ascend_until_branch skips over all the values assert!(zip.descend_to(b"12345")); - assert_eq!(zip.ascend_until_branch(), Some(1)); + assert_eq!(zip.ascend_until_branch(), 1); assert_eq!(zip.path(), b"1234"); - assert_eq!(zip.ascend_until_branch(), Some(3)); + assert_eq!(zip.ascend_until_branch(), 3); assert_eq!(zip.path(), b"1"); - assert_eq!(zip.ascend_until_branch(), Some(1)); + assert_eq!(zip.ascend_until_branch(), 1); assert_eq!(zip.path(), b""); - assert_eq!(zip.ascend_until_branch(), None); + assert_eq!(zip.ascend_until_branch(), 0); assert!(zip.at_root()); } @@ -3491,15 +3495,15 @@ pub(crate) mod zipper_moving_tests { //Test that ascend_until stops when transitioning from non-existent path assert_eq!(zip.descend_to(b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB"), false); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); //Test that jump all the way back to where we want to be - assert_eq!(zip.ascend_until(), Some(126)); + assert_eq!(zip.ascend_until(), 126); assert_eq!(zip.path(), b"A"); - assert_eq!(zip.ascend_until(), Some(1)); + assert_eq!(zip.ascend_until(), 1); assert_eq!(zip.path(), b""); - assert_eq!(zip.ascend_until(), None); + assert_eq!(zip.ascend_until(), 0); } pub const ZIPPER_INDEXED_MOVEMENT_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; @@ -3512,7 +3516,7 @@ pub(crate) mod zipper_moving_tests { if *zipper.path().last().unwrap() == byte { break } else { - assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.ascend(1), 1); } } } @@ -3539,7 +3543,7 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.child_count(), 0); - assert_eq!(zipper.ascend(3), Ok(())); + assert_eq!(zipper.ascend(3), 3); assert_eq!(zipper.path(), b"ar"); assert_eq!(zipper.child_count(), 1); } @@ -3558,7 +3562,7 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.is_val(), true); zipper.descend_to(b"e"); assert_eq!(zipper.is_val(), true); - assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.ascend(1), 1); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); zipper.descend_until(None); @@ -4033,7 +4037,7 @@ pub(crate) mod zipper_iteration_tests { assert_eq!(zipper.path(), b"1a1C"); assert_eq!(zipper.to_next_k_path(1), false); assert_eq!(zipper.path(), b"1a1"); - assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.ascend(1), 1); assert_eq!(zipper.path(), b"1a"); assert_eq!(zipper.to_next_k_path(1), true); assert_eq!(zipper.path(), b"1b"); @@ -4173,7 +4177,7 @@ pub(crate) mod zipper_iteration_tests { //Try with a `descend_to` & `ascend` test_loop(&mut zipper, |zipper, path| assert!(zipper.descend_to(path)), - |zipper, steps| assert!(zipper.ascend(steps).is_ok()), + |zipper, steps| assert!(zipper.ascend(steps) == steps), ); //Try with a `descend_to_byte` & `ascend_byte` @@ -4325,7 +4329,7 @@ mod tests { zipper.descend_to(b"e"); assert_eq!(zipper.is_val(), true); assert_eq!(zipper.val(), Some(&"romane")); - assert_eq!(zipper.ascend(1), Ok(())); + assert_eq!(zipper.ascend(1), 1); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); assert_eq!(zipper.val(), None); diff --git a/src/zipper_head.rs b/src/zipper_head.rs index 96d6e595..641eb33c 100644 --- a/src/zipper_head.rs +++ b/src/zipper_head.rs @@ -1054,7 +1054,7 @@ mod tests { // Test the original zipper assert_eq!(z.val(), Some(&42)); assert_eq!(z.to_next_sibling_byte(), None); - assert_eq!(z.ascend_until(), Some(6)); + assert_eq!(z.ascend_until(), 6); assert_eq!(z.path(), b""); assert_eq!(z.origin_path(), b"A"); assert_eq!(z.val(), Some(&24)); @@ -1135,7 +1135,7 @@ mod tests { //Try pre-creating trie in the parent that will be visited by the child zipper b_zipper.descend_to(b"-children-0+metadata"); b_zipper.set_val(-3); - b_zipper.ascend(10).unwrap(); + assert_eq!(b_zipper.ascend(10), 10); //Make a ZipperHead on the WriteZipper, and make two more parallel zippers let b_head = b_zipper.zipper_head(); From 69fa7063284cfe227662b7d6317369eabb2e31fc Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 9 Oct 2025 20:56:26 -0500 Subject: [PATCH 08/18] renamed TrackPath to PathTracker --- src/lib.rs | 2 +- src/{track_path.rs => path_tracker.rs} | 40 +++++++++++++++++--------- src/zipper.rs | 2 +- 3 files changed, 28 insertions(+), 16 deletions(-) rename src/{track_path.rs => path_tracker.rs} (83%) diff --git a/src/lib.rs b/src/lib.rs index 6c2db677..4e7b9115 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,7 +90,7 @@ pub mod utils; pub mod experimental; /// Wrapper to allow tracking path for a blind zipper -pub mod track_path; +pub mod path_tracker; /// Compact representation of the trie #[cfg(feature = "arena_compact")] diff --git a/src/track_path.rs b/src/path_tracker.rs similarity index 83% rename from src/track_path.rs rename to src/path_tracker.rs index 90aa2c0a..853fa95e 100644 --- a/src/track_path.rs +++ b/src/path_tracker.rs @@ -11,13 +11,25 @@ use crate::{ /// /// This allows having nested virtual zippers that don't maintain their /// own path buffer, such that they don't repeat the work of copying paths. -pub struct TrackPath { +/// +/// Example: +/// ```rust +/// use crate::pathmap::zipper::{ZipperPath, ZipperMoving}; +/// // the example uses `PathMap`, but this works with any zipper. +/// let btm = pathmap::PathMap::from_iter([(b"hello", ())]); +/// let zipper = btm.read_zipper(); +/// let mut with_path = pathmap::zipper::PathTracker::new(zipper); +/// assert_eq!(with_path.descend_to_existing("hello"), 5); +/// println!("current path: {:?}", with_path.path()); +/// assert_eq!(with_path.path(), b"hello"); +/// ``` +pub struct PathTracker { zipper: Z, path: Vec, origin_len: usize, } -impl TrackPath { +impl PathTracker { pub fn new(mut zipper: Z) -> Self { zipper.reset(); Self { @@ -36,13 +48,13 @@ impl TrackPath { } } -impl Zipper for TrackPath { +impl Zipper for PathTracker { #[inline] fn path_exists(&self) -> bool { self.zipper.path_exists() } #[inline] fn is_val(&self) -> bool { self.zipper.is_val() } #[inline] fn child_count(&self) -> usize { self.zipper.child_count() } #[inline] fn child_mask(&self) -> ByteMask { self.zipper.child_mask() } } -impl ZipperMoving for TrackPath { +impl ZipperMoving for PathTracker { #[inline] fn at_root(&self) -> bool { self.zipper.at_root() } fn reset(&mut self) { self.zipper.reset(); @@ -132,26 +144,26 @@ impl ZipperMoving for TrackPath { // fn to_next_step(&mut self) -> bool; } -impl ZipperIteration for TrackPath { } +impl ZipperIteration for PathTracker { } -impl ZipperPath for TrackPath { +impl ZipperPath for PathTracker { fn path(&self) -> &[u8] { &self.path[self.origin_len..] } } -impl ZipperAbsolutePath for TrackPath { +impl ZipperAbsolutePath for PathTracker { fn origin_path(&self) -> &[u8] { &self.path } fn root_prefix_path(&self) -> &[u8] { &self.path[..self.origin_len] } } -impl, V> ZipperValues for TrackPath { +impl, V> ZipperValues for PathTracker { fn val(&self) -> Option<&V> { self.zipper.val() } } -impl<'a, Z: ZipperReadOnlyValues<'a, V>, V> ZipperReadOnlyValues<'a, V> for TrackPath { +impl<'a, Z: ZipperReadOnlyValues<'a, V>, V> ZipperReadOnlyValues<'a, V> for PathTracker { fn get_val(&self) -> Option<&'a V> { self.zipper.get_val() } } -impl<'a, Z: ZipperReadOnlyConditionalValues<'a, V>, V> ZipperReadOnlyConditionalValues<'a, V> for TrackPath { +impl<'a, Z: ZipperReadOnlyConditionalValues<'a, V>, V> ZipperReadOnlyConditionalValues<'a, V> for PathTracker { type WitnessT = Z::WitnessT; fn witness<'w>(&self) -> Self::WitnessT { self.zipper.witness() } fn get_val_with_witness<'w>(&self, witness: &'w Self::WitnessT) -> Option<&'w V> where 'a: 'w { @@ -159,7 +171,7 @@ impl<'a, Z: ZipperReadOnlyConditionalValues<'a, V>, V> ZipperReadOnlyConditional } } -impl ZipperPathBuffer for TrackPath { +impl ZipperPathBuffer for PathTracker { unsafe fn origin_path_assert_len(&self, len: usize) -> &[u8] { let ptr = self.path.as_ptr(); unsafe { core::slice::from_raw_parts(ptr, len) } @@ -172,7 +184,7 @@ impl ZipperPathBuffer for TrackPath { #[cfg(test)] mod tests { - use super::{TrackPath}; + use super::{PathTracker}; use crate::{ PathMap, zipper::{zipper_iteration_tests, zipper_moving_tests}, @@ -183,7 +195,7 @@ mod tests { keys.into_iter().map(|k| (k, ())).collect::>() }, |trie: &mut PathMap<()>, path: &[u8]| { - TrackPath::with_origin(trie.read_zipper_at_path(path), path) + PathTracker::with_origin(trie.read_zipper_at_path(path), path) } ); @@ -192,7 +204,7 @@ mod tests { keys.into_iter().map(|k| (k, ())).collect::>() }, |trie: &mut PathMap<()>, path: &[u8]| { - TrackPath::with_origin(trie.read_zipper_at_path(path), path) + PathTracker::with_origin(trie.read_zipper_at_path(path), path) } ); } diff --git a/src/zipper.rs b/src/zipper.rs index 543ddd85..63c44262 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -20,7 +20,7 @@ pub use crate::zipper_head::*; pub use crate::product_zipper::{ProductZipper, ProductZipperG}; pub use crate::overlay_zipper::{OverlayZipper}; pub use crate::prefix_zipper::{PrefixZipper}; -pub use crate::track_path::{TrackPath}; +pub use crate::path_tracker::{PathTracker}; pub use crate::empty_zipper::{EmptyZipper}; pub use crate::poly_zipper::PolyZipper; use crate::zipper_tracking::*; From 6336a68786a421ebb8565bde2973e14d2a605b87 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Thu, 9 Oct 2025 20:15:15 -0600 Subject: [PATCH 09/18] Updating ZipperMoving API as per discussion in github PR for blind_zippers. Making all `ascend_` variants always return the number of bytes ascended UPDATE: Merging with Igor's nearly identical work --- src/empty_zipper.rs | 5 +++-- src/experimental.rs | 7 ++++--- src/overlay_zipper.rs | 2 +- src/prefix_zipper.rs | 12 ++++-------- src/product_zipper.rs | 9 ++++++++- src/write_zipper.rs | 4 ++-- src/zipper.rs | 28 ++++++++++++---------------- 7 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/empty_zipper.rs b/src/empty_zipper.rs index 4294c51f..78f603a9 100644 --- a/src/empty_zipper.rs +++ b/src/empty_zipper.rs @@ -47,9 +47,10 @@ impl ZipperMoving for EmptyZipper { fn descend_first_byte(&mut self) -> Option { None } fn descend_until(&mut self, _dst_path: Option<&mut Vec>) -> bool { false } fn ascend(&mut self, steps: usize) -> usize { - if steps > self.path.len() - self.path_start_idx { + let old_path_len = self.path.len() - self.path_start_idx; + if steps > old_path_len { self.reset(); - self.path.len() - self.path_start_idx + old_path_len } else { self.path.truncate(self.path.len() - self.path_start_idx - steps); steps diff --git a/src/experimental.rs b/src/experimental.rs index 856e57c2..13d32f75 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -62,8 +62,9 @@ impl ZipperMoving for FullZipper { } fn ascend(&mut self, steps: usize) -> usize { if steps > self.path.len() { + let old_depth = self.path.len(); self.path.clear(); - self.path.len() + old_depth } else { self.path.truncate(self.path.len() - steps); steps @@ -73,10 +74,10 @@ impl ZipperMoving for FullZipper { self.path.pop().is_some() } fn ascend_until(&mut self) -> usize { - self.path.pop().is_some() as usize // not sure? + self.ascend(1) } fn ascend_until_branch(&mut self) -> usize { - self.path.pop().is_some() as usize // not sure? What's the difference with the previous? + self.ascend(1) } fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling(true) } fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index 26d8e1d9..73e50019 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -380,7 +380,7 @@ use super::{OverlayZipper}; |keys: &[&[u8]]| { let cutoff = keys.len() / 3 * 2; // eprintln!("keys={:?}", &keys); - eprintln!("a_keys={:?}\nb_keys={:?}", &keys[..cutoff], &keys[cutoff..]); + // eprintln!("a_keys={:?}\nb_keys={:?}", &keys[..cutoff], &keys[cutoff..]); let a = keys[..cutoff].into_iter().map(|k| (k, ())).collect::>(); let b = keys[cutoff..].into_iter().map(|k| (k, ())).collect::>(); (a, b) diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index fc8a2042..638fb8c7 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -128,10 +128,8 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> steps }; } + if self.position.is_source() { - // let Err(remaining) = self.source.ascend(remaining) else { - // return Ok(()); - // }; let len_before = self.source.path().len(); if self.source.ascend(remaining) == remaining { return steps @@ -162,11 +160,11 @@ impl<'prefix, Z> PrefixZipper<'prefix, Z> // } let len_before = self.source.path().len(); let was_good = if VAL { - self.source.ascend_until() + self.source.ascend_until() > 0 } else { - self.source.ascend_until_branch() + self.source.ascend_until_branch() > 0 }; - if was_good > 0 && ((VAL && self.source.is_val()) || self.source.child_count() > 1) { + if was_good && ((VAL && self.source.is_val()) || self.source.child_count() > 1) { let len_after = self.source.path().len(); return len_before - len_after; } @@ -418,14 +416,12 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> #[inline] fn ascend_until(&mut self) -> usize { let ascended = self.ascend_until_n::(); - if ascended == 0 { return 0; } self.path.truncate(self.path.len() - ascended); ascended } #[inline] fn ascend_until_branch(&mut self) -> usize { let ascended = self.ascend_until_n::(); - if ascended == 0 { return 0; } self.path.truncate(self.path.len() - ascended); ascended } diff --git a/src/product_zipper.rs b/src/product_zipper.rs index 12e884f5..17d59329 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -504,7 +504,7 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, let Some(&byte) = self.path().last() else { return None; }; - assert!(self.ascend(1) == 1, "must ascend"); + debug_assert_eq!(self.ascend(1), 1, "must ascend"); let child_mask = self.child_mask(); let Some(sibling_byte) = (if next { child_mask.next_bit(byte) @@ -1376,6 +1376,13 @@ mod tests { assert_eq!(pz.path_indices()[1], 16); assert_eq!(pz.path(), b"nopqrstuvwxyzbowph"); } + + //GOAT. We want another PZ test, now that the PZ behavior has changed to not require values. + // This test ought to assembled a map with a single dangling path, stitch multiple of them + // together, so the resulting virtual product trie is just one long path with repetitions, + // and then validate that ascend, ascend_until, ascend_until_branch, etc. all do the right + // thing traversing across multiple factors, not stopping spuriously at the factor stitch points. + } // --- END OF MACRO GENERATED MOD --- }; diff --git a/src/write_zipper.rs b/src/write_zipper.rs index eba0f2e6..81c4e487 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -1608,10 +1608,10 @@ impl <'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> WriteZipperC let downstream_node = self.get_focus().into_option(); - let fully_ascended = self.ascend(n); + let fully_ascended = self.ascend(n) == n; self.graft_internal(downstream_node); - fully_ascended == n + fully_ascended } /// See [ZipperWriting::meet_into] pub fn meet_into>(&mut self, read_zipper: &Z, prune: bool) -> AlgebraicStatus where V: Lattice { diff --git a/src/zipper.rs b/src/zipper.rs index 63c44262..019ab22a 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -281,12 +281,10 @@ pub trait ZipperMoving: Zipper { descended } - /// Ascends the zipper `steps` steps. + /// Ascends the zipper `steps` steps. Returns the number of bytes ascended /// - /// Returns the number of ascended steps. - /// - /// If the root is fewer than `steps` steps from the zipper's position, then this method will stop at - /// the root and return the number of ascended steps. + /// If the zipper's focus is fewer than `n` steps from the root, then this method will stop at + /// the root and return the number of steps ascended, which may be smaller than `steps`. fn ascend(&mut self, steps: usize) -> usize; /// Ascends the zipper up a single byte. Equivalent to passing `1` to [ascend](Self::ascend) @@ -294,18 +292,16 @@ pub trait ZipperMoving: Zipper { self.ascend(1) == 1 } - /// Ascends the zipper to the nearest upstream branch point or value. - /// - /// Returns the number of ascended steps. If at root, doesn't move and returns 0. - /// - /// NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. + /// Ascends the zipper to the nearest upstream branch point or value. Returns the number of bytes + /// ascended. Returns `0` if the zipper was already at the root + // + // NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. fn ascend_until(&mut self) -> usize; - /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. - /// - /// Returns the number of ascended steps. If at root, doesn't move and returns 0. - /// - /// NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. + /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. Returns + /// the number of bytes ascended. Returns `0` if the zipper was already at the root + // + // NOTE: A default implementation could be provided, but all current zippers have more optimal native implementations. fn ascend_until_branch(&mut self) -> usize; /// Moves the zipper's focus to the next sibling byte with the same parent @@ -4177,7 +4173,7 @@ pub(crate) mod zipper_iteration_tests { //Try with a `descend_to` & `ascend` test_loop(&mut zipper, |zipper, path| assert!(zipper.descend_to(path)), - |zipper, steps| assert!(zipper.ascend(steps) == steps), + |zipper, steps| assert_eq!(zipper.ascend(steps), steps), ); //Try with a `descend_to_byte` & `ascend_byte` From 302690e8c2afd57e1a74c3080d1bc1d6748c741a Mon Sep 17 00:00:00 2001 From: Igor Malovitsa Date: Thu, 9 Oct 2025 22:20:54 -0500 Subject: [PATCH 10/18] implemented new APIs for arena_compact --- src/arena_compact.rs | 107 ++++++++++++++++++++++++------------------- src/viz.rs | 2 +- 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/src/arena_compact.rs b/src/arena_compact.rs index 1fd5c99a..e5c67eb3 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -86,7 +86,7 @@ use crate::{ utils::{BitMask, ByteMask, find_prefix_overlap}, zipper::{ Zipper, ZipperValues, ZipperForking, ZipperAbsolutePath, ZipperIteration, - ZipperMoving, ZipperPathBuffer, ZipperReadOnlyValues, + ZipperMoving, ZipperPathBuffer, ZipperPath, ZipperReadOnlyValues, ZipperConcrete, ZipperReadOnlyConditionalValues, }, }; @@ -834,7 +834,7 @@ impl ArenaCompactTree> { /// let tree1 = ArenaCompactTree::from_zipper(btm.read_zipper(), |_v| 0); /// let mut zipper = tree1.read_zipper(); /// for path in items { - /// use pathmap::zipper::ZipperMoving; + /// use pathmap::zipper::{ZipperMoving, ZipperPath}; /// zipper.reset(); /// assert!(zipper.descend_to_existing(path) == path.len()); /// assert_eq!(zipper.path(), path.as_bytes()); @@ -1502,24 +1502,23 @@ where Storage: AsRef<[u8]> self.invalid == 0 } - fn ascend_to_branch(&mut self, need_value: bool) -> bool { + fn ascend_to_branch(&mut self, need_value: bool) -> usize { self.trace_pos(); - let mut moved = false; + let orig_len = self.path.len(); if self.invalid > 0 { - moved = true; if !self.ascend_invalid(None) { - return false; + return orig_len - self.path.len(); } match &self.cur_node { Node::Line(line) => { if need_value && line.value.is_some() { - return true; + return orig_len - self.path.len(); } } Node::Branch(node) => { if need_value && node.value.is_some() { - return true; + return orig_len - self.path.len(); } } } @@ -1529,13 +1528,11 @@ where Storage: AsRef<[u8]> let mut this_steps = top_frame.node_depth .min(self.path.len() - self.origin_depth); top_frame.node_depth = 0; - moved |= this_steps > 0; if self.stack.len() > 1 { self.stack.pop(); let prev = self.stack.last().unwrap(); self.cur_node = self.tree.get_node(prev.node_id).0; nchildren = prev.child_count; - moved = true; this_steps += 1; } self.path.truncate(self.path.len() - this_steps); @@ -1550,7 +1547,7 @@ where Storage: AsRef<[u8]> break; } } - moved + return orig_len - self.path.len(); } fn descend_cond(&mut self, path: &[u8], on_value: bool) -> usize { @@ -1615,26 +1612,27 @@ where Storage: AsRef<[u8]> descended } - fn to_sibling(&mut self, next: bool) -> bool { + fn to_sibling(&mut self, next: bool) -> Option { let top_frame = self.stack.last().unwrap(); if self.stack.len() <= 1 || top_frame.node_depth > 0 { // can't move to sibling at root, or along the path - return false; + return None; } let top2_frame = &self.stack[self.stack.len() - 2]; let sibling_idx = if next { let idx = top2_frame.child_index + 1; if idx >= top2_frame.child_count { - return false; + return None; } idx } else { if top2_frame.child_index == 0 { - return false; + return None; } top2_frame.child_index - 1 }; - self.ascend(1) && self.descend_indexed_byte(sibling_idx) + debug_assert_eq!(self.ascend(1), 1); + self.descend_indexed_byte(sibling_idx) } } @@ -1716,6 +1714,12 @@ where Storage: AsRef<[u8]> } } +impl<'tree, Storage, Value> ZipperPath for ACTZipper<'tree, Storage, Value> +where Storage: AsRef<[u8]> +{ + /// Returns the path from the zipper's root to the current focus + fn path(&self) -> &[u8] { &self.path[self.origin_depth..] } +} /// An interface to enable moving a zipper around the trie and inspecting paths impl<'tree, Storage, Value> ZipperMoving for ACTZipper<'tree, Storage, Value> where Storage: AsRef<[u8]> @@ -1734,9 +1738,6 @@ where Storage: AsRef<[u8]> self.invalid = 0; } - /// Returns the path from the zipper's root to the current focus - fn path(&self) -> &[u8] { &self.path[self.origin_depth..] } - /// Returns the total number of values contained at and below the zipper's focus, including the focus itself /// /// WARNING: This is not a cheap method. It may have an order-N cost @@ -1808,11 +1809,12 @@ where Storage: AsRef<[u8]> /// WARNING: The branch represented by a given index is not guaranteed to be stable across modifications /// to the trie. This method should only be used as part of a directed traversal operation, but /// index-based paths may not be stored as locations within the trie. - fn descend_indexed_byte(&mut self, idx: usize) -> bool { + fn descend_indexed_byte(&mut self, idx: usize) -> Option { if self.invalid > 0 { - return false; + return None; } self.trace_pos(); + let byte; let mut child_id: Option = None; match &self.cur_node { Node::Line(line) => { @@ -1820,22 +1822,23 @@ where Storage: AsRef<[u8]> let path = self.tree.get_line(line.path); let rest_path = &path[top_frame.node_depth..]; if idx != 0 || rest_path.is_empty() { - return false; + return None; } self.path.push(rest_path[0]); + byte = Some(rest_path[0]); if let (true, Some(line_child)) = (rest_path.len() == 1, line.child) { child_id = Some(line_child); } else { top_frame.node_depth += 1; - return true; + return byte; } } Node::Branch(node) => { let top_frame = self.stack.last_mut().unwrap(); if idx > top_frame.child_count { - return false; + return None; } - let byte = node.bytemask.indexed_bit::(idx); + byte = node.bytemask.indexed_bit::(idx); if let Some(byte) = byte { if top_frame.next_id.is_some() && top_frame.child_index + 1 == idx { child_id = top_frame.next_id; @@ -1855,22 +1858,23 @@ where Storage: AsRef<[u8]> self.stack.push(StackFrame::from(&node, child_id)); self.cur_node = node; } - child_id.is_some() + byte } /// Descends the zipper's focus one step into the first child branch in a depth-first traversal /// /// NOTE: This method should have identical behavior to passing `0` to [descend_indexed_byte](ZipperMoving::descend_indexed_byte), /// although with less overhead - fn descend_first_byte(&mut self) -> bool { + fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } /// Descends the zipper's focus until a branch or a value is encountered. Returns `true` if the focus /// moved otherwise returns `false` - fn descend_until(&mut self) -> bool { + fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.trace_pos(); let mut descended = false; + let orig_len = self.path.len(); 'descend: while self.child_count() == 1 { let child_id; match &self.cur_node { @@ -1911,6 +1915,9 @@ where Storage: AsRef<[u8]> } } } + if let Some(dst) = dst { + dst.extend_from_slice(&self.path[orig_len..]); + } descended } @@ -1918,26 +1925,27 @@ where Storage: AsRef<[u8]> /// /// If the root is fewer than `n` steps from the zipper's position, then this method will stop at /// the root and return `false` - fn ascend(&mut self, mut steps: usize) -> bool { + fn ascend(&mut self, steps: usize) -> usize { + let mut remaining = steps; self.trace_pos(); - if !self.ascend_invalid(Some(&mut steps)) { - return false; + if !self.ascend_invalid(Some(&mut remaining)) { + return steps - remaining; } while let Some(top_frame) = self.stack.last_mut() { let rest_path = &self.path[self.origin_depth..]; - let mut this_steps = steps.min(top_frame.node_depth).min(rest_path.len()); + let mut this_steps = remaining.min(top_frame.node_depth).min(rest_path.len()); top_frame.node_depth -= this_steps; - steps -= this_steps; - if top_frame.node_depth == 0 && self.stack.len() > 1 && steps > 0 { + remaining -= this_steps; + if top_frame.node_depth == 0 && self.stack.len() > 1 && remaining > 0 { self.stack.pop(); let prev = self.stack.last().unwrap(); self.cur_node = self.tree.get_node(prev.node_id).0; this_steps += 1; - steps -= 1; + remaining -= 1; } self.path.truncate(self.path.len() - this_steps); - if self.at_root() || steps == 0 { - return steps == 0 && this_steps > 0; + if self.at_root() || remaining == 0 { + return steps - remaining; } } unreachable!(); @@ -1945,29 +1953,29 @@ where Storage: AsRef<[u8]> /// Ascends the zipper up a single byte. Equivalent to passing `1` to [ascend](Self::ascend) fn ascend_byte(&mut self) -> bool { - self.ascend(1) + self.ascend(1) == 1 } /// Ascends the zipper to the nearest upstream branch point or value. Returns `true` if the zipper /// focus moved upwards, otherwise returns `false` if the zipper was already at the root - fn ascend_until(&mut self) -> bool { + fn ascend_until(&mut self) -> usize { self.ascend_to_branch(true) } /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. Returns /// `true` if the zipper focus moved upwards, otherwise returns `false` if the zipper was already at the /// root - fn ascend_until_branch(&mut self) -> bool { + fn ascend_until_branch(&mut self) -> usize { self.ascend_to_branch(false) } #[inline] - fn to_next_sibling_byte(&mut self) -> bool { + fn to_next_sibling_byte(&mut self) -> Option { self.to_sibling(true) } #[inline] - fn to_prev_sibling_byte(&mut self) -> bool { + fn to_prev_sibling_byte(&mut self) -> Option { self.to_sibling(false) } @@ -2003,7 +2011,7 @@ where Storage: AsRef<[u8]> /// See: [to_next_k_path](ZipperIteration::to_next_k_path) fn descend_first_k_path(&mut self, k: usize) -> bool { for ii in 0..k { - if !self.descend_first_byte() { + if self.descend_first_byte().is_none() { self.ascend(ii); return false; } @@ -2026,7 +2034,7 @@ where Storage: AsRef<[u8]> let mut depth = k; 'outer: loop { while depth > 0 && self.child_count() <= 1 { - if !self.ascend(1) { + if self.ascend(1) != 1 { break 'outer; } depth -= 1; @@ -2034,16 +2042,16 @@ where Storage: AsRef<[u8]> let stack = self.stack.last_mut().unwrap(); let idx = stack.child_index + 1; if idx >= stack.child_count { - if depth == 0 || !self.ascend(1) { + if depth == 0 || self.ascend(1) != 1 { break 'outer; } depth -= 1; continue 'outer; } - assert!(self.descend_indexed_byte(idx)); + assert!(self.descend_indexed_byte(idx).is_some()); depth += 1; for _ii in 0..k - depth { - if !self.descend_first_byte() { + if self.descend_first_byte().is_none() { continue 'outer; } depth += 1; @@ -2059,7 +2067,10 @@ where Storage: AsRef<[u8]> mod tests { use super::{ArenaCompactTree, ACTZipper}; use crate::{ - morphisms::Catamorphism, PathMap, zipper::{zipper_iteration_tests, zipper_moving_tests, ZipperIteration, ZipperMoving, ZipperValues} + morphisms::Catamorphism, PathMap, zipper::{ + zipper_iteration_tests, zipper_moving_tests, + ZipperIteration, ZipperPath, ZipperValues + }, }; zipper_moving_tests::zipper_moving_tests!(arena_compact_zipper, diff --git a/src/viz.rs b/src/viz.rs index 015f1622..798514c8 100644 --- a/src/viz.rs +++ b/src/viz.rs @@ -225,7 +225,7 @@ fn viz_zipper_logical Date: Fri, 10 Oct 2025 00:42:36 -0600 Subject: [PATCH 11/18] Implementing blind zipper changes for viz, counters, and arena_compact Agh! We did it again. Both implemented the same changes at the same time --- src/arena_compact.rs | 62 ++++++++++++++++++++++++-------------------- src/counters.rs | 2 +- src/path_tracker.rs | 7 ++--- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/arena_compact.rs b/src/arena_compact.rs index e5c67eb3..f2bc7bbd 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -86,7 +86,7 @@ use crate::{ utils::{BitMask, ByteMask, find_prefix_overlap}, zipper::{ Zipper, ZipperValues, ZipperForking, ZipperAbsolutePath, ZipperIteration, - ZipperMoving, ZipperPathBuffer, ZipperPath, ZipperReadOnlyValues, + ZipperMoving, ZipperPath, ZipperPathBuffer, ZipperReadOnlyValues, ZipperConcrete, ZipperReadOnlyConditionalValues, }, }; @@ -828,13 +828,12 @@ impl ArenaCompactTree> { /// Construct [ArenaCompactTree] from a read zipper. /// # Examples /// ``` - /// use pathmap::{PathMap, arena_compact::ArenaCompactTree}; + /// use pathmap::{PathMap, zipper::*, arena_compact::ArenaCompactTree}; /// let items = ["ace", "acf", "adg", "adh", "bjk"]; /// let btm = PathMap::from_iter(items.iter().map(|i| (i, ()))); /// let tree1 = ArenaCompactTree::from_zipper(btm.read_zipper(), |_v| 0); /// let mut zipper = tree1.read_zipper(); /// for path in items { - /// use pathmap::zipper::{ZipperMoving, ZipperPath}; /// zipper.reset(); /// assert!(zipper.descend_to_existing(path) == path.len()); /// assert_eq!(zipper.path(), path.as_bytes()); @@ -1487,38 +1486,44 @@ where Storage: AsRef<[u8]> Some(value) } - fn ascend_invalid(&mut self, limit: Option<&mut usize>) -> bool { + /// Ascends any non-existent portion of the path. Returns the number of steps ascended + /// + /// `limit` sets an upper-bound on the number of steps that will be ascended, and contains + /// the number of steps un-ascended upon return (i.e. if the zipper is already at an + /// existent, aka valid, path) + fn ascend_invalid(&mut self, limit: Option) -> usize { if self.invalid == 0 { - return true; + return 0; } let len = self.path.len(); let mut invalid_cut = self.invalid.min(len - self.origin_depth); if let Some(limit) = limit { - invalid_cut = invalid_cut.min(*limit); - *limit -= invalid_cut; + invalid_cut = invalid_cut.min(limit); } self.path.truncate(len - invalid_cut); self.invalid = self.invalid - invalid_cut; - self.invalid == 0 + invalid_cut } + /// Returns the number of steps ascended fn ascend_to_branch(&mut self, need_value: bool) -> usize { self.trace_pos(); - let orig_len = self.path.len(); + let mut ascended = 0; if self.invalid > 0 { - if !self.ascend_invalid(None) { - return orig_len - self.path.len(); + ascended += self.ascend_invalid(None); + if self.invalid > 0 { + return ascended; } match &self.cur_node { Node::Line(line) => { if need_value && line.value.is_some() { - return orig_len - self.path.len(); + return ascended; } } Node::Branch(node) => { if need_value && node.value.is_some() { - return orig_len - self.path.len(); + return ascended; } } } @@ -1536,6 +1541,7 @@ where Storage: AsRef<[u8]> this_steps += 1; } self.path.truncate(self.path.len() - this_steps); + ascended += this_steps; // eprintln!("path={:?}", self.path); let brk = match &self.cur_node { Node::Branch(node) => { @@ -1547,7 +1553,7 @@ where Storage: AsRef<[u8]> break; } } - return orig_len - self.path.len(); + ascended } fn descend_cond(&mut self, path: &[u8], on_value: bool) -> usize { @@ -1631,8 +1637,11 @@ where Storage: AsRef<[u8]> } top2_frame.child_index - 1 }; - debug_assert_eq!(self.ascend(1), 1); - self.descend_indexed_byte(sibling_idx) + let ascended = self.ascend_byte(); + debug_assert!(ascended); + let result = self.descend_indexed_byte(sibling_idx); + debug_assert!(result.is_some()); + result } } @@ -1720,6 +1729,7 @@ where Storage: AsRef<[u8]> /// Returns the path from the zipper's root to the current focus fn path(&self) -> &[u8] { &self.path[self.origin_depth..] } } + /// An interface to enable moving a zipper around the trie and inspecting paths impl<'tree, Storage, Value> ZipperMoving for ACTZipper<'tree, Storage, Value> where Storage: AsRef<[u8]> @@ -1926,10 +1936,11 @@ where Storage: AsRef<[u8]> /// If the root is fewer than `n` steps from the zipper's position, then this method will stop at /// the root and return `false` fn ascend(&mut self, steps: usize) -> usize { - let mut remaining = steps; self.trace_pos(); - if !self.ascend_invalid(Some(&mut remaining)) { - return steps - remaining; + let mut remaining = steps; + remaining -= self.ascend_invalid(Some(steps)); + if remaining == 0 { + return steps; } while let Some(top_frame) = self.stack.last_mut() { let rest_path = &self.path[self.origin_depth..]; @@ -1951,20 +1962,14 @@ where Storage: AsRef<[u8]> unreachable!(); } - /// Ascends the zipper up a single byte. Equivalent to passing `1` to [ascend](Self::ascend) fn ascend_byte(&mut self) -> bool { self.ascend(1) == 1 } - /// Ascends the zipper to the nearest upstream branch point or value. Returns `true` if the zipper - /// focus moved upwards, otherwise returns `false` if the zipper was already at the root fn ascend_until(&mut self) -> usize { self.ascend_to_branch(true) } - /// Ascends the zipper to the nearest upstream branch point, skipping over values along the way. Returns - /// `true` if the zipper focus moved upwards, otherwise returns `false` if the zipper was already at the - /// root fn ascend_until_branch(&mut self) -> usize { self.ascend_to_branch(false) } @@ -2034,7 +2039,7 @@ where Storage: AsRef<[u8]> let mut depth = k; 'outer: loop { while depth > 0 && self.child_count() <= 1 { - if self.ascend(1) != 1 { + if !self.ascend_byte() { break 'outer; } depth -= 1; @@ -2042,13 +2047,14 @@ where Storage: AsRef<[u8]> let stack = self.stack.last_mut().unwrap(); let idx = stack.child_index + 1; if idx >= stack.child_count { - if depth == 0 || self.ascend(1) != 1 { + if depth == 0 || !self.ascend_byte() { break 'outer; } depth -= 1; continue 'outer; } - assert!(self.descend_indexed_byte(idx).is_some()); + let descended = self.descend_indexed_byte(idx); + debug_assert!(descended.is_some()); depth += 1; for _ii in 0..k - depth { if self.descend_first_byte().is_none() { diff --git a/src/counters.rs b/src/counters.rs index 140e1e36..35e7154c 100644 --- a/src/counters.rs +++ b/src/counters.rs @@ -4,7 +4,7 @@ use crate::trie_node::{TaggedNodeRef, TrieNode}; /// Example usage of counters /// -/// ``` +/// ```ignore /// pathmap::counters::print_traversal(&map.read_zipper()); /// let counters = pathmap::counters::Counters::count_ocupancy(&map); /// counters.print_histogram_by_depth(); diff --git a/src/path_tracker.rs b/src/path_tracker.rs index 853fa95e..533ebcbc 100644 --- a/src/path_tracker.rs +++ b/src/path_tracker.rs @@ -7,10 +7,11 @@ use crate::{ }, }; -/// Wrapper for blind zippers that allows tracking path +/// Wrapper to implement [`ZipperPaths`] for zipper types that implement `ZipperMoving`. +/// This is useful for tracking the path of "blind" zipper types /// -/// This allows having nested virtual zippers that don't maintain their -/// own path buffer, such that they don't repeat the work of copying paths. +/// The "blind" zipper pattern enables nested virtual zippers to efficiently compose, +/// without repeating the work of copying paths. /// /// Example: /// ```rust From 031b30be924e4b0cbf3fbfa4cf9ac24963fa6f0d Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Fri, 10 Oct 2025 01:06:44 -0600 Subject: [PATCH 12/18] Oops. Important stuff was happening inside a debug_assert! breaking release builds --- src/product_zipper.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/product_zipper.rs b/src/product_zipper.rs index 17d59329..ff0f4ee3 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -504,7 +504,8 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ProductZipperG<'trie, PrimaryZ, SecondaryZ, let Some(&byte) = self.path().last() else { return None; }; - debug_assert_eq!(self.ascend(1), 1, "must ascend"); + let ascended = self.ascend(1); + debug_assert_eq!(ascended, 1, "must ascend"); let child_mask = self.child_mask(); let Some(sibling_byte) = (if next { child_mask.next_bit(byte) From 11ac984b0ee5ce774ebf82376ed30db9385e44ae Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Fri, 10 Oct 2025 03:25:43 -0600 Subject: [PATCH 13/18] Fixing issue with prefix_zipper that was causing failures in ACT tests --- src/prefix_zipper.rs | 3 ++- src/zipper.rs | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index 638fb8c7..284eb5e8 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -359,7 +359,8 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { let mask = self.child_mask(); let byte = mask.indexed_bit::(child_idx)?; - debug_assert!(self.descend_to_byte(byte)); + let descended = self.descend_to_byte(byte); + debug_assert!(descended); Some(byte) } diff --git a/src/zipper.rs b/src/zipper.rs index 019ab22a..0e3a5a5e 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -3505,10 +3505,11 @@ pub(crate) mod zipper_moving_tests { pub const ZIPPER_INDEXED_MOVEMENT_TEST1_KEYS: &[&[u8]] = &[b"arrow", b"bow", b"cannon", b"romane", b"romanus", b"romulus", b"rubens", b"ruber", b"rubicon", b"rubicundus", b"rom'i"]; pub fn indexed_zipper_movement1(mut zipper: Z) { - //descends a single specific byte using `descend_indexed_byte`. Just for testing. A real user would use `descend_towards` + //descends a single specific byte using `descend_indexed_byte`. Just for testing. A real user would use `descend_to` or `descend_to_byte` fn descend_byte(zipper: &mut Z, byte: u8) { for i in 0..zipper.child_count() { - assert!(zipper.descend_indexed_byte(i).is_some()); + let descended = zipper.descend_indexed_byte(i); + assert!(descended.is_some()); if *zipper.path().last().unwrap() == byte { break } else { From 18e20806af523d5be9520e71e9c784669971db97 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Fri, 10 Oct 2025 23:29:28 -0600 Subject: [PATCH 14/18] Updating `ZipperMoving::descend_until` to accept generic write sink as opposed to `Option<&mut Vec>` and fixing a couple other bugs & todos along the way --- benches/oeis.rs | 6 +-- pathmap-derive/src/lib.rs | 4 +- src/arena_compact.rs | 8 +--- src/empty_zipper.rs | 2 +- src/experimental.rs | 6 +-- src/morphisms.rs | 4 +- src/overlay_zipper.rs | 16 +++++-- src/path_tracker.rs | 8 ++-- src/prefix_zipper.rs | 4 +- src/product_zipper.rs | 14 ++++-- src/utils/debug/diff_zipper.rs | 10 ++-- src/utils/ints.rs | 2 +- src/write_zipper.rs | 8 ++-- src/zipper.rs | 84 +++++++++++----------------------- src/zipper_head.rs | 6 +-- 15 files changed, 79 insertions(+), 103 deletions(-) diff --git a/benches/oeis.rs b/benches/oeis.rs index 03a36aa4..7a532a69 100644 --- a/benches/oeis.rs +++ b/benches/oeis.rs @@ -1,13 +1,13 @@ use std::io::Read; use std::usize; use pathmap::PathMap; -use pathmap::zipper::{Zipper, ZipperValues, ZipperMoving, ZipperWriting, ZipperCreation}; +use pathmap::zipper::{Zipper, ZipperValues, ZipperMoving, ZipperPath, ZipperWriting, ZipperCreation}; use num::BigInt; use divan::{Divan, Bencher, black_box}; const MAX_OFFSET: u8 = 10; -fn drop_symbol_head_byte + Zipper + ZipperMoving>(loc: &mut Z) { +fn drop_symbol_head_byte + Zipper + ZipperMoving + ZipperPath>(loc: &mut Z) { let mut it = loc.child_mask().iter(); let p = loc.path().to_vec(); @@ -15,7 +15,7 @@ fn drop_symbol_head_byte + Zipper + ZipperMoving>(loc: & if b == 0 { continue } assert!(loc.descend_to(&[b])); loc.join_k_path_into(b as usize, true); - assert!(loc.ascend(1)); + assert_eq!(loc.ascend(1), 1); } loc.reset(); loc.descend_to(&p[..]); diff --git a/pathmap-derive/src/lib.rs b/pathmap-derive/src/lib.rs index c1e0bc87..ff4d8a92 100644 --- a/pathmap-derive/src/lib.rs +++ b/pathmap-derive/src/lib.rs @@ -300,9 +300,9 @@ pub fn derive_poly_zipper(input: TokenStream) -> TokenStream { } } - fn descend_until(&mut self, mut dst_path: Option<&mut Vec>) -> bool { + fn descend_until(&mut self, desc_bytes: W) -> bool { match self { - #(#variant_arms => inner.descend_until(dst_path),)* + #(#variant_arms => inner.descend_until(desc_bytes),)* } } diff --git a/src/arena_compact.rs b/src/arena_compact.rs index f2bc7bbd..5f419b84 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -1879,9 +1879,7 @@ where Storage: AsRef<[u8]> self.descend_indexed_byte(0) } - /// Descends the zipper's focus until a branch or a value is encountered. Returns `true` if the focus - /// moved otherwise returns `false` - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { + fn descend_until(&mut self, mut desc_bytes: W) -> bool { self.trace_pos(); let mut descended = false; let orig_len = self.path.len(); @@ -1925,9 +1923,7 @@ where Storage: AsRef<[u8]> } } } - if let Some(dst) = dst { - dst.extend_from_slice(&self.path[orig_len..]); - } + let _ = desc_bytes.write_all(&self.path[orig_len..]); descended } diff --git a/src/empty_zipper.rs b/src/empty_zipper.rs index 78f603a9..f9a03d54 100644 --- a/src/empty_zipper.rs +++ b/src/empty_zipper.rs @@ -45,7 +45,7 @@ impl ZipperMoving for EmptyZipper { } fn descend_indexed_byte(&mut self, _idx: usize) -> Option { None } fn descend_first_byte(&mut self) -> Option { None } - fn descend_until(&mut self, _dst_path: Option<&mut Vec>) -> bool { false } + fn descend_until(&mut self, _desc_bytes: W) -> bool { false } fn ascend(&mut self, steps: usize) -> usize { let old_path_len = self.path.len() - self.path_start_idx; if steps > old_path_len { diff --git a/src/experimental.rs b/src/experimental.rs index 13d32f75..fef05675 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -55,10 +55,8 @@ impl ZipperMoving for FullZipper { self.path.push(0); Some(0) } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { - self.path.push(0); // not sure? - if let Some(dst) = dst { dst.push(0) } - true + fn descend_until(&mut self, _desc_bytes: W) -> bool { + false } fn ascend(&mut self, steps: usize) -> usize { if steps > self.path.len() { diff --git a/src/morphisms.rs b/src/morphisms.rs index a37dba3a..dacef1bc 100644 --- a/src/morphisms.rs +++ b/src/morphisms.rs @@ -415,7 +415,7 @@ fn cata_side_effect_body<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(mut z: //Descend to the next forking point, or leaf let mut is_leaf = false; while z.child_count() < 2 { - if !z.descend_until(None) { + if !z.descend_until(std::io::sink()) { is_leaf = true; break; } @@ -735,7 +735,7 @@ fn into_cata_cached_body<'a, Z, V: 'a, W, E, AlgF, Cache, const JUMPING: bool>( // Descend until leaf or branch let mut is_leaf = false; 'descend: while zipper.child_count() < 2 { - if !zipper.descend_until(None) { + if !zipper.descend_until(std::io::sink()) { is_leaf = true; break 'descend; } diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index 73e50019..0c3a8c34 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -212,11 +212,10 @@ impl ZipperMoving Some(byte) } - fn descend_until(&mut self, _dst: Option<&mut Vec>) -> bool { - // TODO: track dst + fn descend_until(&mut self, mut desc_bytes: W) -> bool { let start_depth = self.a.path().len(); - let desc_a = self.a.descend_until(None); - let desc_b = self.b.descend_until(None); + let desc_a = self.a.descend_until(std::io::sink()); + let desc_b = self.b.descend_until(std::io::sink()); let path_a = &self.a.path()[start_depth..]; let path_b = &self.b.path()[start_depth..]; if !desc_a && !desc_b { @@ -255,7 +254,14 @@ impl ZipperMoving let ascended = self.b.ascend(to_ascend); debug_assert_eq!(ascended, to_ascend); } - overlap > 0 + debug_assert_eq!(self.a.path(), self.b.path()); + debug_assert_eq!(start_depth + overlap, self.a.path().len()); + if overlap > 0 { + let _ = desc_bytes.write_all(&self.a.path()[start_depth..]); + true + } else { + false + } } fn ascend(&mut self, steps: usize) -> usize { diff --git a/src/path_tracker.rs b/src/path_tracker.rs index 533ebcbc..74c9df1c 100644 --- a/src/path_tracker.rs +++ b/src/path_tracker.rs @@ -132,12 +132,10 @@ impl ZipperMoving for PathTracker { self.path.push(byte); Some(byte) } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { + fn descend_until(&mut self, mut desc_bytes: W) -> bool { let orig_len = self.path.len(); - let descended = self.zipper.descend_until(Some(&mut self.path)); - if let Some(dst) = dst { - dst.extend_from_slice(&self.path[orig_len..]); - } + let descended = self.zipper.descend_until(&mut self.path); + let _ = desc_bytes.write_all(&self.path[orig_len..]); descended } // TODO: using default impl. re-using zipper's own `to_next_step` implementation diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index 284eb5e8..65bf8254 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -369,7 +369,7 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.descend_indexed_byte(0) } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { + fn descend_until(&mut self, desc_bytes: W) -> bool { if self.position.is_invalid() { return false; } @@ -378,7 +378,7 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.position = PrefixPos::Source; } let len_before = self.source.path().len(); - if !self.source.descend_until(dst) { + if !self.source.descend_until(desc_bytes) { return false; } let path = self.source.path(); diff --git a/src/product_zipper.rs b/src/product_zipper.rs index f18b29da..52a3d35d 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -239,8 +239,8 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.ensure_descend_next_factor(); result } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { - let result = self.z.descend_until(dst); + fn descend_until(&mut self, desc_bytes: W) -> bool { + let result = self.z.descend_until(desc_bytes); self.ensure_descend_next_factor(); result } @@ -729,19 +729,19 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { + fn descend_until(&mut self, desc_bytes: W) -> bool { self.enter_factors(); let rv = if let Some(idx) = self.factor_idx(false) { let zipper = &mut self.secondary[idx]; let before = zipper.path().len(); - let rv = zipper.descend_until(dst); + let rv = zipper.descend_until(desc_bytes); let path = zipper.path(); if path.len() > before { self.primary.descend_to(&path[before..]); } rv } else { - self.primary.descend_until(dst) + self.primary.descend_until(desc_bytes) }; self.enter_factors(); rv @@ -1385,6 +1385,10 @@ mod tests { // together, so the resulting virtual product trie is just one long path with repetitions, // and then validate that ascend, ascend_until, ascend_until_branch, etc. all do the right // thing traversing across multiple factors, not stopping spuriously at the factor stitch points. + // + // UPDATE, Also test `descend_until` in this case, because the correct behavior should be seamlessly + // descend flowing across multiple factor zippers in one call, and some of the impls don't appear to + // do that. } // --- END OF MACRO GENERATED MOD --- diff --git a/src/utils/debug/diff_zipper.rs b/src/utils/debug/diff_zipper.rs index ee8b5643..c29a502a 100644 --- a/src/utils/debug/diff_zipper.rs +++ b/src/utils/debug/diff_zipper.rs @@ -123,13 +123,17 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { - let a = self.a.descend_until(dst); - let b = self.b.descend_until(None); + fn descend_until(&mut self, mut desc_bytes: W) -> bool { + let mut a_bytes: Vec = vec![]; + let mut b_bytes: Vec = vec![]; + let a = self.a.descend_until(&mut a_bytes); + let b = self.b.descend_until(&mut b_bytes); if self.log_moves { println!("DiffZipper: descend_until") } + assert_eq!(a_bytes, b_bytes); assert_eq!(a, b); + let _ = desc_bytes.write_all(&a_bytes); a } fn ascend(&mut self, steps: usize) -> usize { diff --git a/src/utils/ints.rs b/src/utils/ints.rs index 2230e060..7f0ba2de 100644 --- a/src/utils/ints.rs +++ b/src/utils/ints.rs @@ -302,7 +302,7 @@ fn int_range_generator_5() { drop(buildz); let mut z = zh.read_zipper_at_path(&[0]).unwrap(); - z.descend_until(None); + z.descend_until(std::io::sink()); z.descend_first_byte(); let _z2 = zh.read_zipper_at_path(z.origin_path()).unwrap(); diff --git a/src/write_zipper.rs b/src/write_zipper.rs index 81c4e487..86d0fc03 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -352,7 +352,7 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -508,7 +508,7 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -688,7 +688,7 @@ impl ZipperMoving for WriteZipperO fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -4264,7 +4264,7 @@ mod tests { assert_eq!(zipper.path(), b""); assert_eq!(zipper.val_count(), 2); - assert_eq!(zipper.descend_until(None), true); + assert_eq!(zipper.descend_until(std::io::sink()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.val_count(), 1); } diff --git a/src/zipper.rs b/src/zipper.rs index 0e3a5a5e..27dc300a 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -25,28 +25,6 @@ pub use crate::empty_zipper::{EmptyZipper}; pub use crate::poly_zipper::PolyZipper; use crate::zipper_tracking::*; - - -// mod goat { -// use crate as pathmap; -// use pathmap::*; -// use pathmap::zipper::*; - -// #[derive(PolyZipper)] -// enum MyPolyZipper<'trie, 'path, V: Clone + Send + Sync + Unpin> { -// Tracked(ReadZipperTracked<'trie, 'path, V>), -// Untracked(ReadZipperUntracked<'trie, 'path, V>), -// } - -// #[test] -// pub fn goat_f() { -// let map = PathMap::<()>::new(); - -// let x = MyPolyZipper::from(map.read_zipper()); -// } -// } - - /// The most fundamantal interface for a zipper, compatible with all zipper types pub trait Zipper { /// Returns `true` if the zipper's focus is on a path within the trie, otherwise `false` @@ -260,19 +238,20 @@ pub trait ZipperMoving: Zipper { else { self.descend_indexed_byte( cc- 1) } } - /// Descends the zipper's focus until a branch or a value is encountered. Returns `true` if the focus - /// moved otherwise returns `false` + /// Descends the zipper's focus until a branch, a value, or the path end is encountered. Returns `true` + /// if the focus moved otherwise returns `false` /// /// If there is a value at the focus, the zipper will descend to the next value or branch, however the /// zipper will not descend further if this method is called with the focus already on a branch. /// - /// If the `dst_path` argument is non-None, the descended path bytes will be pushed onto provided `Vec`. - fn descend_until(&mut self, mut dst_path: Option<&mut Vec>) -> bool { + /// Any descended bytes will be written to `desc_bytes`. Pass [`std::io::sink`] if you don't need this + /// information. + fn descend_until(&mut self, mut desc_bytes: W) -> bool { let mut descended = false; while self.child_count() == 1 { descended = true; - if let (Some(dst_path), Some(byte)) = (&mut dst_path, self.descend_first_byte()) { - dst_path.push(byte); + if let Some(byte) = self.descend_first_byte() { + let _ = desc_bytes.write_all(&[byte]); } if self.is_val() { break; @@ -492,7 +471,7 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { if self.is_val() { return true } - if self.descend_until(None) { + if self.descend_until(std::io::sink()) { if self.is_val() { return true } @@ -527,7 +506,7 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { let mut any = false; while self.descend_last_byte().is_some() { any = true; - self.descend_until(None); + self.descend_until(std::io::sink()); } any } @@ -775,7 +754,7 @@ impl ZipperMoving for &mut Z where Z: ZipperMoving + Zipper { fn descend_to_byte(&mut self, k: u8) -> bool { (**self).descend_to_byte(k) } fn descend_indexed_byte(&mut self, idx: usize) -> Option { (**self).descend_indexed_byte(idx) } fn descend_first_byte(&mut self) -> Option { (**self).descend_first_byte() } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { (**self).descend_until(dst) } + fn descend_until(&mut self, desc_bytes: W) -> bool { (**self).descend_until(desc_bytes) } fn ascend(&mut self, steps: usize) -> usize { (**self).ascend(steps) } fn ascend_byte(&mut self) -> bool { (**self).ascend_byte() } fn ascend_until(&mut self) -> usize { (**self).ascend_until() } @@ -911,7 +890,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -1060,7 +1039,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -1259,7 +1238,7 @@ impl ZipperMoving for ReadZipperOw fn descend_to_byte(&mut self, k: u8) -> bool { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, dst: Option<&mut Vec>) -> bool { self.z.descend_until(dst) } + fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -1676,17 +1655,12 @@ pub(crate) mod read_zipper_core { } } - fn descend_until(&mut self, mut dst: Option<&mut Vec>) -> bool { + fn descend_until(&mut self, mut desc_bytes: W) -> bool { debug_assert!(self.is_regularized()); let mut moved = false; while self.child_count() == 1 { moved = true; - // ??? Rust pls ??? - let dst: Option<&mut Vec> = match &mut dst { - Some(x) => Some(x), - None => None, - }; - self.descend_first(dst); + self.descend_first(&mut desc_bytes); if self.is_val_internal() { break; } @@ -2672,30 +2646,26 @@ pub(crate) mod read_zipper_core { /// Internal method implementing part of [Self::descend_until], but doesn't pay attention to to [Self::child_count] #[inline] - fn descend_first(&mut self, mut dst: Option<&mut Vec>) { + fn descend_first(&mut self, desc_bytes: &mut W) { self.prepare_buffers(); match self.focus_node.first_child_from_key(self.node_key()) { (Some(prefix), Some(child_node)) => { //Step to a new node self.prefix_buf.extend(prefix); - if let Some(ref mut dst) = dst { - dst.extend_from_slice(prefix); - } + let _ = desc_bytes.write_all(prefix); self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); *self.focus_node = child_node; self.focus_iter_token = NODE_ITER_INVALID; //If we're at the root of the new node, descend to the first child if prefix.len() == 0 { - self.descend_first(dst) + self.descend_first(desc_bytes) } }, (Some(prefix), None) => { //Stay within the same node self.prefix_buf.extend(prefix); - if let Some(ref mut dst) = dst { - dst.extend_from_slice(prefix); - } + let _ = desc_bytes.write_all(prefix); }, (None, _) => unreachable!() } @@ -3342,7 +3312,7 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_descend_until_test1(mut zip: Z) { for key in ZIPPER_DESCEND_UNTIL_TEST1_KEYS { - assert!(zip.descend_until(None)); + assert!(zip.descend_until(std::io::sink())); assert_eq!(zip.path(), *key); } } @@ -3523,20 +3493,20 @@ pub(crate) mod zipper_moving_tests { descend_byte(&mut zipper, b'r'); assert_eq!(zipper.path(), b"r"); assert_eq!(zipper.child_count(), 2); - assert_eq!(zipper.descend_until(None), false); + assert_eq!(zipper.descend_until(std::io::sink()), false); descend_byte(&mut zipper, b'o'); assert_eq!(zipper.path(), b"ro"); assert_eq!(zipper.child_count(), 1); - assert_eq!(zipper.descend_until(None), true); + assert_eq!(zipper.descend_until(std::io::sink()), true); assert_eq!(zipper.path(), b"rom"); assert_eq!(zipper.child_count(), 3); zipper.reset(); - assert_eq!(zipper.descend_until(None), false); + assert_eq!(zipper.descend_until(std::io::sink()), false); descend_byte(&mut zipper, b'a'); assert_eq!(zipper.path(), b"a"); assert_eq!(zipper.child_count(), 1); - assert_eq!(zipper.descend_until(None), true); + assert_eq!(zipper.descend_until(std::io::sink()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.child_count(), 0); @@ -3562,7 +3532,7 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.ascend(1), 1); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); - zipper.descend_until(None); + zipper.descend_until(std::io::sink()); assert_eq!(zipper.is_val(), true); } @@ -4330,7 +4300,7 @@ mod tests { zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); assert_eq!(zipper.val(), None); - zipper.descend_until(None); + zipper.descend_until(std::io::sink()); assert_eq!(zipper.is_val(), true); assert_eq!(zipper.val(), Some(&"romanus")); } @@ -4665,7 +4635,7 @@ mod tests { assert_eq!(zipper.path(), b""); assert_eq!(zipper.val_count(), 2); - assert_eq!(zipper.descend_until(None), true); + assert_eq!(zipper.descend_until(std::io::sink()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.val_count(), 1); } diff --git a/src/zipper_head.rs b/src/zipper_head.rs index 641eb33c..0ddc1fa2 100644 --- a/src/zipper_head.rs +++ b/src/zipper_head.rs @@ -1038,7 +1038,7 @@ mod tests { let mut z = zh.read_zipper_at_path(b"A").unwrap(); assert_eq!(z.val(), Some(&24)); assert_eq!(z.to_next_sibling_byte(), None); - z.descend_until(None); + z.descend_until(std::io::sink()); assert_eq!(z.path(), b"BCDEFG"); assert_eq!(z.origin_path(), b"ABCDEFG"); assert_eq!(z.val(), Some(&42)); @@ -1406,7 +1406,7 @@ mod tests { //Make sure we cleaned up the dangling path, but nothing else let mut rz = zh.read_zipper_at_borrowed_path(b"a_path_").unwrap(); - assert!(rz.descend_until(None)); + assert!(rz.descend_until(std::io::sink())); assert_eq!(rz.path(), b"to_somewhere"); drop(rz); @@ -1415,7 +1415,7 @@ mod tests { zh.cleanup_write_zipper(wz); let mut rz = zh.read_zipper_at_borrowed_path(b"a_path_").unwrap(); assert_eq!(rz.path(), b""); - assert!(rz.descend_until(None)); + assert!(rz.descend_until(std::io::sink())); assert_eq!(rz.path(), b"to_somewhere"); drop(rz); From 703808f352c0b78451ac6201d8bd55278770d878 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Tue, 14 Oct 2025 23:29:17 -0600 Subject: [PATCH 15/18] Adding `focus_byte` method to `ZipperMoving` trait, to permit default impls of sibling movement methods --- pathmap-derive/src/lib.rs | 7 +++++++ src/arena_compact.rs | 13 +++++++++++-- src/empty_zipper.rs | 8 ++++++++ src/experimental.rs | 4 ++++ src/overlay_zipper.rs | 7 +++++++ src/path_tracker.rs | 8 ++++++++ src/prefix_zipper.rs | 5 ++++- src/product_zipper.rs | 8 ++++++++ src/utils/debug/diff_zipper.rs | 6 ++++++ src/write_zipper.rs | 8 ++++++++ src/zipper.rs | 16 ++++++++++++++++ 11 files changed, 87 insertions(+), 3 deletions(-) diff --git a/pathmap-derive/src/lib.rs b/pathmap-derive/src/lib.rs index 902fc2d6..d29b2c12 100644 --- a/pathmap-derive/src/lib.rs +++ b/pathmap-derive/src/lib.rs @@ -246,6 +246,13 @@ pub fn derive_poly_zipper(input: TokenStream) -> TokenStream { } } + #[inline] + fn focus_byte(&self) -> Option { + match self { + #(#variant_arms => inner.focus_byte(),)* + } + } + fn reset(&mut self) { match self { #(#variant_arms => inner.reset(),)* diff --git a/src/arena_compact.rs b/src/arena_compact.rs index 4ec39705..75916a08 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -1659,7 +1659,7 @@ where Storage: AsRef<[u8]> fn val(&self) -> Option<&u64> { let value = self.get_value()?; if self.tree.value.get() != value { - self.tree.value.set(value); + self.tree.value.set(value); } let ptr = self.tree.value.as_ptr(); // technically if someone borrows the value twice, they will hit UB @@ -1703,7 +1703,7 @@ where Storage: AsRef<[u8]> fn get_val(&self) -> Option<&'tree u64> { let value = self.get_value()?; if self.tree.value.get() != value { - self.tree.value.set(value); + self.tree.value.set(value); } let ptr = self.tree.value.as_ptr(); Some(unsafe { &*ptr }) @@ -1748,6 +1748,15 @@ where Storage: AsRef<[u8]> self.invalid = 0; } + #[inline] + fn focus_byte(&self) -> Option { + if self.path.len() > self.origin_depth { + self.path.last().cloned() + } else { + None + } + } + /// Returns the total number of values contained at and below the zipper's focus, including the focus itself /// /// WARNING: This is not a cheap method. It may have an order-N cost diff --git a/src/empty_zipper.rs b/src/empty_zipper.rs index e3133d3e..3d54b663 100644 --- a/src/empty_zipper.rs +++ b/src/empty_zipper.rs @@ -34,6 +34,14 @@ impl Zipper for EmptyZipper { impl ZipperMoving for EmptyZipper { fn at_root(&self) -> bool { self.path.len() == self.path_start_idx } fn reset(&mut self) { self.path.truncate(self.path_start_idx) } + #[inline] + fn focus_byte(&self) -> Option { + if self.path.len() > self.path_start_idx { + self.path.last().cloned() + } else { + None + } + } fn val_count(&self) -> usize { 0 } fn descend_to>(&mut self, k: K) { self.path.extend_from_slice(k.as_ref()); diff --git a/src/experimental.rs b/src/experimental.rs index d158f1fd..1e6bda17 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -37,6 +37,10 @@ impl ZipperPathBuffer for FullZipper { impl ZipperMoving for FullZipper { fn at_root(&self) -> bool { self.path.len() == 0 } fn reset(&mut self) { self.path.clear() } + #[inline] + fn focus_byte(&self) -> Option { + self.path.last().cloned() + } fn val_count(&self) -> usize { usize::MAX/2 } // usize::MAX is a dangerous default for overflow fn descend_to>(&mut self, k: K) { self.path.extend_from_slice(k.as_ref()); diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index 13fd66c4..5f1ab51e 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -145,6 +145,13 @@ impl ZipperMoving self.b.reset(); } + #[inline] + fn focus_byte(&self) -> Option { + let byte = self.a.focus_byte(); + debug_assert_eq!(byte, self.b.focus_byte()); + byte + } + fn val_count(&self) -> usize { todo!() } diff --git a/src/path_tracker.rs b/src/path_tracker.rs index 9dac6027..6881d0ea 100644 --- a/src/path_tracker.rs +++ b/src/path_tracker.rs @@ -61,6 +61,14 @@ impl ZipperMoving for PathTracker { self.zipper.reset(); self.path.truncate(self.origin_len); } + #[inline] + fn focus_byte(&self) -> Option { + if self.path.len() > self.origin_len { + self.path.last().cloned() + } else { + None + } + } fn val_count(&self) -> usize { todo!() } fn descend_to>(&mut self, path: K) { let path = path.as_ref(); diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index 3891e06d..28100b38 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -313,7 +313,10 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> PrefixPos::Source => self.prefix.len() <= self.origin_depth && self.source.at_root(), } } - + #[inline] + fn focus_byte(&self) -> Option { + self.path.last().cloned() + } fn reset(&mut self) { self.prepare_buffers(); self.path.truncate(self.origin_depth); diff --git a/src/product_zipper.rs b/src/product_zipper.rs index 8aba9a20..bac71a34 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -178,6 +178,10 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn at_root(&self) -> bool { self.path().len() == 0 } + #[inline] + fn focus_byte(&self) -> Option { + self.z.focus_byte() + } fn reset(&mut self) { self.factor_paths.clear(); self.z.reset() @@ -701,6 +705,10 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim fn at_root(&self) -> bool { self.path().is_empty() } + #[inline] + fn focus_byte(&self) -> Option { + self.primary.path().last().cloned() + } fn reset(&mut self) { self.factor_paths.clear(); for secondary in &mut self.secondary { diff --git a/src/utils/debug/diff_zipper.rs b/src/utils/debug/diff_zipper.rs index 8281ba49..c5c0f325 100644 --- a/src/utils/debug/diff_zipper.rs +++ b/src/utils/debug/diff_zipper.rs @@ -60,6 +60,12 @@ impl ZipperMoving for DiffZi println!("DiffZipper: reset") } } + fn focus_byte(&self) -> Option { + let a = self.a.focus_byte(); + let b = self.b.focus_byte(); + assert_eq!(a, b); + a + } fn val_count(&self) -> usize { let a = self.a.val_count(); let b = self.b.val_count(); diff --git a/src/write_zipper.rs b/src/write_zipper.rs index d95aa129..bb02c2e6 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -346,6 +346,7 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperSubtries impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving for WriteZipperTracked<'a, 'path, V, A> { fn at_root(&self) -> bool { self.z.at_root() } + fn focus_byte(&self) -> Option { self.z.focus_byte() } fn reset(&mut self) { self.z.reset() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) { self.z.descend_to(k) } @@ -502,6 +503,7 @@ impl<'a, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperSubtries impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving for WriteZipperUntracked<'a, 'path, V, A> { fn at_root(&self) -> bool { self.z.at_root() } + fn focus_byte(&self) -> Option { self.z.focus_byte() } fn reset(&mut self) { self.z.reset() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) { self.z.descend_to(k) } @@ -682,6 +684,7 @@ impl ZipperSubtries for Writ impl ZipperMoving for WriteZipperOwned { fn at_root(&self) -> bool { self.z.at_root() } + fn focus_byte(&self) -> Option { self.z.focus_byte() } fn reset(&mut self) { self.z.reset() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) { self.z.descend_to(k) } @@ -959,6 +962,11 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving self.key.prefix_buf.len() <= self.key.origin_path.len() } + #[inline] + fn focus_byte(&self) -> Option { + self.key.prefix_buf.last().cloned() + } + fn reset(&mut self) { self.focus_stack.to_root(); self.key.prefix_buf.truncate(self.key.origin_path.len()); diff --git a/src/zipper.rs b/src/zipper.rs index f78ac477..0672f886 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -120,6 +120,13 @@ pub trait ZipperMoving: Zipper { // self.path().len() == 0 // } + /// Returns the path byte at the zipper's focus, or `None` if the zipper is at the root + /// + /// NOTE: This method is not aware of the zipper's position within a larger trie, and + /// therefore it will always return `None` when the zipper is at the root, even if + /// [`root_prefix_path`](ZipperAbsolutePath::root_prefix_path) returns a non-empty slice. + fn focus_byte(&self) -> Option; + /// Resets the zipper's focus back to its root fn reset(&mut self) { while !self.at_root() { @@ -768,6 +775,7 @@ impl Zipper for &mut Z where Z: Zipper { impl ZipperMoving for &mut Z where Z: ZipperMoving + Zipper { fn at_root(&self) -> bool { (**self).at_root() } fn reset(&mut self) { (**self).reset() } + fn focus_byte(&self) -> Option { (**self).focus_byte() } fn val_count(&self) -> usize { (**self).val_count() } fn descend_to>(&mut self, k: K) { (**self).descend_to(k) } fn descend_to_check>(&mut self, k: K) -> bool { (**self).descend_to_check(k) } @@ -906,6 +914,7 @@ impl ZipperSubtries for Read impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ReadZipperTracked<'trie, '_, V, A> { fn at_root(&self) -> bool { self.z.at_root() } + fn focus_byte(&self) -> Option { self.z.focus_byte() } fn reset(&mut self) { self.z.reset() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) { self.z.descend_to(k) } @@ -1058,6 +1067,7 @@ impl ZipperSubtries for Read impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> ZipperMoving for ReadZipperUntracked<'trie, '_, V, A> { fn at_root(&self) -> bool { self.z.at_root() } + fn focus_byte(&self) -> Option { self.z.focus_byte() } fn reset(&mut self) { self.z.reset() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) { self.z.descend_to(k) } @@ -1260,6 +1270,7 @@ impl ZipperSubtries for Read impl ZipperMoving for ReadZipperOwned { fn at_root(&self) -> bool { self.z.at_root() } + fn focus_byte(&self) -> Option { self.z.focus_byte() } fn reset(&mut self) { self.z.reset() } fn val_count(&self) -> usize { self.z.val_count() } fn descend_to>(&mut self, k: K) { self.z.descend_to(k) } @@ -1573,6 +1584,11 @@ pub(crate) mod read_zipper_core { self.prefix_buf.len() <= self.origin_path.len() } + #[inline] + fn focus_byte(&self) -> Option { + self.prefix_buf.last().cloned() + } + fn reset(&mut self) { self.ancestors.truncate(1); match self.ancestors.pop() { From 52a6798edc424e7bef7ed584331d2b86724154e9 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Wed, 15 Oct 2025 00:54:17 -0600 Subject: [PATCH 16/18] Re-adding default impls for ZipperMoving to_sibling methods --- src/zipper.rs | 97 ++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 52 deletions(-) diff --git a/src/zipper.rs b/src/zipper.rs index 0672f886..9a958853 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -114,11 +114,9 @@ pub trait ZipperSubtries: Zi /// will never change. pub trait ZipperMoving: Zipper { /// Returns `true` if the zipper's focus is at its root, and it cannot ascend further, otherwise returns `false` - fn at_root(&self) -> bool; - //GOAT, This default impl isn't compatible with the blind ZipperMoving anymore. But there is value in expressing this equivalence... Not sure where to do it. Maybe the docs for `path()`? - // -> bool { - // self.path().len() == 0 - // } + fn at_root(&self) -> bool { + self.focus_byte().is_none() + } /// Returns the path byte at the zipper's focus, or `None` if the zipper is at the root /// @@ -316,29 +314,24 @@ pub trait ZipperMoving: Zipper { /// /// This method is equivalent to calling [ZipperMoving::ascend] with `1`, followed by [ZipperMoving::descend_indexed_byte] /// where the index passed is 1 more than the index of the current focus position. - fn to_next_sibling_byte(&mut self) -> Option; - //GOAT, this default impl is incompatible with the blind zippers. :-( That argues that perhaps it belongs in another trait, e.g. ZipperIteration - // { - // let cur_byte = match self.path().last() { - // Some(byte) => *byte, - // None => return false - // }; - // if !self.ascend_byte() { - // return false - // } - // let mask = self.child_mask(); - // match mask.next_bit(cur_byte) { - // Some(byte) => { - // self.descend_to_byte(byte); - // debug_assert!(self.path_exists()); - // true - // }, - // None => { - // self.descend_to_byte(cur_byte); - // false - // } - // } - // } + fn to_next_sibling_byte(&mut self) -> Option { + let cur_byte = self.focus_byte()?; + if !self.ascend_byte() { + return None + } + let mask = self.child_mask(); + match mask.next_bit(cur_byte) { + Some(byte) => { + self.descend_to_byte(byte); + debug_assert!(self.path_exists()); + Some(byte) + }, + None => { + self.descend_to_byte(cur_byte); + None + } + } + } /// Moves the zipper's focus to the previous sibling byte with the same parent /// @@ -347,30 +340,25 @@ pub trait ZipperMoving: Zipper { /// /// This method is equivalent to calling [Self::ascend] with `1`, followed by [Self::descend_indexed_byte] /// where the index passed is 1 less than the index of the current focus position. - fn to_prev_sibling_byte(&mut self) -> Option; - //GOAT, this default impl is incompatible with the blind zippers. :-( That argues that perhaps it belongs in another trait, e.g. ZipperIteration - // { - // let cur_byte = match self.path().last() { - // Some(byte) => *byte, - // None => return false - // }; - // if !self.ascend_byte() { - // return false - // } - // let mask = self.child_mask(); - // match mask.prev_bit(cur_byte) { - // Some(byte) => { - // self.descend_to_byte(byte); - // debug_assert!(self.path_exists()); - // true - // }, - // None => { - // self.descend_to_byte(cur_byte); - // debug_assert!(self.path_exists()); - // false - // } - // } - // } + fn to_prev_sibling_byte(&mut self) -> Option { + let cur_byte = self.focus_byte()?; + if !self.ascend_byte() { + return None + } + let mask = self.child_mask(); + match mask.prev_bit(cur_byte) { + Some(byte) => { + self.descend_to_byte(byte); + debug_assert!(self.path_exists()); + Some(byte) + }, + None => { + self.descend_to_byte(cur_byte); + debug_assert!(self.path_exists()); + None + } + } + } /// Advances the zipper to visit every existing path within the trie in a depth-first order /// @@ -654,6 +642,11 @@ pub trait ZipperConcrete { /// which doesn't track it's own path in the trie. pub trait ZipperPath: ZipperMoving { /// Returns the path from the zipper's root to the current focus + /// + /// This method will always return a zero-length slice when [`at_root`](ZipperMoving::at_root) returns `true`, + /// and a non-zero-length slice when it returns `false`. + /// + /// `z.path().last()` is guaranteed to equal the return value of [`focus_byte`](ZipperMoving::focus_byte). fn path(&self) -> &[u8]; /// Moves the zipper's focus to a specific location specified by `path`, relative to the zipper's root From eb8013963bd6cd037837758dd1c1289092cebf89 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Wed, 15 Oct 2025 01:00:30 -0600 Subject: [PATCH 17/18] Updating book to clarify that the path is no longer on `ZipperMoving` but now is part of the new `ZipperPath` trait --- pathmap-book/src/1.02.00_zippers.md | 1 + pathmap-book/src/1.02.03_zipper_paths.md | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pathmap-book/src/1.02.00_zippers.md b/pathmap-book/src/1.02.00_zippers.md index 81888fe1..f3768dcf 100644 --- a/pathmap-book/src/1.02.00_zippers.md +++ b/pathmap-book/src/1.02.00_zippers.md @@ -16,6 +16,7 @@ Zipper capabilities are defined across a number of traits. There are many diffe | [`ZipperValues`](./1.02.02_zipper_values.md#zippervalues) | Access values | | [`ZipperReadOnlyValues`](./1.02.02_zipper_values.md#zipperreadonlyvalues) | Access values with extended lifetime | | [`ZipperReadOnlyConditionalValues`](./1.02.02_zipper_values.md#zipperreadonlyconditionalvalues) | Access values with `witness` pattern | +| [`ZipperPath`](./1.02.03_zipper_paths.md#zipper_relative_path) | Get basic path information | | [`ZipperAbsolutePath`](./1.02.03_zipper_paths.md#zipperabsolutepath) | Get more complete path information | | [`ZipperPathBuffer`](./1.02.03_zipper_paths.md#zipperpathbuffer) | Control zipper's internal buffer allocation | | [`ZipperMoving`](./1.02.04_zipper_moving.md) | Moves the zipper's focus within the trie | diff --git a/pathmap-book/src/1.02.03_zipper_paths.md b/pathmap-book/src/1.02.03_zipper_paths.md index 85af6925..86302698 100644 --- a/pathmap-book/src/1.02.03_zipper_paths.md +++ b/pathmap-book/src/1.02.03_zipper_paths.md @@ -1,10 +1,10 @@ # Paths and Absolute Paths -Some zippers maintain can expose their focus position within the trie as a `path`. The following traits expose access to the path buffer in some way. +Some zippers maintain and can expose their focus position within the trie as a contiguous `path` of type `&[u8]`. The following traits expose access to the path buffer in some way. ## Zipper Relative Path -The [`path`] method in [`ZipperMoving`] returns the current path from the zipper's root to its focus as a byte slice. This represents the sequence of bytes traversed to reach the current focus position. +The [`path`] method in the [`ZipperPath`] trait returns the current path from the zipper's root to its focus, as a byte slice. This represents the sequence of bytes traversed to reach the current focus position. -Note that this path is relative to the zipper's root, which may not be the same as the absolute path from the original data structure's root if the zipper was created with a prefix or from a subtrie. +Note that this path is relative to the zipper's root, which may not be the same as the absolute path from the original data structure's root if the zipper was created with a prefix or from within a subtrie. ## ZipperAbsolutePath The [`ZipperAbsolutePath`] trait provides methods to access a more complete path slice, including the [`origin_path`], and may extend above the zipper's root. From b982c8aeee9d6ec7a2f514091bffe9a7d4c8b4b6 Mon Sep 17 00:00:00 2001 From: Luke Peterson Date: Wed, 15 Oct 2025 07:14:37 -0600 Subject: [PATCH 18/18] Adding `PathObserver` trait, and taking it as an argument for `descend_until` --- pathmap-derive/src/lib.rs | 4 +- src/arena_compact.rs | 26 ++++--- src/dense_byte_node.rs | 10 +-- src/empty_zipper.rs | 2 +- src/experimental.rs | 2 +- src/morphisms.rs | 4 +- src/overlay_zipper.rs | 11 +-- src/path_tracker.rs | 7 +- src/prefix_zipper.rs | 4 +- src/product_zipper.rs | 12 ++-- src/utils/debug/diff_zipper.rs | 4 +- src/utils/ints.rs | 2 +- src/write_zipper.rs | 8 +-- src/zipper.rs | 125 ++++++++++++++++++++++++--------- src/zipper_head.rs | 6 +- 15 files changed, 145 insertions(+), 82 deletions(-) diff --git a/pathmap-derive/src/lib.rs b/pathmap-derive/src/lib.rs index d29b2c12..530d848e 100644 --- a/pathmap-derive/src/lib.rs +++ b/pathmap-derive/src/lib.rs @@ -321,9 +321,9 @@ pub fn derive_poly_zipper(input: TokenStream) -> TokenStream { } } - fn descend_until(&mut self, desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { match self { - #(#variant_arms => inner.descend_until(desc_bytes),)* + #(#variant_arms => inner.descend_until(obs),)* } } diff --git a/src/arena_compact.rs b/src/arena_compact.rs index 75916a08..6959509c 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -76,16 +76,16 @@ //! [if (header&0x3f != 0) first_child: varint64] //! [ line_offset: varint64] //! ``` +use core::cell::Cell; +use core::marker::PhantomData; use std::{io::Write, hash::Hasher}; -use std::cell::Cell; -use std::marker::PhantomData; use fast_slice_utils::starts_with; use crate::{ morphisms::Catamorphism, utils::{BitMask, ByteMask, find_prefix_overlap}, zipper::{ - Zipper, ZipperValues, ZipperForking, ZipperAbsolutePath, ZipperIteration, + PathObserver, Zipper, ZipperValues, ZipperForking, ZipperAbsolutePath, ZipperIteration, ZipperMoving, ZipperPath, ZipperPathBuffer, ZipperReadOnlyValues, ZipperConcrete, ZipperReadOnlyConditionalValues, }, @@ -433,8 +433,8 @@ const SI_PREFIX: &[u8] = b"KMGTPE"; struct SiCount(usize); -impl std::fmt::Display for SiCount { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { +impl core::fmt::Display for SiCount { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { let mut value = self.0 as f64; if value < 1000.0 { return write!(fmt, "{value:3.0}"); @@ -448,8 +448,8 @@ impl std::fmt::Display for SiCount { } } -impl std::fmt::Debug for Counters { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { +impl core::fmt::Debug for Counters { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { let total_size = self.nodes_size + self.lines_size + self.line_data_size + 16 + 8; write!(fmt, @@ -1885,10 +1885,9 @@ where Storage: AsRef<[u8]> self.descend_indexed_byte(0) } - fn descend_until(&mut self, mut desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.trace_pos(); let mut descended = false; - let orig_len = self.path.len(); 'descend: while self.child_count() == 1 { let child_id; match &self.cur_node { @@ -1896,9 +1895,13 @@ where Storage: AsRef<[u8]> let top_frame = self.stack.last_mut().unwrap(); let path = self.tree.get_line(line.path); let rest_path = &path[top_frame.node_depth..]; + if obs.remaining_limit() != usize::MAX && obs.remaining_limit() < rest_path.len() { + break 'descend; + } let line_child_hack = if line.child.is_some() { 1 } else { 0 }; top_frame.node_depth += rest_path.len() - line_child_hack; self.path.extend_from_slice(rest_path); + obs.descend_to(rest_path); child_id = line.child; if line.value.is_some() { descended = true; @@ -1908,7 +1911,11 @@ where Storage: AsRef<[u8]> Node::Branch(node) => { let Some(byte) = node.bytemask.iter().next() else { break 'descend }; + if obs.remaining_limit() < 1 { + break 'descend; + } self.path.push(byte); + obs.descend_to_byte(byte); child_id = node.first_child; } } @@ -1929,7 +1936,6 @@ where Storage: AsRef<[u8]> } } } - let _ = desc_bytes.write_all(&self.path[orig_len..]); descended } diff --git a/src/dense_byte_node.rs b/src/dense_byte_node.rs index 90c3b152..99324ab5 100644 --- a/src/dense_byte_node.rs +++ b/src/dense_byte_node.rs @@ -2,7 +2,7 @@ use core::fmt::{Debug, Formatter}; use core::ptr; use std::collections::HashMap; -use std::hint::unreachable_unchecked; +use core::hint::unreachable_unchecked; use crate::alloc::Allocator; use crate::ring::*; @@ -54,7 +54,7 @@ pub type CellByteNode = ByteNode, A>; #[repr(C)] pub struct ByteNode { #[cfg(feature = "slim_ptrs")] - refcnt: std::sync::atomic::AtomicU32, + refcnt: core::sync::atomic::AtomicU32, pub mask: ByteMask, #[cfg(feature = "nightly")] values: Vec, @@ -100,7 +100,7 @@ impl> Clone for ByteN fn clone(&self) -> Self { Self { #[cfg(feature = "slim_ptrs")] - refcnt: std::sync::atomic::AtomicU32::new(1), + refcnt: core::sync::atomic::AtomicU32::new(1), mask: self.mask, values: self.values.clone(), alloc: self.alloc.clone(), @@ -109,7 +109,7 @@ impl> Clone for ByteN } impl> Debug for ByteNode { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { //Recursively printing a whole tree will get pretty unwieldy. Should do something // like serialization for inspection using standard tools. write!(f, "ByteNode {{count={}", self.values.len())?; @@ -134,7 +134,7 @@ impl> ByteNode fn new_with_fields_in(mask: ByteMask, values: ValuesVec, alloc: A) -> Self { Self { #[cfg(feature = "slim_ptrs")] - refcnt: std::sync::atomic::AtomicU32::new(1), + refcnt: core::sync::atomic::AtomicU32::new(1), mask, values: values.v, alloc, diff --git a/src/empty_zipper.rs b/src/empty_zipper.rs index 3d54b663..a6d7b09e 100644 --- a/src/empty_zipper.rs +++ b/src/empty_zipper.rs @@ -51,7 +51,7 @@ impl ZipperMoving for EmptyZipper { } fn descend_indexed_byte(&mut self, _idx: usize) -> Option { None } fn descend_first_byte(&mut self) -> Option { None } - fn descend_until(&mut self, _desc_bytes: W) -> bool { false } + fn descend_until(&mut self, _obs: &mut Obs) -> bool { false } fn ascend(&mut self, steps: usize) -> usize { let old_path_len = self.path.len() - self.path_start_idx; if steps > old_path_len { diff --git a/src/experimental.rs b/src/experimental.rs index 1e6bda17..67aa62d4 100644 --- a/src/experimental.rs +++ b/src/experimental.rs @@ -57,7 +57,7 @@ impl ZipperMoving for FullZipper { self.path.push(0); Some(0) } - fn descend_until(&mut self, _desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { false } fn ascend(&mut self, steps: usize) -> usize { diff --git a/src/morphisms.rs b/src/morphisms.rs index dacef1bc..d4159fe0 100644 --- a/src/morphisms.rs +++ b/src/morphisms.rs @@ -415,7 +415,7 @@ fn cata_side_effect_body<'a, Z, V: 'a, W, Err, AlgF, const JUMPING: bool>(mut z: //Descend to the next forking point, or leaf let mut is_leaf = false; while z.child_count() < 2 { - if !z.descend_until(std::io::sink()) { + if !z.descend_until(&mut ()) { is_leaf = true; break; } @@ -735,7 +735,7 @@ fn into_cata_cached_body<'a, Z, V: 'a, W, E, AlgF, Cache, const JUMPING: bool>( // Descend until leaf or branch let mut is_leaf = false; 'descend: while zipper.child_count() < 2 { - if !zipper.descend_until(std::io::sink()) { + if !zipper.descend_until(&mut ()) { is_leaf = true; break 'descend; } diff --git a/src/overlay_zipper.rs b/src/overlay_zipper.rs index 5f1ab51e..b3cf6a51 100644 --- a/src/overlay_zipper.rs +++ b/src/overlay_zipper.rs @@ -18,7 +18,7 @@ use fast_slice_utils::find_prefix_overlap; use crate::utils::{BitMask, ByteMask}; -use crate::zipper::{Zipper, ZipperMoving, ZipperPath, ZipperIteration, ZipperValues}; +use crate::zipper::{PathObserver, Zipper, ZipperMoving, ZipperPath, ZipperIteration, ZipperValues}; /// Zipper that traverses a virtual trie formed by fusing the tries of two other zippers pub struct OverlayZipper @@ -221,10 +221,11 @@ impl ZipperMoving Some(byte) } - fn descend_until(&mut self, mut desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let start_depth = self.a.path().len(); - let desc_a = self.a.descend_until(std::io::sink()); - let desc_b = self.b.descend_until(std::io::sink()); + //GOAT: TODO. We need to shadow the `obs` with a type that reflects the current obs remaining_limit + let desc_a = self.a.descend_until(&mut ()); + let desc_b = self.b.descend_until(&mut ()); let path_a = &self.a.path()[start_depth..]; let path_b = &self.b.path()[start_depth..]; if !desc_a && !desc_b { @@ -266,7 +267,7 @@ impl ZipperMoving debug_assert_eq!(self.a.path(), self.b.path()); debug_assert_eq!(start_depth + overlap, self.a.path().len()); if overlap > 0 { - let _ = desc_bytes.write_all(&self.a.path()[start_depth..]); + obs.descend_to(&self.a.path()[start_depth..]); true } else { false diff --git a/src/path_tracker.rs b/src/path_tracker.rs index 6881d0ea..b850dfb4 100644 --- a/src/path_tracker.rs +++ b/src/path_tracker.rs @@ -1,7 +1,7 @@ use crate::{ utils::ByteMask, zipper::{ - Zipper, ZipperAbsolutePath, ZipperMoving, ZipperIteration, + PathObserver, Zipper, ZipperAbsolutePath, ZipperMoving, ZipperIteration, ZipperPath, ZipperPathBuffer, ZipperValues, ZipperReadOnlyValues, ZipperReadOnlyConditionalValues, }, @@ -148,10 +148,11 @@ impl ZipperMoving for PathTracker { self.path.push(byte); Some(byte) } - fn descend_until(&mut self, mut desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let orig_len = self.path.len(); + //GOAT, we need to create a wrapper around path that captures the `obs` remaining_limit, so the wrapped zipper doesn't descend. let descended = self.zipper.descend_until(&mut self.path); - let _ = desc_bytes.write_all(&self.path[orig_len..]); + obs.descend_to(&self.path[orig_len..]); descended } // TODO: using default impl. re-using zipper's own `to_next_step` implementation diff --git a/src/prefix_zipper.rs b/src/prefix_zipper.rs index 28100b38..7942e015 100644 --- a/src/prefix_zipper.rs +++ b/src/prefix_zipper.rs @@ -388,7 +388,7 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.descend_indexed_byte(0) } - fn descend_until(&mut self, desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { if self.position.is_invalid() { return false; } @@ -397,7 +397,7 @@ impl<'prefix, Z> ZipperMoving for PrefixZipper<'prefix, Z> self.position = PrefixPos::Source; } let len_before = self.source.path().len(); - if !self.source.descend_until(desc_bytes) { + if !self.source.descend_until(obs) { return false; } let path = self.source.path(); diff --git a/src/product_zipper.rs b/src/product_zipper.rs index bac71a34..7058a6f4 100644 --- a/src/product_zipper.rs +++ b/src/product_zipper.rs @@ -266,10 +266,10 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper self.ensure_descend_next_factor(); result } - fn descend_until(&mut self, mut desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let mut moved = false; while self.z.child_count() == 1 { - moved |= self.z.descend_until(&mut desc_bytes); + moved |= self.z.descend_until(obs); self.ensure_descend_next_factor(); if self.z.is_val() { break; @@ -770,21 +770,21 @@ impl<'trie, PrimaryZ, SecondaryZ, V> ZipperMoving for ProductZipperG<'trie, Prim fn descend_first_byte(&mut self) -> Option { self.descend_indexed_byte(0) } - fn descend_until(&mut self, mut desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let mut moved = false; self.enter_factors(); while self.child_count() == 1 { moved |= if let Some(idx) = self.factor_idx(false) { let zipper = &mut self.secondary[idx]; let before = zipper.path().len(); - let rv = zipper.descend_until(&mut desc_bytes); + let rv = zipper.descend_until(obs); let path = zipper.path(); if path.len() > before { self.primary.descend_to(&path[before..]); } rv } else { - self.primary.descend_until(&mut desc_bytes) + self.primary.descend_until(obs) }; self.enter_factors(); if self.is_val() { @@ -1518,7 +1518,7 @@ mod tests { assert_eq!(pz.is_val(), false); // test descend_until - assert_eq!(pz.descend_until(std::io::sink()), true); + assert_eq!(pz.descend_until(&mut ()), true); assert_eq!(pz.path(), full_path); assert_eq!(pz.path_exists(), true); assert_eq!(pz.child_count(), 0); diff --git a/src/utils/debug/diff_zipper.rs b/src/utils/debug/diff_zipper.rs index c5c0f325..060b7087 100644 --- a/src/utils/debug/diff_zipper.rs +++ b/src/utils/debug/diff_zipper.rs @@ -127,7 +127,7 @@ impl ZipperMoving for DiffZi assert_eq!(a, b); a } - fn descend_until(&mut self, mut desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let mut a_bytes: Vec = vec![]; let mut b_bytes: Vec = vec![]; let a = self.a.descend_until(&mut a_bytes); @@ -137,7 +137,7 @@ impl ZipperMoving for DiffZi } assert_eq!(a_bytes, b_bytes); assert_eq!(a, b); - let _ = desc_bytes.write_all(&a_bytes); + obs.descend_to(&a_bytes); a } fn ascend(&mut self, steps: usize) -> usize { diff --git a/src/utils/ints.rs b/src/utils/ints.rs index 7f0ba2de..052b6140 100644 --- a/src/utils/ints.rs +++ b/src/utils/ints.rs @@ -302,7 +302,7 @@ fn int_range_generator_5() { drop(buildz); let mut z = zh.read_zipper_at_path(&[0]).unwrap(); - z.descend_until(std::io::sink()); + z.descend_until(&mut ()); z.descend_first_byte(); let _z2 = zh.read_zipper_at_path(z.origin_path()).unwrap(); diff --git a/src/write_zipper.rs b/src/write_zipper.rs index bb02c2e6..c5e5b4e9 100644 --- a/src/write_zipper.rs +++ b/src/write_zipper.rs @@ -353,7 +353,7 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn descend_to_byte(&mut self, k: u8) { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.z.descend_until(obs) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -510,7 +510,7 @@ impl<'a, 'path, V: Clone + Send + Sync + Unpin, A: Allocator + 'a> ZipperMoving fn descend_to_byte(&mut self, k: u8) { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.z.descend_until(obs) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -691,7 +691,7 @@ impl ZipperMoving for WriteZipperO fn descend_to_byte(&mut self, k: u8) { self.z.descend_to_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.z.descend_until(obs) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -4299,7 +4299,7 @@ mod tests { assert_eq!(zipper.path(), b""); assert_eq!(zipper.val_count(), 2); - assert_eq!(zipper.descend_until(std::io::sink()), true); + assert_eq!(zipper.descend_until(&mut ()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.val_count(), 1); } diff --git a/src/zipper.rs b/src/zipper.rs index 9a958853..72f97437 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -267,15 +267,12 @@ pub trait ZipperMoving: Zipper { /// /// If there is a value at the focus, the zipper will descend to the next value or branch, however the /// zipper will not descend further if this method is called with the focus already on a branch. - /// - /// Any descended bytes will be written to `desc_bytes`. Pass [`std::io::sink`] if you don't need this - /// information. - fn descend_until(&mut self, mut desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { let mut descended = false; - while self.child_count() == 1 { + while self.child_count() == 1 && obs.remaining_limit() > 0 { descended = true; if let Some(byte) = self.descend_first_byte() { - let _ = desc_bytes.write_all(&[byte]); + let _ = obs.descend_to_byte(byte); } if self.is_val() { break; @@ -381,6 +378,56 @@ pub trait ZipperMoving: Zipper { } } +/// Implemented on types used to observe the effects of methods from [`ZipperMoving`], [`ZipperIteration`], +/// and other methods that affect a zipper's focus position +pub trait PathObserver { + /// Informs the `PathObserver` that the zipper is descending `path` bytes, relative + /// to the zipper's current focus + fn descend_to(&mut self, path: &[u8]); + + /// Equivalent to `self.descend_to(&[byte])` but with slightly less overhead + fn descend_to_byte(&mut self, byte: u8) { + self.descend_to(&[byte]); + } + + /// Informs the `PathObserver` that the zipper is ascending `steps` bytes, relative + /// to the zipper's current focus + fn ascend(&mut self, steps: usize); + + /// Returns the number of bytes remaining in the observer's buffer. Pass [`usize::MAX`] to + /// indicate that the buffer may grow dynamically or that a limit does not apply + // + //GOAT, after implementing (and in some cases failing to implement) graceful exits from + // methods when the limit is insufficient, I am coming to believe exposing this limit here + // is the wrong design. It puts tons of branches into the zipper methods we are hoping to + // streamline, so it'll come with a perf and maintainability cost. Also, really long paths + // are already considered an anti-pattern, so we shouldn't bump up against a need for really + // big buffers very often. It seems to me that a `PathObserver` that wants to absolutely + // avoid a realloc in zipper movement should take care to preallocate a sufficient buffer + // for the common case, and spill the unwritten bytes somehow inside its implementation, + // setting whatever retry flags are needed for the caller. + fn remaining_limit(&self) -> usize { + usize::MAX + } +} + +impl PathObserver for Vec { + fn descend_to(&mut self, path: &[u8]) { + self.extend_from_slice(path); + } + fn descend_to_byte(&mut self, byte: u8) { + self.push(byte) + } + fn ascend(&mut self, steps: usize) { + self.truncate(self.len() - steps) + } +} + +impl PathObserver for () { + fn descend_to(&mut self, _path: &[u8]) { } + fn ascend(&mut self, _steps: usize) { } +} + /// An interface to access values through a [Zipper] that cannot modify the trie. Allows /// references with lifetimes that may outlive the zipper /// @@ -485,7 +532,7 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { if self.is_val() { return true } - if self.descend_until(std::io::sink()) { + if self.descend_until(&mut ()) { if self.is_val() { return true } @@ -520,7 +567,7 @@ pub trait ZipperIteration: ZipperMoving + ZipperPath { let mut any = false; while self.descend_last_byte().is_some() { any = true; - self.descend_until(std::io::sink()); + self.descend_until(&mut ()); } any } @@ -778,7 +825,7 @@ impl ZipperMoving for &mut Z where Z: ZipperMoving + Zipper { fn descend_to_existing_byte(&mut self, k: u8) -> bool { (**self).descend_to_existing_byte(k) } fn descend_indexed_byte(&mut self, idx: usize) -> Option { (**self).descend_indexed_byte(idx) } fn descend_first_byte(&mut self) -> Option { (**self).descend_first_byte() } - fn descend_until(&mut self, desc_bytes: W) -> bool { (**self).descend_until(desc_bytes) } + fn descend_until(&mut self, obs: &mut Obs) -> bool { (**self).descend_until(obs) } fn ascend(&mut self, steps: usize) -> usize { (**self).ascend(steps) } fn ascend_byte(&mut self) -> bool { (**self).ascend_byte() } fn ascend_until(&mut self) -> usize { (**self).ascend_until() } @@ -918,7 +965,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn descend_to_existing_byte(&mut self, k: u8) -> bool { self.z.descend_to_existing_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.z.descend_until(obs) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -1071,7 +1118,7 @@ impl<'trie, V: Clone + Send + Sync + Unpin + 'trie, A: Allocator + 'trie> Zipper fn descend_to_existing_byte(&mut self, k: u8) -> bool { self.z.descend_to_existing_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.z.descend_until(obs) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -1274,7 +1321,7 @@ impl ZipperMoving for ReadZipperOw fn descend_to_existing_byte(&mut self, k: u8) -> bool { self.z.descend_to_existing_byte(k) } fn descend_indexed_byte(&mut self, child_idx: usize) -> Option { self.z.descend_indexed_byte(child_idx) } fn descend_first_byte(&mut self) -> Option { self.z.descend_first_byte() } - fn descend_until(&mut self, desc_bytes: W) -> bool { self.z.descend_until(desc_bytes) } + fn descend_until(&mut self, obs: &mut Obs) -> bool { self.z.descend_until(obs) } fn to_next_sibling_byte(&mut self) -> Option { self.z.to_next_sibling_byte() } fn to_prev_sibling_byte(&mut self) -> Option { self.z.to_prev_sibling_byte() } fn ascend(&mut self, steps: usize) -> usize { self.z.ascend(steps) } @@ -1730,12 +1777,12 @@ pub(crate) mod read_zipper_core { } } - fn descend_until(&mut self, mut desc_bytes: W) -> bool { + fn descend_until(&mut self, obs: &mut Obs) -> bool { debug_assert!(self.is_regularized()); let mut moved = false; while self.child_count() == 1 { moved = true; - self.descend_first(&mut desc_bytes); + self.descend_first(obs); if self.is_val_internal() { break; } @@ -2689,26 +2736,34 @@ pub(crate) mod read_zipper_core { /// Internal method implementing part of [Self::descend_until], but doesn't pay attention to to [Self::child_count] #[inline] - fn descend_first(&mut self, desc_bytes: &mut W) { + fn descend_first(&mut self, obs: &mut Obs) { self.prepare_buffers(); match self.focus_node.first_child_from_key(self.node_key()) { (Some(prefix), Some(child_node)) => { - //Step to a new node - self.prefix_buf.extend(prefix); - let _ = desc_bytes.write_all(prefix); - self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); - *self.focus_node = child_node; - self.focus_iter_token = NODE_ITER_INVALID; + if obs.remaining_limit() == usize::MAX || obs.remaining_limit() <= prefix.len() { + //Step to a new node + self.prefix_buf.extend(prefix); + obs.descend_to(prefix); + self.ancestors.push((*self.focus_node.clone(), self.focus_iter_token, self.prefix_buf.len())); + *self.focus_node = child_node; + self.focus_iter_token = NODE_ITER_INVALID; - //If we're at the root of the new node, descend to the first child - if prefix.len() == 0 { - self.descend_first(desc_bytes) + //If we're at the root of the new node, descend to the first child + if prefix.len() == 0 { + self.descend_first(obs) + } + } else { + return } }, (Some(prefix), None) => { - //Stay within the same node - self.prefix_buf.extend(prefix); - let _ = desc_bytes.write_all(prefix); + if obs.remaining_limit() == usize::MAX || obs.remaining_limit() <= prefix.len() { + //Stay within the same node + self.prefix_buf.extend(prefix); + obs.descend_to(prefix); + } else { + return + } }, (None, _) => unreachable!() } @@ -3356,7 +3411,7 @@ pub(crate) mod zipper_moving_tests { pub fn zipper_descend_until_test1(mut zip: Z) { for key in ZIPPER_DESCEND_UNTIL_TEST1_KEYS { - assert!(zip.descend_until(std::io::sink())); + assert!(zip.descend_until(&mut ())); assert_eq!(zip.path(), *key); } } @@ -3546,20 +3601,20 @@ pub(crate) mod zipper_moving_tests { descend_byte(&mut zipper, b'r'); assert_eq!(zipper.path(), b"r"); assert_eq!(zipper.child_count(), 2); - assert_eq!(zipper.descend_until(std::io::sink()), false); + assert_eq!(zipper.descend_until(&mut ()), false); descend_byte(&mut zipper, b'o'); assert_eq!(zipper.path(), b"ro"); assert_eq!(zipper.child_count(), 1); - assert_eq!(zipper.descend_until(std::io::sink()), true); + assert_eq!(zipper.descend_until(&mut ()), true); assert_eq!(zipper.path(), b"rom"); assert_eq!(zipper.child_count(), 3); zipper.reset(); - assert_eq!(zipper.descend_until(std::io::sink()), false); + assert_eq!(zipper.descend_until(&mut ()), false); descend_byte(&mut zipper, b'a'); assert_eq!(zipper.path(), b"a"); assert_eq!(zipper.child_count(), 1); - assert_eq!(zipper.descend_until(std::io::sink()), true); + assert_eq!(zipper.descend_until(&mut ()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.child_count(), 0); @@ -3587,7 +3642,7 @@ pub(crate) mod zipper_moving_tests { assert_eq!(zipper.ascend(1), 1); zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); - zipper.descend_until(std::io::sink()); + zipper.descend_until(&mut ()); assert_eq!(zipper.is_val(), true); } @@ -4370,7 +4425,7 @@ mod tests { zipper.descend_to(b"u"); assert_eq!(zipper.is_val(), false); assert_eq!(zipper.val(), None); - zipper.descend_until(std::io::sink()); + zipper.descend_until(&mut ()); assert_eq!(zipper.is_val(), true); assert_eq!(zipper.val(), Some(&"romanus")); } @@ -4707,7 +4762,7 @@ mod tests { assert_eq!(zipper.path(), b""); assert_eq!(zipper.val_count(), 2); - assert_eq!(zipper.descend_until(std::io::sink()), true); + assert_eq!(zipper.descend_until(&mut ()), true); assert_eq!(zipper.path(), b"arrow"); assert_eq!(zipper.val_count(), 1); } diff --git a/src/zipper_head.rs b/src/zipper_head.rs index 07a6ecc4..203f12a9 100644 --- a/src/zipper_head.rs +++ b/src/zipper_head.rs @@ -1044,7 +1044,7 @@ mod tests { let mut z = zh.read_zipper_at_path(b"A").unwrap(); assert_eq!(z.val(), Some(&24)); assert_eq!(z.to_next_sibling_byte(), None); - z.descend_until(std::io::sink()); + z.descend_until(&mut ()); assert_eq!(z.path(), b"BCDEFG"); assert_eq!(z.origin_path(), b"ABCDEFG"); assert_eq!(z.val(), Some(&42)); @@ -1412,7 +1412,7 @@ mod tests { //Make sure we cleaned up the dangling path, but nothing else let mut rz = zh.read_zipper_at_borrowed_path(b"a_path_").unwrap(); - assert!(rz.descend_until(std::io::sink())); + assert!(rz.descend_until(&mut ())); assert_eq!(rz.path(), b"to_somewhere"); drop(rz); @@ -1421,7 +1421,7 @@ mod tests { zh.cleanup_write_zipper(wz); let mut rz = zh.read_zipper_at_borrowed_path(b"a_path_").unwrap(); assert_eq!(rz.path(), b""); - assert!(rz.descend_until(std::io::sink())); + assert!(rz.descend_until(&mut ())); assert_eq!(rz.path(), b"to_somewhere"); drop(rz);