anathema-templates 0.2.11

Anathema template parser (aml)
Documentation
use std::collections::HashMap;

use super::parser::Expr;
use super::{Expression, LogicalOp, Op};
use crate::error::{ParseErrorKind, Result};
use crate::expressions::Equality;
use crate::strings::Strings;
use crate::token::Operator;

pub(super) fn eval(expr: Expr, strings: &Strings) -> Result<Expression, ParseErrorKind> {
    let output = match expr {
        Expr::Primitive(val) => Expression::Primitive(val),
        Expr::Ident(string_id) => {
            let string = strings.get_unchecked(string_id);
            Expression::Ident(string)
        }
        Expr::Str(string_id) => {
            let string = strings.get_unchecked(string_id);
            Expression::Str(string)
        }
        Expr::Array { lhs, index } => {
            let lhs = eval(*lhs, strings)?;
            let index = eval(*index, strings)?;
            Expression::Index(lhs.into(), index.into())
        }
        Expr::Binary { op, lhs, rhs } => match op {
            Operator::Dot => {
                let lhs = eval(*lhs, strings)?.into();
                let string_id = match *rhs {
                    Expr::Ident(s) => s,
                    _ => {
                        return Err(ParseErrorKind::InvalidToken {
                            expected: "this can only be an ident",
                        });
                    }
                };
                let rhs = strings.get_unchecked(string_id);
                Expression::Index(lhs, Expression::Str(rhs).into())
            }
            Operator::DotDot => {
                let (lhs, rhs) = (eval(*lhs, strings)?.into(), eval(*rhs, strings)?.into());
                Expression::Range(lhs, rhs)
            }
            Operator::Mul | Operator::Plus | Operator::Minus | Operator::Div | Operator::Mod => {
                let (lhs, rhs) = (eval(*lhs, strings)?.into(), eval(*rhs, strings)?.into());
                let op = match op {
                    Operator::Mul => Op::Mul,
                    Operator::Plus => Op::Add,
                    Operator::Minus => Op::Sub,
                    Operator::Div => Op::Div,
                    Operator::Mod => Op::Mod,
                    _ => unreachable!(),
                };
                Expression::Op(lhs, rhs, op)
            }
            Operator::EqualEqual
            | Operator::NotEqual
            | Operator::GreaterThan
            | Operator::GreaterThanOrEqual
            | Operator::LessThan
            | Operator::LessThanOrEqual => {
                let equality = match op {
                    Operator::EqualEqual => Equality::Eq,
                    Operator::NotEqual => Equality::NotEq,
                    Operator::GreaterThan => Equality::Gt,
                    Operator::GreaterThanOrEqual => Equality::Gte,
                    Operator::LessThan => Equality::Lt,
                    Operator::LessThanOrEqual => Equality::Lte,
                    _ => unreachable!(),
                };
                Expression::Equality(eval(*lhs, strings)?.into(), eval(*rhs, strings)?.into(), equality)
            }
            Operator::And | Operator::Or => {
                let logical_op = match op {
                    Operator::And => LogicalOp::And,
                    Operator::Or => LogicalOp::Or,
                    _ => unreachable!(),
                };
                let lhs = eval(*lhs, strings)?.into();
                let rhs = eval(*rhs, strings)?.into();

                Expression::LogicalOp(lhs, rhs, logical_op)
            }
            Operator::Either => {
                let (lhs, rhs) = (eval(*lhs, strings)?.into(), eval(*rhs, strings)?.into());
                Expression::Either(lhs, rhs)
            }
            _ => return Err(ParseErrorKind::InvalidToken { expected: "" }),
        },
        Expr::Unary { op, expr } => {
            let expr = eval(*expr, strings)?;

            match op {
                Operator::Not => Expression::Not(expr.into()),
                Operator::Minus => Expression::Negative(expr.into()),
                _ => {
                    return Err(ParseErrorKind::InvalidToken {
                        expected: "either ! or -",
                    });
                }
            }
        }
        Expr::List(list) => Expression::List(
            list.into_iter()
                .map(|expr| eval(expr, strings))
                .collect::<Result<Vec<_>, _>>()?,
        ),
        Expr::Map(map) => {
            let mut inner = HashMap::default();
            for (key, value) in map.into_iter() {
                let key = match eval(key, strings)? {
                    Expression::Str(s) | Expression::Ident(s) => s,
                    _ => return Err(ParseErrorKind::InvalidKey),
                };
                inner.insert(key, eval(value, strings)?);
            }

            Expression::Map(inner)
        }
        Expr::Call { fun, args } => {
            let args = args
                .into_iter()
                .map(|expr| eval(expr, strings))
                .collect::<Result<Vec<_>, _>>()?;

            Expression::Call {
                fun: eval(*fun, strings)?.into(),
                args,
            }
        }
    };

    Ok(output)
}

#[cfg(test)]
mod test {

    use super::*;
    use crate::expressions::parser::parse_expr;
    use crate::lexer::Lexer;
    use crate::strings::Strings;
    use crate::token::Tokens;

    fn eval_src(input: &'static str) -> Expression {
        let input = input.into();
        let mut strings = Strings::new();
        let lexer = Lexer::new(&input, &mut strings);
        let tokens = lexer.collect::<Result<_, _>>().unwrap();
        let mut tokens = Tokens::new(tokens, input.len());
        parse_expr(&mut tokens, &strings).unwrap()
    }

    #[test]
    fn ident() {
        let expr = eval_src("ident");
        assert_eq!(expr.to_string(), "ident");
    }

    #[test]
    fn index() {
        let expr = eval_src("a[x]");
        assert_eq!(expr.to_string(), "<a>[x]");
    }

