use std::collections::btree_map as map;
use std::iter::Enumerate;
use std::slice;
use std::vec as list;
use crate::render::value::lookup;
use crate::types::ast;
use crate::types::span::Span;
use crate::value::ValueCow;
use crate::{Error, Result, Value};
#[cfg_attr(internal_debug, derive(Debug))]
pub enum LoopState<'a> {
ListBorrowed {
i: &'a str,
iter: Enumerate<slice::Iter<'a, Value>>,
value: Option<(usize, &'a Value)>,
},
ListOwned {
i: &'a str,
iter: Enumerate<list::IntoIter<Value>>,
value: Option<(usize, Value)>,
},
MapBorrowed {
k: &'a str,
v: &'a str,
iter: Enumerate<map::Iter<'a, String, Value>>,
value: Option<(usize, (&'a String, &'a Value))>,
},
MapOwned {
k: &'a str,
v: &'a str,
iter: Enumerate<map::IntoIter<String, Value>>,
value: Option<(usize, (String, Value))>,
},
}
impl<'a> LoopState<'a> {
pub fn new(
source: &'a str,
vars: &'a ast::LoopVars,
iterable: ValueCow<'a>,
span: Span,
) -> Result<Self> {
let human = iterable.human();
let err = || {
Error::render(
format!("expected iterable, but expression evaluated to {human}"),
source,
span,
)
};
let unpack_list_item = |vars: &'a ast::LoopVars| match vars {
ast::LoopVars::Item(item) => Ok(item),
ast::LoopVars::KeyValue(kv) => Err(Error::render(
"cannot unpack list item into two variables",
source,
kv.span,
)),
};
let unpack_map_item = |vars: &'a ast::LoopVars| match vars {
ast::LoopVars::Item(item) => Err(Error::render(
"cannot unpack map item into one variable",
source,
item.span,
)),
ast::LoopVars::KeyValue(kv) => Ok(kv),
};
match iterable {
ValueCow::Borrowed(v) => match v {
Value::List(list) => {
let item = unpack_list_item(vars)?;
Ok(Self::ListBorrowed {
i: &source[item.span],
iter: list.iter().enumerate(),
value: None,
})
}
Value::Map(map) => {
let kv = unpack_map_item(vars)?;
Ok(Self::MapBorrowed {
k: &source[kv.key.span],
v: &source[kv.value.span],
iter: map.iter().enumerate(),
value: None,
})
}
_ => Err(err()),
},
ValueCow::Owned(v) => match v {
Value::List(list) => {
let item = unpack_list_item(vars)?;
Ok(Self::ListOwned {
i: &source[item.span],
iter: list.into_iter().enumerate(),
value: None,
})
}
Value::Map(map) => {
let kv = unpack_map_item(vars)?;
Ok(Self::MapOwned {
k: &source[kv.key.span],
v: &source[kv.value.span],
iter: map.into_iter().enumerate(),
value: None,
})
}
_ => Err(err()),
},
}
}
pub fn iterate(&mut self) -> Option<()> {
match self {
Self::ListBorrowed { iter, value, .. } => {
*value = Some(iter.next()?);
}
Self::ListOwned { iter, value, .. } => {
*value = Some(iter.next()?);
}
Self::MapBorrowed { iter, value, .. } => {
*value = Some(iter.next()?);
}
Self::MapOwned { iter, value, .. } => {
*value = Some(iter.next()?);
}
}
Some(())
}
pub fn lookup_var(&self, source: &str, var: &ast::Var) -> Result<Option<ValueCow<'a>>> {
let name = match var.first().access {
ast::Access::Index(_) => return Ok(None),
ast::Access::Key(ast::Ident { span }) => &source[span],
};
if name == "loop" {
return self.lookup_loop(source, &var.path);
}
macro_rules! resolve {
($v:expr) => {{
let mut v = $v;
for m in var.rest() {
v = match lookup(source, v, m)? {
Some(v) => v,
None => return Ok(None),
};
}
v
}};
}
let err = |span| Error::render("cannot index into string", source, span);
match self {
Self::ListBorrowed {
i,
value: Some((_, value)),
..
} if name == *i => {
let v = resolve!(*value);
Ok(Some(ValueCow::Borrowed(v)))
}
Self::ListOwned {
i,
value: Some((_, value)),
..
} if name == *i => {
let v = resolve!(value);
Ok(Some(ValueCow::Owned(v.clone())))
}
Self::MapBorrowed {
k,
value: Some((_, (string, _))),
..
} if name == *k => {
if let [m, ..] = var.rest() {
return Err(err(m.span));
}
Ok(Some(ValueCow::Owned(Value::String((*string).clone()))))
}
Self::MapOwned {
k,
value: Some((_, (string, _))),
..
} if name == *k => {
if let [m, ..] = var.rest() {
return Err(err(m.span));
}
Ok(Some(ValueCow::Owned(Value::String(string.clone()))))
}
Self::MapBorrowed {
v,
value: Some((_, (_, value))),
..
} if name == *v => {
let v = resolve!(*value);
Ok(Some(ValueCow::Borrowed(v)))
}
Self::MapOwned {
v,
value: Some((_, (_, value))),
..
} if name == *v => {
let v = resolve!(value);
Ok(Some(ValueCow::Owned(v.clone())))
}
_ => Ok(None),
}
}
pub fn lookup_loop(&self, source: &str, path: &[ast::Member]) -> Result<Option<ValueCow<'a>>> {
let (i, rem) = match self.current_index_and_rem() {
Some((i, rem)) => (i, rem),
None => return Ok(None),
};
if path.len() == 1 {
return Ok(Some(ValueCow::Owned(Value::from([
("index", Value::Integer(i as i64)),
("first", Value::Bool(i == 0)),
("last", Value::Bool(rem == 0)),
]))));
}
let member = &path[1];
let name = match member.access {
ast::Access::Index(_) => {
return Err(Error::render(
"cannot index into map with integer",
source,
member.span,
))
}
ast::Access::Key(ast::Ident { span }) => &source[span],
};
let v = match (&member.op, name) {
(_, "index") => Value::Integer(i as i64),
(_, "first") => Value::Bool(i == 0),
(_, "last") => Value::Bool(rem == 0),
(ast::AccessOp::Optional, _) => Value::None,
(ast::AccessOp::Direct, _) => {
return Err(Error::render("not found in map", source, member.span))
}
};
if !path[2..].is_empty() {
return Err(Error::render(
format!("cannot index into {}", v.human()),
source,
path[2].span,
));
}
Ok(Some(ValueCow::Owned(v)))
}
fn current_index_and_rem(&self) -> Option<(usize, usize)> {
match self {
LoopState::ListBorrowed {
iter,
value: Some((i, _)),
..
} => Some((*i, iter.len())),
LoopState::ListOwned {
iter,
value: Some((i, _)),
..
} => Some((*i, iter.len())),
LoopState::MapBorrowed {
iter,
value: Some((i, _)),
..
} => Some((*i, iter.len())),
LoopState::MapOwned {
iter,
value: Some((i, _)),
..
} => Some((*i, iter.len())),
_ => None,
}
}
}