minijinja/vm/
loop_object.rs

1use std::fmt;
2use std::sync::atomic::{AtomicUsize, Ordering};
3use std::sync::{Arc, Mutex};
4
5use crate::error::{Error, ErrorKind};
6use crate::value::{Enumerator, Object, Value, ValueIter};
7use crate::vm::state::State;
8
9pub(crate) struct LoopState {
10    pub(crate) with_loop_var: bool,
11
12    // if we're popping the frame, do we want to jump somewhere?  The
13    // first item is the target jump instruction, the second argument
14    // tells us if we need to end capturing.
15    pub(crate) current_recursion_jump: Option<(u32, bool)>,
16    pub(crate) object: Arc<Loop>,
17
18    // Depending on if adjacent_loop_items is enabled or not, the iterator
19    // is stored either on the loop state or in the loop object.  This is
20    // done because when the feature is disabled, we can avoid using a mutex.
21    #[cfg(not(feature = "adjacent_loop_items"))]
22    iter: ValueIter,
23}
24
25impl LoopState {
26    pub fn new(
27        iter: ValueIter,
28        depth: usize,
29        with_loop_var: bool,
30        recurse_jump_target: Option<u32>,
31        current_recursion_jump: Option<(u32, bool)>,
32    ) -> LoopState {
33        // for an iterator where the lower and upper bound are matching we can
34        // consider them to have ExactSizeIterator semantics.  We do however not
35        // expect ExactSizeIterator bounds themselves to support iteration by
36        // other means.
37        let len = match iter.size_hint() {
38            (lower, Some(upper)) if lower == upper => Some(lower),
39            _ => None,
40        };
41        LoopState {
42            with_loop_var,
43            current_recursion_jump,
44            object: Arc::new(Loop {
45                idx: AtomicUsize::new(!0usize),
46                len,
47                depth,
48                recurse_jump_target,
49                last_changed_value: Mutex::default(),
50                #[cfg(feature = "adjacent_loop_items")]
51                iter: Mutex::new(AdjacentLoopItemIterWrapper::new(iter)),
52            }),
53            #[cfg(not(feature = "adjacent_loop_items"))]
54            iter,
55        }
56    }
57
58    pub fn did_not_iterate(&self) -> bool {
59        self.object.idx.load(Ordering::Relaxed) == 0
60    }
61
62    pub fn next(&mut self) -> Option<Value> {
63        self.object.idx.fetch_add(1, Ordering::Relaxed);
64        #[cfg(feature = "adjacent_loop_items")]
65        {
66            self.object.iter.lock().unwrap().next()
67        }
68        #[cfg(not(feature = "adjacent_loop_items"))]
69        {
70            self.iter.next()
71        }
72    }
73}
74
75#[cfg(feature = "adjacent_loop_items")]
76pub(crate) struct AdjacentLoopItemIterWrapper {
77    prev_item: Option<Value>,
78    current_item: Option<Value>,
79    next_item: Option<Value>,
80    iter: ValueIter,
81}
82
83#[cfg(feature = "adjacent_loop_items")]
84impl AdjacentLoopItemIterWrapper {
85    pub fn new(iter: ValueIter) -> AdjacentLoopItemIterWrapper {
86        AdjacentLoopItemIterWrapper {
87            prev_item: None,
88            current_item: None,
89            next_item: None,
90            iter,
91        }
92    }
93
94    fn next(&mut self) -> Option<Value> {
95        self.prev_item = self.current_item.take();
96        self.current_item = self.next_item.take().or_else(|| self.iter.next());
97        self.current_item.clone()
98    }
99
100    fn prev_item(&self) -> Value {
101        self.prev_item.clone().unwrap_or_default()
102    }
103
104    fn next_item(&mut self) -> Value {
105        self.next_item.clone().unwrap_or_else(|| {
106            self.next_item = self.iter.next();
107            self.next_item.clone().unwrap_or_default()
108        })
109    }
110}
111
112pub(crate) struct Loop {
113    pub len: Option<usize>,
114    pub idx: AtomicUsize,
115    pub depth: usize,
116    pub last_changed_value: Mutex<Option<Vec<Value>>>,
117    pub recurse_jump_target: Option<u32>,
118    #[cfg(feature = "adjacent_loop_items")]
119    iter: Mutex<AdjacentLoopItemIterWrapper>,
120}
121
122impl fmt::Debug for Loop {
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        f.debug_struct("Loop")
125            .field("len", &self.len)
126            .field("idx", &self.idx)
127            .field("depth", &self.depth)
128            .finish()
129    }
130}
131
132impl Object for Loop {
133    fn call(self: &Arc<Self>, _state: &State, _args: &[Value]) -> Result<Value, Error> {
134        // this could happen if a filter or some other code where to get hold
135        // on the loop and try to call it.  The template execution itself will
136        // not end up here as the CallFunction opcode has a special code path
137        // for loop recursion.
138        Err(Error::new(
139            ErrorKind::InvalidOperation,
140            "loop recursion cannot be called this way",
141        ))
142    }
143
144    fn call_method(
145        self: &Arc<Self>,
146        _state: &State,
147        name: &str,
148        args: &[Value],
149    ) -> Result<Value, Error> {
150        if name == "changed" {
151            let mut last_changed_value = self.last_changed_value.lock().unwrap();
152            let value = args.to_owned();
153            let changed = last_changed_value.as_ref() != Some(&value);
154            if changed {
155                *last_changed_value = Some(value);
156                Ok(Value::from(true))
157            } else {
158                Ok(Value::from(false))
159            }
160        } else if name == "cycle" {
161            let idx = self.idx.load(Ordering::Relaxed);
162            match args.get(idx % args.len()) {
163                Some(arg) => Ok(arg.clone()),
164                None => Ok(Value::UNDEFINED),
165            }
166        } else {
167            Err(Error::from(ErrorKind::UnknownMethod))
168        }
169    }
170
171    fn enumerate(self: &Arc<Self>) -> Enumerator {
172        Enumerator::Str(&[
173            "index0",
174            "index",
175            "length",
176            "revindex",
177            "revindex0",
178            "first",
179            "last",
180            "depth",
181            "depth0",
182            #[cfg(feature = "adjacent_loop_items")]
183            "previtem",
184            #[cfg(feature = "adjacent_loop_items")]
185            "nextitem",
186        ])
187    }
188
189    fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
190        let key = some!(key.as_str());
191        let idx = self.idx.load(Ordering::Relaxed) as u64;
192        // if we never iterated, then all attributes are undefined.
193        // this can happen in some rare circumstances where the engine
194        // did not manage to iterate
195        if idx == !0 {
196            return Some(Value::UNDEFINED);
197        }
198        let len = self.len.map(|x| x as u64);
199        match key {
200            "index0" => Some(Value::from(idx)),
201            "index" => Some(Value::from(idx + 1)),
202            "length" => Some(len.map(Value::from).unwrap_or(Value::UNDEFINED)),
203            "revindex" => Some(
204                len.map(|len| Value::from(len.saturating_sub(idx)))
205                    .unwrap_or(Value::UNDEFINED),
206            ),
207            "revindex0" => Some(
208                len.map(|len| Value::from(len.saturating_sub(idx).saturating_sub(1)))
209                    .unwrap_or(Value::UNDEFINED),
210            ),
211            "first" => Some(Value::from(idx == 0)),
212            "last" => Some(len.map_or(Value::from(false), |len| {
213                Value::from(len == 0 || idx == len - 1)
214            })),
215            "depth" => Some(Value::from(self.depth + 1)),
216            "depth0" => Some(Value::from(self.depth)),
217            #[cfg(feature = "adjacent_loop_items")]
218            "previtem" => Some(self.iter.lock().unwrap().prev_item()),
219            #[cfg(feature = "adjacent_loop_items")]
220            "nextitem" => Some(self.iter.lock().unwrap().next_item()),
221            _ => None,
222        }
223    }
224
225    fn render(self: &Arc<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226        write!(
227            f,
228            "<loop {}/{}>",
229            self.idx.load(Ordering::Relaxed),
230            match self.len {
231                Some(ref len) => len as &dyn fmt::Display,
232                None => &"?" as &dyn fmt::Display,
233            },
234        )
235    }
236}