femstache 0.1.5

Embrace the feminine mustache to unlock your templating dreams! Or something, I don't know. :3
Documentation
use femstache_parser::nodes::{Access, BinOp, Expr, ExprValue, Literal};

use crate::{
    TemplateInput,
    env::TemplateEnv,
    error::{EvalError, EvalResult},
    value::Value,
};

pub fn eval_expr<'i, I: TemplateInput>(env: &TemplateEnv<'i, I>, expr: &Expr) -> EvalResult<Value> {
    match expr {
        Expr::Value(v) => match v {
            ExprValue::Ident(ident) => env
                .get(&ident.data)
                .ok_or_else(|| EvalError::VariableNotFound(ident.clone())),
            ExprValue::Literal(literal) => Ok(match literal {
                Literal::Null(_) => Value::Null,
                Literal::True(_) => Value::Bool(true),
                Literal::False(_) => Value::Bool(false),
                Literal::String(s) => Value::String(s.as_str().to_owned()),
                Literal::List(l) => {
                    let mut items = Vec::new();
                    for item in &l.items {
                        let value = eval_expr(env, &item)?;
                        items.push(value);
                    }
                    Value::List(items)
                }
            }),
            ExprValue::Group(group) => {
                let r = eval_expr(env, &group.expr)?;
                Ok(r)
            }
        },
        Expr::Access(st, Access::Member(ma)) => {
            let st = eval_expr(env, st)?;
            let Some(fields) = st.as_struct() else {
                return Err(EvalError::InvalidType);
            };
            let Some(field) = fields.get(&ma.ident.data) else {
                return Err(EvalError::NoMember(ma.ident.clone()));
            };
            Ok(field.clone())
        }
        Expr::Access(st, Access::Dict(da)) => {
            let st = eval_expr(env, st)?;
            let key = eval_expr(env, &da.expr)?;
            let Some(map) = st.as_dict() else {
                return Err(EvalError::InvalidType);
            };
            let Some(key_str) = key.as_str() else {
                return Err(EvalError::InvalidType);
            };
            let Some(field) = map.get(key_str) else {
                return Err(EvalError::NoDictKey(key_str.to_owned()));
            };
            Ok(field.clone())
        }
        Expr::BinOp(lhs, binop, rhs) => {
            let lhs = eval_expr(env, lhs)?;
            match binop {
                BinOp::Eq(_) => {
                    let rhs = eval_expr(env, rhs)?;
                    Ok(Value::Bool(lhs == rhs))
                }
                BinOp::Neq(_) => {
                    let rhs = eval_expr(env, rhs)?;
                    Ok(Value::Bool(lhs != rhs))
                }
                BinOp::PlusPlus(_) => {
                    let rhs = eval_expr(env, rhs)?;
                    let Some(ls) = lhs.as_str() else {
                        return Err(EvalError::InvalidType);
                    };
                    let Some(rs) = rhs.as_str() else {
                        return Err(EvalError::InvalidType);
                    };
                    let mut full = String::with_capacity(ls.len() + rs.len());
                    full.push_str(ls);
                    full.push_str(rs);
                    Ok(Value::String(full))
                }
            }
        }
    }
}