use crate::node::{CompiledNode, ScopeBinding};
use crate::opcode::OpCode;
pub(crate) fn frames_pushed_for_child(opcode: OpCode, index: usize, len: usize) -> u32 {
#[cfg(feature = "ext-array")]
if matches!(opcode, OpCode::Sort) {
return u32::from(index == 2);
}
#[cfg(feature = "ext-array")]
if matches!(opcode, OpCode::GroupBy | OpCode::Distinct) {
return u32::from(index == 1);
}
#[cfg(feature = "error-handling")]
if matches!(opcode, OpCode::Try) {
return u32::from(len >= 2 && index == len - 1);
}
let _ = len;
u32::from(
matches!(
opcode,
OpCode::Filter
| OpCode::Map
| OpCode::All
| OpCode::Some
| OpCode::None
| OpCode::Reduce
) && index == 1,
)
}
pub(crate) fn resolve(root: &mut CompiledNode) -> bool {
let mut needs_ancestors = false;
resolve_at(root, 0, &mut needs_ancestors);
needs_ancestors
}
fn resolve_at(node: &mut CompiledNode, depth: u32, needs_ancestors: &mut bool) {
match node {
CompiledNode::Var {
scope_level,
binding,
..
} => {
*binding = ScopeBinding::resolve(depth, *scope_level);
*needs_ancestors |= *binding == ScopeBinding::Ancestor;
}
#[cfg(feature = "ext-control")]
CompiledNode::Exists(data) => {
data.binding = ScopeBinding::resolve(depth, data.scope_level);
*needs_ancestors |= data.binding == ScopeBinding::Ancestor;
}
CompiledNode::BuiltinOperator {
opcode: OpCode::Val,
..
} => *needs_ancestors = true,
_ => {}
}
if let CompiledNode::BuiltinOperator { opcode, args, .. } = node {
let opcode = *opcode;
let len = args.len();
for (index, child) in args.iter_mut().enumerate() {
resolve_at(
child,
depth + frames_pushed_for_child(opcode, index, len),
needs_ancestors,
);
}
return;
}
node.visit_children_mut(&mut |child| resolve_at(child, depth, needs_ancestors));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::ScopeBinding;
#[test]
fn iterator_bodies_push_one_frame() {
for op in [
OpCode::Filter,
OpCode::Map,
OpCode::All,
OpCode::Some,
OpCode::None,
OpCode::Reduce,
] {
assert_eq!(frames_pushed_for_child(op, 0, 3), 0, "{op:?} source");
assert_eq!(frames_pushed_for_child(op, 1, 3), 1, "{op:?} body");
}
}
#[test]
fn reduce_initial_accumulator_is_frameless() {
assert_eq!(frames_pushed_for_child(OpCode::Reduce, 2, 3), 0);
}
#[test]
fn bodyless_iterators_push_nothing() {
for op in [OpCode::Min, OpCode::Max, OpCode::Merge] {
for index in 0..3 {
assert_eq!(frames_pushed_for_child(op, index, 3), 0, "{op:?}[{index}]");
}
}
}
#[cfg(feature = "ext-array")]
#[test]
fn sort_and_key_extractors() {
assert_eq!(frames_pushed_for_child(OpCode::Sort, 1, 3), 0);
assert_eq!(frames_pushed_for_child(OpCode::Sort, 2, 3), 1);
for op in [OpCode::GroupBy, OpCode::Distinct] {
assert_eq!(frames_pushed_for_child(op, 0, 2), 0, "{op:?} source");
assert_eq!(frames_pushed_for_child(op, 1, 2), 1, "{op:?} key");
}
}
#[cfg(feature = "error-handling")]
#[test]
fn try_catch_arm_only_for_multi_arg() {
assert_eq!(frames_pushed_for_child(OpCode::Try, 0, 1), 0);
assert_eq!(frames_pushed_for_child(OpCode::Try, 0, 2), 0);
assert_eq!(frames_pushed_for_child(OpCode::Try, 1, 2), 1);
assert_eq!(frames_pushed_for_child(OpCode::Try, 1, 3), 0);
assert_eq!(frames_pushed_for_child(OpCode::Try, 2, 3), 1);
}
#[test]
fn resolution_table() {
use ScopeBinding::{Ancestor, Current, Root};
let expected = [
(0, 0, Root), (0, 1, Root), (0, 5, Root), (1, 0, Current), (1, 1, Root), (1, 2, Root),
(2, 0, Current),
(2, 1, Current), (2, 2, Root), (2, 3, Root),
(3, 0, Current),
(3, 1, Current), (3, 2, Ancestor), (3, 3, Root), (4, 2, Ancestor),
(4, 3, Ancestor),
(4, 4, Root),
];
for (depth, level, want) in expected {
assert_eq!(
ScopeBinding::resolve(depth, level),
want,
"resolve(depth={depth}, level={level})"
);
}
}
#[test]
fn outermost_frame_is_never_addressable() {
for depth in 1..8u32 {
for level in 0..depth + 3 {
let b = ScopeBinding::resolve(depth, level);
if b == ScopeBinding::Ancestor {
assert!(
(2..depth).contains(&level),
"Ancestor at depth={depth} level={level} is out of the \
reachable interior range 2..{depth}"
);
}
}
}
}
#[test]
fn plain_operators_push_nothing() {
for index in 0..4 {
assert_eq!(frames_pushed_for_child(OpCode::Add, index, 4), 0);
assert_eq!(frames_pushed_for_child(OpCode::If, index, 4), 0);
}
}
}