use crate::ast::{AstNode, BinaryOp, PathStep, Stage};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AstTransformError {
#[error("{code}: {message}")]
Coded { code: &'static str, message: String },
}
fn coded(code: &'static str, message: impl Into<String>) -> AstTransformError {
AstTransformError::Coded {
code,
message: message.into(),
}
}
const MAX_TRANSFORM_DEPTH: usize = 1000;
const MAX_LABEL_SUBSTITUTION_DEPTH: usize = 1000;
const AST_TRANSFORM_RED_ZONE: usize = 128 * 1024;
const AST_TRANSFORM_GROW_STACK_SIZE: usize = 8 * 1024 * 1024;
fn check_transform_depth(depth: usize) -> Result<(), AstTransformError> {
if depth > MAX_TRANSFORM_DEPTH {
Err(coded(
"U1002",
format!(
"Stack overflow - maximum expression nesting depth ({}) exceeded while post-processing the parsed expression",
MAX_TRANSFORM_DEPTH
),
))
} else {
Ok(())
}
}
fn check_label_substitution_depth(depth: usize) -> Result<(), AstTransformError> {
if depth > MAX_LABEL_SUBSTITUTION_DEPTH {
Err(coded(
"U1002",
format!(
"Stack overflow - maximum expression nesting depth ({}) exceeded while finalizing the parsed expression",
MAX_LABEL_SUBSTITUTION_DEPTH
),
))
} else {
Ok(())
}
}
fn push_ast_node_children(node: AstNode, stack: &mut Vec<AstNode>) {
match node {
AstNode::Path { steps } => {
for step in steps {
push_path_step_children(step, stack);
}
}
AstNode::Binary { lhs, rhs, .. } => {
stack.push(*lhs);
stack.push(*rhs);
}
AstNode::Unary { operand, .. } => stack.push(*operand),
AstNode::Function { args, .. } => stack.extend(args),
AstNode::Call { procedure, args } => {
stack.push(*procedure);
stack.extend(args);
}
AstNode::Lambda { body, .. } => stack.push(*body),
AstNode::Array(elements) | AstNode::Block(elements) | AstNode::ArrayGroup(elements) => {
stack.extend(elements);
}
AstNode::Object(pairs) => {
for (k, v) in pairs {
stack.push(k);
stack.push(v);
}
}
AstNode::ObjectTransform { input, pattern } => {
stack.push(*input);
for (k, v) in pattern {
stack.push(k);
stack.push(v);
}
}
AstNode::Conditional {
condition,
then_branch,
else_branch,
} => {
stack.push(*condition);
stack.push(*then_branch);
if let Some(e) = else_branch {
stack.push(*e);
}
}
AstNode::Sort { input, terms } => {
stack.push(*input);
for (e, _asc) in terms {
stack.push(e);
}
}
AstNode::Transform {
location,
update,
delete,
} => {
stack.push(*location);
stack.push(*update);
if let Some(d) = delete {
stack.push(*d);
}
}
AstNode::FunctionApplication(inner) | AstNode::Predicate(inner) => stack.push(*inner),
_ => {}
}
}
fn push_path_step_children(step: PathStep, stack: &mut Vec<AstNode>) {
stack.push(step.node);
push_stage_children(step.stages, stack);
}
fn push_stage_children(stages: Vec<Stage>, stack: &mut Vec<AstNode>) {
for stage in stages {
if let Stage::Filter(e) = stage {
stack.push(*e);
}
}
}
fn drop_ast_node_iteratively(node: AstNode) {
let mut stack = vec![node];
while let Some(n) = stack.pop() {
push_ast_node_children(n, &mut stack);
}
}
fn drop_path_steps_iteratively(steps: Vec<PathStep>) {
let mut stack = Vec::new();
for step in steps {
push_path_step_children(step, &mut stack);
}
while let Some(n) = stack.pop() {
push_ast_node_children(n, &mut stack);
}
}
fn drop_stages_iteratively(stages: Vec<Stage>) {
let mut stack = Vec::new();
push_stage_children(stages, &mut stack);
while let Some(n) = stack.pop() {
push_ast_node_children(n, &mut stack);
}
}
fn max_transform_depth_error() -> AstTransformError {
coded(
"U1002",
format!(
"Stack overflow - maximum expression nesting depth ({}) exceeded while post-processing the parsed expression",
MAX_TRANSFORM_DEPTH
),
)
}
#[derive(Debug, Clone)]
struct PendingAncestor {
label: String,
level: usize,
}
struct AncestryState {
next_label: usize,
aliases: HashMap<String, String>,
}
impl AncestryState {
fn new() -> Self {
AncestryState {
next_label: 0,
aliases: HashMap::new(),
}
}
fn fresh_label(&mut self) -> String {
let label = format!("!{}", self.next_label);
self.next_label += 1;
label
}
fn canonical(&self, label: &str) -> String {
let mut cur = label;
while let Some(next) = self.aliases.get(cur) {
cur = next;
}
cur.to_string()
}
}
struct Transformed {
node: AstNode,
pending: Vec<PendingAncestor>,
}
impl Transformed {
fn leaf(node: AstNode) -> Self {
Transformed {
node,
pending: Vec::new(),
}
}
}
pub fn resolve_ancestry(ast: AstNode) -> Result<AstNode, AstTransformError> {
let mut state = AncestryState::new();
let transformed = transform_node(ast, &mut state, 0)?;
if !transformed.pending.is_empty() || matches!(transformed.node, AstNode::Parent(_)) {
return Err(coded(
"S0217",
"The parent operator % cannot be used at this point in the expression",
));
}
substitute_labels(transformed.node, &state, 0)
}
fn substitute_labels(
node: AstNode,
state: &AncestryState,
depth: usize,
) -> Result<AstNode, AstTransformError> {
check_label_substitution_depth(depth)?;
stacker::maybe_grow(
AST_TRANSFORM_RED_ZONE,
AST_TRANSFORM_GROW_STACK_SIZE,
|| substitute_labels_impl(node, state, depth),
)
}
fn substitute_labels_impl(
node: AstNode,
state: &AncestryState,
depth: usize,
) -> Result<AstNode, AstTransformError> {
let depth = depth + 1;
Ok(match node {
AstNode::Parent(label) => AstNode::Parent(state.canonical(&label)),
AstNode::Path { steps } => AstNode::Path {
steps: steps
.into_iter()
.map(|s| -> Result<PathStep, AstTransformError> {
Ok(PathStep {
node: substitute_labels(s.node, state, depth)?,
stages: s
.stages
.into_iter()
.map(|st| -> Result<Stage, AstTransformError> {
Ok(match st {
Stage::Filter(e) => Stage::Filter(Box::new(substitute_labels(
*e, state, depth,
)?)),
Stage::Index(v) => Stage::Index(v),
})
})
.collect::<Result<Vec<_>, _>>()?,
..s
})
})
.collect::<Result<Vec<_>, _>>()?,
},
AstNode::Block(exprs) => AstNode::Block(
exprs
.into_iter()
.map(|e| substitute_labels(e, state, depth))
.collect::<Result<Vec<_>, _>>()?,
),
AstNode::Binary { op, lhs, rhs } => AstNode::Binary {
op,
lhs: Box::new(substitute_labels(*lhs, state, depth)?),
rhs: Box::new(substitute_labels(*rhs, state, depth)?),
},
AstNode::Unary { op, operand } => AstNode::Unary {
op,
operand: Box::new(substitute_labels(*operand, state, depth)?),
},
AstNode::Array(elements) => AstNode::Array(
elements
.into_iter()
.map(|e| substitute_labels(e, state, depth))
.collect::<Result<Vec<_>, _>>()?,
),
AstNode::Function {
name,
args,
is_builtin,
} => AstNode::Function {
name,
args: args
.into_iter()
.map(|a| substitute_labels(a, state, depth))
.collect::<Result<Vec<_>, _>>()?,
is_builtin,
},
AstNode::Call { procedure, args } => AstNode::Call {
procedure: Box::new(substitute_labels(*procedure, state, depth)?),
args: args
.into_iter()
.map(|a| substitute_labels(a, state, depth))
.collect::<Result<Vec<_>, _>>()?,
},
AstNode::Lambda {
params,
body,
signature,
thunk,
} => AstNode::Lambda {
params,
body: Box::new(substitute_labels(*body, state, depth)?),
signature,
thunk,
},
AstNode::Object(pairs) => AstNode::Object(
pairs
.into_iter()
.map(|(k, v)| -> Result<(AstNode, AstNode), AstTransformError> {
Ok((
substitute_labels(k, state, depth)?,
substitute_labels(v, state, depth)?,
))
})
.collect::<Result<Vec<_>, _>>()?,
),
AstNode::ObjectTransform { input, pattern } => AstNode::ObjectTransform {
input: Box::new(substitute_labels(*input, state, depth)?),
pattern: pattern
.into_iter()
.map(|(k, v)| -> Result<(AstNode, AstNode), AstTransformError> {
Ok((
substitute_labels(k, state, depth)?,
substitute_labels(v, state, depth)?,
))
})
.collect::<Result<Vec<_>, _>>()?,
},
AstNode::Conditional {
condition,
then_branch,
else_branch,
} => AstNode::Conditional {
condition: Box::new(substitute_labels(*condition, state, depth)?),
then_branch: Box::new(substitute_labels(*then_branch, state, depth)?),
else_branch: match else_branch {
Some(e) => Some(Box::new(substitute_labels(*e, state, depth)?)),
None => None,
},
},
AstNode::Sort { input, terms } => AstNode::Sort {
input: Box::new(substitute_labels(*input, state, depth)?),
terms: terms
.into_iter()
.map(|(e, asc)| -> Result<(AstNode, bool), AstTransformError> {
Ok((substitute_labels(e, state, depth)?, asc))
})
.collect::<Result<Vec<_>, _>>()?,
},
AstNode::Transform {
location,
update,
delete,
} => AstNode::Transform {
location: Box::new(substitute_labels(*location, state, depth)?),
update: Box::new(substitute_labels(*update, state, depth)?),
delete: match delete {
Some(d) => Some(Box::new(substitute_labels(*d, state, depth)?)),
None => None,
},
},
AstNode::FunctionApplication(inner) => {
AstNode::FunctionApplication(Box::new(substitute_labels(*inner, state, depth)?))
}
AstNode::ArrayGroup(elements) => AstNode::ArrayGroup(
elements
.into_iter()
.map(|e| substitute_labels(e, state, depth))
.collect::<Result<Vec<_>, _>>()?,
),
AstNode::Predicate(inner) => {
AstNode::Predicate(Box::new(substitute_labels(*inner, state, depth)?))
}
other => other,
})
}
enum BindingMarker {
Focus(String),
Index(String),
}
fn apply_marker_to_step(step: &mut PathStep, marker: BindingMarker) {
match marker {
BindingMarker::Focus(var_name) => step.focus = Some(var_name),
BindingMarker::Index(var_name) => step.index_var = Some(var_name),
}
step.is_tuple = true;
}
fn check_focus_bind_target(
marker: &BindingMarker,
target_stages: &[crate::ast::Stage],
target_node: &AstNode,
) -> Result<(), AstTransformError> {
if !matches!(marker, BindingMarker::Focus(_)) {
return Ok(());
}
if !target_stages.is_empty() {
return Err(coded(
"S0215",
"A context variable binding must precede any predicates on a step",
));
}
if matches!(target_node, AstNode::Sort { .. }) {
return Err(coded(
"S0216",
"A context variable binding must precede the 'order-by' clause on a step",
));
}
Ok(())
}
fn splice_marker_steps(
transformed: Transformed,
marker: BindingMarker,
) -> Result<(Vec<PathStep>, Vec<PendingAncestor>), AstTransformError> {
let Transformed { node, pending } = transformed;
let steps = match node {
AstNode::Path { mut steps } => {
if matches!(marker, BindingMarker::Index(_)) {
while steps.len() >= 2
&& matches!(steps.last().map(|s| &s.node), Some(AstNode::Predicate(_)))
{
let pred = steps.pop().unwrap();
if let AstNode::Predicate(inner) = pred.node {
steps.last_mut().unwrap().stages.push(Stage::Filter(inner));
}
}
}
if let Some(last) = steps.last_mut() {
check_focus_bind_target(&marker, &last.stages, &last.node)?;
if let (BindingMarker::Index(var), true) = (&marker, last.index_var.is_some()) {
last.stages.push(Stage::Index(var.clone()));
last.is_tuple = true;
} else {
apply_marker_to_step(last, marker);
}
}
steps
}
other => {
check_focus_bind_target(&marker, &[], &other)?;
let mut step = PathStep::new(other);
apply_marker_to_step(&mut step, marker);
vec![step]
}
};
Ok((steps, pending))
}
fn wrap_marker_as_path(
transformed: Transformed,
marker: BindingMarker,
) -> Result<Transformed, AstTransformError> {
let (steps, pending) = splice_marker_steps(transformed, marker)?;
Ok(Transformed {
node: AstNode::Path { steps },
pending,
})
}
fn transform_node(
node: AstNode,
state: &mut AncestryState,
depth: usize,
) -> Result<Transformed, AstTransformError> {
if depth > MAX_TRANSFORM_DEPTH {
drop_ast_node_iteratively(node);
return Err(max_transform_depth_error());
}
stacker::maybe_grow(
AST_TRANSFORM_RED_ZONE,
AST_TRANSFORM_GROW_STACK_SIZE,
|| transform_node_impl(node, state, depth),
)
}
fn transform_node_impl(
node: AstNode,
state: &mut AncestryState,
depth: usize,
) -> Result<Transformed, AstTransformError> {
let depth = depth + 1;
match node {
AstNode::Path { steps } => {
let (transformed_steps, pending) = transform_path_steps(steps, state, depth)?;
Ok(Transformed {
node: AstNode::Path {
steps: transformed_steps,
},
pending,
})
}
AstNode::Block(exprs) => {
let mut pending = Vec::new();
let mut transformed_exprs = Vec::with_capacity(exprs.len());
for e in exprs {
let t = transform_node(e, state, depth)?;
pending.extend(t.pending);
transformed_exprs.push(t.node);
}
Ok(Transformed {
node: AstNode::Block(transformed_exprs),
pending,
})
}
AstNode::Parent(_) => {
let label = state.fresh_label();
Ok(Transformed {
node: AstNode::Parent(label.clone()),
pending: vec![PendingAncestor { label, level: 1 }],
})
}
AstNode::Binary {
op: BinaryOp::FocusBind,
lhs,
rhs,
} => {
let var_name = match *rhs {
AstNode::Variable(name) => name,
_ => unreachable!("parser guarantees FocusBind's rhs is always Variable"),
};
let transformed_lhs = transform_node(*lhs, state, depth)?;
wrap_marker_as_path(transformed_lhs, BindingMarker::Focus(var_name))
}
AstNode::Binary {
op: BinaryOp::IndexBind,
lhs,
rhs,
} => {
let var_name = match *rhs {
AstNode::Variable(name) => name,
_ => unreachable!("parser guarantees IndexBind's rhs is always Variable"),
};
let transformed_lhs = transform_node(*lhs, state, depth)?;
wrap_marker_as_path(transformed_lhs, BindingMarker::Index(var_name))
}
other => transform_children(other, state, depth),
}
}
fn transform_children(
node: AstNode,
state: &mut AncestryState,
depth: usize,
) -> Result<Transformed, AstTransformError> {
if depth > MAX_TRANSFORM_DEPTH {
drop_ast_node_iteratively(node);
return Err(max_transform_depth_error());
}
stacker::maybe_grow(
AST_TRANSFORM_RED_ZONE,
AST_TRANSFORM_GROW_STACK_SIZE,
|| transform_children_impl(node, state, depth),
)
}
fn transform_children_impl(
node: AstNode,
state: &mut AncestryState,
depth: usize,
) -> Result<Transformed, AstTransformError> {
let depth = depth + 1;
match node {
AstNode::Binary { op, lhs, rhs } => {
let lhs_t = transform_node(*lhs, state, depth)?;
let rhs_t = transform_node(*rhs, state, depth)?;
let mut pending = lhs_t.pending;
pending.extend(rhs_t.pending);
Ok(Transformed {
node: AstNode::Binary {
op,
lhs: Box::new(lhs_t.node),
rhs: Box::new(rhs_t.node),
},
pending,
})
}
AstNode::Unary { op, operand } => {
let t = transform_node(*operand, state, depth)?;
Ok(Transformed {
node: AstNode::Unary {
op,
operand: Box::new(t.node),
},
pending: t.pending,
})
}
AstNode::Array(elements) => {
let mut pending = Vec::new();
let mut transformed = Vec::with_capacity(elements.len());
for e in elements {
let t = transform_node(e, state, depth)?;
pending.extend(t.pending);
transformed.push(t.node);
}
Ok(Transformed {
node: AstNode::Array(transformed),
pending,
})
}
AstNode::Function {
name,
args,
is_builtin,
} => {
let mut pending = Vec::new();
let mut transformed = Vec::with_capacity(args.len());
for a in args {
let t = transform_node(a, state, depth)?;
pending.extend(t.pending);
transformed.push(t.node);
}
Ok(Transformed {
node: AstNode::Function {
name,
args: transformed,
is_builtin,
},
pending,
})
}
AstNode::Call { procedure, args } => {
let procedure_t = transform_node(*procedure, state, depth)?;
let mut pending = Vec::new();
let mut transformed_args = Vec::with_capacity(args.len());
for a in args {
let t = transform_node(a, state, depth)?;
pending.extend(t.pending);
transformed_args.push(t.node);
}
Ok(Transformed {
node: AstNode::Call {
procedure: Box::new(procedure_t.node),
args: transformed_args,
},
pending,
})
}
AstNode::Lambda {
params,
body,
signature,
thunk,
} => {
let body_t = transform_node(*body, state, depth)?;
Ok(Transformed::leaf(AstNode::Lambda {
params,
body: Box::new(body_t.node),
signature,
thunk,
}))
}
AstNode::Object(pairs) => {
let mut pending = Vec::new();
let mut transformed = Vec::with_capacity(pairs.len());
for (k, v) in pairs {
let k_t = transform_node(k, state, depth)?;
pending.extend(k_t.pending);
let v_t = transform_node(v, state, depth)?;
pending.extend(v_t.pending);
transformed.push((k_t.node, v_t.node));
}
Ok(Transformed {
node: AstNode::Object(transformed),
pending,
})
}
AstNode::ObjectTransform { input, pattern } => {
let input_t = transform_node(*input, state, depth)?;
let mut pending = input_t.pending;
let mut transformed_pattern = Vec::with_capacity(pattern.len());
for (k, v) in pattern {
let k_t = transform_node(k, state, depth)?;
pending.extend(k_t.pending);
let v_t = transform_node(v, state, depth)?;
pending.extend(v_t.pending);
transformed_pattern.push((k_t.node, v_t.node));
}
Ok(Transformed {
node: AstNode::ObjectTransform {
input: Box::new(input_t.node),
pattern: transformed_pattern,
},
pending,
})
}
AstNode::Conditional {
condition,
then_branch,
else_branch,
} => {
let condition_t = transform_node(*condition, state, depth)?;
let then_t = transform_node(*then_branch, state, depth)?;
let mut pending = condition_t.pending;
pending.extend(then_t.pending);
let else_t = match else_branch {
Some(e) => Some(transform_node(*e, state, depth)?),
None => None,
};
let else_node = else_t.map(|t| {
pending.extend(t.pending);
Box::new(t.node)
});
Ok(Transformed {
node: AstNode::Conditional {
condition: Box::new(condition_t.node),
then_branch: Box::new(then_t.node),
else_branch: else_node,
},
pending,
})
}
AstNode::Sort { input, terms } => {
let input_t = transform_node(*input, state, depth)?;
let was_path = matches!(input_t.node, AstNode::Path { .. });
let mut steps = match input_t.node {
AstNode::Path { steps } => steps,
other => vec![PathStep::new(other)],
};
let mut pending = input_t.pending;
let mut transformed_terms = Vec::with_capacity(terms.len());
for (expr, asc) in terms {
let t = transform_node(expr, state, depth)?;
for slot in t.pending {
let remaining =
walk_backward(&mut steps, &slot.label, slot.level, state, depth + 1)?;
if remaining > 0 {
pending.push(PendingAncestor {
label: slot.label,
level: remaining,
});
}
}
transformed_terms.push((t.node, asc));
}
let input_node = if was_path {
AstNode::Path { steps }
} else {
let s = steps.pop().expect("single wrapped step");
if s.is_tuple || s.ancestor_label.is_some() {
AstNode::Path { steps: vec![s] }
} else {
s.node
}
};
Ok(Transformed {
node: AstNode::Sort {
input: Box::new(input_node),
terms: transformed_terms,
},
pending,
})
}
AstNode::Transform {
location,
update,
delete,
} => {
let location_t = transform_node(*location, state, depth)?;
let update_t = transform_node(*update, state, depth)?;
let mut pending = location_t.pending;
pending.extend(update_t.pending);
let delete_t = match delete {
Some(d) => Some(transform_node(*d, state, depth)?),
None => None,
};
let delete_node = delete_t.map(|t| {
pending.extend(t.pending);
Box::new(t.node)
});
Ok(Transformed {
node: AstNode::Transform {
location: Box::new(location_t.node),
update: Box::new(update_t.node),
delete: delete_node,
},
pending,
})
}
AstNode::FunctionApplication(inner) => {
let t = transform_node(*inner, state, depth)?;
Ok(Transformed {
node: AstNode::FunctionApplication(Box::new(t.node)),
pending: t.pending,
})
}
AstNode::ArrayGroup(elements) => {
let mut pending = Vec::new();
let mut transformed = Vec::with_capacity(elements.len());
for e in elements {
let t = transform_node(e, state, depth)?;
pending.extend(t.pending);
transformed.push(t.node);
}
Ok(Transformed {
node: AstNode::ArrayGroup(transformed),
pending,
})
}
AstNode::Predicate(inner) => {
let t = transform_node(*inner, state, depth)?;
Ok(Transformed {
node: AstNode::Predicate(Box::new(t.node)),
pending: t.pending,
})
}
other => Ok(Transformed::leaf(other)),
}
}
fn transform_path_steps(
steps: Vec<PathStep>,
state: &mut AncestryState,
depth: usize,
) -> Result<(Vec<PathStep>, Vec<PendingAncestor>), AstTransformError> {
if depth > MAX_TRANSFORM_DEPTH {
drop_path_steps_iteratively(steps);
return Err(max_transform_depth_error());
}
stacker::maybe_grow(
AST_TRANSFORM_RED_ZONE,
AST_TRANSFORM_GROW_STACK_SIZE,
|| transform_path_steps_impl(steps, state, depth),
)
}
fn transform_path_steps_impl(
steps: Vec<PathStep>,
state: &mut AncestryState,
depth: usize,
) -> Result<(Vec<PathStep>, Vec<PendingAncestor>), AstTransformError> {
let depth = depth + 1;
let mut resolved: Vec<PathStep> = Vec::with_capacity(steps.len());
let mut own_pending: Vec<Vec<PendingAncestor>> = Vec::with_capacity(steps.len());
let mut pred_pending: Vec<Vec<PendingAncestor>> = Vec::with_capacity(steps.len());
for step in steps {
let (spliced, pending) = migrate_binding_markers(step, state, depth)?;
let last_idx = spliced.len().saturating_sub(1);
let mut pending_opt = Some(pending);
for (i, mut s) in spliced.into_iter().enumerate() {
let mut pp: Vec<PendingAncestor> = Vec::new();
let stages = std::mem::take(&mut s.stages);
let mut new_stages = Vec::with_capacity(stages.len());
for stage in stages {
match stage {
Stage::Filter(expr) => {
let t = transform_node(*expr, state, depth)?;
pp.extend(t.pending);
new_stages.push(Stage::Filter(Box::new(t.node)));
}
Stage::Index(v) => new_stages.push(Stage::Index(v)),
}
}
s.stages = new_stages;
resolved.push(s);
pred_pending.push(pp);
if i == last_idx {
own_pending.push(pending_opt.take().unwrap_or_default());
} else {
own_pending.push(Vec::new());
}
}
}
let mut path_pending: Vec<PendingAncestor> = Vec::new();
for i in 0..resolved.len() {
for pending in std::mem::take(&mut pred_pending[i]) {
let remaining = resolve_predicate_slot(
&mut resolved,
i,
&pending.label,
pending.level,
state,
depth + 1,
)?;
if remaining > 0 {
path_pending.push(PendingAncestor {
label: pending.label,
level: remaining,
});
}
}
let pending_here = std::mem::take(&mut own_pending[i]);
for pending in pending_here {
let remaining = walk_backward(
&mut resolved[..i],
&pending.label,
pending.level,
state,
depth + 1,
)?;
if remaining > 0 {
path_pending.push(PendingAncestor {
label: pending.label,
level: remaining,
});
}
}
}
Ok((resolved, path_pending))
}
fn resolve_predicate_slot(
resolved: &mut [PathStep],
i: usize,
label: &str,
level: usize,
state: &mut AncestryState,
depth: usize,
) -> Result<usize, AstTransformError> {
let (prefix, rest) = resolved.split_at_mut(i);
let remaining = if level == 1 {
seek_parent_step(&mut rest[0], label, 1, state, depth + 1)?
} else {
level - 1
};
if remaining == 0 {
Ok(0)
} else {
walk_backward(prefix, label, remaining, state, depth + 1)
}
}
fn walk_backward(
steps: &mut [PathStep],
label: &str,
level: usize,
state: &mut AncestryState,
depth: usize,
) -> Result<usize, AstTransformError> {
check_transform_depth(depth)?;
stacker::maybe_grow(
AST_TRANSFORM_RED_ZONE,
AST_TRANSFORM_GROW_STACK_SIZE,
|| walk_backward_impl(steps, label, level, state, depth),
)
}
fn walk_backward_impl(
steps: &mut [PathStep],
label: &str,
mut level: usize,
state: &mut AncestryState,
depth: usize,
) -> Result<usize, AstTransformError> {
let depth = depth + 1;
let mut index = steps.len();
while level > 0 {
if index == 0 {
return Ok(level);
}
index -= 1;
loop {
while index > 0 && matches!(steps[index].node, AstNode::Predicate(_)) {
index -= 1;
}
let mut prev = None;
if index > 0 {
let mut j = index - 1;
loop {
if !matches!(steps[j].node, AstNode::Predicate(_)) {
prev = Some(j);
break;
}
if j == 0 {
break;
}
j -= 1;
}
}
match prev {
Some(p) if steps[index].focus.is_some() && steps[p].focus.is_some() => {
index = p;
}
_ => break,
}
}
level = seek_parent_step(&mut steps[index], label, level, state, depth)?;
}
Ok(0)
}
fn seek_parent_step(
step: &mut PathStep,
label: &str,
level: usize,
state: &mut AncestryState,
depth: usize,
) -> Result<usize, AstTransformError> {
check_transform_depth(depth)?;
stacker::maybe_grow(
AST_TRANSFORM_RED_ZONE,
AST_TRANSFORM_GROW_STACK_SIZE,
|| seek_parent_step_impl(step, label, level, state, depth),
)
}
fn seek_parent_step_impl(
step: &mut PathStep,
label: &str,
level: usize,
state: &mut AncestryState,
depth: usize,
) -> Result<usize, AstTransformError> {
let depth = depth + 1;
match &mut step.node {
AstNode::Name(_) | AstNode::Wildcard => {
let remaining = level - 1;
if remaining == 0 {
match &step.ancestor_label {
Some(existing) => {
state.aliases.insert(label.to_string(), existing.clone());
}
None => {
step.ancestor_label = Some(label.to_string());
}
}
step.is_tuple = true;
}
Ok(remaining)
}
AstNode::Parent(_) => Ok(level + 1),
AstNode::FunctionApplication(inner) => {
step.is_tuple = true;
seek_parent_wrapped(inner.as_mut(), label, level, state, depth)
}
AstNode::Block(exprs) => match exprs.last_mut() {
Some(last) => {
step.is_tuple = true;
seek_parent_wrapped(last, label, level, state, depth)
}
None => Ok(level),
},
_ => Err(coded(
"S0217",
"The parent operator % cannot derive an ancestor from this kind of path step",
)),
}
}
fn seek_parent_wrapped(
node: &mut AstNode,
label: &str,
level: usize,
state: &mut AncestryState,
depth: usize,
) -> Result<usize, AstTransformError> {
check_transform_depth(depth)?;
stacker::maybe_grow(
AST_TRANSFORM_RED_ZONE,
AST_TRANSFORM_GROW_STACK_SIZE,
|| seek_parent_wrapped_impl(node, label, level, state, depth),
)
}
fn seek_parent_wrapped_impl(
node: &mut AstNode,
label: &str,
level: usize,
state: &mut AncestryState,
depth: usize,
) -> Result<usize, AstTransformError> {
let depth = depth + 1;
match node {
AstNode::Path { steps } => walk_backward(steps, label, level, state, depth),
AstNode::Block(exprs) => match exprs.last_mut() {
Some(last) => seek_parent_wrapped(last, label, level, state, depth),
None => Ok(level),
},
_ => Err(coded(
"S0217",
"The parent operator % cannot derive an ancestor from this kind of expression",
)),
}
}
fn migrate_binding_markers(
mut step: PathStep,
state: &mut AncestryState,
depth: usize,
) -> Result<(Vec<PathStep>, Vec<PendingAncestor>), AstTransformError> {
match step.node {
AstNode::Binary {
op: BinaryOp::FocusBind,
lhs,
rhs,
} => {
let var_name = match *rhs {
AstNode::Variable(name) => name,
_ => unreachable!("parser guarantees FocusBind's rhs is always Variable"),
};
match transform_node(*lhs, state, depth + 1) {
Ok(transformed_lhs) => {
splice_marker_steps(transformed_lhs, BindingMarker::Focus(var_name))
}
Err(e) => {
drop_stages_iteratively(std::mem::take(&mut step.stages));
Err(e)
}
}
}
AstNode::Binary {
op: BinaryOp::IndexBind,
lhs,
rhs,
} => {
let var_name = match *rhs {
AstNode::Variable(name) => name,
_ => unreachable!("parser guarantees IndexBind's rhs is always Variable"),
};
match transform_node(*lhs, state, depth + 1) {
Ok(transformed_lhs) => {
splice_marker_steps(transformed_lhs, BindingMarker::Index(var_name))
}
Err(e) => {
drop_stages_iteratively(std::mem::take(&mut step.stages));
Err(e)
}
}
}
other => match transform_node(other, state, depth + 1) {
Ok(t) => {
step.node = t.node;
Ok((vec![step], t.pending))
}
Err(e) => {
drop_stages_iteratively(std::mem::take(&mut step.stages));
Err(e)
}
},
}
}
#[cfg(test)]
mod tests {
use super::*;
fn resolve_path(expr: &str) -> Vec<PathStep> {
let ast = crate::parser::Parser::new(expr.to_string())
.unwrap()
.parse()
.unwrap();
match resolve_ancestry(ast).unwrap() {
AstNode::Path { steps } => steps,
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_parent_inside_predicate_resolves_against_enclosing_step() {
let steps = resolve_path("Account.Order.Product[%.OrderID='order104'].SKU");
assert_eq!(steps.len(), 4);
assert!(matches!(steps[2].node, AstNode::Name(ref n) if n == "Product"));
let product_label = steps[2].ancestor_label.clone();
assert!(product_label.is_some(), "Product must be tagged");
assert!(steps[2].is_tuple);
assert!(
steps[1].ancestor_label.is_none(),
"Order must NOT be tagged"
);
match &steps[2].stages[0] {
Stage::Index(_) => unreachable!("no index stage in this test"),
Stage::Filter(expr) => match expr.as_ref() {
AstNode::Binary { lhs, .. } => match lhs.as_ref() {
AstNode::Path { steps: inner } => match &inner[0].node {
AstNode::Parent(label) => {
assert_eq!(Some(label.clone()), product_label)
}
other => panic!("expected Parent, got {:?}", other),
},
other => panic!("expected inner Path, got {:?}", other),
},
other => panic!("expected Binary, got {:?}", other),
},
}
}
#[test]
fn test_parent_chain_inside_predicate_resolves_two_levels() {
let steps = resolve_path("Account.Order.Product[%.%.`Account Name`='Firefly'].SKU");
assert_eq!(steps.len(), 4);
let product_label = steps[2].ancestor_label.clone();
let order_label = steps[1].ancestor_label.clone();
assert!(product_label.is_some(), "Product must be tagged");
assert!(order_label.is_some(), "Order must be tagged");
assert_ne!(product_label, order_label);
match &steps[2].stages[0] {
Stage::Index(_) => unreachable!("no index stage in this test"),
Stage::Filter(expr) => match expr.as_ref() {
AstNode::Binary { lhs, .. } => match lhs.as_ref() {
AstNode::Path { steps: inner } => {
match &inner[0].node {
AstNode::Parent(l) => assert_eq!(Some(l.clone()), product_label),
other => panic!("expected Parent, got {:?}", other),
}
match &inner[1].node {
AstNode::Parent(l) => assert_eq!(Some(l.clone()), order_label),
other => panic!("expected Parent, got {:?}", other),
}
}
other => panic!("expected inner Path, got {:?}", other),
},
other => panic!("expected Binary, got {:?}", other),
},
}
}
#[test]
fn test_parent_predicate_on_parent_step_itself() {
let steps = resolve_path("Account.Order.Product.Price.%[%.OrderID='order103'].SKU");
assert_eq!(steps.len(), 6);
assert!(matches!(steps[4].node, AstNode::Parent(_)));
let price_label = steps[3].ancestor_label.clone();
let product_label = steps[2].ancestor_label.clone();
assert!(
price_label.is_some(),
"Price must be tagged (by the % step)"
);
assert!(
product_label.is_some(),
"Product must be tagged (by the predicate %)"
);
assert_ne!(price_label, product_label);
}
#[test]
fn test_two_predicates_share_and_differ_labels() {
let steps = resolve_path(
"Account.Order.Product[%.OrderID='order104'][%.%.`Account Name`='Firefly'].SKU",
);
assert_eq!(steps.len(), 4);
assert_eq!(steps[2].stages.len(), 2);
let product_label = steps[2].ancestor_label.clone();
let order_label = steps[1].ancestor_label.clone();
assert!(product_label.is_some());
assert!(order_label.is_some());
assert_ne!(product_label, order_label);
match &steps[2].stages[0] {
Stage::Index(_) => unreachable!("no index stage in this test"),
Stage::Filter(expr) => match expr.as_ref() {
AstNode::Binary { lhs, .. } => match lhs.as_ref() {
AstNode::Path { steps: inner } => match &inner[0].node {
AstNode::Parent(l) => assert_eq!(Some(l.clone()), product_label),
other => panic!("{:?}", other),
},
other => panic!("{:?}", other),
},
other => panic!("{:?}", other),
},
}
match &steps[2].stages[1] {
Stage::Index(_) => unreachable!("no index stage in this test"),
Stage::Filter(expr) => match expr.as_ref() {
AstNode::Binary { lhs, .. } => match lhs.as_ref() {
AstNode::Path { steps: inner } => {
match &inner[0].node {
AstNode::Parent(l) => assert_eq!(Some(l.clone()), product_label),
other => panic!("{:?}", other),
}
match &inner[1].node {
AstNode::Parent(l) => assert_eq!(Some(l.clone()), order_label),
other => panic!("{:?}", other),
}
}
other => panic!("{:?}", other),
},
other => panic!("{:?}", other),
},
}
}
#[test]
fn test_parent_inside_sort_term_resolves_to_last_input_step() {
let ast = crate::parser::Parser::new("Account.Order.Product.SKU^(%.Price)".to_string())
.unwrap()
.parse()
.unwrap();
match resolve_ancestry(ast).unwrap() {
AstNode::Sort { input, terms } => {
let steps = match input.as_ref() {
AstNode::Path { steps } => steps,
other => panic!("expected Path input, got {:?}", other),
};
assert_eq!(steps.len(), 4);
assert!(matches!(steps[3].node, AstNode::Name(ref n) if n == "SKU"));
let sku_label = steps[3].ancestor_label.clone();
assert!(sku_label.is_some(), "SKU must be tagged");
match &terms[0].0 {
AstNode::Path { steps: inner } => match &inner[0].node {
AstNode::Parent(l) => assert_eq!(Some(l.clone()), sku_label),
other => panic!("{:?}", other),
},
other => panic!("{:?}", other),
}
}
other => panic!("expected Sort, got {:?}", other),
}
}
#[test]
fn test_two_sort_terms_share_and_differ_labels() {
let ast = crate::parser::Parser::new(
"Account.Order.Product.SKU^(%.Price, >%.%.OrderID)".to_string(),
)
.unwrap()
.parse()
.unwrap();
match resolve_ancestry(ast).unwrap() {
AstNode::Sort { input, terms } => {
let steps = match input.as_ref() {
AstNode::Path { steps } => steps,
other => panic!("{:?}", other),
};
let sku_label = steps[3].ancestor_label.clone();
let product_label = steps[2].ancestor_label.clone();
assert!(sku_label.is_some());
assert!(product_label.is_some());
assert_ne!(sku_label, product_label);
assert_eq!(terms.len(), 2);
match &terms[1].0 {
AstNode::Path { steps: inner } => {
match &inner[0].node {
AstNode::Parent(l) => assert_eq!(Some(l.clone()), sku_label),
other => panic!("{:?}", other),
}
match &inner[1].node {
AstNode::Parent(l) => assert_eq!(Some(l.clone()), product_label),
other => panic!("{:?}", other),
}
}
other => panic!("{:?}", other),
}
}
other => panic!("expected Sort, got {:?}", other),
}
}
#[test]
fn test_focus_bind_becomes_step_flag() {
let ast = AstNode::Path {
steps: vec![PathStep::new(AstNode::Binary {
op: BinaryOp::FocusBind,
lhs: Box::new(AstNode::Name("Order".to_string())),
rhs: Box::new(AstNode::Variable("o".to_string())),
})],
};
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 1);
assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "Order"));
assert_eq!(steps[0].focus, Some("o".to_string()));
assert!(steps[0].is_tuple);
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_index_bind_becomes_step_flag() {
let ast = AstNode::Path {
steps: vec![PathStep::new(AstNode::Binary {
op: BinaryOp::IndexBind,
lhs: Box::new(AstNode::Name("arr".to_string())),
rhs: Box::new(AstNode::Variable("i".to_string())),
})],
};
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 1);
assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "arr"));
assert_eq!(steps[0].index_var, Some("i".to_string()));
assert!(steps[0].is_tuple);
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_bare_parent_at_top_level_is_s0217() {
let err = resolve_ancestry(AstNode::Parent(String::new())).unwrap_err();
assert!(err.to_string().starts_with("S0217"));
}
#[test]
fn test_path_step_with_stages_preserved() {
let ast = AstNode::Path {
steps: vec![PathStep::with_stages(
AstNode::Name("Order".to_string()),
vec![Stage::Filter(Box::new(AstNode::Boolean(true)))],
)],
};
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps[0].stages.len(), 1);
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_bare_focus_bind_no_dot() {
let ast = crate::parser::Parser::new("Order@$o".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 1);
assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "Order"));
assert_eq!(steps[0].focus, Some("o".to_string()));
assert!(steps[0].is_tuple);
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_focus_bind_on_final_step_of_multistep_path() {
let ast = crate::parser::Parser::new("Account.Order@$o".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 2);
assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "Account"));
assert!(steps[0].focus.is_none());
assert!(!steps[0].is_tuple);
assert!(matches!(steps[1].node, AstNode::Name(ref n) if n == "Order"));
assert_eq!(steps[1].focus, Some("o".to_string()));
assert!(steps[1].is_tuple);
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_bare_index_bind() {
let ast = crate::parser::Parser::new("arr#$i".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 1);
assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "arr"));
assert_eq!(steps[0].index_var, Some("i".to_string()));
assert!(steps[0].is_tuple);
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_bare_parent_inside_function_args_is_s0217() {
let ast = crate::parser::Parser::new("$count(%)".to_string())
.unwrap()
.parse()
.unwrap();
let err = resolve_ancestry(ast).unwrap_err();
assert!(err.to_string().starts_with("S0217"));
}
#[test]
fn test_real_parser_focus_bind_multistep_prefix_and_suffix_is_flat() {
let ast = crate::parser::Parser::new("Account.Order@$o.Product".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 3, "expected a flat 3-step path");
assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "Account"));
assert!(steps[0].focus.is_none());
assert!(!steps[0].is_tuple);
assert!(matches!(steps[1].node, AstNode::Name(ref n) if n == "Order"));
assert_eq!(steps[1].focus, Some("o".to_string()));
assert!(steps[1].is_tuple);
assert!(matches!(steps[2].node, AstNode::Name(ref n) if n == "Product"));
assert!(steps[2].focus.is_none());
}
other => panic!("expected flat Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_index_bind_multistep_prefix_and_suffix_is_flat() {
let ast = crate::parser::Parser::new("Account.Order#$i.Product".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 3, "expected a flat 3-step path");
assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "Account"));
assert!(steps[0].index_var.is_none());
assert!(!steps[0].is_tuple);
assert!(matches!(steps[1].node, AstNode::Name(ref n) if n == "Order"));
assert_eq!(steps[1].index_var, Some("i".to_string()));
assert!(steps[1].is_tuple);
assert!(matches!(steps[2].node, AstNode::Name(ref n) if n == "Product"));
assert!(steps[2].index_var.is_none());
}
other => panic!("expected flat Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_single_level_parent_resolves_to_previous_step() {
let ast = crate::parser::Parser::new("Account.Order.%".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 3);
assert!(matches!(steps[0].node, AstNode::Name(ref n) if n == "Account"));
assert!(steps[0].ancestor_label.is_none());
assert!(matches!(steps[1].node, AstNode::Name(ref n) if n == "Order"));
assert!(steps[1].ancestor_label.is_some());
assert!(steps[1].is_tuple);
match &steps[2].node {
AstNode::Parent(label) => {
assert_eq!(Some(label.clone()), steps[1].ancestor_label);
}
other => panic!("expected Parent(label), got {:?}", other),
}
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_chained_parent_resolves_two_levels_back() {
let ast = crate::parser::Parser::new("Account.Order.Product.%.%".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 5);
assert!(steps[0].ancestor_label.is_none(), "Account untagged");
let order_label = steps[1].ancestor_label.clone();
let product_label = steps[2].ancestor_label.clone();
assert!(order_label.is_some(), "Order must be tagged");
assert!(product_label.is_some(), "Product must be tagged");
assert_ne!(
order_label, product_label,
"two distinct % chains must get distinct labels"
);
match &steps[3].node {
AstNode::Parent(label) => assert_eq!(Some(label.clone()), product_label),
other => panic!("expected Parent(label), got {:?}", other),
}
match &steps[4].node {
AstNode::Parent(label) => assert_eq!(Some(label.clone()), order_label),
other => panic!("expected Parent(label), got {:?}", other),
}
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_object_constructor_percent_tags_preceding_step() {
let ast =
crate::parser::Parser::new("Account.Order.Product.{'order': %.OrderID}".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 4);
assert!(matches!(steps[2].node, AstNode::Name(ref n) if n == "Product"));
let product_label = steps[2].ancestor_label.clone();
assert!(product_label.is_some());
assert!(steps[2].is_tuple);
match &steps[3].node {
AstNode::Object(pairs) => {
assert_eq!(pairs.len(), 1);
match &pairs[0].1 {
AstNode::Path { steps: inner } => {
assert_eq!(inner.len(), 2);
match &inner[0].node {
AstNode::Parent(label) => {
assert_eq!(Some(label.clone()), product_label)
}
other => panic!("expected Parent(label), got {:?}", other),
}
}
other => panic!("expected inner Path, got {:?}", other),
}
}
other => panic!("expected Object, got {:?}", other),
}
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_object_constructor_two_percent_chains_share_and_differ() {
let ast = crate::parser::Parser::new(
"Account.Order.Product.{'Product':`Product Name`,'Order':%.OrderID,'Account':%.%.`Account Name`}"
.to_string(),
)
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 4);
let product_label = steps[2].ancestor_label.clone();
let order_label = steps[1].ancestor_label.clone();
assert!(product_label.is_some(), "Product must be tagged");
assert!(order_label.is_some(), "Order must be tagged");
assert_ne!(product_label, order_label);
match &steps[3].node {
AstNode::Object(pairs) => {
assert_eq!(pairs.len(), 3);
match &pairs[1].1 {
AstNode::Path { steps: inner } => match &inner[0].node {
AstNode::Parent(label) => {
assert_eq!(Some(label.clone()), product_label)
}
other => panic!("expected Parent(label), got {:?}", other),
},
other => panic!("expected inner Path, got {:?}", other),
}
match &pairs[2].1 {
AstNode::Path { steps: inner } => {
assert_eq!(inner.len(), 3);
match &inner[0].node {
AstNode::Parent(label) => {
assert_eq!(Some(label.clone()), product_label)
}
other => panic!("expected Parent(label), got {:?}", other),
}
match &inner[1].node {
AstNode::Parent(label) => {
assert_eq!(Some(label.clone()), order_label)
}
other => panic!("expected Parent(label), got {:?}", other),
}
}
other => panic!("expected inner Path, got {:?}", other),
}
}
other => panic!("expected Object, got {:?}", other),
}
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_percent_through_parenthesized_step_function_application() {
let ast = crate::parser::Parser::new("Account.(Order.Product).%".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 3);
assert!(steps[1].is_tuple, "the wrapping step must be flagged tuple");
match &steps[1].node {
AstNode::FunctionApplication(inner) => match inner.as_ref() {
AstNode::Path { steps: inner_steps } => {
assert_eq!(inner_steps.len(), 2);
assert!(
matches!(inner_steps[0].node, AstNode::Name(ref n) if n == "Order")
);
assert!(
matches!(inner_steps[1].node, AstNode::Name(ref n) if n == "Product")
);
let product_label = inner_steps[1].ancestor_label.clone();
assert!(product_label.is_some(), "Product must be tagged");
match &steps[2].node {
AstNode::Parent(label) => {
assert_eq!(Some(label.clone()), product_label)
}
other => panic!("expected Parent(label), got {:?}", other),
}
}
other => panic!("expected inner Path, got {:?}", other),
},
other => panic!("expected FunctionApplication, got {:?}", other),
}
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_percent_through_leading_paren_block() {
let ast =
crate::parser::Parser::new("(Account.Order).(Product).{'x': %.OrderID}".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Path { steps } => {
assert_eq!(steps.len(), 3);
match &steps[0].node {
AstNode::Block(exprs) => {
assert_eq!(exprs.len(), 1);
match &exprs[0] {
AstNode::Path { steps: inner } => {
assert_eq!(inner.len(), 2);
assert!(
matches!(inner[0].node, AstNode::Name(ref n) if n == "Account")
);
assert!(
matches!(inner[1].node, AstNode::Name(ref n) if n == "Order")
);
assert!(
inner[1].ancestor_label.is_none(),
"Order must NOT be tagged -- % resolves one level back, at Product"
);
}
other => panic!("expected inner Path, got {:?}", other),
}
}
other => panic!("expected Block, got {:?}", other),
}
assert!(!steps[0].is_tuple);
assert!(steps[1].is_tuple, "the wrapping step must be flagged tuple");
match &steps[1].node {
AstNode::FunctionApplication(inner) => match inner.as_ref() {
AstNode::Path { steps: inner_steps } => {
assert_eq!(inner_steps.len(), 1);
assert!(
matches!(inner_steps[0].node, AstNode::Name(ref n) if n == "Product")
);
let product_label = inner_steps[0].ancestor_label.clone();
assert!(product_label.is_some(), "Product must be tagged");
match &steps[2].node {
AstNode::Object(pairs) => match &pairs[0].1 {
AstNode::Path { steps: value_steps } => {
match &value_steps[0].node {
AstNode::Parent(label) => {
assert_eq!(Some(label.clone()), product_label)
}
other => {
panic!("expected Parent(label), got {:?}", other)
}
}
}
other => panic!("expected value Path, got {:?}", other),
},
other => panic!("expected Object, got {:?}", other),
}
}
other => panic!("expected inner Path, got {:?}", other),
},
other => panic!("expected FunctionApplication, got {:?}", other),
}
}
other => panic!("expected Path, got {:?}", other),
}
}
#[test]
fn test_real_parser_percent_cannot_derive_ancestor_from_literal() {
let ast = crate::parser::Parser::new("%.OrderID".to_string())
.unwrap()
.parse()
.unwrap();
let err = resolve_ancestry(ast).unwrap_err();
assert!(err.to_string().starts_with("S0217"));
}
#[test]
fn test_real_parser_lambda_body_percent_does_not_bubble_or_error() {
let ast = crate::parser::Parser::new("function(){ % }".to_string())
.unwrap()
.parse()
.unwrap();
let result = resolve_ancestry(ast).unwrap();
match result {
AstNode::Lambda { body, .. } => {
assert!(matches!(*body, AstNode::Parent(_)));
}
other => panic!("expected Lambda, got {:?}", other),
}
}
#[test]
fn test_real_parser_focus_bind_after_predicate_is_s0215() {
let ast = crate::parser::Parser::new("Account.Order[1]@$o.Product".to_string())
.unwrap()
.parse()
.unwrap();
let err = resolve_ancestry(ast).unwrap_err();
assert!(err.to_string().starts_with("S0215"), "got: {}", err);
}
#[test]
fn test_real_parser_focus_bind_after_sort_is_s0216() {
let ast = crate::parser::Parser::new(
"Account.Order^(>OrderID)@$o.Product.{ 'name':`Product Name`, 'orderid':$o.OrderID }"
.to_string(),
)
.unwrap()
.parse()
.unwrap();
let err = resolve_ancestry(ast).unwrap_err();
assert!(err.to_string().starts_with("S0216"), "got: {}", err);
}
#[test]
fn test_real_parser_index_bind_after_predicate_is_not_an_error() {
let ast = crate::parser::Parser::new("Account.Order[1]#$o.Product".to_string())
.unwrap()
.parse()
.unwrap();
assert!(resolve_ancestry(ast).is_ok());
}
}