Skip to main content

expr/
eval.rs

1use crate::ast::node::Node;
2use crate::ast::program::Program;
3use crate::functions::{array, json, string, ExprCall, Function};
4use crate::{bail, Context, Result, Value};
5use crate::parser::compile;
6use indexmap::IndexMap;
7use once_cell::sync::Lazy;
8use std::fmt;
9use std::fmt::{Debug, Formatter};
10
11/// Run a compiled expr program, using the default environment
12pub fn run(program: Program, ctx: &Context) -> Result<Value> {
13    DEFAULT_ENVIRONMENT.run(program, ctx)
14}
15
16/// Compile and run an expr program in one step, using the default environment.
17///
18/// Example:
19/// ```
20/// use expr::{Context, eval};
21/// let ctx = Context::default();
22/// assert_eq!(eval("1 + 2", &ctx).unwrap().to_string(), "3");
23/// ```
24pub fn eval(code: &str, ctx: &Context) -> Result<Value> {
25    DEFAULT_ENVIRONMENT.eval(code, ctx)
26}
27
28/// Struct containing custom environment setup for expr evaluation (e.g. custom
29/// function definitions)
30///
31/// Example:
32///
33/// ```
34/// use expr::{Context, Environment, Value};
35/// let mut env = Environment::new();
36/// let ctx = Context::default();
37/// env.add_function("add", |c| {
38///   let mut sum = 0;
39///     for arg in c.args {
40///       if let Value::Number(n) = arg {
41///         sum += n;
42///        } else {
43///          panic!("Invalid argument: {arg:?}");
44///        }
45///     }
46///   Ok(sum.into())
47/// });
48/// assert_eq!(env.eval("add(1, 2, 3)", &ctx).unwrap().to_string(), "6");
49/// ```
50#[derive(Default)]
51pub struct Environment<'a> {
52    pub(crate) functions: IndexMap<String, Function<'a>>,
53}
54
55impl Debug for Environment<'_> {
56    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
57        f.debug_struct("ExprEnvironment").finish()
58    }
59}
60
61impl<'a> Environment<'a> {
62    /// Create a new environment with default set of functions
63    pub fn new() -> Self {
64        let mut p = Self {
65            functions: IndexMap::new(),
66        };
67        string::add_string_functions(&mut p);
68        array::add_array_functions(&mut p);
69        json::add_json_functions(&mut p);
70        p
71    }
72
73    /// Add a function for expr programs to call
74    ///
75    /// Example:
76    /// ```
77    /// use expr::{Context, Environment, Value};
78    /// let mut env = Environment::new();
79    /// let ctx = Context::default();
80    /// env.add_function("add", |c| {
81    ///   let mut sum = 0;
82    ///     for arg in c.args {
83    ///       if let Value::Number(n) = arg {
84    ///         sum += n;
85    ///        } else {
86    ///          panic!("Invalid argument: {arg:?}");
87    ///        }
88    ///     }
89    ///   Ok(sum.into())
90    /// });
91    /// assert_eq!(env.eval("add(1, 2, 3)", &ctx).unwrap().to_string(), "6");
92    /// ```
93    pub fn add_function<F>(&mut self, name: &str, f: F)
94    where
95        F: Fn(ExprCall) -> Result<Value> + 'a + Sync + Send,
96    {
97        self.functions.insert(name.to_string(), Box::new(f));
98    }
99
100    /// Run a compiled expr program
101    pub fn run(&self, program: Program, ctx: &Context) -> Result<Value> {
102        let mut ctx = ctx.clone();
103        ctx.insert("$env".to_string(), Value::Map(ctx.0.clone()));
104        for (id, expr) in program.lines {
105            ctx.insert(id, self.eval_expr(&ctx, expr)?);
106        }
107        self.eval_expr(&ctx, program.expr)
108    }
109
110    /// Compile and run an expr program in one step
111    ///
112    /// Example:
113    /// ```
114    /// use std::collections::HashMap;
115    /// use expr::{Context, Environment};
116    /// let env = Environment::new();
117    /// let ctx = Context::default();
118    /// assert_eq!(env.eval("1 + 2", &ctx).unwrap().to_string(), "3");
119    /// ```
120    pub fn eval(&self, code: &str, ctx: &Context) -> Result<Value> {
121        let program = compile(code)?;
122        self.run(program, ctx)
123    }
124
125    pub fn eval_expr(&self, ctx: &Context, node: Node) -> Result<Value> {
126        let value = match node {
127            Node::Value(value) => value,
128            Node::Ident(id) => {
129                if let Some(value) = ctx.get(&id) {
130                    value.clone()
131                } else if let Some(item) = ctx
132                    .get("#")
133                    .and_then(|o| o.as_map())
134                    .and_then(|m| m.get(&id))
135                {
136                    item.clone()
137                } else {
138                    bail!("unknown variable: {id}")
139                }
140            }
141            Node::Func {
142                ident,
143                args,
144                predicate,
145            } => {
146                let args = args
147                    .into_iter()
148                    .map(|e| self.eval_expr(ctx, e))
149                    .collect::<Result<_>>()?;
150                self.eval_func(ctx, ident, args, predicate.map(|l| *l))?
151            },
152            Node::Operation {
153                left,
154                operator,
155                right,
156            } => self.eval_operator(ctx, operator, *left, *right)?,
157            Node::Unary { operator, node } => self.eval_unary_operator(ctx, operator, *node)?,
158            Node::Postfix { operator, node } => self.eval_postfix_operator(ctx, operator, *node)?,
159            Node::Array(a) => Value::Array(
160                a.into_iter()
161                    .map(|e| self.eval_expr(ctx, e))
162                    .collect::<Result<_>>()?,
163            ), // node => bail!("unexpected node: {node:?}"),
164            Node::Range(start, end) => match (self.eval_expr(ctx, *start)?, self.eval_expr(ctx, *end)?) {
165                (Value::Number(start), Value::Number(end)) => {
166                    Value::Array((start..=end).map(Value::Number).collect())
167                }
168                (start, end) => bail!("invalid range: {start:?}..{end:?}"),
169            }
170        };
171        Ok(value)
172    }
173}
174
175pub(crate) static DEFAULT_ENVIRONMENT: Lazy<Environment> = Lazy::new(|| {
176    Environment::new()
177});