expy 0.0.2

Embeddable & extensible expression evaluator
Documentation
//! Expression parser.

use std::cell::RefCell;

use derive_more::IsVariant;
use derive_new::new;
use itertools::Itertools;
use pest::Parser;
use pest::iterators::Pairs;
use pest::pratt_parser::{PrattParser, Op, Assoc};
use pest_derive::Parser;
use thiserror::Error;

use super::eval::{Context, Error as EvalError};
use super::model::{Expr, Ident, Literal, UnaryOp, BinaryOp, Value};


#[derive(Parser)]
#[grammar = "grammar.pest"]
struct Grammar;


/// SAX-style handler of the intermediate parsing results of expressions.
pub trait Handler<R> {
    /// Type of the term that this handler produces & operates on.
    ///
    /// Corresponds to the "primary" parsing term in a Pratt parser.
    type Term;

    // literals

    fn bool(&mut self, raw: &str) -> Self::Term;
    fn int(&mut self, raw: &str) -> Self::Term;
    fn float(&mut self, raw: &str) -> Self::Term;
    fn symbol(&mut self, name: &str) -> Self::Term;

    // terms

    fn ident(&mut self, raw: &str) -> Self::Term;

    #[cfg(glam)]
    fn vector(&mut self, items: impl IntoIterator<Item=Self::Term>) -> Self::Term;

    fn parens(&mut self, inner: Self::Term) -> Self::Term {
        inner
    }

    // nested expressions

    fn unary_expr(&mut self, op: UnaryOp, arg: Self::Term) -> Self::Term;
    fn binary_expr(&mut self, lhs: Self::Term, op: BinaryOp, rhs: Self::Term) -> Self::Term;

    fn call(&mut self, target: Self::Term, args: impl IntoIterator<Item=Self::Term>) -> Self::Term;
    fn subscript(&mut self, target: Self::Term, index: Self::Term) -> Self::Term;
    fn access(&mut self, target: Self::Term, member: &str) -> Self::Term;
}


/// Driver of the parsing process.
///
/// For given input, it generates SAX-style "events" for a [`Handler`] to consume.
pub struct Driver<H: Handler<Rule>> {
    pratt: PrattParser<Rule>,
    handler: RefCell<H>,
}

impl<H: Handler<Rule>> Driver<H> {
    pub fn new(handler: H) -> Self {
        let pratt = PrattParser::new()
            .op(Op::prefix(Rule::neg) | Op::prefix(Rule::not))
            .op(Op::infix(Rule::and, Assoc::Left) | Op::infix(Rule::or, Assoc::Left))
            .op(
                Op::infix(Rule::eq, Assoc::Left) | Op::infix(Rule::not_eq, Assoc::Left) |
                Op::infix(Rule::less, Assoc::Left) | Op::infix(Rule::less_eq, Assoc::Left) |
                Op::infix(Rule::greater, Assoc::Left) | Op::infix(Rule::greater_eq, Assoc::Left))
            .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left))
            .op(Op::infix(Rule::mul, Assoc::Left) | Op::infix(Rule::div, Assoc::Left))
            .op(Op::infix(Rule::pow, Assoc::Right))
            .op(Op::postfix(Rule::call) | Op::postfix(Rule::subscript) | Op::postfix(Rule::access))
        ;
        Self { pratt, handler: RefCell::new(handler) }
    }
}

impl<H: Handler<Rule>> Driver<H> {
    /// Process the expression input, returning the final result
    /// that has been produced by the underlying [`Handler`].
    pub fn process(&self, input: impl AsRef<str>) -> Result<H::Term, Error> {
        let mut expr = Grammar::parse(Rule::input, input.as_ref())?;

        // Unpack the parsing result to get to the inside of `expr` rule,
        // as this is what PrattParser expects.
        let pairs = expr.next().unwrap().into_inner();

        self.validate_optional_features(&pairs)?;
        Ok(self.handle_expr(pairs))
    }

    fn validate_optional_features(&self, pairs: &Pairs<Rule>) -> Result<(), Error> {
        if cfg!(glam) {
            // TODO: do we want to keep this validation? evaluation needs to verify
            // the item count later on anyway, but this at least helps catching errors with
            // "prepared" expressions
            for pair in pairs.clone().find_tagged("glam") {
                match pair.as_rule() {
                    Rule::vector => {
                        let len = pair.into_inner().len();
                        if ![2, 3, 4].contains(&len) {
                            return Err(Error::Unsupported(format!("vector of length {}", len)));
                        }
                    },
                    _ => unreachable!(),
                }
            }
        } else if pairs.find_first_tagged("glam").is_some() {
            // TODO: include the position information where the unsupported feature was used,
            // possibly by just synthesizing a pest error ourselves
            return Err(Error::Unsupported("vector syntax".into()));
        }
        Ok(())
    }

