diff --git a/crates/plotnik-lib/src/engine/cursor.rs b/crates/plotnik-lib/src/engine/cursor.rs index dc803fe0..d1375570 100644 --- a/crates/plotnik-lib/src/engine/cursor.rs +++ b/crates/plotnik-lib/src/engine/cursor.rs @@ -78,6 +78,18 @@ impl<'t> CursorWrapper<'t> { self.cursor.field_id() } + /// Get current tree depth (root is 0). + #[inline] + pub fn depth(&self) -> u32 { + self.cursor.depth() + } + + /// Move cursor to parent node. + #[inline] + pub fn goto_parent(&mut self) -> bool { + self.cursor.goto_parent() + } + /// Check if a node type is trivia. #[inline] pub fn is_trivia(&self, node: &Node<'_>) -> bool { diff --git a/crates/plotnik-lib/src/engine/frame.rs b/crates/plotnik-lib/src/engine/frame.rs index 78773410..e32ade0b 100644 --- a/crates/plotnik-lib/src/engine/frame.rs +++ b/crates/plotnik-lib/src/engine/frame.rs @@ -10,8 +10,8 @@ pub struct Frame { pub return_addr: u16, /// Parent frame index (for cactus stack). pub parent: Option, - /// Cursor position before Call navigation (for restoration on Return). - pub cursor_position: u32, + /// Tree depth at Call time (for level restoration on Return). + pub saved_depth: u32, } /// Append-only arena for frames (cactus stack implementation). @@ -35,25 +35,25 @@ impl FrameArena { } /// Push a new frame, returns its index. - pub fn push(&mut self, return_addr: u16, cursor_position: u32) -> u32 { + pub fn push(&mut self, return_addr: u16, saved_depth: u32) -> u32 { let idx = self.frames.len() as u32; self.frames.push(Frame { return_addr, parent: self.current, - cursor_position, + saved_depth, }); self.current = Some(idx); idx } - /// Pop the current frame, returning its return address and cursor position. + /// Pop the current frame, returning its return address and saved depth. /// /// Panics if the stack is empty. pub fn pop(&mut self) -> (u16, u32) { let current_idx = self.current.expect("pop on empty frame stack"); let frame = self.frames[current_idx as usize]; self.current = frame.parent; - (frame.return_addr, frame.cursor_position) + (frame.return_addr, frame.saved_depth) } /// Restore frame state for backtracking. diff --git a/crates/plotnik-lib/src/engine/vm.rs b/crates/plotnik-lib/src/engine/vm.rs index c1043148..297ffa46 100644 --- a/crates/plotnik-lib/src/engine/vm.rs +++ b/crates/plotnik-lib/src/engine/vm.rs @@ -231,12 +231,13 @@ impl<'t> VM<'t> { self.navigate_to_field(c.nav, c.node_field, tracer)?; - // Save cursor position AFTER navigation - this is where the callee starts matching. - // On Return, we restore to this position so Nav::Next in quantifier loops works correctly. - let saved_cursor = self.cursor.descendant_index(); + // Save tree depth AFTER navigation. On Return, we go up to this depth. + // This allows continue_search advances at the same level to be preserved, + // while still restoring level when the callee descended into children. + let saved_depth = self.cursor.depth(); tracer.trace_call(c.target.get()); - self.frames.push(c.next.get(), saved_cursor); + self.frames.push(c.next.get(), saved_depth); self.recursion_depth += 1; self.ip = c.target.get(); Ok(()) @@ -296,18 +297,24 @@ impl<'t> VM<'t> { return Err(RuntimeError::Accept); } - let (return_addr, saved_cursor) = self.frames.pop(); + let (return_addr, saved_depth) = self.frames.pop(); self.recursion_depth -= 1; // Prune frames (O(1) amortized) self.frames.prune(self.checkpoints.max_frame_ref()); - // Set matched_node BEFORE restoring cursor so effects after + // Set matched_node BEFORE going up so effects after // a Call can capture the node that the callee matched. self.matched_node = Some(self.cursor.node()); - // Restore cursor position for subsequent navigation (e.g., Nav::Next in quantifier loop) - self.cursor.goto_descendant(saved_cursor); + // Go up to saved depth level. This preserves sibling advances + // (continue_search at same level) while restoring level when + // the callee descended into children. + while self.cursor.depth() > saved_depth { + if !self.cursor.goto_parent() { + break; + } + } self.ip = return_addr; Ok(())