diff --git a/src/arena_compact.rs b/src/arena_compact.rs index c05f9af5..70397698 100644 --- a/src/arena_compact.rs +++ b/src/arena_compact.rs @@ -2668,17 +2668,9 @@ where Storage: AsRef<[u8]> return start_len - self.path.len(); } - match &self.cur_node { - Node::Line(line) => { - if need_value && line.value.is_some() { - return start_len - self.path.len(); - } - } - Node::Branch(node) => { - if need_value && node.value.is_some() { - return start_len - self.path.len(); - } - } + //The deepest real ancestor stops the ascent if the *focus* has a value (when needed) or branches + if (need_value && self.is_val()) || self.child_count() > 1 { + return start_len - self.path.len(); } } while let Some(top_frame) = self.stack.last_mut() { @@ -4232,4 +4224,51 @@ mod tests { assert_eq!(az.val(), None); assert!(!az.path_exists()); } + + /// `ACTZipper` sibling steps from a focus off the trie + #[test] + fn act_zipper_sibling_step_from_an_off_trie_focus() { + use crate::zipper::*; + let mut m = PathMap::::new(); + { let mut w = m.write_zipper(); w.set_val(38); } + m.insert(&[1u8], 5); + m.insert(&[1u8, 0, 2], 22); + m.insert(&[3u8], 7); + let t = ArenaCompactTree::from_zipper(m.read_zipper(), |&v| v); + + //One byte off the trie, with a sibling on either side + let mut az = t.read_zipper_u64(); + az.descend_to(&[2u8]); + assert!(!az.path_exists()); + assert_eq!(az.to_next_sibling_byte(), Some(3)); + assert_eq!(az.path(), &[3u8]); + assert_eq!(az.val(), Some(&7)); + az.ascend(1); + az.descend_to(&[2u8]); + assert_eq!(az.to_prev_sibling_byte(), Some(1)); + assert_eq!(az.path(), &[1u8]); + assert_eq!(az.val(), Some(&5)); + + //No sibling on that side: the zipper stays where it was + let mut az = t.read_zipper_u64(); + az.descend_to(&[0u8]); + assert_eq!(az.to_prev_sibling_byte(), None); + assert_eq!(az.path(), &[0u8]); + assert!(!az.path_exists()); + assert_eq!(az.to_next_sibling_byte(), Some(1)); + + //Two bytes off the trie: the parent is not real, so there is no sibling + let mut az = t.read_zipper_u64(); + az.descend_to(&[2u8, 0]); + assert_eq!(az.to_next_sibling_byte(), None); + assert_eq!(az.path(), &[2u8, 0]); + + //`to_next_step` from an off-trie focus visits what follows it + let mut az = t.read_zipper_u64(); + az.descend_to(&[0u8]); + let mut seen = Vec::new(); + while az.to_next_step() { seen.push(az.path().to_vec()); } + assert_eq!(seen, vec![vec![1u8], vec![1, 0], vec![1, 0, 2], vec![3]]); + } + } diff --git a/src/zipper.rs b/src/zipper.rs index 1506e55b..fcede695 100644 --- a/src/zipper.rs +++ b/src/zipper.rs @@ -3725,6 +3725,30 @@ pub(crate) mod zipper_moving_tests { crate::zipper::zipper_moving_tests::run_test(&mut temp_store, $make_z, &[], crate::zipper::zipper_moving_tests::zipper_ascend_until_test5) } + #[test] + fn [<$z_name _ascend_until_from_off_trie_root>]() { + let mut temp_store = $read_keys(crate::zipper::zipper_moving_tests::OFF_TRIE_ASCEND_KEYS); + crate::zipper::zipper_moving_tests::run_test(&mut temp_store, $make_z, &[], crate::zipper::zipper_moving_tests::ascend_until_from_off_trie_root) + } + + #[test] + fn [<$z_name _ascend_until_from_off_trie_line_root>]() { + let mut temp_store = $read_keys(crate::zipper::zipper_moving_tests::OFF_TRIE_ASCEND_KEYS); + crate::zipper::zipper_moving_tests::run_test(&mut temp_store, $make_z, &[1], crate::zipper::zipper_moving_tests::ascend_until_from_off_trie_line_root) + } + + #[test] + fn [<$z_name _ascend_until_from_off_trie_mid_line_root>]() { + let mut temp_store = $read_keys(crate::zipper::zipper_moving_tests::OFF_TRIE_ASCEND_KEYS); + crate::zipper::zipper_moving_tests::run_test(&mut temp_store, $make_z, &[1, 2], crate::zipper::zipper_moving_tests::ascend_until_from_off_trie_mid_line_root) + } + + #[test] + fn [<$z_name _ascend_until_from_off_trie_value_branch_root>]() { + let mut temp_store = $read_keys(crate::zipper::zipper_moving_tests::OFF_TRIE_ASCEND_KEYS); + crate::zipper::zipper_moving_tests::run_test(&mut temp_store, $make_z, &[7], crate::zipper::zipper_moving_tests::ascend_until_from_off_trie_value_branch_root) + } + #[test] fn [<$z_name _indexed_zipper_movement1>]() { let mut temp_store = $read_keys(crate::zipper::zipper_moving_tests::ZIPPER_INDEXED_MOVEMENT_TEST1_KEYS); @@ -4046,6 +4070,72 @@ pub(crate) mod zipper_moving_tests { assert!(zipper.path_exists()); } + pub const OFF_TRIE_ASCEND_KEYS: &[&[u8]] = &[ + &[1, 2, 3, 4], &[5, 2], &[5, 6], &[7], &[7, 8], &[7, 9], + ]; + + type OffTrieAscendCase = (&'static [u8], bool, usize, &'static [u8], bool, usize, Option, &'static [u8], bool); + + /// Checks an off-trie ascent and then verifies that the landed focus remains usable. + fn run_off_trie_ascend_cases(mut zipper: Z, cases: &[OffTrieAscendCase]) { + for &(focus, need_value, steps, path, is_val, children, first, after_path, after_is_val) in cases { + zipper.reset(); + zipper.descend_to(focus); + assert!(!zipper.path_exists(), "focus {focus:?}"); + let actual_steps = if need_value { zipper.ascend_until() } else { zipper.ascend_until_branch() }; + assert_eq!(actual_steps, steps, "focus {focus:?}, need_value {need_value}"); + assert_eq!(zipper.path(), path, "focus {focus:?}, need_value {need_value}"); + assert!(zipper.path_exists(), "focus {focus:?}, need_value {need_value}"); + assert_eq!(zipper.is_val(), is_val, "focus {focus:?}, need_value {need_value}"); + assert_eq!(zipper.child_count(), children, "focus {focus:?}, need_value {need_value}"); + assert_eq!(zipper.descend_first_byte(), first, "focus {focus:?}, need_value {need_value}"); + assert_eq!(zipper.path(), after_path, "focus {focus:?}, need_value {need_value}"); + assert_eq!(zipper.is_val(), after_is_val, "focus {focus:?}, need_value {need_value}"); + } + } + + /// Rooted at the map root: 9 off-trie focuses, each tested with both ascent modes. + pub fn ascend_until_from_off_trie_root(zipper: Z) { + run_off_trie_ascend_cases(zipper, &[ + (&[1,2,9],false,3,&[],false,3,Some(1),&[1],false), (&[1,2,9],true,3,&[],false,3,Some(1),&[1],false), + (&[1,2,3,9],false,4,&[],false,3,Some(1),&[1],false), (&[1,2,3,9],true,4,&[],false,3,Some(1),&[1],false), + (&[1,2,3,4,9],false,5,&[],false,3,Some(1),&[1],false), (&[1,2,3,4,9],true,1,&[1,2,3,4],true,0,None,&[1,2,3,4],true), + (&[1,9,9],false,3,&[],false,3,Some(1),&[1],false), (&[1,9,9],true,3,&[],false,3,Some(1),&[1],false), + (&[5,9],false,1,&[5],false,2,Some(2),&[5,2],true), (&[5,9],true,1,&[5],false,2,Some(2),&[5,2],true), + (&[5,2,9],false,2,&[5],false,2,Some(2),&[5,2],true), (&[5,2,9],true,1,&[5,2],true,0,None,&[5,2],true), + (&[7,9,9],false,2,&[7],true,2,Some(8),&[7,8],true), (&[7,9,9],true,1,&[7,9],true,0,None,&[7,9],true), + (&[7,8,9,9],false,3,&[7],true,2,Some(8),&[7,8],true), (&[7,8,9,9],true,2,&[7,8],true,0,None,&[7,8],true), + (&[9],false,1,&[],false,3,Some(1),&[1],false), (&[9],true,1,&[],false,3,Some(1),&[1],false), + ]); + } + + /// Rooted at byte 1, the start of the compressed line. + pub fn ascend_until_from_off_trie_line_root(zipper: Z) { + run_off_trie_ascend_cases(zipper, &[ + (&[2,9],false,2,&[],false,1,Some(2),&[2],false), (&[2,9],true,2,&[],false,1,Some(2),&[2],false), + (&[2,3,9],false,3,&[],false,1,Some(2),&[2],false), (&[2,3,9],true,3,&[],false,1,Some(2),&[2],false), + (&[2,3,4,9],false,4,&[],false,1,Some(2),&[2],false), (&[2,3,4,9],true,1,&[2,3,4],true,0,None,&[2,3,4],true), + (&[9,9],false,2,&[],false,1,Some(2),&[2],false), (&[9,9],true,2,&[],false,1,Some(2),&[2],false), + ]); + } + + /// Rooted partway through the compressed line. + pub fn ascend_until_from_off_trie_mid_line_root(zipper: Z) { + run_off_trie_ascend_cases(zipper, &[ + (&[9],false,1,&[],false,1,Some(3),&[3],false), (&[9],true,1,&[],false,1,Some(3),&[3],false), + (&[3,9],false,2,&[],false,1,Some(3),&[3],false), (&[3,9],true,2,&[],false,1,Some(3),&[3],false), + (&[3,4,9],false,3,&[],false,1,Some(3),&[3],false), (&[3,4,9],true,1,&[3,4],true,0,None,&[3,4],true), + ]); + } + + /// Rooted at byte 7, which is both a value and a branch. + pub fn ascend_until_from_off_trie_value_branch_root(zipper: Z) { + run_off_trie_ascend_cases(zipper, &[ + (&[9,9],false,2,&[],true,2,Some(8),&[8],true), (&[9,9],true,1,&[9],true,0,None,&[9],true), + (&[8,9,9],false,3,&[],true,2,Some(8),&[8],true), (&[8,9,9],true,2,&[8],true,0,None,&[8],true), + ]); + } + 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) {