use core::iter::Peekable;
pub enum Associativity {
Left,
Right,
}
pub trait Op {
fn precedence(&self) -> usize;
fn associativity(&self) -> Associativity;
}
pub trait Expr<O: Op> {
fn from_op(lhs: Self, op: O, rhs: Self) -> Self;
}
pub fn climb<O: Op, T: Expr<O>>(head: T, iter: impl IntoIterator<Item = (O, T)>) -> T {
climb1(head, &mut iter.into_iter().peekable(), 0)
}
fn climb1<O: Op, T: Expr<O>, I>(mut x: T, iter: &mut Peekable<I>, min_prec: usize) -> T
where
I: Iterator<Item = (O, T)>,
{
while let Some((op, mut rhs)) = iter.next_if(|(op, _)| op.precedence() >= min_prec) {
let right_assoc = matches!(op.associativity(), Associativity::Right);
let this_prec = op.precedence();
while let Some(next) = iter.peek() {
let next_prec = next.0.precedence();
if next_prec > this_prec || (right_assoc && next_prec == this_prec) {
rhs = climb1(rhs, iter, next_prec);
} else {
break;
}
}
x = T::from_op(x, op, rhs);
}
x
}
#[test]
fn test() {
enum Arith {
Add,
Sub,
Mul,
Div,
}
impl Op for Arith {
fn precedence(&self) -> usize {
match self {
Arith::Add | Arith::Sub => 0,
Arith::Mul | Arith::Div => 1,
}
}
fn associativity(&self) -> Associativity {
Associativity::Right
}
}
impl Expr<Arith> for isize {
fn from_op(lhs: Self, op: Arith, rhs: Self) -> Self {
match op {
Arith::Add => lhs + rhs,
Arith::Sub => lhs - rhs,
Arith::Mul => lhs * rhs,
Arith::Div => lhs / rhs,
}
}
}
use Arith::{Add, Div, Mul, Sub};
let head: isize = 1;
let tail = [(Add, 2), (Mul, 3), (Sub, 6), (Div, 2)];
let out = climb(head, tail);
assert_eq!(out, 4);
}