minijinja/vm/
context.rs

1use std::borrow::Cow;
2use std::collections::{BTreeMap, HashSet};
3use std::fmt;
4
5#[cfg(feature = "macros")]
6use std::sync::Arc;
7
8use crate::environment::Environment;
9use crate::error::{Error, ErrorKind};
10use crate::value::Value;
11use crate::vm::loop_object::LoopState;
12
13#[cfg(feature = "macros")]
14use crate::vm::closure_object::Closure;
15
16type Locals<'env> = BTreeMap<&'env str, Value>;
17
18pub(crate) struct Frame<'env> {
19    pub(crate) locals: Locals<'env>,
20    pub(crate) ctx: Value,
21    pub(crate) current_loop: Option<LoopState>,
22
23    // normally a frame does not carry a closure, but it can when a macro is
24    // declared.  Once that happens, all writes to the frames locals are also
25    // duplicated into the closure.  Macros declared on that level, then share
26    // the closure object to enclose the parent values.  This emulates the
27    // behavior of closures in Jinja2.
28    #[cfg(feature = "macros")]
29    pub(crate) closure: Option<Arc<Closure>>,
30}
31
32impl<'env> Default for Frame<'env> {
33    fn default() -> Frame<'env> {
34        Frame::new(Value::UNDEFINED)
35    }
36}
37
38impl<'env> Frame<'env> {
39    /// Creates a new frame with the given context and no validation
40    pub fn new(ctx: Value) -> Frame<'env> {
41        Frame {
42            locals: Locals::new(),
43            ctx,
44            current_loop: None,
45            #[cfg(feature = "macros")]
46            closure: None,
47        }
48    }
49
50    /// Creates a new frame with the given context and validates the value is not invalid
51    pub fn new_checked(root: Value) -> Result<Frame<'env>, Error> {
52        Ok(Frame::new(ok!(root.validate())))
53    }
54}
55
56#[cfg(feature = "internal_debug")]
57impl fmt::Debug for Frame<'_> {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        let mut m = f.debug_map();
60        m.entry(&"locals", &self.locals);
61        if let Some(LoopState {
62            object: ref controller,
63            ..
64        }) = self.current_loop
65        {
66            m.entry(&"loop", controller);
67        }
68        if !self.ctx.is_undefined() {
69            m.entry(&"ctx", &self.ctx);
70        }
71        m.finish()
72    }
73}
74
75#[cfg_attr(feature = "internal_debug", derive(Debug))]
76pub(crate) struct Stack {
77    values: Vec<Value>,
78}
79
80impl Default for Stack {
81    fn default() -> Stack {
82        Stack {
83            values: Vec::with_capacity(16),
84        }
85    }
86}
87
88impl Stack {
89    pub fn push(&mut self, arg: Value) {
90        self.values.push(arg);
91    }
92
93    #[track_caller]
94    pub fn pop(&mut self) -> Value {
95        self.values.pop().unwrap()
96    }
97
98    pub fn reverse_top(&mut self, n: usize) {
99        let start = self.values.len() - n;
100        self.values[start..].reverse();
101    }
102
103    pub fn get_call_args(&mut self, n: Option<u16>) -> &[Value] {
104        let n = match n {
105            Some(n) => n as usize,
106            None => self.pop().as_usize().unwrap(),
107        };
108        &self.values[self.values.len() - n..]
109    }
110
111    pub fn drop_top(&mut self, n: usize) {
112        self.values.truncate(self.values.len() - n);
113    }
114
115    pub fn try_pop(&mut self) -> Option<Value> {
116        self.values.pop()
117    }
118
119    #[track_caller]
120    pub fn peek(&self) -> &Value {
121        self.values.last().unwrap()
122    }
123}
124
125impl From<Vec<Value>> for Stack {
126    fn from(values: Vec<Value>) -> Stack {
127        Stack { values }
128    }
129}
130
131pub(crate) struct Context<'env> {
132    env: &'env Environment<'env>,
133    stack: Vec<Frame<'env>>,
134    outer_stack_depth: usize,
135    recursion_limit: usize,
136}
137
138impl fmt::Debug for Context<'_> {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        let mut vars = Vec::from_iter(self.known_variables(false));
141        vars.sort();
142        f.debug_map()
143            .entries(vars.into_iter().map(|key| {
144                let value = self.load(&key).unwrap_or_default();
145                (key, value)
146            }))
147            .finish()
148    }
149}
150
151impl<'env> Context<'env> {
152    /// Creates an empty context.
153    pub fn new(env: &'env Environment<'env>) -> Context<'env> {
154        Context {
155            env,
156            stack: Vec::with_capacity(32),
157            outer_stack_depth: 0,
158            recursion_limit: env.recursion_limit(),
159        }
160    }
161
162    /// Creates a context
163    pub fn new_with_frame(env: &'env Environment<'env>, frame: Frame<'env>) -> Context<'env> {
164        let mut rv = Context::new(env);
165        rv.stack.push(frame);
166        rv
167    }
168
169    /// The env
170    #[inline(always)]
171    pub fn env(&self) -> &'env Environment<'env> {
172        self.env
173    }
174
175    /// Stores a variable in the context.
176    pub fn store(&mut self, key: &'env str, value: Value) {
177        let top = self.stack.last_mut().unwrap();
178        #[cfg(feature = "macros")]
179        {
180            if let Some(ref closure) = top.closure {
181                closure.store(key, value.clone());
182            }
183        }
184        top.locals.insert(key, value);
185    }
186
187    /// Adds a value to a closure if missing.
188    ///
189    /// All macros declare on a certain level reuse the same closure.  This is done
190    /// to emulate the behavior of how scopes work in Jinja2 in Python.  The
191    /// unfortunate downside is that this has to be done with a `Mutex`.
192    #[cfg(feature = "macros")]
193    pub fn enclose(&mut self, key: &str) {
194        self.stack
195            .last_mut()
196            .unwrap()
197            .closure
198            .as_mut()
199            .unwrap()
200            .clone()
201            .store_if_missing(key, || self.load(key).unwrap_or(Value::UNDEFINED));
202    }
203
204    /// Loads the closure and returns it.
205    #[cfg(feature = "macros")]
206    pub fn closure(&mut self) -> Option<&Arc<Closure>> {
207        self.stack.last_mut().unwrap().closure.as_ref()
208    }
209
210    /// Temporarily takes the closure.
211    ///
212    /// This is done because includes are in the same scope as the module that
213    /// triggers the import, but we do not want to allow closures to be modified
214    /// from another file as this would be very confusing.
215    ///
216    /// This means that if you override a variable referenced by a macro after
217    /// including in the parent template, it will not override the value seen by
218    /// the macro.
219    #[cfg(all(feature = "multi_template", feature = "macros"))]
220    pub fn take_closure(&mut self) -> Option<Arc<Closure>> {
221        self.stack.last_mut().unwrap().closure.take()
222    }
223
224    /// Puts the closure back.
225    #[cfg(feature = "macros")]
226    pub fn reset_closure(&mut self, closure: Option<Arc<Closure>>) {
227        self.stack.last_mut().unwrap().closure = closure;
228    }
229
230    /// Return the base context value
231    #[cfg(feature = "macros")]
232    pub fn clone_base(&self) -> Value {
233        self.stack
234            .first()
235            .map(|x| x.ctx.clone())
236            .unwrap_or_default()
237    }
238
239    /// Looks up a variable in the context.
240    pub fn load(&self, key: &str) -> Option<Value> {
241        for frame in self.stack.iter().rev() {
242            // look at locals first
243            if let Some(value) = frame.locals.get(key) {
244                return Some(value.clone());
245            }
246
247            // if we are a loop, check if we are looking up the special loop var.
248            if let Some(ref l) = frame.current_loop {
249                if l.with_loop_var && key == "loop" {
250                    return Some(Value::from_dyn_object(l.object.clone()));
251                }
252            }
253
254            // perform a fast lookup.  This one will not produce errors if the
255            // context is undefined or of the wrong type.
256            if let Some(rv) = frame.ctx.get_attr_fast(key) {
257                return Some(rv);
258            }
259        }
260
261        self.env.get_global(key)
262    }
263
264    /// Returns an iterable of all declared variables.
265    pub fn known_variables(&self, with_globals: bool) -> HashSet<Cow<'_, str>> {
266        let mut seen = HashSet::<Cow<'_, str>>::new();
267        for frame in self.stack.iter().rev() {
268            for key in frame.locals.keys() {
269                if !seen.contains(&Cow::Borrowed(*key)) {
270                    seen.insert(Cow::Borrowed(key));
271                }
272            }
273
274            if let Some(ref l) = frame.current_loop {
275                if l.with_loop_var && !seen.contains("loop") {
276                    seen.insert(Cow::Borrowed("loop"));
277                }
278            }
279
280            if let Ok(iter) = frame.ctx.try_iter() {
281                for key in iter {
282                    if let Some(str_key) = key.as_str() {
283                        if !seen.contains(&Cow::Borrowed(str_key))
284                            && frame.ctx.get_item(&key).is_ok()
285                        {
286                            seen.insert(Cow::Owned(str_key.to_owned()));
287                        }
288                    }
289                }
290            }
291        }
292        if with_globals {
293            seen.extend(self.env.globals().map(|x| Cow::Borrowed(x.0)));
294        }
295        seen
296    }
297
298    /// Pushes a new layer.
299    pub fn push_frame(&mut self, layer: Frame<'env>) -> Result<(), Error> {
300        ok!(self.check_depth());
301        self.stack.push(layer);
302        Ok(())
303    }
304
305    /// Pops the topmost layer.
306    #[track_caller]
307    pub fn pop_frame(&mut self) -> Frame {
308        self.stack.pop().unwrap()
309    }
310
311    /// Returns the root locals (exports)
312    #[track_caller]
313    pub fn exports(&self) -> &Locals<'env> {
314        &self.stack.first().unwrap().locals
315    }
316
317    /// Returns the current locals mutably.
318    #[track_caller]
319    #[cfg(feature = "multi_template")]
320    pub fn current_locals_mut(&mut self) -> &mut Locals<'env> {
321        &mut self.stack.last_mut().unwrap().locals
322    }
323
324    /// Returns the current innermost loop state.
325    pub fn current_loop(&mut self) -> Option<&mut LoopState> {
326        self.stack
327            .iter_mut()
328            .rev()
329            .filter_map(|x| x.current_loop.as_mut())
330            .next()
331    }
332
333    /// The real depth of the context.
334    pub fn depth(&self) -> usize {
335        self.outer_stack_depth + self.stack.len()
336    }
337
338    /// Increase the stack depth.
339    #[allow(unused)]
340    pub fn incr_depth(&mut self, delta: usize) -> Result<(), Error> {
341        self.outer_stack_depth += delta;
342        ok!(self.check_depth());
343        Ok(())
344    }
345
346    /// Decrease the stack depth.
347    #[allow(unused)]
348    pub fn decr_depth(&mut self, delta: usize) {
349        self.outer_stack_depth -= delta;
350    }
351
352    fn check_depth(&self) -> Result<(), Error> {
353        if self.depth() > self.recursion_limit {
354            return Err(Error::new(
355                ErrorKind::InvalidOperation,
356                "recursion limit exceeded",
357            ));
358        }
359        Ok(())
360    }
361}