fastn_resolved/evalexpr/context/
mod.rs

1//! A context defines methods to retrieve variable values and call functions for literals in an expression tree.
2//! If mutable, it also allows to assign to variables.
3//!
4//! This crate implements two basic variants, the `EmptyContext`, that returns `None` for each identifier and cannot be manipulated, and the `HashMapContext`, that stores its mappings in hash maps.
5//! The HashMapContext is type-safe and returns an error if the user tries to assign a value of a different type than before to an identifier.
6
7use std::{collections::HashMap, iter};
8
9use fastn_resolved::evalexpr::{
10    function::Function,
11    value::{value_type::ValueType, Value},
12    EvalexprError, EvalexprResult,
13};
14
15mod predefined;
16
17/// An immutable context.
18pub trait Context {
19    /// Returns the value that is linked to the given identifier.
20    fn get_value(&self, identifier: &str) -> Option<&Value>;
21
22    /// Calls the function that is linked to the given identifier with the given argument.
23    /// If no function with the given identifier is found, this method returns `EvalexprError::FunctionIdentifierNotFound`.
24    fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value>;
25}
26
27/// A context that allows to assign to variables.
28pub trait ContextWithMutableVariables: Context {
29    /// Sets the variable with the given identifier to the given value.
30    fn set_value(&mut self, _identifier: String, _value: Value) -> EvalexprResult<()> {
31        Err(EvalexprError::ContextNotMutable)
32    }
33}
34
35/// A context that allows to assign to function identifiers.
36pub trait ContextWithMutableFunctions: Context {
37    /// Sets the function with the given identifier to the given function.
38    fn set_function(&mut self, _identifier: String, _function: Function) -> EvalexprResult<()> {
39        Err(EvalexprError::ContextNotMutable)
40    }
41}
42
43/// A context that allows to iterate over its variable names with their values.
44///
45/// **Note:** this trait will change after GATs are stabilised, because then we can get rid of the lifetime in the trait definition.
46pub trait IterateVariablesContext<'a> {
47    /// The iterator type for iterating over variable name-value pairs.
48    type VariableIterator: 'a + Iterator<Item = (String, Value)>;
49    /// The iterator type for iterating over variable names.
50    type VariableNameIterator: 'a + Iterator<Item = String>;
51
52    /// Returns an iterator over pairs of variable names and values.
53    fn iter_variables(&'a self) -> Self::VariableIterator;
54
55    /// Returns an iterator over variable names.
56    fn iter_variable_names(&'a self) -> Self::VariableNameIterator;
57}
58
59/*/// A context that allows to retrieve functions programmatically.
60pub trait GetFunctionContext: Context {
61    /// Returns the function that is linked to the given identifier.
62    ///
63    /// This might not be possible for all functions, as some might be hard-coded.
64    /// In this case, a special error variant should be returned (Not yet implemented).
65    fn get_function(&self, identifier: &str) -> Option<&Function>;
66}*/
67
68/// A context that returns `None` for each identifier.
69#[derive(Debug, Default)]
70pub struct EmptyContext;
71
72impl Context for EmptyContext {
73    fn get_value(&self, _identifier: &str) -> Option<&Value> {
74        None
75    }
76
77    fn call_function(&self, identifier: &str, _argument: &Value) -> EvalexprResult<Value> {
78        Err(EvalexprError::FunctionIdentifierNotFound(
79            identifier.to_string(),
80        ))
81    }
82}
83
84impl<'a> IterateVariablesContext<'a> for EmptyContext {
85    type VariableIterator = iter::Empty<(String, Value)>;
86    type VariableNameIterator = iter::Empty<String>;
87
88    fn iter_variables(&self) -> Self::VariableIterator {
89        iter::empty()
90    }
91
92    fn iter_variable_names(&self) -> Self::VariableNameIterator {
93        iter::empty()
94    }
95}
96
97/// A context that stores its mappings in hash maps.
98///
99/// *Value and function mappings are stored independently, meaning that there can be a function and a value with the same identifier.*
100///
101/// This context is type-safe, meaning that an identifier that is assigned a value of some type once cannot be assigned a value of another type.
102#[derive(Clone, Debug, Default)]
103pub struct HashMapContext {
104    variables: HashMap<String, Value>,
105    functions: HashMap<String, Function>,
106}
107
108impl HashMapContext {
109    /// Constructs a `HashMapContext` with no mappings.
110    pub fn new() -> Self {
111        Default::default()
112    }
113}
114
115impl Context for HashMapContext {
116    fn get_value(&self, identifier: &str) -> Option<&Value> {
117        self.variables.get(identifier)
118    }
119
120    fn call_function(&self, identifier: &str, argument: &Value) -> EvalexprResult<Value> {
121        if let Some(function) = self.functions.get(identifier) {
122            function.call(argument)
123        } else {
124            Err(EvalexprError::FunctionIdentifierNotFound(
125                identifier.to_string(),
126            ))
127        }
128    }
129}
130
131impl ContextWithMutableVariables for HashMapContext {
132    fn set_value(&mut self, identifier: String, value: Value) -> EvalexprResult<()> {
133        if let Some(existing_value) = self.variables.get_mut(&identifier) {
134            if ValueType::from(&existing_value) == ValueType::from(&value) {
135                *existing_value = value;
136                return Ok(());
137            } else {
138                return Err(EvalexprError::expected_type(existing_value, value));
139            }
140        }
141
142        // Implicit else, because `self.variables` and `identifier` are not unborrowed in else
143        self.variables.insert(identifier, value);
144        Ok(())
145    }
146}
147
148impl ContextWithMutableFunctions for HashMapContext {
149    fn set_function(&mut self, identifier: String, function: Function) -> EvalexprResult<()> {
150        self.functions.insert(identifier, function);
151        Ok(())
152    }
153}
154
155impl<'a> IterateVariablesContext<'a> for HashMapContext {
156    type VariableIterator = std::iter::Map<
157        std::collections::hash_map::Iter<'a, String, Value>,
158        fn((&String, &Value)) -> (String, Value),
159    >;
160    type VariableNameIterator =
161        std::iter::Cloned<std::collections::hash_map::Keys<'a, String, Value>>;
162
163    fn iter_variables(&'a self) -> Self::VariableIterator {
164        self.variables
165            .iter()
166            .map(|(string, value)| (string.clone(), value.clone()))
167    }
168
169    fn iter_variable_names(&'a self) -> Self::VariableNameIterator {
170        self.variables.keys().cloned()
171    }
172}
173
174/// This macro provides a convenient syntax for creating a static context.
175///
176/// # Examples
177///
178/// ```rust
179/// use fastn_resolved::evalexpr::*;
180///
181/// let ctx = fastn_resolved::context_map! {
182///     "x" => 8,
183///     "f" => Function::new(|_| Ok(42.into()))
184/// }.unwrap(); // Do proper error handling here
185///
186/// assert_eq!(eval_with_context("x + f()", &ctx), Ok(50.into()));
187/// ```
188#[macro_export]
189macro_rules! context_map {
190    // Termination (allow missing comma at the end of the argument list)
191    ( ($ctx:expr) $k:expr => Function::new($($v:tt)*) ) =>
192        { $crate::context_map!(($ctx) $k => Function::new($($v)*),) };
193    ( ($ctx:expr) $k:expr => $v:expr ) =>
194        { $crate::context_map!(($ctx) $k => $v,)  };
195    // Termination
196    ( ($ctx:expr) ) => { Ok(()) };
197
198    // The user has to specify a literal 'Function::new' in order to create a function
199    ( ($ctx:expr) $k:expr => Function::new($($v:tt)*) , $($tt:tt)*) => {{
200        $crate::evalexpr::ContextWithMutableFunctions::set_function($ctx, $k.into(), $crate::evalexpr::Function::new($($v)*))
201            .and($crate::context_map!(($ctx) $($tt)*))
202    }};
203    // add a value, and chain the eventual error with the ones in the next values
204    ( ($ctx:expr) $k:expr => $v:expr , $($tt:tt)*) => {{
205        $crate::evalexpr::ContextWithMutableVariables::set_value($ctx, $k.into(), $v.into())
206            .and($crate::context_map!(($ctx) $($tt)*))
207    }};
208
209    // Create a context, then recurse to add the values in it
210    ( $($tt:tt)* ) => {{
211        let mut context = $crate::evalexpr::HashMapContext::new();
212        $crate::context_map!((&mut context) $($tt)*)
213            .map(|_| context)
214    }};
215}