minijinja/vm/state.rs
1use std::borrow::Cow;
2use std::cell::Cell;
3use std::collections::{BTreeMap, BTreeSet};
4use std::fmt;
5use std::sync::{Arc, Mutex};
6
7use crate::compiler::instructions::Instructions;
8use crate::environment::Environment;
9use crate::error::{Error, ErrorKind};
10use crate::output::Output;
11use crate::template::Template;
12use crate::utils::{AutoEscape, UndefinedBehavior};
13use crate::value::{ArgType, Object, Value};
14use crate::vm::context::Context;
15
16#[cfg(feature = "fuel")]
17use crate::vm::fuel::FuelTracker;
18
19/// When macros are used, the state carries an `id` counter. Whenever a state is
20/// created, the counter is incremented. This exists because macros can keep a reference
21/// to instructions from another state by index. Without this counter it would
22/// be possible for a macro to be called with a different state (different id)
23/// which mean we likely panic.
24#[cfg(feature = "macros")]
25static STATE_ID: std::sync::atomic::AtomicIsize = std::sync::atomic::AtomicIsize::new(0);
26
27/// Provides access to the current execution state of the engine.
28///
29/// A read only reference is passed to filter functions and similar objects to
30/// allow limited interfacing with the engine. The state is useful to look up
31/// information about the engine in filter, test or global functions. It not
32/// only provides access to the template environment but also the context
33/// variables of the engine, the current auto escaping behavior as well as the
34/// auto escape flag.
35///
36/// In some testing scenarios or more advanced use cases you might need to get
37/// a [`State`]. The state is managed as part of the template execution but the
38/// initial state can be retrieved via [`Template::new_state`](crate::Template::new_state).
39/// The most common way to get hold of the state however is via functions of filters.
40///
41/// **Notes on lifetimes:** the state object exposes some of the internal
42/// lifetimes through the type. You should always elide these lifetimes
43/// as there might be lifetimes added or removed between releases.
44pub struct State<'template, 'env> {
45 pub(crate) ctx: Context<'env>,
46 pub(crate) current_block: Option<&'env str>,
47 pub(crate) auto_escape: Cell<AutoEscape>,
48 pub(crate) instructions: &'template Instructions<'env>,
49 pub(crate) temps: Arc<Mutex<BTreeMap<Box<str>, Value>>>,
50 pub(crate) blocks: BTreeMap<&'env str, BlockStack<'template, 'env>>,
51 #[allow(unused)]
52 pub(crate) loaded_templates: BTreeSet<&'env str>,
53 #[cfg(feature = "macros")]
54 pub(crate) id: isize,
55 #[cfg(feature = "macros")]
56 pub(crate) macros: std::sync::Arc<Vec<(&'template Instructions<'env>, u32)>>,
57 #[cfg(feature = "macros")]
58 pub(crate) closure_tracker: std::sync::Arc<crate::vm::closure_object::ClosureTracker>,
59 #[cfg(feature = "fuel")]
60 pub(crate) fuel_tracker: Option<std::sync::Arc<FuelTracker>>,
61}
62
63impl fmt::Debug for State<'_, '_> {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 let mut ds = f.debug_struct("State");
66 ds.field("name", &self.instructions.name());
67 ds.field("current_block", &self.current_block);
68 ds.field("auto_escape", &self.auto_escape.get());
69 ds.field("ctx", &self.ctx);
70 ds.field("env", &self.env());
71 ds.finish()
72 }
73}
74
75impl<'template, 'env> State<'template, 'env> {
76 /// Creates a new state.
77 pub(crate) fn new(
78 ctx: Context<'env>,
79 auto_escape: AutoEscape,
80 instructions: &'template Instructions<'env>,
81 blocks: BTreeMap<&'env str, BlockStack<'template, 'env>>,
82 ) -> State<'template, 'env> {
83 State {
84 #[cfg(feature = "macros")]
85 id: STATE_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
86 current_block: None,
87 auto_escape: Cell::new(auto_escape),
88 instructions,
89 blocks,
90 temps: Default::default(),
91 loaded_templates: BTreeSet::new(),
92 #[cfg(feature = "macros")]
93 macros: Default::default(),
94 #[cfg(feature = "macros")]
95 closure_tracker: Default::default(),
96 #[cfg(feature = "fuel")]
97 fuel_tracker: ctx.env().fuel().map(FuelTracker::new),
98 ctx,
99 }
100 }
101
102 /// Creates an empty state for an environment.
103 pub(crate) fn new_for_env(env: &'env Environment) -> State<'env, 'env> {
104 State::new(
105 Context::new(env),
106 AutoEscape::None,
107 &crate::compiler::instructions::EMPTY_INSTRUCTIONS,
108 BTreeMap::new(),
109 )
110 }
111
112 /// Returns a reference to the current environment.
113 #[inline(always)]
114 pub fn env(&self) -> &'env Environment<'env> {
115 self.ctx.env()
116 }
117
118 /// Returns the name of the current template.
119 pub fn name(&self) -> &str {
120 self.instructions.name()
121 }
122
123 /// Returns the current value of the auto escape flag.
124 #[inline(always)]
125 pub fn auto_escape(&self) -> AutoEscape {
126 self.auto_escape.get()
127 }
128
129 pub(crate) fn with_auto_escape<R>(
130 &self,
131 auto_escape: AutoEscape,
132 f: impl FnOnce(&State<'template, 'env>) -> R,
133 ) -> R {
134 if self.auto_escape.get() == auto_escape {
135 return f(self);
136 }
137
138 let old = self.auto_escape.replace(auto_escape);
139 let rv = f(self);
140 self.auto_escape.set(old);
141 rv
142 }
143
144 /// Returns the current undefined behavior.
145 #[inline(always)]
146 pub fn undefined_behavior(&self) -> UndefinedBehavior {
147 self.env().undefined_behavior()
148 }
149
150 /// Returns the name of the innermost block.
151 #[inline(always)]
152 pub fn current_block(&self) -> Option<&str> {
153 self.current_block
154 }
155
156 /// Looks up a variable by name in the context.
157 ///
158 /// # Note on Closures
159 ///
160 /// Macros and call blocks analyze which variables are referenced and
161 /// create closures for them. This means that unless a variable is defined
162 /// as a [global](Environment::add_global) in the environment, was passed in the
163 /// initial render context, or was referenced by a macro, this method won't be
164 /// able to find it.
165 #[inline(always)]
166 pub fn lookup(&self, name: &str) -> Option<Value> {
167 self.ctx.load(name)
168 }
169
170 /// Looks up a global macro and calls it.
171 ///
172 /// This looks up a value as [`lookup`](Self::lookup) does and calls it
173 /// with the passed args.
174 #[cfg(feature = "macros")]
175 #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
176 pub fn call_macro(&self, name: &str, args: &[Value]) -> Result<String, Error> {
177 let f = ok!(self.lookup(name).ok_or_else(|| Error::new(
178 crate::error::ErrorKind::UnknownFunction,
179 "macro not found"
180 )));
181 f.call(self, args).map(Into::into)
182 }
183
184 /// Renders a block with the given name into a string.
185 ///
186 /// This method works like [`Template::render`](crate::Template::render) but
187 /// it only renders a specific block in the template. The first argument is
188 /// the name of the block.
189 ///
190 /// This renders only the block `hi` in the template:
191 ///
192 /// ```
193 /// # use minijinja::{Environment, context};
194 /// # fn test() -> Result<(), minijinja::Error> {
195 /// # let mut env = Environment::new();
196 /// # env.add_template("hello", "{% block hi %}Hello {{ name }}!{% endblock %}")?;
197 /// let tmpl = env.get_template("hello")?;
198 /// let mut rendered = tmpl
199 /// .render_captured(context!(name => "John"))?;
200 /// let rv = rendered.with_state_mut(|state| state.render_block("hi"))?;
201 /// println!("{}", rv);
202 /// # Ok(()) }
203 /// ```
204 ///
205 /// Note that rendering a block is a stateful operation. If an error
206 /// is returned the module has to be re-created as the internal state
207 /// can end up corrupted. This also means you can only render blocks
208 /// if you have a mutable reference to the state which is not possible
209 /// from within filters or similar.
210 #[cfg(feature = "multi_template")]
211 #[cfg_attr(docsrs, doc(cfg(feature = "multi_template")))]
212 pub fn render_block(&mut self, block: &str) -> Result<String, Error> {
213 let mut buf = String::new();
214 crate::vm::Vm::new(self.env())
215 .call_block(block, self, &mut Output::new(&mut buf))
216 .map(|_| buf)
217 }
218
219 /// Renders a block with the given name into an [`io::Write`](std::io::Write).
220 ///
221 /// For details see [`render_block`](Self::render_block).
222 #[cfg(feature = "multi_template")]
223 #[cfg_attr(docsrs, doc(cfg(feature = "multi_template")))]
224 pub fn render_block_to_write<W>(&mut self, block: &str, w: W) -> Result<(), Error>
225 where
226 W: std::io::Write,
227 {
228 let mut wrapper = crate::output::WriteWrapper { w, err: None };
229 crate::vm::Vm::new(self.env())
230 .call_block(block, self, &mut Output::new(&mut wrapper))
231 .map(|_| ())
232 .map_err(|err| wrapper.take_err(err))
233 }
234
235 /// Returns a list of the names of all exports (top-level variables).
236 pub fn exports(&self) -> Vec<&str> {
237 self.ctx.exports().keys().copied().collect()
238 }
239
240 /// Returns a list of all known variables.
241 ///
242 /// This list contains all variables that are currently known to the state.
243 /// To retrieve the values you can use [`lookup`](Self::lookup). This will
244 /// include all the globals of the environment. Note that if the context
245 /// has been initialized with an object that lies about variables (eg: it
246 /// does not correctly implement enumeration), the returned list might not
247 /// be complete.
248 pub fn known_variables(&self) -> Vec<Cow<'_, str>> {
249 Vec::from_iter(self.ctx.known_variables(true))
250 }
251
252 /// Fetches a template by name with path joining.
253 ///
254 /// This works like [`Environment::get_template`] with the difference that the lookup
255 /// undergoes path joining. If the environment has a configured path joining callback,
256 /// it will be invoked with the name of the current template as parent template.
257 ///
258 /// For more information see [`Environment::set_path_join_callback`].
259 pub fn get_template(&self, name: &str) -> Result<Template<'env, 'env>, Error> {
260 self.env()
261 .get_template(&self.env().join_template_path(name, self.name()))
262 }
263
264 /// Invokes a filter with some arguments.
265 ///
266 /// ```
267 /// # use minijinja::Environment;
268 /// # let mut env = Environment::new();
269 /// # env.add_filter("upper", |x: &str| x.to_uppercase());
270 /// # let tmpl = env.template_from_str("").unwrap();
271 /// # let state = tmpl.new_state();
272 /// let rv = state.apply_filter("upper", &["hello world".into()]).unwrap();
273 /// assert_eq!(rv.as_str(), Some("HELLO WORLD"));
274 /// ```
275 pub fn apply_filter(&self, filter: &str, args: &[Value]) -> Result<Value, Error> {
276 match self.env().get_filter(filter) {
277 Some(filter) => filter.call(self, args),
278 None => Err(Error::from(ErrorKind::UnknownFilter)),
279 }
280 }
281
282 /// Invokes a test function on a value.
283 ///
284 /// ```
285 /// # use minijinja::Environment;
286 /// # let mut env = Environment::new();
287 /// # env.add_test("even", |x: i32| x % 2 == 0);
288 /// # let tmpl = env.template_from_str("").unwrap();
289 /// # let state = tmpl.new_state();
290 /// let rv = state.perform_test("even", &[42i32.into()]).unwrap();
291 /// assert!(rv);
292 /// ```
293 pub fn perform_test(&self, test: &str, args: &[Value]) -> Result<bool, Error> {
294 match self.env().get_test(test) {
295 Some(test) => test.call(self, args).map(|x| x.is_true()),
296 None => Err(Error::from(ErrorKind::UnknownTest)),
297 }
298 }
299
300 /// Formats a value to a string using the formatter on the environment.
301 ///
302 /// ```
303 /// # use minijinja::{value::Value, Environment};
304 /// # let mut env = Environment::new();
305 /// # let tmpl = env.template_from_str("").unwrap();
306 /// # let state = tmpl.new_state();
307 /// let rv = state.format(Value::from(42)).unwrap();
308 /// assert_eq!(rv, "42");
309 /// ```
310 pub fn format(&self, value: Value) -> Result<String, Error> {
311 let mut rv = String::new();
312 let mut out = Output::new(&mut rv);
313 self.env().format(&value, self, &mut out).map(|_| rv)
314 }
315
316 /// Returns the fuel levels.
317 ///
318 /// When the fuel feature is enabled, during evaluation the template will keep
319 /// track of how much fuel it has consumed. If the fuel tracker is turned on
320 /// the returned value will be `Some((consumed, remaining))`. If fuel tracking
321 /// is not enabled, `None` is returned instead.
322 #[cfg(feature = "fuel")]
323 #[cfg_attr(docsrs, doc(cfg(feature = "fuel")))]
324 pub fn fuel_levels(&self) -> Option<(u64, u64)> {
325 self.fuel_tracker
326 .as_ref()
327 .map(|x| (x.consumed(), x.remaining()))
328 }
329
330 /// Looks up a temp and returns it.
331 ///
332 /// Temps are similar to context values but the engine never looks them up
333 /// on their own and they are not scoped. The lifetime of temps is limited
334 /// to the rendering process of a template. Temps are useful so that
335 /// filters and other things can temporarily stash away state without having
336 /// to resort to thread locals which are hard to manage. Unlike context
337 /// variables, temps can also be modified during evaluation by filters and
338 /// functions.
339 ///
340 /// Temps are values but if you want to hold complex state you can store a
341 /// custom object there.
342 ///
343 /// # Example
344 ///
345 /// ```
346 /// use minijinja::{Value, State};
347 ///
348 /// fn inc(state: &State) -> Value {
349 /// let old = state
350 /// .get_temp("my_counter")
351 /// .unwrap_or_else(|| Value::from(0i64));
352 /// let new = Value::from(i64::try_from(old).unwrap() + 1);
353 /// state.set_temp("my_counter", new.clone());
354 /// new
355 /// }
356 /// ```
357 pub fn get_temp(&self, name: &str) -> Option<Value> {
358 self.temps.lock().unwrap().get(name).cloned()
359 }
360
361 /// Inserts a temp and returns the old temp.
362 ///
363 /// For more information see [`get_temp`](Self::get_temp).
364 pub fn set_temp(&self, name: &str, value: Value) -> Option<Value> {
365 self.temps
366 .lock()
367 .unwrap()
368 .insert(name.to_owned().into(), value)
369 }
370
371 /// Shortcut for registering an object as a temp.
372 ///
373 /// If the value is already there, it's returned as an object. If it's
374 /// not there yet, the function is invoked to create it.
375 ///
376 /// # Example
377 ///
378 /// ```
379 /// use std::sync::atomic::{AtomicUsize, Ordering};
380 /// use minijinja::{Value, State};
381 /// use minijinja::value::Object;
382 ///
383 /// #[derive(Debug, Default)]
384 /// struct MyObject(AtomicUsize);
385 ///
386 /// impl Object for MyObject {}
387 ///
388 /// fn inc(state: &State) -> Value {
389 /// let obj = state.get_or_set_temp_object("my_counter", MyObject::default);
390 /// let old = obj.0.fetch_add(1, Ordering::AcqRel);
391 /// Value::from(old + 1)
392 /// }
393 /// ```
394 ///
395 /// # Panics
396 ///
397 /// This will panic if the value registered under that name is not
398 /// the object expected.
399 pub fn get_or_set_temp_object<O, F>(&self, name: &str, f: F) -> Arc<O>
400 where
401 O: Object + 'static,
402 F: FnOnce() -> O,
403 {
404 self.get_temp(name)
405 .unwrap_or_else(|| {
406 let rv = Value::from_object(f());
407 self.set_temp(name, rv.clone());
408 rv
409 })
410 .downcast_object()
411 .expect("downcast unexpectedly failed. Name conflict?")
412 }
413
414 #[cfg(feature = "debug")]
415 pub(crate) fn make_debug_info(
416 &self,
417 pc: u32,
418 instructions: &Instructions<'_>,
419 ) -> crate::debug::DebugInfo {
420 crate::debug::DebugInfo {
421 template_source: Some(instructions.source().to_string()),
422 referenced_locals: instructions
423 .get_referenced_names(pc)
424 .into_iter()
425 .filter_map(|n| Some((n.to_string(), some!(self.lookup(n)))))
426 .collect(),
427 }
428 }
429}
430
431impl<'a> ArgType<'a> for &State<'_, '_> {
432 type Output = &'a State<'a, 'a>;
433
434 fn from_value(_value: Option<&'a Value>) -> Result<Self::Output, Error> {
435 Err(Error::new(
436 ErrorKind::InvalidOperation,
437 "cannot use state type in this position",
438 ))
439 }
440
441 fn from_state_and_value(
442 state: Option<&'a State>,
443 _value: Option<&'a Value>,
444 ) -> Result<(Self::Output, usize), Error> {
445 match state {
446 None => Err(Error::new(ErrorKind::InvalidOperation, "state unavailable")),
447 Some(state) => Ok((state, 0)),
448 }
449 }
450}
451
452/// Tracks a block and its parents for super.
453#[derive(Default)]
454pub(crate) struct BlockStack<'template, 'env> {
455 instructions: Vec<&'template Instructions<'env>>,
456 depth: usize,
457}
458
459impl<'template, 'env> BlockStack<'template, 'env> {
460 pub fn new(instructions: &'template Instructions<'env>) -> BlockStack<'template, 'env> {
461 BlockStack {
462 instructions: vec![instructions],
463 depth: 0,
464 }
465 }
466
467 pub fn instructions(&self) -> &'template Instructions<'env> {
468 self.instructions.get(self.depth).copied().unwrap()
469 }
470
471 #[cfg(feature = "multi_template")]
472 pub fn len(&self) -> usize {
473 self.instructions.len()
474 }
475
476 pub fn push(&mut self) -> bool {
477 if self.depth + 1 < self.instructions.len() {
478 self.depth += 1;
479 true
480 } else {
481 false
482 }
483 }
484
485 #[track_caller]
486 pub fn pop(&mut self) {
487 self.depth = self.depth.checked_sub(1).unwrap()
488 }
489
490 #[cfg(feature = "multi_template")]
491 pub fn append_instructions(&mut self, instructions: &'template Instructions<'env>) {
492 self.instructions.push(instructions);
493 }
494}