use alloc::boxed::Box;
use alloc::vec::Vec;
use super::{P, XPathError};
use crate::xpath::ast::{BinaryOp, Expr};
const OPERATORS: &[(&str, bool, BinaryOp, u8)] = &[
("or", true, BinaryOp::Or, 0),
("and", true, BinaryOp::And, 1),
("!=", false, BinaryOp::Ne, 2),
("=", false, BinaryOp::Eq, 2),
("<=", false, BinaryOp::Le, 3),
(">=", false, BinaryOp::Ge, 3),
("<", false, BinaryOp::Lt, 3),
(">", false, BinaryOp::Gt, 3),
("+", false, BinaryOp::Add, 4),
("-", false, BinaryOp::Sub, 4),
("*", false, BinaryOp::Mul, 5),
("div", true, BinaryOp::Div, 5),
("mod", true, BinaryOp::Mod, 5),
];
impl P<'_> {
pub(super) fn parse_binary(
&mut self,
min_prec: u8,
) -> Result<Expr, XPathError> {
let mut first = self.parse_unary()?;
let mut rest = Vec::new();
let mut level = u8::MAX;
while let Some((op, prec)) = self.eat_binary_op(min_prec) {
self.charge()?;
if prec < level && !rest.is_empty() {
first = chain(first, core::mem::take(&mut rest));
}
level = prec;
rest.push((op, self.parse_binary(prec + 1)?));
}
Ok(chain(first, rest))
}
fn eat_binary_op(&mut self, min_prec: u8) -> Option<(BinaryOp, u8)> {
let save = self.i;
let found = OPERATORS.iter().find(|&&(tok, word, _, _)| {
if word {
self.eat_word(tok)
} else {
self.eat(tok)
}
});
match found {
Some(&(_, _, op, prec)) if prec >= min_prec => Some((op, prec)),
_ => {
self.i = save;
None
}
}
}
fn parse_unary(&mut self) -> Result<Expr, XPathError> {
self.charge_node()?;
let mut negations = 0_usize;
while self.eat("-") {
self.charge()?;
negations += 1;
}
let operand = self.parse_union()?;
Ok(match negations {
0 => operand,
n if n % 2 == 1 => Expr::Negate(Box::new(operand)),
_ => Expr::Negate(Box::new(Expr::Negate(Box::new(operand)))),
})
}
}
pub(super) fn chain(first: Expr, mut rest: Vec<(BinaryOp, Expr)>) -> Expr {
if rest.len() > 1 {
return Expr::Chain {
first: Box::new(first),
rest,
};
}
match rest.pop() {
None => first,
Some((op, rhs)) => Expr::Binary {
op,
lhs: Box::new(first),
rhs: Box::new(rhs),
},
}
}