use crate::ast::{ValueExpr, expr::IsCheckKind, span::SourceSpan};
impl ValueExpr {
pub fn for_each_child<'a>(&'a self, f: &mut impl FnMut(&'a ValueExpr)) {
match self {
Self::Literal(_) | Self::Variable { .. } | Self::Parameter { .. } => {}
Self::PropertyAccess { target, .. } | Self::PropertyExists { target, .. } => f(target),
Self::ListLiteral { items, .. }
| Self::PathConstructor {
elements: items, ..
}
| Self::AllDifferent { items, .. }
| Self::Same { items, .. } => {
for item in items {
f(item);
}
}
Self::RecordLiteral { fields, .. } => {
for (_, value) in fields {
f(value);
}
}
Self::BinaryOp { lhs, rhs, .. } => {
f(lhs);
f(rhs);
}
Self::UnaryOp { operand, .. } => f(operand),
Self::FunctionCall { args, .. } => {
for arg in args {
f(arg);
}
}
Self::DurationBetween { start, end, .. } => {
f(start);
f(end);
}
Self::Normalize { source, .. } => f(source),
Self::Trim {
character, source, ..
} => {
if let Some(character) = character {
f(character);
}
f(source);
}
Self::IsCheck { operand, kind, .. } => {
f(operand);
if let IsCheckKind::SourceOf(value) | IsCheckKind::DestinationOf(value) = kind {
f(value);
}
}
Self::InList { operand, list, .. } => {
f(operand);
for item in list {
f(item);
}
}
Self::InListExpression { operand, list, .. } => {
f(operand);
f(list);
}
Self::Case {
branches,
else_branch,
..
} => {
for (condition, value) in branches {
f(condition);
f(value);
}
if let Some(value) = else_branch {
f(value);
}
}
Self::Cast { value, .. } => f(value),
Self::Exists { .. } | Self::ValueSubquery { .. } => {}
}
}
pub fn for_each_child_mut(&mut self, f: &mut impl FnMut(&mut ValueExpr)) {
match self {
Self::Literal(_) | Self::Variable { .. } | Self::Parameter { .. } => {}
Self::PropertyAccess { target, .. } | Self::PropertyExists { target, .. } => f(target),
Self::ListLiteral { items, .. }
| Self::PathConstructor {
elements: items, ..
}
| Self::AllDifferent { items, .. }
| Self::Same { items, .. } => {
for item in items {
f(item);
}
}
Self::RecordLiteral { fields, .. } => {
for (_, value) in fields {
f(value);
}
}
Self::BinaryOp { lhs, rhs, .. } => {
f(lhs);
f(rhs);
}
Self::UnaryOp { operand, .. } => f(operand),
Self::FunctionCall { args, .. } => {
for arg in args {
f(arg);
}
}
Self::DurationBetween { start, end, .. } => {
f(start);
f(end);
}
Self::Normalize { source, .. } => f(source),
Self::Trim {
character, source, ..
} => {
if let Some(character) = character {
f(character);
}
f(source);
}
Self::IsCheck { operand, kind, .. } => {
f(operand);
if let IsCheckKind::SourceOf(value) | IsCheckKind::DestinationOf(value) = kind {
f(value);
}
}
Self::InList { operand, list, .. } => {
f(operand);
for item in list {
f(item);
}
}
Self::InListExpression { operand, list, .. } => {
f(operand);
f(list);
}
Self::Case {
branches,
else_branch,
..
} => {
for (condition, value) in branches {
f(condition);
f(value);
}
if let Some(value) = else_branch {
f(value);
}
}
Self::Cast { value, .. } => f(value),
Self::Exists { .. } | Self::ValueSubquery { .. } => {}
}
}
pub fn for_each_span_mut(&mut self, f: &mut impl FnMut(&mut SourceSpan)) {
match self {
Self::Literal(literal) => f(literal.span_mut()),
Self::Variable { span, .. }
| Self::Parameter { span, .. }
| Self::PropertyAccess { span, .. }
| Self::ListLiteral { span, .. }
| Self::RecordLiteral { span, .. }
| Self::PathConstructor { span, .. }
| Self::BinaryOp { span, .. }
| Self::UnaryOp { span, .. }
| Self::FunctionCall { span, .. }
| Self::DurationBetween { span, .. }
| Self::IsCheck { span, .. }
| Self::InList { span, .. }
| Self::InListExpression { span, .. }
| Self::AllDifferent { span, .. }
| Self::Same { span, .. }
| Self::PropertyExists { span, .. }
| Self::Case { span, .. }
| Self::Exists { span, .. }
| Self::ValueSubquery { span, .. }
| Self::Normalize { span, .. }
| Self::Trim { span, .. }
| Self::Cast { span, .. } => f(span),
}
}
}
#[cfg(test)]
mod tests {
use selene_core::DbString;
use crate::ast::ValueExpr;
use crate::ast::expr::{BinaryOp, IsCheckKind, Literal};
use crate::ast::span::SourceSpan;
fn span(offset: u32) -> SourceSpan {
SourceSpan::new(offset, 1)
}
fn db_string(value: &str) -> DbString {
selene_core::db_string(value).expect("test string fits DB string cap")
}
fn var(name: &str, offset: u32) -> ValueExpr {
ValueExpr::Variable {
name: db_string(name),
span: span(offset),
}
}
fn int(value: i64, offset: u32) -> ValueExpr {
ValueExpr::Literal(Literal::Integer(value, span(offset)))
}
#[test]
fn for_each_child_binary_op_yields_both_operands_in_order() {
let expr = ValueExpr::BinaryOp {
op: BinaryOp::Add,
lhs: Box::new(int(1, 10)),
rhs: Box::new(int(2, 20)),
span: span(0),
};
let mut seen = Vec::new();
expr.for_each_child(&mut |child| seen.push(child.span().byte_offset));
assert_eq!(seen, vec![10, 20]);
}
#[test]
fn for_each_child_list_literal_yields_every_item() {
let expr = ValueExpr::ListLiteral {
items: vec![int(1, 1), int(2, 2), int(3, 3)],
span: span(0),
};
let mut seen = Vec::new();
expr.for_each_child(&mut |child| seen.push(child.span().byte_offset));
assert_eq!(seen, vec![1, 2, 3]);
}
#[test]
fn for_each_child_case_yields_branches_then_else() {
let expr = ValueExpr::Case {
branches: vec![(int(1, 1), int(2, 2)), (int(3, 3), int(4, 4))],
else_branch: Some(Box::new(int(5, 5))),
span: span(0),
};
let mut seen = Vec::new();
expr.for_each_child(&mut |child| seen.push(child.span().byte_offset));
assert_eq!(seen, vec![1, 2, 3, 4, 5]);
}
#[test]
fn for_each_child_is_check_yields_operand_then_kind_value() {
let expr = ValueExpr::IsCheck {
operand: Box::new(var("n", 1)),
kind: IsCheckKind::SourceOf(Box::new(var("e", 2))),
negated: false,
span: span(0),
};
let mut seen = Vec::new();
expr.for_each_child(&mut |child| seen.push(child.span().byte_offset));
assert_eq!(seen, vec![1, 2]);
}
#[test]
fn for_each_child_trim_skips_absent_character() {
let expr = ValueExpr::Trim {
spec: crate::ast::expr::TrimSpec::Both,
character: None,
source: Box::new(var("s", 7)),
span: span(0),
};
let mut seen = Vec::new();
expr.for_each_child(&mut |child| seen.push(child.span().byte_offset));
assert_eq!(seen, vec![7]);
}
#[test]
fn for_each_child_subquery_yields_nothing() {
let pipeline = crate::parse("RETURN 1")
.ok()
.and_then(|statement| match statement {
crate::Statement::Query(pipeline) => Some(pipeline),
_ => None,
})
.expect("RETURN 1 parses to a query pipeline");
let expr = ValueExpr::ValueSubquery {
body: Box::new(pipeline),
span: span(0),
};
let mut count = 0;
expr.for_each_child(&mut |_| count += 1);
assert_eq!(count, 0);
}
#[test]
fn for_each_child_mut_can_rewrite_children() {
let mut expr = ValueExpr::BinaryOp {
op: BinaryOp::Add,
lhs: Box::new(var("a", 1)),
rhs: Box::new(var("b", 2)),
span: span(0),
};
expr.for_each_child_mut(&mut |child| {
if matches!(child, ValueExpr::Variable { .. }) {
*child = int(0, 99);
}
});
let mut kinds = Vec::new();
expr.for_each_child(&mut |child| kinds.push(matches!(child, ValueExpr::Literal(_))));
assert_eq!(kinds, vec![true, true]);
}
#[test]
fn for_each_span_mut_visits_only_the_owning_node_span() {
let mut expr = ValueExpr::BinaryOp {
op: BinaryOp::Add,
lhs: Box::new(int(1, 10)),
rhs: Box::new(int(2, 20)),
span: span(0),
};
let mut visited = Vec::new();
expr.for_each_span_mut(&mut |s| {
visited.push(s.byte_offset);
s.byte_offset = s.byte_offset.saturating_add(100);
});
assert_eq!(visited, vec![0]);
let ValueExpr::BinaryOp { span, lhs, rhs, .. } = &expr else {
unreachable!("constructed a BinaryOp");
};
assert_eq!(span.byte_offset, 100);
assert_eq!(lhs.span().byte_offset, 10);
assert_eq!(rhs.span().byte_offset, 20);
}
#[test]
fn for_each_span_mut_reaches_literal_inner_span() {
let mut expr = int(7, 42);
let mut visited = Vec::new();
expr.for_each_span_mut(&mut |s| visited.push(s.byte_offset));
assert_eq!(visited, vec![42]);
}
#[test]
fn recursive_span_walk_offsets_every_span() {
fn offset_all(expr: &mut ValueExpr, by: u32) {
expr.for_each_span_mut(&mut |s| s.byte_offset = s.byte_offset.saturating_add(by));
expr.for_each_child_mut(&mut |child| offset_all(child, by));
}
let mut expr = ValueExpr::BinaryOp {
op: BinaryOp::Add,
lhs: Box::new(int(1, 10)),
rhs: Box::new(ValueExpr::UnaryOp {
op: crate::ast::expr::UnaryOp::Negate,
operand: Box::new(int(2, 30)),
span: span(20),
}),
span: span(0),
};
offset_all(&mut expr, 1000);
let mut all = Vec::new();
fn gather(expr: &ValueExpr, out: &mut Vec<u32>) {
let mut e = expr.clone();
e.for_each_span_mut(&mut |s| out.push(s.byte_offset));
expr.for_each_child(&mut |child| gather(child, out));
}
gather(&expr, &mut all);
all.sort_unstable();
assert_eq!(all, vec![1000, 1010, 1020, 1030]);
}
}