use crate::core::environment::{LocalEntry, LoopCtx, RunSignal};
use crate::core::eval;
use crate::core::exec::ExecOutcome;
use crate::core::{Element, ElementKind, Expr};
use crate::error::{FlowKind, Result, TemplateError};
use crate::template::TModel;
use std::cell::RefCell;
use std::rc::Rc;
pub struct IteratorBlock {
pub seq: Expr,
pub var: String,
pub var2: Option<String>,
pub body: Vec<Element>,
pub else_: Option<Vec<Element>>,
}
impl IteratorBlock {
pub fn new(
seq: Expr,
var: String,
var2: Option<String>,
body: Vec<Element>,
else_: Option<Vec<Element>>,
) -> Self {
IteratorBlock {
seq,
var,
var2,
body,
else_,
}
}
pub(crate) fn exec(&self, env: &mut crate::core::Environment) -> Result<ExecOutcome> {
exec_list(
env,
&self.seq,
&self.var,
&self.var2,
&self.body,
&self.else_,
)
}
}
fn exec_list(
env: &mut crate::core::Environment,
seq_expr: &crate::core::Expr,
var: &str,
var2: &Option<String>,
body: &[Element],
else_: &Option<Vec<Element>>,
) -> Result<ExecOutcome> {
let listed = eval::eval(env, seq_expr)?;
let listed = if listed.is_nothing() && env.settings.classic_compatible {
TModel::from_sequence(Vec::new())
} else {
listed
};
let hash_listing = var2.is_some()
|| (var.is_empty()
&& body
.iter()
.any(|el| matches!(&el.kind, ElementKind::Items { var2: Some(_), .. })));
let mut items: crate::core::environment::PendingItems =
materialize_list_items(env, &listed, hash_listing)?;
if !items.has_next()? {
if let Some(e) = else_ {
return Ok(ExecOutcome::Next(e.clone()));
}
return Ok(ExecOutcome::Done);
}
let lc = Rc::new(RefCell::new(LoopCtx {
var_name: var.to_string(),
var2_name: var2.clone(),
value: None,
key: None,
index: 0,
has_next: false,
pending: items,
items_entered: false,
}));
env.push_local(LocalEntry::Loop(lc.clone()));
let r = if !var.is_empty() {
run_loop_iterations(env, &lc, body)
} else {
match env.run(body) {
Ok(RunSignal::Completed) => Ok(ExecOutcome::Done),
Ok(RunSignal::Returned(v)) => Ok(ExecOutcome::ReturnValue(v)),
Err(e) => Err(e),
}
};
env.pop_local();
r
}
fn materialize_list_items(
env: &mut crate::core::Environment,
listed: &TModel,
hash_listing: bool,
) -> Result<crate::core::environment::PendingItems> {
if hash_listing {
let ex = listed.hash_ex.clone().ok_or_else(|| {
TemplateError::misc(format!(
"The value you try to list is a {}, thus you must specify only one loop variable after the \"as\" (there's no separate key and value).",
listed.type_name
))
})?;
let mut out = std::collections::VecDeque::new();
for (key, value) in ex.entries()? {
out.push_back(crate::core::environment::LoopItem {
key: Some(TModel::from_scalar(key)),
value: Some(value),
});
}
return Ok(crate::core::environment::PendingItems::eager(out));
}
if let Some(rs) = &listed.range {
if !rs.unbounded {
return Ok(crate::core::environment::PendingItems::range(
crate::core::environment::RangeIterState {
start: rs.start,
index: 0,
cap: rs.count,
ascending: rs.ascending,
},
));
}
}
if let Some(c) = &listed.collection {
let iter = c.iterator()?;
return Ok(crate::core::environment::PendingItems::lazy(Box::new(
iter.map(|r| {
r.map(|v| crate::core::environment::LoopItem {
key: None,
value: Some(v),
})
}),
)));
}
if let Some(s) = &listed.sequence {
let size = s.size()?;
let mut out = std::collections::VecDeque::new();
for i in 0..size {
let v = s.get(i)?;
out.push_back(crate::core::environment::LoopItem {
key: None,
value: Some(v),
});
}
return Ok(crate::core::environment::PendingItems::eager(out));
}
if env.settings.classic_compatible {
return Ok(crate::core::environment::PendingItems::eager(
std::collections::VecDeque::from([crate::core::environment::LoopItem {
key: None,
value: Some(listed.clone()),
}]),
));
}
let _ = env;
Err(TemplateError::misc(format!(
"The value you try to list is a {}; it must be a sequence or collection.",
listed.type_name
)))
}
pub(crate) fn run_loop_iterations(
env: &mut crate::core::Environment,
lc: &Rc<RefCell<LoopCtx>>,
block: &[Element],
) -> Result<ExecOutcome> {
loop {
let item_pending = {
let mut c = lc.borrow_mut();
match c.pending.pop()? {
Some(i) => {
c.key = i.key.clone();
c.value = i.value;
c.has_next = c.pending.has_next()?;
true
}
None => false,
}
};
if !item_pending {
break;
}
match env.run(block) {
Ok(RunSignal::Completed) => {}
Ok(RunSignal::Returned(v)) => return Ok(ExecOutcome::ReturnValue(v)),
Err(TemplateError::Flow(FlowKind::Break)) => break,
Err(TemplateError::Flow(FlowKind::Continue)) => {
lc.borrow_mut().index += 1;
continue;
}
Err(e) => return Err(e),
}
lc.borrow_mut().index += 1;
}
Ok(ExecOutcome::Done)
}