Skip to main content

expr/
eval.rs

1use crate::ast::node::Node;
2use crate::ast::program::Program;
3use crate::context::ContextScope;
4use crate::functions::{
5    array, bitwise, collection, convert, json, misc, number, string, ExprCall, Function,
6};
7use crate::parser::compile;
8use crate::{bail, ContextProvider, Result, Value};
9use indexmap::IndexMap;
10use std::sync::LazyLock;
11use std::fmt;
12use std::fmt::{Debug, Formatter};
13
14/// Run a compiled expr program, using the default environment
15pub fn run(program: &Program, ctx: &dyn ContextProvider) -> Result<Value> {
16    DEFAULT_ENVIRONMENT.run(program, ctx)
17}
18
19/// Compile and run an expr program in one step, using the default environment.
20///
21/// Example:
22/// ```
23/// use expr::{Context, eval};
24/// let ctx = Context::default();
25/// assert_eq!(eval("1 + 2", &ctx).unwrap().to_string(), "3");
26/// ```
27pub fn eval(code: &str, ctx: &dyn ContextProvider) -> Result<Value> {
28    DEFAULT_ENVIRONMENT.eval(code, ctx)
29}
30
31/// Struct containing custom environment setup for expr evaluation (e.g. custom
32/// function definitions)
33///
34/// Example:
35///
36/// ```
37/// use expr::{Context, Environment, Value};
38/// let mut env = Environment::new();
39/// let ctx = Context::default();
40/// env.add_function("add", |c| {
41///   let mut sum = 0;
42///     for arg in c.args {
43///       if let Value::Integer(n) = arg {
44///         sum += n;
45///        } else {
46///          panic!("Invalid argument: {arg:?}");
47///        }
48///     }
49///   Ok(sum.into())
50/// });
51/// assert_eq!(env.eval("add(1, 2, 3)", &ctx).unwrap().to_string(), "6");
52/// ```
53pub struct Environment<'a> {
54    pub(crate) functions: IndexMap<String, Function<'a>>,
55}
56
57impl Debug for Environment<'_> {
58    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
59        f.debug_struct("ExprEnvironment").finish()
60    }
61}
62
63impl Default for Environment<'_> {
64    fn default() -> Self {
65        Self::new()
66    }
67}
68
69impl<'a> Environment<'a> {
70    /// Create a new environment with default set of functions
71    pub fn new() -> Self {
72        let mut p = Self {
73            functions: IndexMap::new(),
74        };
75        string::add_string_functions(&mut p);
76        #[cfg(feature = "temporal")]
77        crate::functions::temporal::add_temporal_functions(&mut p);
78        #[cfg(not(feature = "temporal"))]
79        crate::functions::add_disabled_functions(
80            &mut p,
81            "temporal",
82            &["now", "date", "duration", "timezone"],
83        );
84        array::add_array_functions(&mut p);
85        bitwise::add_bitwise_functions(&mut p);
86        collection::add_collection_functions(&mut p);
87        convert::add_conversion_functions(&mut p);
88        json::add_json_functions(&mut p);
89        misc::add_misc_functions(&mut p);
90        number::add_number_functions(&mut p);
91        p
92    }
93
94    /// Add a function for expr programs to call
95    ///
96    /// Example:
97    /// ```
98    /// use expr::{Context, Environment, Value};
99    /// let mut env = Environment::new();
100    /// let ctx = Context::default();
101    /// env.add_function("add", |c| {
102    ///   let mut sum = 0;
103    ///     for arg in c.args {
104    ///       if let Value::Integer(n) = arg {
105    ///         sum += n;
106    ///        } else {
107    ///          panic!("Invalid argument: {arg:?}");
108    ///        }
109    ///     }
110    ///   Ok(sum.into())
111    /// });
112    /// assert_eq!(env.eval("add(1, 2, 3)", &ctx).unwrap().to_string(), "6");
113    /// ```
114    pub fn add_function<F>(&mut self, name: &str, f: F)
115    where
116        F: Fn(ExprCall) -> Result<Value> + 'a + Sync + Send,
117    {
118        self.functions.insert(name.to_string(), Box::new(f));
119    }
120
121    /// Run a compiled expr program
122    pub fn run(&self, program: &Program, ctx: &dyn ContextProvider) -> Result<Value> {
123        let mut ctx = ContextScope::new(ctx);
124        for (id, expr) in &program.lines {
125            ctx.insert(id, self.eval_expr(&ctx, expr)?);
126        }
127        self.eval_expr(&ctx, &program.expr)
128    }
129
130    pub(crate) fn run_with_binding(
131        &self,
132        program: &Program,
133        ctx: &dyn ContextProvider,
134        key: &str,
135        value: Value,
136    ) -> Result<Value> {
137        self.run_with_bindings(program, ctx, [(key, value)])
138    }
139
140    pub(crate) fn run_with_bindings<'b>(
141        &self,
142        program: &Program,
143        ctx: &dyn ContextProvider,
144        bindings: impl IntoIterator<Item = (&'b str, Value)>,
145    ) -> Result<Value> {
146        let mut scope = ContextScope::new(ctx);
147        for (key, value) in bindings {
148            scope.insert(key, value);
149        }
150        self.run(program, &scope)
151    }
152
153    /// Compile and run an expr program in one step
154    ///
155    /// Example:
156    /// ```
157    /// use std::collections::HashMap;
158    /// use expr::{Context, Environment};
159    /// let env = Environment::new();
160    /// let ctx = Context::default();
161    /// assert_eq!(env.eval("1 + 2", &ctx).unwrap().to_string(), "3");
162    /// ```
163    pub fn eval(&self, code: &str, ctx: &dyn ContextProvider) -> Result<Value> {
164        let program = compile(code)?;
165        self.run(&program, ctx)
166    }
167
168    pub fn eval_expr(&self, ctx: &dyn ContextProvider, node: &Node) -> Result<Value> {
169        let value = match node {
170            Node::Value(value) => value.clone(),
171            Node::Ident(id) => {
172                if id == "$env" {
173                    Value::Map(ctx.environment().0)
174                } else if let Some(value) = ctx.get(id) {
175                    value.clone()
176                } else if let Some(item) = ctx
177                    .get("#")
178                    .and_then(|o| o.as_map())
179                    .and_then(|m| m.get(id))
180                {
181                    item.clone()
182                } else {
183                    bail!("unknown variable: {id}")
184                }
185            }
186            Node::Func {
187                ident,
188                args,
189                predicate,
190            } => {
191                let args = args
192                    .iter()
193                    .map(|e| self.eval_expr(ctx, e))
194                    .collect::<Result<_>>()?;
195                self.eval_func(ctx, ident, args, predicate.as_deref())?
196            }
197            #[cfg(feature = "regex")]
198            Node::Operation {
199                left,
200                operator,
201                right,
202                compiled_regex,
203            } => self.eval_operator(ctx, operator, left, right, compiled_regex.as_ref())?,
204            #[cfg(not(feature = "regex"))]
205            Node::Operation {
206                left,
207                operator,
208                right,
209            } => self.eval_operator(ctx, operator, left, right)?,
210            Node::Unary { operator, node } => self.eval_unary_operator(ctx, operator, node)?,
211            Node::Postfix { operator, node } => self.eval_postfix_operator(ctx, operator, node)?,
212            Node::Array(a) => Value::Array(
213                a.iter()
214                    .map(|e| self.eval_expr(ctx, e))
215                    .collect::<Result<_>>()?,
216            ), // node => bail!("unexpected node: {node:?}"),
217            Node::Range(start, end) => {
218                match (self.eval_expr(ctx, start)?, self.eval_expr(ctx, end)?) {
219                    (Value::Integer(start), Value::Integer(end)) => {
220                        Value::Array((start..=end).map(Value::Integer).collect())
221                    }
222                    (start, end) => bail!("invalid range: {start:?}..{end:?}"),
223                }
224            }
225            Node::Conditional { condition, consequent, alternative } => {
226                match self.eval_expr(ctx, condition)? {
227                    Value::Bool(true) => self.run(consequent, ctx)?,
228                    Value::Bool(false) => self.run(alternative, ctx)?,
229                    value => bail!("Invalid condition for if: {value:?}"),
230                }
231            }
232        };
233        Ok(value)
234    }
235}
236
237pub(crate) static DEFAULT_ENVIRONMENT: LazyLock<Environment> = LazyLock::new(Environment::new);