1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
//! An API for evaluators that can be used to evaluate code fragments in the parsed template. use std::collections::HashMap; use std::any::{TypeId, Any}; use crate::ast::SyntaxNode; use crate::units::Position; /// Trait that describes an ability to evaluate code in template. /// /// Any struct that implements this trait should be able to interpret a given AST and evaluate it. /// AST are not constructed by the [Evaluator], they are consumed by it. /// pub trait Evaluator { fn evaluate(&self, syntax_node: &SyntaxNode, context: &mut Context) -> Result<String, SyntaxError>; } /// Context that is passed while evaluating an AST by an [Evaluator]. /// /// Contains all variables and states that can be shared between functions during evaluations. /// Functions should be free to store any state that will be shared between function invocations. /// /// Variables are stored in a map, keys are Strings and values are also Strings. /// State is stored in a heterogeneous container, which means that is accepts any struct. /// Structs in this store are identifier by their [TypeId]. /// pub struct Context { variables: HashMap<String, String>, states: HashMap<TypeId, Box<dyn Any>>, } impl Context { pub fn empty() -> Context { Context { variables: HashMap::new(), states: HashMap::new(), } } pub fn with_variables(variables: HashMap<String, String>) -> Context { Context { variables, states: HashMap::new(), } } pub fn set_variable(&mut self, name: &str, value: &str) { self.variables.insert(name.to_string(), value.to_string()); } pub fn get_variable(&self, name: &str) -> Option<&String> { self.variables.get(name) } pub fn save_state<T: Any>(&mut self, state: T) { let key = TypeId::of::<T>(); self.states.insert(key, Box::new(state)); } pub fn get_state<T: Any>(&self) -> Option<&T> { let key = TypeId::of::<T>(); self.states.get(&key) .map(|it| it.downcast_ref::<T>() .unwrap() ) } } /// An error that can happen during evaluation with full info about where and what happened. /// /// Contains additional fields that describe [EvaluationError]. /// This is a wrapper of [EvaluationError] that provides more info about the error so the user can see /// where the error is and how they can fix it. /// /// Unlike [EvaluationError], it can be created where a much broader context is available /// and when additional info can be supplied (eg. position of currently evaluated block). /// #[derive(Debug, PartialEq)] pub struct SyntaxError { pub relative_pos: Position, pub invocation_pos: Position, pub description: EvaluationError, } impl SyntaxError { /// Creates new [SyntaxError] with given [EvaluationError]. /// /// Preferred way to create [SyntaxError] when the position of error is unknown. /// When the relative position is known, please construct this using `at_pos`. pub fn new(error: EvaluationError) -> SyntaxError { SyntaxError { relative_pos: Position::Unknown, invocation_pos: Position::Unknown, description: error, } } /// Creates new [SyntaxError] with given [EvaluationError] at known relative position.. pub fn at_position(position: Position, error: EvaluationError) -> SyntaxError { SyntaxError { relative_pos: position, invocation_pos: Position::Unknown, description: error } } } /// An error that can happen during evaluation. /// /// Used by an [Evaluator] or [Function] to indicate that something bad happened. /// This is a very short description of what is happening - it can be created in a much narrow context, /// and then wrapped by an [SyntaxError] in a context where more info can be supplied. /// /// It is intended to be used in cases when you don't want to pass all unused data just in case /// to be able to create an [SyntaxError] if it happens. It contains only the most necessary /// info about the error. /// #[derive(Debug, PartialEq)] pub enum EvaluationError { UnexpectedElements { last_expected: Option<SyntaxNode>, unexpected_elements: Vec<SyntaxNode>, }, UnknownSymbol { symbol: String, }, InvalidArguments { description: Option<String>, arguments: Vec<SyntaxNode>, }, InvalidValues { description: Option<String>, values: Vec<String>, }, } /// A function that can be used to add features to the template. /// /// Any struct that implements this trait adds additional features that can additional *function* /// which will be available to anyone using given Evaluator that has it installed. /// /// For example, you might implement custom function for date parsing etc etc. /// /// During evaluation, an original Evaluator will be supplied to enable parameter evaluation. /// Parameter evaluation with a supplied Evaluator is optional and a given [Function] can evaluate /// them independently. pub trait Function { fn evaluate(&self, evaluator: &dyn Evaluator, parameters: &[SyntaxNode], context: &mut Context) -> Result<String, SyntaxError>; } /// Impl for [Function] that allows to use lambda as a function in [Evaluator]. /// /// Allows to use `Fn(&dyn Evaluator, &[SyntaxNode], &mut Context) -> Result<String, SyntaxError>` as [Function] in [Evaluator]. /// For other implementations, see [crate::functions]. /// /// Example: /// ``` /// use rubble_templates_core::evaluator::{Evaluator, Function, SyntaxError, Context}; /// use std::collections::HashMap; /// use rubble_templates_core::ast::SyntaxNode; /// /// fn plus_function(evaluator: &dyn Evaluator, parameters: &[SyntaxNode], context: &mut Context) -> Result<String, SyntaxError> { /// Ok( /// parameters.iter() /// .map(|node| /// evaluator.evaluate(node, context).unwrap().parse::<i32>().unwrap() /// ) /// .sum::<i32>() /// .to_string() /// ) /// } /// /// let mut functions: HashMap<String, Box<dyn Function>> = HashMap::new(); /// functions.insert("plus".to_string(), Box::new(plus_function)); // will be treated as Box<dyn Function> /// ``` impl<F> Function for F where F: Fn(&dyn Evaluator, &[SyntaxNode], &mut Context) -> Result<String, SyntaxError> { fn evaluate(&self, evaluator: &dyn Evaluator, parameters: &[SyntaxNode], context: &mut Context) -> Result<String, SyntaxError> { self(evaluator, ¶meters, context) } }