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, temporal, ExprCall, Function,
6};
7use crate::parser::compile;
8use crate::{bail, ContextProvider, Result, Value};
9use indexmap::IndexMap;
10use once_cell::sync::Lazy;
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        temporal::add_temporal_functions(&mut p);
77        array::add_array_functions(&mut p);
78        bitwise::add_bitwise_functions(&mut p);
79        collection::add_collection_functions(&mut p);
80        convert::add_conversion_functions(&mut p);
81        json::add_json_functions(&mut p);
82        misc::add_misc_functions(&mut p);
83        number::add_number_functions(&mut p);
84        p
85    }
86
87    /// Add a function for expr programs to call
88    ///
89    /// Example:
90    /// ```
91    /// use expr::{Context, Environment, Value};
92    /// let mut env = Environment::new();
93    /// let ctx = Context::default();
94    /// env.add_function("add", |c| {
95    ///   let mut sum = 0;
96    ///     for arg in c.args {
97    ///       if let Value::Integer(n) = arg {
98    ///         sum += n;
99    ///        } else {
100    ///          panic!("Invalid argument: {arg:?}");
101    ///        }
102    ///     }
103    ///   Ok(sum.into())
104    /// });
105    /// assert_eq!(env.eval("add(1, 2, 3)", &ctx).unwrap().to_string(), "6");
106    /// ```
107    pub fn add_function<F>(&mut self, name: &str, f: F)
108    where
109        F: Fn(ExprCall) -> Result<Value> + 'a + Sync + Send,
110    {
111        self.functions.insert(name.to_string(), Box::new(f));
112    }
113
114    /// Run a compiled expr program
115    pub fn run(&self, program: &Program, ctx: &dyn ContextProvider) -> Result<Value> {
116        let mut ctx = ContextScope::new(ctx);
117        for (id, expr) in &program.lines {
118            ctx.insert(id, self.eval_expr(&ctx, expr)?);
119        }
120        self.eval_expr(&ctx, &program.expr)
121    }
122
123    pub(crate) fn run_with_binding(
124        &self,
125        program: &Program,
126        ctx: &dyn ContextProvider,
127        key: &str,
128        value: Value,
129    ) -> Result<Value> {
130        self.run_with_bindings(program, ctx, [(key, value)])
131    }
132
133    pub(crate) fn run_with_bindings<'b>(
134        &self,
135        program: &Program,
136        ctx: &dyn ContextProvider,
137        bindings: impl IntoIterator<Item = (&'b str, Value)>,
138    ) -> Result<Value> {
139        let mut scope = ContextScope::new(ctx);
140        for (key, value) in bindings {
141            scope.insert(key, value);
142        }
143        self.run(program, &scope)
144    }
145
146    /// Compile and run an expr program in one step
147    ///
148    /// Example:
149    /// ```
150    /// use std::collections::HashMap;
151    /// use expr::{Context, Environment};
152    /// let env = Environment::new();
153    /// let ctx = Context::default();
154    /// assert_eq!(env.eval("1 + 2", &ctx).unwrap().to_string(), "3");
155    /// ```
156    pub fn eval(&self, code: &str, ctx: &dyn ContextProvider) -> Result<Value> {
157        let program = compile(code)?;
158        self.run(&program, ctx)
159    }
160
161    pub fn eval_expr(&self, ctx: &dyn ContextProvider, node: &Node) -> Result<Value> {
162        let value = match node {
163            Node::Value(value) => value.clone(),
164            Node::Ident(id) => {
165                if id == "$env" {
166                    Value::Map(ctx.environment().0)
167                } else if let Some(value) = ctx.get(id) {
168                    value.clone()
169                } else if let Some(item) = ctx
170                    .get("#")
171                    .and_then(|o| o.as_map())
172                    .and_then(|m| m.get(id))
173                {
174                    item.clone()
175                } else {
176                    bail!("unknown variable: {id}")
177                }
178            }
179            Node::Func {
180                ident,
181                args,
182                predicate,
183            } => {
184                let args = args
185                    .iter()
186                    .map(|e| self.eval_expr(ctx, e))
187                    .collect::<Result<_>>()?;
188                self.eval_func(ctx, ident, args, predicate.as_deref())?
189            }
190            Node::Operation {
191                left,
192                operator,
193                right,
194                compiled_regex,
195            } => self.eval_operator(ctx, operator, left, right, compiled_regex.as_ref())?,
196            Node::Unary { operator, node } => self.eval_unary_operator(ctx, operator, node)?,
197            Node::Postfix { operator, node } => self.eval_postfix_operator(ctx, operator, node)?,
198            Node::Array(a) => Value::Array(
199                a.iter()
200                    .map(|e| self.eval_expr(ctx, e))
201                    .collect::<Result<_>>()?,
202            ), // node => bail!("unexpected node: {node:?}"),
203            Node::Range(start, end) => {
204                match (self.eval_expr(ctx, start)?, self.eval_expr(ctx, end)?) {
205                    (Value::Integer(start), Value::Integer(end)) => {
206                        Value::Array((start..=end).map(Value::Integer).collect())
207                    }
208                    (start, end) => bail!("invalid range: {start:?}..{end:?}"),
209                }
210            }
211            Node::Conditional { condition, consequent, alternative } => {
212                match self.eval_expr(ctx, condition)? {
213                    Value::Bool(true) => self.run(consequent, ctx)?,
214                    Value::Bool(false) => self.run(alternative, ctx)?,
215                    value => bail!("Invalid condition for if: {value:?}"),
216                }
217            }
218        };
219        Ok(value)
220    }
221}
222
223pub(crate) static DEFAULT_ENVIRONMENT: Lazy<Environment> = Lazy::new(Environment::new);