    fn handle_expr(&self, pairs: Pairs<Rule>) -> H::Term {
        self.pratt
            .map_primary(|primary| match primary.as_rule() {
                Rule::r#bool => self.handler.borrow_mut().bool(primary.as_str()),
                Rule::int => self.handler.borrow_mut().int(primary.as_str()),
                Rule::float => self.handler.borrow_mut().float(primary.as_str()),
                Rule::symbol => {
                    let name = primary.into_inner().next().unwrap().as_str();
                    self.handler.borrow_mut().symbol(name)
                },
                Rule::ident => self.handler.borrow_mut().ident(primary.as_str()),
                #[cfg(glam)]
                Rule::vector => {
                    // We have to collect() here because we'd otherwise hit simultaneous
                    // mutable borrows of the Handler RefCell.
                    let items: Vec<_> = primary.into_inner()
                        .map(|elem| self.handle_expr(elem.into_inner()))
                        .collect();
                    self.handler.borrow_mut().vector(items)
                },
                Rule::expr => {
                    let inner = self.handle_expr(primary.into_inner());
                    self.handler.borrow_mut().parens(inner)
                },
                _ => unreachable!(),
            })
            .map_prefix(|op, rhs| {
                let op = match op.as_rule() {
                    Rule::neg => UnaryOp::Neg,
                    Rule::not => UnaryOp::Not,
                    _ => unreachable!(),
                };
                self.handler.borrow_mut().unary_expr(op, rhs)
            })
            .map_infix(|left, op, right| {
                let op = match op.as_rule() {
                    Rule::and => BinaryOp::And,
                    Rule::or => BinaryOp::Or,
                    Rule::eq => BinaryOp::Eq,
                    Rule::not_eq => BinaryOp::NotEq,
                    Rule::less => BinaryOp::Less,
                    Rule::less_eq => BinaryOp::LessOrEq,
                    Rule::greater => BinaryOp::Greater,
                    Rule::greater_eq => BinaryOp::GreaterOrEq,
                    Rule::add => BinaryOp::Add,
                    Rule::sub => BinaryOp::Sub,
                    Rule::mul => BinaryOp::Mul,
                    Rule::div => BinaryOp::Div,
                    Rule::pow => BinaryOp::Pow,
                    _ => unreachable!(),
                };
                self.handler.borrow_mut().binary_expr(left, op, right)
            })
            .map_postfix(|lhs, op| match op.as_rule() {
                Rule::call => {
                    // We have to collect() here because we'd otherwise hit simultaneous
                    // mutable borrows of the Handler RefCell.
                    let args: Vec<_> =  op.into_inner()
                        .map(|a| self.handle_expr(a.into_inner()))
                        .collect();
                    self.handler.borrow_mut().call(lhs, args)
                },
                Rule::subscript => {
                    let index = self.handle_expr(op.into_inner().next().unwrap().into_inner());
                    self.handler.borrow_mut().subscript(lhs, index)
                },
                Rule::access => {
                    let member = op.into_inner().next().unwrap();
                    self.handler.borrow_mut().access(lhs, member.as_str())
                },
                _ => unreachable!(),
            })
            .parse(pairs)
    }
}


//
// Handlers
//

/// Handler type which produces the AST of expressions.
pub(crate) struct AstHandler;

impl Handler<Rule> for AstHandler {
    type Term = Expr;

    fn bool(&mut self, raw: &str) -> Self::Term {
        Expr::literal(raw.parse::<bool>().unwrap())
    }

    fn int(&mut self, raw: &str) -> Self::Term {
        Expr::literal(raw.parse::<i64>().unwrap())
    }

    fn float(&mut self, raw: &str) -> Self::Term {
        Expr::literal(raw.parse::<f32>().unwrap())
    }

    fn symbol(&mut self, name: &str) -> Self::Term {
        Expr::Literal(Literal::symbol(name))
    }

    fn ident(&mut self, raw: &str) -> Self::Term {
        Expr::ref_(raw.to_owned())
    }

    #[cfg(glam)]
    fn vector(&mut self, items: impl IntoIterator<Item=Self::Term>) -> Self::Term {
        Expr::vector(items)
    }

    fn unary_expr(&mut self, op: UnaryOp, arg: Self::Term) -> Self::Term {
        Expr::unary(op, arg)
    }

    fn binary_expr(&mut self, lhs: Self::Term, op: BinaryOp, rhs: Self::Term) -> Self::Term {
        Expr::binary(op, lhs, rhs)
    }

    fn call(&mut self, target: Self::Term, args: impl IntoIterator<Item=Self::Term>) -> Self::Term {
        Expr::call(target, args)
    }

    fn subscript(&mut self, target: Self::Term, index: Self::Term) -> Self::Term {
        Expr::subscript(target, index)
    }

