liquid_interpreter/
stack.rs

1use std::borrow;
2
3use itertools;
4use liquid_error::{Error, Result};
5use liquid_value::{Object, PathRef, Scalar, Value};
6
7use super::ValueStore;
8
9#[derive(Clone, Default, Debug)]
10struct Frame {
11    name: Option<String>,
12    data: Object,
13}
14
15impl Frame {
16    fn new() -> Self {
17        Default::default()
18    }
19
20    fn with_name<S: Into<String>>(name: S) -> Self {
21        Self {
22            name: Some(name.into()),
23            data: Object::new(),
24        }
25    }
26}
27
28/// Stack of variables.
29#[derive(Debug, Clone)]
30pub struct Stack<'g> {
31    globals: Option<&'g ValueStore>,
32    stack: Vec<Frame>,
33    // State of variables created through increment or decrement tags.
34    indexes: Object,
35}
36
37impl<'g> Stack<'g> {
38    /// Create an empty stack
39    pub fn empty() -> Self {
40        Self {
41            globals: None,
42            indexes: Object::new(),
43            // Mutable frame for globals.
44            stack: vec![Frame::new()],
45        }
46    }
47
48    /// Create a stack initialized with read-only `ValueStore`.
49    pub fn with_globals(globals: &'g ValueStore) -> Self {
50        let mut stack = Self::empty();
51        stack.globals = Some(globals);
52        stack
53    }
54
55    /// Creates a new variable scope chained to a parent scope.
56    pub(crate) fn push_frame(&mut self) {
57        self.stack.push(Frame::new());
58    }
59
60    /// Creates a new variable scope chained to a parent scope.
61    pub(crate) fn push_named_frame<S: Into<String>>(&mut self, name: S) {
62        self.stack.push(Frame::with_name(name));
63    }
64
65    /// Removes the topmost stack frame from the local variable stack.
66    ///
67    /// # Panics
68    ///
69    /// This method will panic if popping the topmost frame results in an
70    /// empty stack. Given that a context is created with a top-level stack
71    /// frame already in place, emptying the stack should never happen in a
72    /// well-formed program.
73    pub(crate) fn pop_frame(&mut self) {
74        if self.stack.pop().is_none() {
75            panic!("Unbalanced push/pop, leaving the stack empty.")
76        };
77    }
78
79    /// The name of the currently active template.
80    pub fn frame_name(&self) -> Option<&str> {
81        self.stack
82            .iter()
83            .rev()
84            .find_map(|f| f.name.as_ref().map(|s| s.as_str()))
85    }
86
87    /// Recursively index into the stack.
88    pub fn try_get(&self, path: PathRef) -> Option<&Value> {
89        let frame = self.find_path_frame(path)?;
90
91        frame.try_get_variable(path)
92    }
93
94    /// Recursively index into the stack.
95    pub fn get(&self, path: PathRef) -> Result<&Value> {
96        let frame = self.find_path_frame(path).ok_or_else(|| {
97            let key = path
98                .iter()
99                .next()
100                .cloned()
101                .unwrap_or_else(|| Scalar::new("nil"));
102            let globals = itertools::join(self.globals().iter(), ", ");
103            Error::with_msg("Unknown variable")
104                .context("requested variable", key.to_str().into_owned())
105                .context("available variables", globals)
106        })?;
107
108        frame.get_variable(path)
109    }
110
111    fn globals(&self) -> Vec<&str> {
112        let mut globals = self.globals.map(|g| g.roots()).unwrap_or_default();
113        for frame in self.stack.iter() {
114            globals.extend(frame.data.roots());
115        }
116        globals.sort();
117        globals.dedup();
118        globals
119    }
120
121    fn find_path_frame<'a>(&'a self, path: PathRef) -> Option<&'a ValueStore> {
122        let key = path.iter().next()?;
123        let key = key.to_str();
124        self.find_frame(key.as_ref())
125    }
126
127    fn find_frame<'a>(&'a self, name: &str) -> Option<&'a ValueStore> {
128        for frame in self.stack.iter().rev() {
129            if frame.data.contains_root(name) {
130                return Some(&frame.data);
131            }
132        }
133
134        if self.globals.map(|g| g.contains_root(name)).unwrap_or(false) {
135            return self.globals;
136        }
137
138        if self.indexes.contains_root(name) {
139            return Some(&self.indexes);
140        }
141
142        None
143    }
144
145    /// Used by increment and decrement tags
146    pub fn set_index<S>(&mut self, name: S, val: Value) -> Option<Value>
147    where
148        S: Into<borrow::Cow<'static, str>>,
149    {
150        self.indexes.insert(name.into(), val)
151    }
152
153    /// Used by increment and decrement tags
154    pub fn get_index<'a>(&'a self, name: &str) -> Option<&'a Value> {
155        self.indexes.get(name)
156    }
157
158    /// Sets a value in the global context.
159    pub fn set_global<S>(&mut self, name: S, val: Value) -> Option<Value>
160    where
161        S: Into<borrow::Cow<'static, str>>,
162    {
163        self.global_frame().insert(name.into(), val)
164    }
165
166    /// Sets a value to the rendering context.
167    /// Note that it needs to be wrapped in a liquid::Value.
168    ///
169    /// # Panics
170    ///
171    /// Panics if there is no frame on the local values stack. Context
172    /// instances are created with a top-level stack frame in place, so
173    /// this should never happen in a well-formed program.
174    pub fn set<S>(&mut self, name: S, val: Value) -> Option<Value>
175    where
176        S: Into<borrow::Cow<'static, str>>,
177    {
178        self.current_frame().insert(name.into(), val)
179    }
180
181    fn current_frame(&mut self) -> &mut Object {
182        match self.stack.last_mut() {
183            Some(frame) => &mut frame.data,
184            None => panic!("Global frame removed."),
185        }
186    }
187
188    fn global_frame(&mut self) -> &mut Object {
189        match self.stack.first_mut() {
190            Some(frame) => &mut frame.data,
191            None => panic!("Global frame removed."),
192        }
193    }
194}
195
196impl<'g> Default for Stack<'g> {
197    fn default() -> Self {
198        Self::empty()
199    }
200}
201
202#[cfg(test)]
203mod test {
204    use super::*;
205
206    #[test]
207    fn stack_find_frame() {
208        let mut stack = Stack::empty();
209        stack.set_global("number", Value::scalar(42f64));
210        assert!(stack.find_frame("number").is_some(),);
211    }
212
213    #[test]
214    fn stack_find_frame_failure() {
215        let mut stack = Stack::empty();
216        let mut post = Object::new();
217        post.insert("number".into(), Value::scalar(42f64));
218        stack.set_global("post", Value::Object(post));
219        assert!(stack.find_frame("post.number").is_none());
220    }
221
222    #[test]
223    fn stack_get() {
224        let mut stack = Stack::empty();
225        let mut post = Object::new();
226        post.insert("number".into(), Value::scalar(42f64));
227        stack.set_global("post", Value::Object(post));
228        let indexes = [Scalar::new("post"), Scalar::new("number")];
229        assert_eq!(stack.get(&indexes).unwrap(), &Value::scalar(42f64));
230    }
231
232}