mod frame;
mod reference;
pub(crate) use frame::ContextFrame;
pub(crate) use reference::ContextRef;
use super::value::DataValue;
#[cfg(all(test, feature = "serde_json"))]
use bumpalo::Bump;
use smallvec::SmallVec;
const INLINE_FRAMES: usize = 4;
pub(crate) struct ContextStack<'a> {
root: &'a DataValue<'a>,
top: Option<ContextFrame<'a>>,
parents: SmallVec<[ContextFrame<'a>; INLINE_FRAMES]>,
error_path: Vec<u32>,
cse_slots: SmallVec<[Option<&'a DataValue<'a>>; 8]>,
#[cfg(feature = "error-handling")]
catch_depth: u32,
#[cfg(feature = "error-handling")]
thrown_slot: Option<&'a DataValue<'a>>,
#[cfg(feature = "trace")]
tracer: Option<crate::trace::TraceCollector>,
}
impl<'a> ContextStack<'a> {
#[inline]
pub(crate) fn new(root: &'a DataValue<'a>) -> Self {
Self {
root,
top: None,
parents: SmallVec::new(),
error_path: Vec::new(),
cse_slots: SmallVec::new(),
#[cfg(feature = "error-handling")]
catch_depth: 0,
#[cfg(feature = "error-handling")]
thrown_slot: None,
#[cfg(feature = "trace")]
tracer: None,
}
}
#[cfg(all(test, feature = "serde_json"))]
#[inline]
pub(crate) fn from_value(root: &'a serde_json::Value, arena: &'a Bump) -> Self {
let av = crate::arena::value::value_to_data(root, arena);
Self::new(arena.alloc(av))
}
#[cfg(feature = "trace")]
#[inline]
pub(crate) fn attach_tracer(&mut self, tracer: crate::trace::TraceCollector) {
self.tracer = Some(tracer);
}
#[cfg(feature = "trace")]
#[inline]
pub(crate) fn detach_tracer(&mut self) -> Option<crate::trace::TraceCollector> {
self.tracer.take()
}
#[cfg(feature = "trace")]
#[inline]
pub(crate) fn has_tracer(&self) -> bool {
self.tracer.is_some()
}
#[cfg(feature = "trace")]
#[inline]
fn with_tracer<F: FnOnce(&mut crate::trace::TraceCollector)>(&mut self, f: F) {
if let Some(tracer) = self.tracer.as_mut() {
f(tracer);
}
}
#[inline]
pub(crate) fn is_tracing(&self) -> bool {
#[cfg(feature = "trace")]
{
self.has_tracer()
}
#[cfg(not(feature = "trace"))]
{
false
}
}
#[cfg(all(feature = "trace", feature = "serde_json"))]
pub(crate) fn current_data_as_value(&self) -> serde_json::Value {
crate::arena::data_to_value(self.current().data())
}
#[cfg(all(feature = "trace", feature = "serde_json"))]
pub(crate) fn record_node_result(
&mut self,
node_id: u32,
ctx_data: serde_json::Value,
result: &crate::Result<&'a crate::arena::DataValue<'a>>,
) {
self.with_tracer(|collector| match result {
Ok(av) => {
let v = crate::arena::data_to_value(av);
collector.record_step(node_id, ctx_data, v);
}
Err(e) => {
collector.record_error(node_id, ctx_data, e.to_string());
}
});
}
#[cfg(feature = "trace")]
#[inline]
pub(crate) fn trace_push_iteration(&mut self, index: u32, total: u32) {
self.with_tracer(|c| c.push_iteration(index, total));
}
#[cfg(feature = "trace")]
#[inline]
pub(crate) fn trace_pop_iteration(&mut self) {
self.with_tracer(|c| c.pop_iteration());
}
#[inline]
pub(crate) fn root_input(&self) -> &'a DataValue<'a> {
self.root
}
#[inline]
pub(crate) fn depth(&self) -> usize {
self.parents.len() + usize::from(self.top.is_some())
}
#[inline]
pub(crate) fn cse_slot(&self, slot: u16) -> Option<&'a DataValue<'a>> {
self.cse_slots.get(slot as usize).copied().flatten()
}
#[inline]
pub(crate) fn fill_cse_slot(&mut self, slot: u16, value: &'a DataValue<'a>) {
let index = slot as usize;
if index >= self.cse_slots.len() {
self.cse_slots.resize(index + 1, None);
}
self.cse_slots[index] = Some(value);
}
#[inline]
pub(crate) fn current(&self) -> ContextRef<'a, '_> {
if let Some(frame) = self.top.as_ref() {
ContextRef::Frame(frame)
} else {
ContextRef::Root(self.root)
}
}
pub(crate) fn get_at_level(&self, level: isize) -> Option<ContextRef<'a, '_>> {
let levels_up = level.unsigned_abs();
if levels_up == 0 {
return Some(self.current());
}
let frame_count = self.depth();
if levels_up >= frame_count {
return Some(ContextRef::Root(self.root));
}
let target_index = frame_count - levels_up;
if target_index == self.parents.len() {
self.top.as_ref().map(ContextRef::Frame)
} else {
self.parents.get(target_index).map(ContextRef::Frame)
}
}
#[inline]
fn push_frame(&mut self, frame: ContextFrame<'a>) {
if let Some(prev) = self.top.replace(frame) {
self.parents.push(prev);
}
}
#[inline]
pub(crate) fn push(&mut self, data: &'a DataValue<'a>) {
self.push_frame(ContextFrame::Data(data));
}
#[inline]
pub(crate) fn push_with_index(&mut self, data: &'a DataValue<'a>, index: usize) {
self.push_frame(ContextFrame::Indexed { data, index });
}
#[inline]
fn push_with_key_index(&mut self, data: &'a DataValue<'a>, index: usize, key: &'a str) {
self.push_frame(ContextFrame::Keyed { data, index, key });
}
#[inline]
fn push_reduce(&mut self, current: &'a DataValue<'a>, accumulator: &'a DataValue<'a>) {
self.push_frame(ContextFrame::Reduce {
current,
accumulator,
});
}
#[inline]
fn replace_top_data(&mut self, data: &'a DataValue<'a>, index: usize) {
if let Some(frame) = self.top.as_mut() {
*frame = ContextFrame::Indexed { data, index };
}
}
#[inline]
fn replace_top_key_data(&mut self, data: &'a DataValue<'a>, index: usize, key: &'a str) {
if let Some(frame) = self.top.as_mut() {
*frame = ContextFrame::Keyed { data, index, key };
}
}
#[inline]
fn replace_reduce_data(&mut self, current: &'a DataValue<'a>, accumulator: &'a DataValue<'a>) {
if let Some(frame) = self.top.as_mut() {
*frame = ContextFrame::Reduce {
current,
accumulator,
};
}
}
#[inline]
pub(crate) fn pop(&mut self) -> Option<ContextFrame<'a>> {
let out = self.top.take();
if out.is_some() {
self.top = self.parents.pop();
}
out
}
#[cold]
#[inline(never)]
pub(crate) fn push_error_step(&mut self, id: u32) {
self.error_path.push(id);
}
#[cfg(feature = "error-handling")]
#[inline]
pub(crate) fn error_path_len(&self) -> usize {
self.error_path.len()
}
#[cfg(feature = "error-handling")]
#[inline]
pub(crate) fn truncate_error_path(&mut self, len: usize) {
self.error_path.truncate(len);
}
#[inline]
pub(crate) fn take_error_path(&mut self) -> Vec<u32> {
std::mem::take(&mut self.error_path)
}
#[cfg(feature = "error-handling")]
#[inline]
pub(crate) fn in_catch_scope(&self) -> bool {
self.catch_depth > 0
}
#[cfg(feature = "error-handling")]
#[inline]
pub(crate) fn enter_catch_scope(&mut self) {
self.catch_depth += 1;
}
#[cfg(feature = "error-handling")]
#[inline]
pub(crate) fn exit_catch_scope(&mut self) {
debug_assert!(self.catch_depth > 0, "unbalanced exit_catch_scope");
self.catch_depth -= 1;
}
#[cfg(feature = "error-handling")]
#[inline]
pub(crate) fn set_thrown_slot(&mut self, payload: &'a DataValue<'a>) {
self.thrown_slot = Some(payload);
}
#[cfg(feature = "error-handling")]
#[inline]
pub(crate) fn take_thrown_slot(&mut self) -> Option<&'a DataValue<'a>> {
self.thrown_slot.take()
}
#[cfg(feature = "error-handling")]
#[inline]
pub(crate) fn clear_thrown_slot(&mut self) {
self.thrown_slot = None;
}
}
pub(crate) struct IterGuard<'g, 'a> {
ctx: &'g mut ContextStack<'a>,
pushed: bool,
}
impl<'g, 'a> IterGuard<'g, 'a> {
#[inline]
pub(crate) fn new(ctx: &'g mut ContextStack<'a>) -> Self {
Self { ctx, pushed: false }
}
#[inline]
pub(crate) fn step_indexed(&mut self, data: &'a DataValue<'a>, index: usize) {
if self.pushed {
self.ctx.replace_top_data(data, index);
} else {
self.ctx.push_with_index(data, index);
self.pushed = true;
}
}
#[inline]
pub(crate) fn step_keyed(&mut self, data: &'a DataValue<'a>, index: usize, key: &'a str) {
if self.pushed {
self.ctx.replace_top_key_data(data, index, key);
} else {
self.ctx.push_with_key_index(data, index, key);
self.pushed = true;
}
}
#[inline]
pub(crate) fn step_reduce(
&mut self,
current: &'a DataValue<'a>,
accumulator: &'a DataValue<'a>,
) {
if self.pushed {
self.ctx.replace_reduce_data(current, accumulator);
} else {
self.ctx.push_reduce(current, accumulator);
self.pushed = true;
}
}
#[inline]
pub(crate) fn stack(&mut self) -> &mut ContextStack<'a> {
self.ctx
}
}
impl Drop for IterGuard<'_, '_> {
#[inline]
fn drop(&mut self) {
if self.pushed {
self.ctx.pop();
}
}
}
#[cfg(all(test, feature = "serde_json"))]
mod tests {
use super::*;
use crate::arena::value::DataValue;
use serde_json::Value;
#[test]
fn lifecycle_indexed() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
assert_eq!(ctx.depth(), 0);
assert!(ctx.current().root_data().is_some(), "root at depth 0");
let a: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(1)));
ctx.push_with_index(a, 0);
assert_eq!(ctx.depth(), 1);
assert_eq!(ctx.current().get_index(), Some(0));
let b: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(2)));
ctx.replace_top_data(b, 1);
assert_eq!(ctx.current().get_index(), Some(1));
ctx.pop();
assert_eq!(ctx.depth(), 0);
}
#[test]
fn lifecycle_keyed() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
let a: &DataValue = arena.alloc(DataValue::Bool(true));
ctx.push_with_key_index(a, 0, "k1");
assert_eq!(ctx.current().get_key(), Some("k1"));
let b: &DataValue = arena.alloc(DataValue::Bool(false));
ctx.replace_top_key_data(b, 1, "k2");
assert_eq!(ctx.current().get_key(), Some("k2"));
assert_eq!(ctx.current().get_index(), Some(1));
}
#[test]
fn lifecycle_reduce() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
let cur: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(1)));
let acc: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(0)));
ctx.push_reduce(cur, acc);
assert_eq!(ctx.depth(), 1);
if let ContextRef::Frame(f) = ctx.current() {
assert!(f.get_reduce_current().is_some());
assert!(f.get_reduce_accumulator().is_some());
} else {
panic!("expected frame");
}
}
#[test]
fn get_at_level_walks_up() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
let a: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(10)));
let b: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(20)));
ctx.push_with_index(a, 0);
ctx.push_with_index(b, 0);
assert_eq!(ctx.depth(), 2);
assert!(ctx.get_at_level(0).and_then(|r| r.frame_data()).is_some());
assert!(ctx.get_at_level(1).and_then(|r| r.frame_data()).is_some());
assert!(ctx.get_at_level(2).and_then(|r| r.root_data()).is_some());
assert!(ctx.get_at_level(5).and_then(|r| r.root_data()).is_some());
}
#[test]
fn pop_restores_parent_frames() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
let a: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(1)));
let b: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(2)));
let c: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(3)));
ctx.push_with_index(a, 10);
ctx.push_with_index(b, 20);
ctx.push_with_index(c, 30);
assert_eq!(ctx.depth(), 3);
assert_eq!(ctx.current().get_index(), Some(30));
assert!(ctx.pop().is_some());
assert_eq!(ctx.depth(), 2);
assert_eq!(ctx.current().get_index(), Some(20), "parent restored");
assert!(ctx.pop().is_some());
assert_eq!(ctx.current().get_index(), Some(10));
assert!(ctx.pop().is_some());
assert_eq!(ctx.depth(), 0);
assert!(ctx.current().root_data().is_some(), "back to root");
assert!(ctx.pop().is_none(), "empty pop is a no-op");
}
#[test]
fn deep_nesting_spills_and_unwinds() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
let depth = INLINE_FRAMES + 4;
for i in 0..depth {
let v: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(
i as i64,
)));
ctx.push_with_index(v, i);
}
assert_eq!(ctx.depth(), depth);
assert_eq!(ctx.current().get_index(), Some(depth - 1));
for levels_up in 1..depth {
let r = ctx.get_at_level(levels_up as isize).expect("in range");
assert_eq!(r.get_index(), Some(depth - levels_up));
}
assert!(
ctx.get_at_level(depth as isize)
.and_then(|r| r.root_data())
.is_some()
);
for i in (0..depth).rev() {
assert_eq!(ctx.current().get_index(), Some(i));
assert!(ctx.pop().is_some());
}
assert_eq!(ctx.depth(), 0);
assert!(ctx.pop().is_none());
}
#[test]
fn iter_guard_pushes_then_pops_indexed() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
assert_eq!(ctx.depth(), 0);
let a: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(1)));
let b: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(2)));
{
let mut g = IterGuard::new(&mut ctx);
g.step_indexed(a, 0);
assert_eq!(g.stack().depth(), 1);
assert_eq!(g.stack().current().get_index(), Some(0));
g.step_indexed(b, 1);
assert_eq!(g.stack().depth(), 1, "replace, not push");
assert_eq!(g.stack().current().get_index(), Some(1));
}
assert_eq!(ctx.depth(), 0, "drop pops");
}
#[test]
fn iter_guard_no_push_no_pop() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
assert_eq!(ctx.depth(), 0);
{
let _g = IterGuard::new(&mut ctx);
}
assert_eq!(ctx.depth(), 0, "drop without push is a no-op");
}
#[test]
fn iter_guard_keyed_and_reduce() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
let a: &DataValue = arena.alloc(DataValue::Bool(true));
let b: &DataValue = arena.alloc(DataValue::Bool(false));
{
let mut g = IterGuard::new(&mut ctx);
g.step_keyed(a, 0, "k1");
assert_eq!(g.stack().current().get_key(), Some("k1"));
g.step_keyed(b, 1, "k2");
assert_eq!(g.stack().current().get_key(), Some("k2"));
assert_eq!(g.stack().current().get_index(), Some(1));
}
assert_eq!(ctx.depth(), 0);
let cur: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(1)));
let acc: &DataValue = arena.alloc(DataValue::Number(datavalue::NumberValue::from_i64(0)));
{
let mut g = IterGuard::new(&mut ctx);
g.step_reduce(cur, acc);
assert_eq!(g.stack().depth(), 1);
g.step_reduce(acc, cur); assert_eq!(g.stack().depth(), 1);
}
assert_eq!(ctx.depth(), 0);
}
#[cfg(feature = "error-handling")]
#[test]
fn error_path_round_trip() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
ctx.push_error_step(1);
ctx.push_error_step(2);
ctx.push_error_step(3);
assert_eq!(ctx.error_path_len(), 3);
ctx.truncate_error_path(1);
let p = ctx.take_error_path();
assert_eq!(p, vec![1]);
}
#[cfg(feature = "error-handling")]
#[test]
fn thrown_slot_and_catch_scope_round_trip() {
let arena = Bump::new();
let root_val = Value::Null;
let mut ctx = ContextStack::from_value(&root_val, &arena);
assert!(!ctx.in_catch_scope());
ctx.enter_catch_scope();
ctx.enter_catch_scope();
assert!(ctx.in_catch_scope());
ctx.exit_catch_scope();
assert!(ctx.in_catch_scope(), "nested scopes count");
ctx.exit_catch_scope();
assert!(!ctx.in_catch_scope());
assert!(ctx.take_thrown_slot().is_none());
let payload: &DataValue = arena.alloc(DataValue::Bool(true));
ctx.set_thrown_slot(payload);
assert!(std::ptr::eq(
ctx.take_thrown_slot().expect("slot set"),
payload
));
assert!(ctx.take_thrown_slot().is_none(), "take consumes");
ctx.set_thrown_slot(payload);
ctx.clear_thrown_slot();
assert!(ctx.take_thrown_slot().is_none(), "clear drops");
}
}