///|
let loop_object_keys : Array[String] = [
"index0", "index", "length", "revindex", "revindex0", "first", "last", "depth",
"depth0", "previtem", "nextitem",
]
///|
/// Wraps the loop iterator so that the previous and next items can be
/// looked at (`loop.previtem` and `loop.nextitem`).
priv struct AdjacentLoopItemIter {
mut prev_item : Value?
mut current_item : Value?
mut next_item : Value?
iter : Iter[Value]
}
///|
fn AdjacentLoopItemIter::next(self : AdjacentLoopItemIter) -> Value? {
self.prev_item = self.current_item
self.current_item = match self.next_item {
Some(v) => {
self.next_item = None
Some(v)
}
None => self.iter.next()
}
self.current_item
}
///|
fn AdjacentLoopItemIter::prev_item(self : AdjacentLoopItemIter) -> Value {
self.prev_item.unwrap_or(Value::undefined())
}
///|
fn AdjacentLoopItemIter::peek_next_item(self : AdjacentLoopItemIter) -> Value {
match self.next_item {
Some(v) => v
None => {
self.next_item = self.iter.next()
self.next_item.unwrap_or(Value::undefined())
}
}
}
///|
/// The object behind the `loop` variable.
priv struct LoopObject {
len : Int?
/// The current index, `-1` before the first iteration.
mut idx : Int
depth : Int
mut last_changed_value : Array[Value]?
recurse_jump_target : Int?
iter : AdjacentLoopItemIter
}
///|
fn LoopObject::get_value_by_str(self : LoopObject, key : String) -> Value? {
let idx = self.idx
// if we never iterated, then all attributes are undefined.
if idx < 0 {
return Some(Value::undefined())
}
match key {
"index" => Some(Value::from_int(idx + 1))
"index0" => Some(Value::from_int(idx))
"length" =>
Some(
match self.len {
Some(len) => Value::from_int(len)
None => Value::undefined()
},
)
"revindex" =>
Some(
match self.len {
Some(len) => Value::from_int(if len > idx { len - idx } else { 0 })
None => Value::undefined()
},
)
"revindex0" =>
Some(
match self.len {
Some(len) =>
Value::from_int(if len - idx - 1 > 0 { len - idx - 1 } else { 0 })
None => Value::undefined()
},
)
"first" => Some(Value::from_bool(idx == 0))
"last" =>
Some(
match self.len {
Some(len) => Value::from_bool(len == 0 || idx == len - 1)
None => Value::from_bool(false)
},
)
"depth" => Some(Value::from_int(self.depth + 1))
"depth0" => Some(Value::from_int(self.depth))
"previtem" => Some(self.iter.prev_item())
"nextitem" => Some(self.iter.peek_next_item())
_ => None
}
}
///|
fn LoopObject::call_method(
self : LoopObject,
name : String,
args : Array[Value],
) -> Value raise TemplateError {
match name {
"changed" => {
let value = args.copy()
let changed = match self.last_changed_value {
Some(last) => last != value
None => true
}
if changed {
self.last_changed_value = Some(value)
Value::from_bool(true)
} else {
Value::from_bool(false)
}
}
"cycle" =>
if args.is_empty() {
Value::undefined()
} else {
let idx = if self.idx < 0 { 0 } else { self.idx }
args[idx % args.length()]
}
_ => raise TemplateError::from_kind(UnknownMethod)
}
}
///|
fn LoopObject::render(self : LoopObject) -> String {
let idx = if self.idx < 0 {
"18446744073709551615"
} else {
self.idx.to_string()
}
let len = match self.len {
Some(len) => len.to_string()
None => "?"
}
""
}
///|
/// Per frame loop state.
priv struct LoopState {
with_loop_var : Bool
/// If we're popping the frame, do we want to jump somewhere? The first
/// item is the target jump instruction, the second tells us if we need to
/// end capturing.
mut current_recursion_jump : (Int, Bool)?
object : LoopObject
value : Value
}
///|
fn LoopState::new(
iter : Iter[Value],
depth : Int,
with_loop_var : Bool,
recurse_jump_target : Int?,
current_recursion_jump : (Int, Bool)?,
) -> LoopState {
let object = {
len: iter.size_hint(),
idx: -1,
depth,
last_changed_value: None,
recurse_jump_target,
iter: { prev_item: None, current_item: None, next_item: None, iter, },
}
{
with_loop_var,
current_recursion_jump,
object,
value: Value::Object(DynObject::new(Loop(object))),
}
}
///|
fn LoopState::did_not_iterate(self : LoopState) -> Bool {
self.object.idx == 0
}
///|
fn LoopState::next(self : LoopState) -> Value? {
self.object.idx += 1
self.object.iter.next()
}