use crate::expr::Expr;
use crate::node::Spanned;
type OpChain<'a> = Option<(&'a Spanned<Expr>, Vec<(&'a str, &'a Spanned<Expr>)>)>;
pub(super) fn flatten_mixed_pipe_chain(expr: &Expr) -> OpChain<'_> {
fn is_pipe(op: &str) -> bool {
matches!(op, "|>" | "|." | "|=")
}
match expr {
Expr::OperatorApplication {
operator,
left,
right,
..
} if is_pipe(operator) => {
let (head, mut tail) =
flatten_mixed_pipe_chain(&left.value).unwrap_or((left.as_ref(), Vec::new()));
tail.push((operator.as_str(), right.as_ref()));
Some((head, tail))
}
_ => None,
}
}
pub(super) fn flatten_right_assoc_chain<'a>(
expr: &'a Expr,
target_op: &str,
) -> Option<Vec<&'a Spanned<Expr>>> {
match expr {
Expr::OperatorApplication {
operator,
left,
right,
..
} if operator == target_op => {
let mut chain = vec![left.as_ref()];
match flatten_right_assoc_chain(&right.value, target_op) {
Some(mut rest) => chain.append(&mut rest),
None => chain.push(right.as_ref()),
}
Some(chain)
}
_ => None,
}
}
pub(super) fn flatten_mixed_cons_append_chain<'a>(expr: &'a Expr) -> OpChain<'a> {
fn is_cons_or_append(op: &str) -> bool {
matches!(op, "::" | "++")
}
match expr {
Expr::OperatorApplication {
operator,
left,
right,
..
} if is_cons_or_append(operator) => {
let mut rest: Vec<(&'a str, &'a Spanned<Expr>)> = Vec::new();
let (head, tail_rest) = match flatten_mixed_cons_append_chain(&right.value) {
Some((head, rest_r)) => {
rest.push((operator.as_str(), head));
for (op, e) in rest_r {
rest.push((op, e));
}
(left.as_ref(), rest)
}
None => {
rest.push((operator.as_str(), right.as_ref()));
(left.as_ref(), rest)
}
};
Some((head, tail_rest))
}
_ => None,
}
}
fn flatten_mixed_bidir_chain<'a>(expr: &'a Expr, pred: &impl Fn(&str) -> bool) -> OpChain<'a> {
match expr {
Expr::OperatorApplication {
operator,
left,
right,
..
} if pred(operator) => {
let (lhead, mut rest) = match flatten_mixed_bidir_chain(&left.value, pred) {
Some((h, r)) => (h, r),
None => (left.as_ref(), Vec::new()),
};
match flatten_mixed_bidir_chain(&right.value, pred) {
Some((rhead, rrest)) => {
rest.push((operator.as_str(), rhead));
for (op, e) in rrest {
rest.push((op, e));
}
}
None => {
rest.push((operator.as_str(), right.as_ref()));
}
};
Some((lhead, rest))
}
_ => None,
}
}
pub(super) fn flatten_mixed_logical_chain(expr: &Expr) -> OpChain<'_> {
flatten_mixed_bidir_chain(expr, &|op| matches!(op, "&&" | "||"))
}
pub(super) fn flatten_mixed_arithmetic_chain(expr: &Expr) -> OpChain<'_> {
flatten_mixed_bidir_chain(expr, &|op| matches!(op, "+" | "-" | "*" | "/" | "//"))
}
pub(super) fn flatten_mixed_comparison_arithmetic_chain(expr: &Expr) -> OpChain<'_> {
let result = flatten_mixed_bidir_chain(expr, &|op| {
matches!(
op,
"==" | "/=" | "<" | ">" | "<=" | ">=" | "+" | "-" | "*" | "/" | "//"
)
})?;
let has_comparison = result
.1
.iter()
.any(|(op, _)| matches!(*op, "==" | "/=" | "<" | ">" | "<=" | ">="));
let has_arithmetic = result
.1
.iter()
.any(|(op, _)| matches!(*op, "+" | "-" | "*" | "/" | "//"));
if has_comparison && has_arithmetic {
Some(result)
} else {
None
}
}
pub(super) fn flatten_as_chain(expr: &Expr) -> OpChain<'_> {
match expr {
Expr::OperatorApplication { operator, .. } => {
let op = operator.as_str();
if matches!(op, "::" | "++") {
flatten_mixed_cons_append_chain(expr)
} else if matches!(op, "&&" | "||") {
flatten_mixed_logical_chain(expr)
} else if matches!(op, "+" | "-" | "*" | "/" | "//") {
flatten_mixed_arithmetic_chain(expr)
} else if matches!(op, ">>" | "<<") {
flatten_right_assoc_chain(expr, op).map(|chain| {
let first = chain[0];
let rest: Vec<(&str, &Spanned<Expr>)> =
chain[1..].iter().map(|e| (op, *e)).collect();
(first, rest)
})
} else {
None
}
}
_ => None,
}
}