Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions crates/plotnik-lib/src/engine/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ pub struct Frame {
pub return_addr: u16,
/// Parent frame index (for cactus stack).
pub parent: Option<u32>,
/// Cursor position before Call navigation (for restoration on Return).
pub cursor_position: u32,
}

/// Append-only arena for frames (cactus stack implementation).
Expand All @@ -33,24 +35,25 @@ impl FrameArena {
}

/// Push a new frame, returns its index.
pub fn push(&mut self, return_addr: u16) -> u32 {
pub fn push(&mut self, return_addr: u16, cursor_position: u32) -> u32 {
let idx = self.frames.len() as u32;
self.frames.push(Frame {
return_addr,
parent: self.current,
cursor_position,
});
self.current = Some(idx);
idx
}

/// Pop the current frame, returning its return address.
/// Pop the current frame, returning its return address and cursor position.
///
/// Panics if the stack is empty.
pub fn pop(&mut self) -> u16 {
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.return_addr, frame.cursor_position)
}

/// Restore frame state for backtracking.
Expand Down
13 changes: 10 additions & 3 deletions crates/plotnik-lib/src/engine/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,12 @@ 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();

tracer.trace_call(c.target.get());
self.frames.push(c.next.get());
self.frames.push(c.next.get(), saved_cursor);
self.recursion_depth += 1;
self.ip = c.target.get();
Ok(())
Expand Down Expand Up @@ -292,16 +296,19 @@ impl<'t> VM<'t> {
return Err(RuntimeError::Accept);
}

let return_addr = self.frames.pop();
let (return_addr, saved_cursor) = self.frames.pop();
self.recursion_depth -= 1;

// Prune frames (O(1) amortized)
self.frames.prune(self.checkpoints.max_frame_ref());

// Set matched_node to current cursor position so effects after
// Set matched_node BEFORE restoring cursor 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);

self.ip = return_addr;
Ok(())
}
Expand Down