use crate::ast::{BinaryOp, Expr};
pub(super) const ORDERED_FUNCTIONS: [&str; 5] = ["first", "last", "tail", "skip", "take"];
const ORDER_KEEPING: [&str; 15] = [
"where",
"select",
"repeat",
"ofType",
"distinct",
"tail",
"skip",
"take",
"intersect",
"exclude",
"union",
"combine",
"trace",
"extension",
"resolve",
];
pub(super) fn unordered(expr: &Expr) -> bool {
match expr {
Expr::Call { name, .. } if name == "children" || name == "descendants" => true,
Expr::Call {
input, name, args, ..
} if ORDER_KEEPING.contains(&name.as_str()) => {
input.as_deref().is_some_and(unordered)
|| (matches!(name.as_str(), "union" | "combine") && args.iter().any(unordered))
}
Expr::Call { name, args, .. } if name == "iif" => args.iter().skip(1).any(unordered),
Expr::Member {
input: Some(input), ..
} => unordered(input),
Expr::Invocation { input, variable } => {
matches!(variable.as_ref(), Expr::This) && unordered(input)
}
Expr::As { operand, .. } => unordered(operand),
Expr::Binary {
op: BinaryOp::Union,
left,
right,
} => unordered(left) || unordered(right),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::unordered;
use crate::parse::parse;
fn of(text: &str) -> bool {
unordered(&parse(text).expect("parses"))
}
#[test]
fn children_and_descendants_are_unordered() {
assert!(of("children()"));
assert!(of("Patient.descendants()"));
}
#[test]
fn order_keeping_steps_carry_the_lack_of_order() {
assert!(of("children().where(true)"));
assert!(of("children().name"));
assert!(of("name | children()"));
assert!(of("name.union(descendants())"));
assert!(of("iif(true, children(), name)"));
}
#[test]
fn a_path_and_a_single_value_are_ordered() {
assert!(!of("Patient.name.given"));
assert!(!of("children().count()"));
assert!(!of("children().exists()"));
assert!(!of("iif(children().exists(), name, name)"));
}
}