    #[test]
    fn dot() {
        let expr = eval_src("a.x.y");
        assert_eq!(expr.to_string(), "<<a>[x]>[y]");
    }

    #[test]
    fn number() {
        let expr = eval_src("123");
        assert_eq!(expr.to_string(), "123");
    }

    #[test]
    fn negative_number() {
        let expr = eval_src("-123");
        assert_eq!(expr.to_string(), "-123");
    }

    #[test]
    fn lookup() {
        let expr = eval_src("a.b.c");
        assert_eq!(expr.to_string(), "<<a>[b]>[c]");
    }

    #[test]
    fn bool() {
        let expr = eval_src("true");
        assert_eq!(expr.to_string(), "true");

        let expr = eval_src("!true");
        assert_eq!(expr.to_string(), "!true");

        let expr = eval_src("!false");
        assert_eq!(expr.to_string(), "!false");

        let expr = eval_src("!!false");
        assert_eq!(expr.to_string(), "!!false");

        let expr = eval_src("!hello");
        assert_eq!(expr.to_string(), "!hello");

        let expr = eval_src("!!hello");
        assert_eq!(expr.to_string(), "!!hello");
    }

    #[test]
    fn strings() {
        let expr = eval_src("'single quote'");
        assert_eq!(expr.to_string(), "single quote");

        let expr = eval_src("\"double quote\"");
        assert_eq!(expr.to_string(), "double quote");
    }

    #[test]
    fn addition() {
        let expr = eval_src("-2 + -3");
        assert_eq!(expr.to_string(), "-2 + -3");

        let expr = eval_src("2 + -3");
        assert_eq!(expr.to_string(), "2 + -3");

        let expr = eval_src("2 + -1");
        assert_eq!(expr.to_string(), "2 + -1");

        let expr = eval_src("-3 + 2");
        assert_eq!(expr.to_string(), "-3 + 2");

        let expr = eval_src("-1 + 2");
        assert_eq!(expr.to_string(), "-1 + 2");

        let expr = eval_src("1 + 2 * 3");
        assert_eq!(expr.to_string(), "1 + 2 * 3");

        let expr = eval_src("a + b * c");
        assert_eq!(expr.to_string(), "a + b * c");
    }

    #[test]
    fn multiplication() {
        let expr = eval_src("2 * 2");
        assert_eq!(expr.to_string(), "2 * 2");

        let expr = eval_src("x * 2 * 2");
        assert_eq!(expr.to_string(), "x * 2 * 2");
    }

    #[test]
    fn subtraction() {
        let expr = eval_src("5 - 4");
        assert_eq!(expr.to_string(), "5 - 4");

        let expr = eval_src("-5 - 4");
        assert_eq!(expr.to_string(), "-5 - 4");

        let expr = eval_src("-5 - -4");
        assert_eq!(expr.to_string(), "-5 - -4");

        let expr = eval_src("a - b");
        assert_eq!(expr.to_string(), "a - b");
    }

    #[test]
    fn division() {
        let expr = eval_src("5 / 4");
        assert_eq!(expr.to_string(), "5 / 4");

        let expr = eval_src("a / b");
        assert_eq!(expr.to_string(), "a / b");

        let expr = eval_src("-a / b");
        assert_eq!(expr.to_string(), "-a / b");
    }

    #[test]
    fn modulo() {
        let expr = eval_src("5 % 4");
        assert_eq!(expr.to_string(), "5 % 4");

        let expr = eval_src("a % 4");
        assert_eq!(expr.to_string(), "a % 4");
    }

    #[test]
    fn function_call() {
        let expr = eval_src("fun(5, 4)");
        assert_eq!(expr.to_string(), "fun(5, 4)");
    }

    #[test]
    fn equality() {
        let expr = eval_src("1 == 1");
        assert_eq!(expr.to_string(), "1 == 1");
    }

    #[test]
    fn not_equal() {
        let expr = eval_src("1 != 1");
        assert_eq!(expr.to_string(), "1 != 1");
    }

    #[test]
    fn greater_than() {
        let expr = eval_src("1 > 1");
        assert_eq!(expr.to_string(), "1 > 1");
    }

    #[test]
    fn greater_than_or_equal_to() {
        let expr = eval_src("1 >= 1");
        assert_eq!(expr.to_string(), "1 >= 1");
    }

    #[test]
    fn less_than() {
        let expr = eval_src("1 < 1");
        assert_eq!(expr.to_string(), "1 < 1");
    }

    #[test]
    fn less_than_or_equal_to() {
        let expr = eval_src("1 <= 1");
        assert_eq!(expr.to_string(), "1 <= 1");
    }

    #[test]
    fn and() {
        let expr = eval_src("true && true");
        assert_eq!(expr.to_string(), "true && true");
    }

    #[test]
    fn or() {
        let expr = eval_src("true || true");
        assert_eq!(expr.to_string(), "true || true");
    }

    #[test]
    fn list() {
        let expr = eval_src("[1, 2, 3]");
        assert_eq!(expr.to_string(), "[1, 2, 3]");
    }

    #[test]
    fn map() {
        let expr = eval_src("[{a: 1}, {b: 89}]");
        assert_eq!(expr.to_string(), "[{a: 1}, {b: 89}]");
    }

    #[test]
    fn either() {
        let expr = eval_src("a?b");
        assert_eq!(expr.to_string(), "a ? b");
    }

    #[test]
    fn either_list_or_list() {
        let expr = eval_src("(a?b)[0]");
        assert_eq!(expr.to_string(), "<a ? b>[0]");
    }

    #[test]
    fn either_or_list() {
        let expr = eval_src("a?b[0]");
        assert_eq!(expr.to_string(), "a ? <b>[0]");
    }
}