    fn access(&mut self, target: Self::Term, member: &str) -> Self::Term {
        Expr::access(target, member.to_owned())
    }
}

/// Handler type which evaluates the expressions immediately.
#[derive(new)]
pub(crate) struct EvalHandler<'h, 'ctx: 'h> {
    context: &'h mut Context<'ctx>,
}

impl<'h, 'ctx> Handler<Rule> for EvalHandler<'h, 'ctx> {
    type Term = Result<Value, EvalError>;

    fn bool(&mut self, raw: &str) -> Self::Term {
        Ok(Value::Bool(raw.parse().unwrap()))
    }

    fn int(&mut self, raw: &str) -> Self::Term {
        Ok(Value::Integer(raw.parse().unwrap()))
    }

    fn float(&mut self, raw: &str) -> Self::Term {
        Ok(Value::Float(raw.parse().unwrap()))
    }

    fn symbol(&mut self, name: &str) -> Self::Term {
        Ok(Value::symbol(name))
    }

    fn ident(&mut self, raw: &str) -> Self::Term {
        self.context.resolve(raw).map(|v| v.clone())
    }

    #[cfg(glam)]
    fn vector(&mut self, items: impl IntoIterator<Item=Self::Term>) -> Self::Term {
        let items: Result<Vec<_>, _> = items.into_iter().collect();
        self.context.eval_vector(&items?)
    }

    fn unary_expr(&mut self, op: UnaryOp, arg: Self::Term) -> Self::Term {
        self.context.eval_unary_expr(op, &arg?)
    }

    fn binary_expr(&mut self, lhs: Self::Term, op: BinaryOp, rhs: Self::Term) -> Self::Term {
        self.context.eval_binary_expr(op, &lhs?, &rhs?)
    }

    fn call(&mut self, target: Self::Term, args: impl IntoIterator<Item=Self::Term>) -> Self::Term {
        let callable = target?.try_to_callable()?;
        let args: Result<Vec<_>, _> = args.into_iter().collect();
        self.context.eval_call(callable, &args?)
    }

    fn subscript(&mut self, target: Self::Term, index: Self::Term) -> Self::Term {
        self.context.eval_subscript(&target?, &index?)
    }

    fn access(&mut self, target: Self::Term, member: &str) -> Self::Term {
        self.context.eval_access(&target?, &Ident::from(member.to_owned()))
    }
}


/// Handler type which formats the expressions according to given settings.
#[derive(Default, new)]
pub(crate) struct FormatHandler {
    format: Format,
}

#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub(crate) enum Format {
    #[default]
    Pretty,
    Minified,
}

impl Handler<Rule> for FormatHandler {
    type Term = String;

    fn bool(&mut self, raw: &str) -> Self::Term {
        raw.to_owned()
    }

    fn int(&mut self, raw: &str) -> Self::Term {
        raw.to_owned()
    }

    fn float(&mut self, raw: &str) -> Self::Term {
        raw.to_owned()
    }

    fn symbol(&mut self, name: &str) -> Self::Term {
        format!("@{}", name)
    }

    fn ident(&mut self, raw: &str) -> Self::Term {
        raw.to_owned()
    }

    #[cfg(glam)]
    fn vector(&mut self, items: impl IntoIterator<Item=Self::Term>) -> Self::Term {
        format!("[{}]", items.into_iter().format(self.comma()))
    }

    fn parens(&mut self, inner: Self::Term) -> Self::Term {
        format!("({})", inner)
    }

    fn unary_expr(&mut self, op: UnaryOp, arg: Self::Term) -> Self::Term {
        format!("{}{}", op, arg)
    }

    fn binary_expr(&mut self, lhs: Self::Term, op: BinaryOp, rhs: Self::Term) -> Self::Term {
        if self.format == Format::Minified || op == BinaryOp::Pow {
            format!("{}{}{}", lhs, op, rhs)
        } else {
            format!("{} {} {}", lhs, op, rhs)
        }
    }

    fn call(&mut self, target: Self::Term, args: impl IntoIterator<Item=Self::Term>) -> Self::Term {
        format!("{}({})", target, args.into_iter().format(self.comma()))
    }

    fn subscript(&mut self, target: Self::Term, index: Self::Term) -> Self::Term {
        format!("{}[{}]", target, index)
    }

    fn access(&mut self, target: Self::Term, member: &str) -> Self::Term {
        format!("{}.{}", target, member)
    }
}

impl FormatHandler {
    fn comma(&self) -> &str {
        match self.format {
            Format::Pretty => ", ",
            Format::Minified => ",",
        }
    }
}


#[derive(Debug, Error, Eq, IsVariant, PartialEq)]
pub enum Error {
    #[error("syntax error: {0}")]
    Syntax(#[from] pest::error::Error<Rule>),

    #[error("unsupported feature: {0}")]
    Unsupported(String),
}