mini_builder_rs/evaluator/
evaluation_context.rs

1use std::collections::HashMap;
2
3use crate::value::Value;
4
5use super::{Evaluate, ValueFunction, Variables};
6
7#[derive(Clone)]
8/// Specifies which file is using the evaluation context.
9pub enum ContextLocation {
10	/// (name of the partial, is it a source)
11	SourceOrTemplate(String, bool),
12	/// Variables file
13	Variables,
14}
15
16impl ContextLocation {
17	pub fn partial(name: impl ToString, is_source: bool) -> Self {
18		Self::SourceOrTemplate(name.to_string(), is_source)
19	}
20
21	pub fn source(name: impl ToString) -> Self {
22		Self::partial(name, true)
23	}
24
25	pub fn template(name: impl ToString) -> Self {
26		Self::partial(name, false)
27	}
28}
29
30/// Specifies under which circumstances the evaluator should log a warning.
31#[derive(Clone, Copy)]
32pub struct EvaluationContextWarnings {
33	pub max_depth_reached: bool,
34	pub variable_not_found: bool,
35	pub function_not_found: bool,
36	pub evaluator_not_found: bool,
37}
38
39impl std::default::Default for EvaluationContextWarnings {
40	fn default() -> Self {
41		Self {
42			max_depth_reached: true,
43			variable_not_found: true,
44			function_not_found: true,
45			evaluator_not_found: true,
46		}
47	}
48}
49
50/// Context that is used in the evaluation process of a block.
51pub struct EvaluationContext<'c, E: Evaluate> {
52	// variables
53	global_variables: &'c Variables,
54	local_variables: Vec<Variables>,
55	// functions
56	evaluators: &'c HashMap<String, E>,
57	functions: &'c HashMap<String, ValueFunction>,
58	// recursion
59	depth: usize,
60	max_depth: usize,
61	// warnings
62	warnings: EvaluationContextWarnings,
63	location_stack: Vec<ContextLocation>,
64}
65
66impl<'c, E: Evaluate> EvaluationContext<'c, E> {
67	pub fn new(
68		global_variables: &'c Variables,
69		mut local_variables: Vec<Variables>,
70		builders: &'c HashMap<String, E>,
71		functions: &'c HashMap<String, Box<dyn Fn(&[Value]) -> Value + 'static>>,
72		max_depth: usize,
73		warnings: EvaluationContextWarnings,
74		context_location: ContextLocation,
75	) -> Self {
76		if local_variables.is_empty() {
77			local_variables.push(HashMap::new());
78		}
79
80		Self {
81			global_variables,
82			local_variables,
83			evaluators: builders,
84			functions,
85			depth: 0,
86			max_depth,
87			warnings,
88			location_stack: vec![context_location],
89		}
90	}
91
92	pub fn spawn_new(
93		&self,
94		local_variables: Vec<Variables>,
95		context_location: ContextLocation,
96	) -> Result<Self, ()> {
97		let depth = self.depth + 1;
98		if depth == self.max_depth {
99			if self.warnings.max_depth_reached {
100				let trace = self.trace();
101				let tail_len = 5.min(trace.len());
102				let trace = &trace[trace.len() - tail_len..];
103				log::warn!("Max depth reached: trace tail: {:?}", trace)
104			}
105			return Err(());
106		}
107
108		let mut location_stack = self.location_stack.clone();
109		location_stack.push(context_location);
110
111		Ok(Self {
112			global_variables: self.global_variables,
113			local_variables,
114			evaluators: self.evaluators,
115			functions: self.functions,
116			depth,
117			max_depth: self.max_depth,
118			warnings: self.warnings.clone(),
119			location_stack,
120		})
121	}
122
123	fn trace(&self) -> Vec<String> {
124		self.location_stack
125			.iter()
126			.map(|location| match location {
127				ContextLocation::SourceOrTemplate(name, is_source) => {
128					if *is_source {
129						format!("source '{name}'")
130					} else {
131						format!("template '{name}'")
132					}
133				}
134				ContextLocation::Variables => format!("variables"),
135			})
136			.collect()
137	}
138
139	pub fn get_evaluator(&self, name: &str) -> Option<&'c E> {
140		let ret = self.evaluators.get(name);
141
142		// optionally warn if no evaluator was found
143		if self.warnings.evaluator_not_found {
144			if ret.is_none() {
145				log::warn!(
146					"No evaluator named `{name}` was found. Trace: {:?}.",
147					self.trace()
148				);
149			}
150		}
151
152		ret
153	}
154
155	pub fn get_function(&self, name: &str) -> Option<&'c ValueFunction> {
156		let ret = self.functions.get(name);
157
158		// if no variable with the given name was found, return `None` and warn
159		if self.warnings.function_not_found {
160			if ret.is_none() {
161				log::warn!(
162					"No function named `{name}` was found. Trace: {:?}.",
163					self.trace()
164				);
165			}
166		}
167
168		ret
169	}
170
171	pub fn get_variable_value(&self, name: &str) -> Option<Value> {
172		// first, search the local variables from last to first (inner to outer scopes)
173		for variables in self.local_variables.iter().rev() {
174			let v = variables.get(name);
175			if v.is_some() {
176				return v.cloned();
177			}
178		}
179
180		// then, search the global variables
181		let v = self.global_variables.get(name);
182		if v.is_some() {
183			return v.cloned();
184		}
185
186		// if no variable with the given name was found, return `None` and warn
187		if self.warnings.variable_not_found {
188			log::warn!(
189				"No variable named `{name}` was found. Trace: {:?}.",
190				self.trace()
191			);
192		}
193
194		None
195	}
196
197	pub fn push_variables(&mut self, variables: Variables) {
198		self.local_variables.push(variables);
199	}
200
201	pub fn pop_variables(&mut self) {
202		self.local_variables.pop();
203	}
204
205	pub fn assign_local_variable(&mut self, name: &str, value: Value) {
206		self.local_variables
207			.last_mut()
208			.unwrap()
209			.insert(name.to_string(), value);
210	}
211}