loose_liquid_core/runtime/
runtime.rs

1use std::sync;
2
3use crate::error::Error;
4use crate::error::Result;
5use crate::model::{Object, ObjectView, Scalar, ScalarCow, Value, ValueCow, ValueView};
6
7use super::PartialStore;
8use super::Renderable;
9
10/// State for rendering a template
11pub trait Runtime {
12    /// Partial templates for inclusion.
13    fn partials(&self) -> &dyn PartialStore;
14
15    /// The name of the currently active template.
16    fn name(&self) -> Option<crate::model::KStringRef<'_>>;
17
18    /// All available values
19    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>>;
20    /// Recursively index into the stack.
21    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>>;
22    /// Recursively index into the stack.
23    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>>;
24
25    /// Sets a value in the global runtime.
26    fn set_global(
27        &self,
28        name: crate::model::KString,
29        val: crate::model::Value,
30    ) -> Option<crate::model::Value>;
31
32    /// Used by increment and decrement tags
33    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value>;
34    /// Used by increment and decrement tags
35    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>>;
36
37    /// Unnamed state for plugins during rendering
38    fn registers(&self) -> &Registers;
39}
40
41impl<'r, R: Runtime + ?Sized> Runtime for &'r R {
42    fn partials(&self) -> &dyn super::PartialStore {
43        <R as Runtime>::partials(self)
44    }
45
46    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
47        <R as Runtime>::name(self)
48    }
49
50    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
51        <R as Runtime>::roots(self)
52    }
53
54    fn try_get(&self, path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
55        <R as Runtime>::try_get(self, path)
56    }
57
58    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
59        <R as Runtime>::get(self, path)
60    }
61
62    fn set_global(
63        &self,
64        name: crate::model::KString,
65        val: crate::model::Value,
66    ) -> Option<crate::model::Value> {
67        <R as Runtime>::set_global(self, name, val)
68    }
69
70    fn set_index(&self, name: crate::model::KString, val: Value) -> Option<Value> {
71        <R as Runtime>::set_index(self, name, val)
72    }
73
74    fn get_index<'a>(&'a self, name: &str) -> Option<ValueCow<'a>> {
75        <R as Runtime>::get_index(self, name)
76    }
77
78    fn registers(&self) -> &super::Registers {
79        <R as Runtime>::registers(self)
80    }
81}
82
83/// Create processing runtime for a template.
84pub struct RuntimeBuilder<'g, 'p> {
85    globals: Option<&'g dyn ObjectView>,
86    partials: Option<&'p dyn PartialStore>,
87}
88
89impl<'c, 'g: 'c, 'p: 'c> RuntimeBuilder<'g, 'p> {
90    /// Creates a new, empty rendering runtime.
91    pub fn new() -> Self {
92        Self {
93            globals: None,
94            partials: None,
95        }
96    }
97
98    /// Initialize the stack with the given globals.
99    pub fn set_globals<'n>(self, values: &'n dyn ObjectView) -> RuntimeBuilder<'n, 'p> {
100        RuntimeBuilder {
101            globals: Some(values),
102            partials: self.partials,
103        }
104    }
105
106    /// Initialize partial-templates available for including.
107    pub fn set_partials<'n>(self, values: &'n dyn PartialStore) -> RuntimeBuilder<'g, 'n> {
108        RuntimeBuilder {
109            globals: self.globals,
110            partials: Some(values),
111        }
112    }
113
114    /// Create the `Runtime`.
115    pub fn build(self) -> impl Runtime + 'c {
116        let partials = self.partials.unwrap_or(&NullPartials);
117        let runtime = RuntimeCore {
118            partials,
119            ..Default::default()
120        };
121        let runtime = super::IndexFrame::new(runtime);
122        let runtime = super::StackFrame::new(runtime, self.globals.unwrap_or(&NullObject));
123        super::GlobalFrame::new(runtime)
124    }
125}
126
127#[derive(Copy, Clone, Debug)]
128struct NullObject;
129
130impl ValueView for NullObject {
131    fn as_debug(&self) -> &dyn std::fmt::Debug {
132        self
133    }
134
135    fn render(&self) -> crate::model::DisplayCow<'_> {
136        Value::Nil.render()
137    }
138    fn source(&self) -> crate::model::DisplayCow<'_> {
139        Value::Nil.source()
140    }
141    fn type_name(&self) -> &'static str {
142        "object"
143    }
144    fn query_state(&self, state: crate::model::State) -> bool {
145        match state {
146            crate::model::State::Truthy => true,
147            crate::model::State::DefaultValue
148            | crate::model::State::Empty
149            | crate::model::State::Blank => false,
150        }
151    }
152
153    fn to_kstr(&self) -> crate::model::KStringCow<'_> {
154        crate::model::KStringCow::from_static("")
155    }
156    fn to_value(&self) -> Value {
157        Value::Object(Object::new())
158    }
159
160    fn as_object(&self) -> Option<&dyn ObjectView> {
161        Some(self)
162    }
163}
164
165impl ObjectView for NullObject {
166    fn as_value(&self) -> &dyn ValueView {
167        self
168    }
169
170    fn size(&self) -> i64 {
171        0
172    }
173
174    fn keys<'k>(&'k self) -> Box<dyn Iterator<Item = crate::model::KStringCow<'k>> + 'k> {
175        let keys = Vec::new().into_iter();
176        Box::new(keys)
177    }
178
179    fn values<'k>(&'k self) -> Box<dyn Iterator<Item = &'k dyn ValueView> + 'k> {
180        let i = Vec::new().into_iter();
181        Box::new(i)
182    }
183
184    fn iter<'k>(
185        &'k self,
186    ) -> Box<dyn Iterator<Item = (crate::model::KStringCow<'k>, &'k dyn ValueView)> + 'k> {
187        let i = Vec::new().into_iter();
188        Box::new(i)
189    }
190
191    fn contains_key(&self, _index: &str) -> bool {
192        false
193    }
194
195    fn get<'s>(&'s self, _index: &str) -> Option<&'s dyn ValueView> {
196        None
197    }
198}
199
200impl Default for RuntimeBuilder<'static, 'static> {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206/// Processing runtime for a template.
207pub struct RuntimeCore<'g> {
208    partials: &'g dyn PartialStore,
209
210    registers: Registers,
211}
212
213impl<'g> RuntimeCore<'g> {
214    /// Create a default `RuntimeCore`.
215    ///
216    /// See `RuntimeBuilder` for more control.
217    pub fn new() -> Self {
218        RuntimeCore::default()
219    }
220
221    /// Partial templates for inclusion.
222    pub fn partials(&self) -> &dyn PartialStore {
223        self.partials
224    }
225}
226
227impl<'g> Runtime for RuntimeCore<'g> {
228    fn partials(&self) -> &dyn PartialStore {
229        self.partials
230    }
231
232    fn name(&self) -> Option<crate::model::KStringRef<'_>> {
233        None
234    }
235
236    fn roots(&self) -> std::collections::BTreeSet<crate::model::KStringCow<'_>> {
237        // Indexes don't count
238        std::collections::BTreeSet::new()
239    }
240
241    fn try_get(&self, _path: &[ScalarCow<'_>]) -> Option<ValueCow<'_>> {
242        None
243    }
244
245    fn get(&self, path: &[ScalarCow<'_>]) -> Result<ValueCow<'_>> {
246        let key = path.first().cloned().unwrap_or_else(|| Scalar::new("nil"));
247        Error::unknown_variable(key.to_kstr()).into_err()
248    }
249
250    fn set_global(
251        &self,
252        _name: crate::model::KString,
253        _val: crate::model::Value,
254    ) -> Option<crate::model::Value> {
255        unreachable!("Must be masked by a global frame");
256    }
257
258    fn set_index(&self, _name: crate::model::KString, _val: Value) -> Option<Value> {
259        unreachable!("Must be masked by a global frame");
260    }
261
262    fn get_index<'a>(&'a self, _name: &str) -> Option<ValueCow<'a>> {
263        None
264    }
265
266    fn registers(&self) -> &Registers {
267        &self.registers
268    }
269}
270
271impl<'g> Default for RuntimeCore<'g> {
272    fn default() -> Self {
273        Self {
274            partials: &NullPartials,
275            registers: Default::default(),
276        }
277    }
278}
279
280/// Unnamed state for plugins during rendering
281pub struct Registers {
282    registers: std::cell::RefCell<anymap2::AnyMap>,
283}
284
285impl Registers {
286    /// Data store for stateful tags/blocks.
287    ///
288    /// If a plugin needs state, it creates a `struct Register : Default` and accesses it via
289    /// `get_mut`.
290    pub fn get_mut<T: std::any::Any + Default>(&self) -> std::cell::RefMut<'_, T> {
291        std::cell::RefMut::map(self.registers.borrow_mut(), |registers| {
292            registers.entry::<T>().or_insert_with(Default::default)
293        })
294    }
295}
296
297impl Default for Registers {
298    fn default() -> Self {
299        Self {
300            registers: std::cell::RefCell::new(anymap2::AnyMap::new()),
301        }
302    }
303}
304
305/// The current interrupt state. The interrupt state is used by
306/// the `break` and `continue` tags to halt template rendering
307/// at a given point and unwind the `render` call stack until
308/// it reaches an enclosing `for_loop`. At that point the interrupt
309/// is cleared, and the `for_loop` carries on processing as directed.
310#[derive(Debug, Clone, PartialEq, Eq, Default)]
311pub struct InterruptRegister {
312    interrupt: Option<Interrupt>,
313}
314
315impl InterruptRegister {
316    /// An interrupt state is active.
317    pub fn interrupted(&self) -> bool {
318        self.interrupt.is_some()
319    }
320
321    /// Sets the interrupt state. Any previous state is obliterated.
322    pub fn set(&mut self, interrupt: Interrupt) {
323        self.interrupt.replace(interrupt);
324    }
325
326    /// Fetches and clears the interrupt state.
327    pub fn reset(&mut self) -> Option<Interrupt> {
328        self.interrupt.take()
329    }
330}
331
332/// Block processing interrupt state.
333#[derive(Debug, Clone, Copy, PartialEq, Eq)]
334pub enum Interrupt {
335    /// Restart processing the current block.
336    Continue,
337    /// Stop processing the current block.
338    Break,
339}
340
341#[derive(Copy, Clone, Debug)]
342struct NullPartials;
343
344impl PartialStore for NullPartials {
345    fn contains(&self, _name: &str) -> bool {
346        false
347    }
348
349    fn names(&self) -> Vec<&str> {
350        Vec::new()
351    }
352
353    fn try_get(&self, _name: &str) -> Option<sync::Arc<dyn Renderable>> {
354        None
355    }
356
357    fn get(&self, name: &str) -> Result<sync::Arc<dyn Renderable>> {
358        Err(Error::with_msg("Partial does not exist").context("name", name.to_owned()))
359    }
360}
361
362#[cfg(test)]
363mod test {
364    use super::*;
365
366    use crate::model::Scalar;
367    use crate::model::Value;
368    use crate::model::ValueViewCmp;
369
370    #[test]
371    fn mask_variables() {
372        let test_path = [Scalar::new("test")];
373
374        let rt = RuntimeBuilder::new().build();
375        rt.set_global("test".into(), Value::scalar(42f64));
376        assert_eq!(&rt.get(&test_path).unwrap(), &ValueViewCmp::new(&42f64));
377
378        {
379            let data = crate::object!({"test": 3});
380            let new_scope = super::super::StackFrame::new(&rt, &data);
381
382            // assert that values are chained to the parent scope
383            assert_eq!(&new_scope.get(&test_path).unwrap(), &ValueViewCmp::new(&3));
384        }
385
386        // assert that the value has reverted to the old one
387        assert_eq!(&rt.get(&test_path).unwrap(), &ValueViewCmp::new(&42));
388    }
389
390    #[test]
391    fn global_variables() {
392        let global_path = [Scalar::new("global")];
393
394        let rt = RuntimeBuilder::new().build();
395
396        {
397            let data = crate::object!({"test": 3});
398            let new_scope = super::super::StackFrame::new(&rt, &data);
399
400            // sat a new val that we will pick up outside the scope
401            new_scope.set_global("global".into(), Value::scalar("some value"));
402        }
403        assert_eq!(
404            &rt.get(&global_path).unwrap(),
405            &ValueViewCmp::new(&"some value")
406        );
407    }
408}