minijinja/
expression.rs

1use std::collections::{BTreeMap, HashSet};
2use std::fmt;
3
4use serde::Serialize;
5
6use crate::compiler::ast;
7use crate::compiler::instructions::Instructions;
8use crate::compiler::meta::find_undeclared;
9use crate::compiler::parser::parse_expr;
10use crate::environment::Environment;
11use crate::error::Error;
12use crate::output::Output;
13use crate::value::Value;
14use crate::vm::Vm;
15
16/// A handle to a compiled expression.
17///
18/// An expression is created via the
19/// [`compile_expression`](Environment::compile_expression) method.  It provides
20/// a method to evaluate the expression and return the result as value object.
21/// This for instance can be used to evaluate simple expressions from user
22/// provided input to implement features such as dynamic filtering.
23///
24/// This is usually best paired with [`context`](crate::context!) to pass
25/// a single value to it.
26///
27/// # Example
28///
29/// ```rust
30/// # use minijinja::{Environment, context};
31/// let env = Environment::new();
32/// let expr = env.compile_expression("number > 10 and number < 20").unwrap();
33/// let rv = expr.eval(context!(number => 15)).unwrap();
34/// assert!(rv.is_true());
35/// ```
36pub struct Expression<'env, 'source> {
37    env: &'env Environment<'source>,
38    instr: ExpressionBacking<'source>,
39}
40
41enum ExpressionBacking<'source> {
42    Borrowed(Instructions<'source>),
43    #[cfg(feature = "loader")]
44    Owned(crate::loader::OwnedInstructions),
45}
46
47impl fmt::Debug for Expression<'_, '_> {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        f.debug_struct("Expression")
50            .field("env", &self.env)
51            .finish()
52    }
53}
54
55impl<'env, 'source> Expression<'env, 'source> {
56    pub(crate) fn new(
57        env: &'env Environment<'source>,
58        instructions: Instructions<'source>,
59    ) -> Expression<'env, 'source> {
60        Expression {
61            env,
62            instr: ExpressionBacking::Borrowed(instructions),
63        }
64    }
65
66    #[cfg(feature = "loader")]
67    pub(crate) fn new_owned(
68        env: &'env Environment<'source>,
69        instructions: crate::loader::OwnedInstructions,
70    ) -> Expression<'env, 'source> {
71        Expression {
72            env,
73            instr: ExpressionBacking::Owned(instructions),
74        }
75    }
76
77    fn instructions(&self) -> &Instructions<'_> {
78        match self.instr {
79            ExpressionBacking::Borrowed(ref x) => x,
80            #[cfg(feature = "loader")]
81            ExpressionBacking::Owned(ref x) => x.borrow_dependent(),
82        }
83    }
84
85    /// Evaluates the expression with some context.
86    ///
87    /// The result of the expression is returned as [`Value`].
88    pub fn eval<S: Serialize>(&self, ctx: S) -> Result<Value, Error> {
89        // reduce total amount of code faling under mono morphization into
90        // this function, and share the rest in _eval.
91        self._eval(Value::from_serialize(&ctx))
92    }
93
94    /// Returns a set of all undeclared variables in the expression.
95    ///
96    /// This works the same as
97    /// [`Template::undeclared_variables`](crate::Template::undeclared_variables).
98    pub fn undeclared_variables(&self, nested: bool) -> HashSet<String> {
99        match parse_expr(self.instructions().source()) {
100            Ok(expr) => find_undeclared(
101                &ast::Stmt::EmitExpr(ast::Spanned::new(
102                    ast::EmitExpr { expr },
103                    Default::default(),
104                )),
105                nested,
106            ),
107            Err(_) => HashSet::new(),
108        }
109    }
110
111    fn _eval(&self, root: Value) -> Result<Value, Error> {
112        Ok(ok!(Vm::new(self.env).eval(
113            self.instructions(),
114            root,
115            &BTreeMap::new(),
116            &mut Output::null(),
117            crate::AutoEscape::None,
118        ))
119        .0
120        .expect("expression evaluation did not leave value on stack"))
121    }
122}