use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::error::Result;
pub type ValueFunc = Arc<dyn Fn(&[Value]) -> Result<Value> + Send + Sync>;
pub enum Value {
Nil,
Bool(bool),
Int(i64),
Float(f64),
String(Arc<str>),
List(Arc<[Value]>),
Map(Arc<BTreeMap<Arc<str>, Value>>),
Function(ValueFunc),
}
impl Clone for Value {
fn clone(&self) -> Self {
match self {
Value::Nil => Value::Nil,
Value::Bool(b) => Value::Bool(*b),
Value::Int(n) => Value::Int(*n),
Value::Float(f) => Value::Float(*f),
Value::String(s) => Value::String(Arc::clone(s)),
Value::List(v) => Value::List(Arc::clone(v)),
Value::Map(m) => Value::Map(Arc::clone(m)),
Value::Function(f) => Value::Function(Arc::clone(f)),
}
}
}
impl fmt::Debug for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Nil => write!(f, "Nil"),
Value::Bool(b) => write!(f, "Bool({b:?})"),
Value::Int(n) => write!(f, "Int({n:?})"),
Value::Float(v) => write!(f, "Float({v:?})"),
Value::String(s) => write!(f, "String({s:?})"),
Value::List(v) => write!(f, "List({v:?})"),
Value::Map(m) => write!(f, "Map({m:?})"),
Value::Function(_) => write!(f, "Function(...)"),
}
}
}
impl Value {
pub fn is_truthy(&self) -> bool {
match self {
Value::Nil => false,
Value::Bool(b) => *b,
Value::Int(n) => *n != 0,
Value::Float(f) => *f != 0.0,
Value::String(s) => !s.is_empty(),
Value::List(v) => !v.is_empty(),
Value::Map(m) => !m.is_empty(),
Value::Function(_) => true,
}
}
pub fn field(&self, name: &str) -> Option<&Value> {
match self {
Value::Map(m) => m.get(name),
_ => None,
}
}
pub fn index(&self, idx: &Value) -> Result<Value> {
fn check_bounds(i: i64, len: usize) -> Result<usize> {
if i < 0 || (i as usize) >= len {
Err(crate::error::TemplateError::IndexOutOfRange { index: i })
} else {
Ok(i as usize)
}
}
match (self, idx) {
(Value::List(v), Value::Int(i)) => Ok(v[check_bounds(*i, v.len())?].clone()),
(Value::List(_), _) => Err(crate::error::TemplateError::Exec(format!(
"cannot index list with type {}",
idx.type_name()
))),
(Value::Map(m), Value::String(k)) => {
Ok(m.get(k.as_ref()).cloned().unwrap_or(Value::Nil))
}
(Value::Map(_), _) => Err(crate::error::TemplateError::Exec(format!(
"cannot index map with type {}",
idx.type_name()
))),
(Value::String(s), Value::Int(i)) => {
let bytes = s.as_bytes();
Ok(Value::Int(bytes[check_bounds(*i, bytes.len())?] as i64))
}
(Value::String(_), _) => Err(crate::error::TemplateError::Exec(format!(
"cannot index string with type {}",
idx.type_name()
))),
(Value::Nil, _) => Err(crate::error::TemplateError::Exec(
"index of untyped nil".into(),
)),
_ => Err(crate::error::TemplateError::Exec(format!(
"cannot index type {}",
self.type_name()
))),
}
}
pub fn type_name(&self) -> &'static str {
match self {
Value::Nil => "nil",
Value::Bool(_) => "bool",
Value::Int(_) => "int",
Value::Float(_) => "float64",
Value::String(_) => "string",
Value::List(_) => "list",
Value::Map(_) => "map",
Value::Function(_) => "func",
}
}
pub fn len(&self) -> Option<usize> {
match self {
Value::String(s) => Some(s.len()),
Value::List(v) => Some(v.len()),
Value::Map(m) => Some(m.len()),
_ => None,
}
}
pub fn is_empty(&self) -> Option<bool> {
self.len().map(|n| n == 0)
}
pub fn slice(&self, start: Option<i64>, end: Option<i64>) -> Result<Value> {
fn resolve(
kind: &str,
start: Option<i64>,
end: Option<i64>,
len: usize,
) -> Result<(usize, usize)> {
let len_i = len as i64;
let start = start.unwrap_or(0);
let end = end.unwrap_or(len_i);
if start < 0 || end < 0 || start > len_i || end > len_i || start > end {
return Err(crate::error::TemplateError::Exec(format!(
"slice: {kind} index out of range [{start}:{end}] with length {len}"
)));
}
Ok((start as usize, end as usize))
}
match self {
Value::List(v) => {
let (s, e) = resolve("list", start, end, v.len())?;
if s == 0 && e == v.len() {
return Ok(Value::List(Arc::clone(v)));
}
Ok(Value::List(Arc::from(&v[s..e])))
}
Value::String(str) => {
let (s, e) = resolve("string", start, end, str.len())?;
if !str.is_char_boundary(s) || !str.is_char_boundary(e) {
return Err(crate::error::TemplateError::Exec(
"slice: index not on UTF-8 character boundary".to_string(),
));
}
if s == 0 && e == str.len() {
return Ok(Value::String(Arc::clone(str)));
}
Ok(Value::String(Arc::from(&str[s..e])))
}
_ => Err(crate::error::TemplateError::Exec(format!(
"slice: cannot slice type {}",
self.type_name()
))),
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
pub fn as_int(&self) -> Option<i64> {
match self {
Value::Int(n) => Some(*n),
Value::Float(f) => Some(*f as i64),
_ => None,
}
}
pub fn as_float(&self) -> Option<f64> {
match self {
Value::Float(f) => Some(*f),
Value::Int(n) => Some(*n as f64),
_ => None,
}
}
pub fn is_function(&self) -> bool {
matches!(self, Value::Function(_))
}
#[doc(hidden)]
pub fn from_entries<const N: usize>(entries: [(String, Value); N]) -> Self {
Value::Map(Arc::new(
entries
.into_iter()
.map(|(k, v)| (Arc::from(k), v))
.collect(),
))
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Value::Nil => write!(f, "<nil>"),
Value::Bool(b) => write!(f, "{b}"),
Value::Int(n) => write!(f, "{n}"),
Value::Float(v) => write!(f, "{v}"),
Value::String(s) => write!(f, "{s}"),
Value::List(v) => {
write!(f, "[")?;
for (i, item) in v.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{item}")?;
}
write!(f, "]")
}
Value::Map(m) => {
write!(f, "map[")?;
for (i, (k, v)) in m.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
write!(f, "{k}:{v}")?;
}
write!(f, "]")
}
Value::Function(_) => write!(f, "<func>"),
}
}
}
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Nil, Value::Nil) => true,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Int(a), Value::Int(b)) => a == b,
(Value::Float(a), Value::Float(b)) => a == b,
(Value::String(a), Value::String(b)) => a == b,
(Value::List(a), Value::List(b)) => a == b,
(Value::Map(a), Value::Map(b)) => a == b,
_ => false,
}
}
}
impl PartialOrd for Value {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
match (self, other) {
(Value::Int(a), Value::Int(b)) => a.partial_cmp(b),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b),
(Value::String(a), Value::String(b)) => a.partial_cmp(b),
_ => None,
}
}
}
pub trait ToValue {
fn to_value(&self) -> Value;
}
impl ToValue for Value {
fn to_value(&self) -> Value {
self.clone()
}
}
impl ToValue for bool {
fn to_value(&self) -> Value {
Value::Bool(*self)
}
}
macro_rules! impl_to_value_int {
($($t:ty),*) => {
$(impl ToValue for $t {
fn to_value(&self) -> Value {
Value::Int(*self as i64)
}
})*
};
}
impl_to_value_int!(i8, i16, i32, i64, u8, u16, u32, u64, isize, usize);
impl ToValue for f32 {
fn to_value(&self) -> Value {
Value::Float(*self as f64)
}
}
impl ToValue for f64 {
fn to_value(&self) -> Value {
Value::Float(*self)
}
}
impl ToValue for str {
fn to_value(&self) -> Value {
Value::String(Arc::from(self))
}
}
impl ToValue for String {
fn to_value(&self) -> Value {
Value::String(Arc::from(self.as_str()))
}
}
impl ToValue for alloc::borrow::Cow<'_, str> {
fn to_value(&self) -> Value {
Value::String(Arc::from(self.as_ref()))
}
}
impl<T: ToValue + ?Sized> ToValue for &T {
fn to_value(&self) -> Value {
(*self).to_value()
}
}
impl<T: ToValue> ToValue for Option<T> {
fn to_value(&self) -> Value {
match self {
Some(v) => v.to_value(),
None => Value::Nil,
}
}
}
fn list_from_iter<'a, T: ToValue + 'a, I: IntoIterator<Item = &'a T>>(iter: I) -> Value {
Value::List(
iter.into_iter()
.map(ToValue::to_value)
.collect::<Vec<_>>()
.into(),
)
}
fn map_from_iter_str<'a, T: ToValue + 'a, I: IntoIterator<Item = (&'a str, &'a T)>>(
iter: I,
) -> Value {
Value::Map(Arc::new(
iter.into_iter()
.map(|(k, v)| (Arc::from(k), v.to_value()))
.collect(),
))
}
impl<T: ToValue> ToValue for [T] {
fn to_value(&self) -> Value {
list_from_iter(self.iter())
}
}
impl<T: ToValue, const N: usize> ToValue for [T; N] {
fn to_value(&self) -> Value {
list_from_iter(self.iter())
}
}
impl<T: ToValue> ToValue for Vec<T> {
fn to_value(&self) -> Value {
list_from_iter(self.iter())
}
}
impl<T: ToValue> ToValue for alloc::collections::VecDeque<T> {
fn to_value(&self) -> Value {
list_from_iter(self.iter())
}
}
impl<T: ToValue> ToValue for alloc::collections::LinkedList<T> {
fn to_value(&self) -> Value {
list_from_iter(self.iter())
}
}
impl<T: ToValue> ToValue for alloc::collections::BTreeSet<T> {
fn to_value(&self) -> Value {
list_from_iter(self.iter())
}
}
#[cfg(feature = "std")]
impl<T: ToValue> ToValue for std::collections::HashSet<T> {
fn to_value(&self) -> Value {
let mut items: Vec<Value> = self.iter().map(ToValue::to_value).collect();
items.sort_by(|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal));
Value::List(items.into())
}
}
impl<T: ToValue> ToValue for BTreeMap<String, T> {
fn to_value(&self) -> Value {
map_from_iter_str(self.iter().map(|(k, v)| (k.as_str(), v)))
}
}
impl<T: ToValue> ToValue for BTreeMap<&str, T> {
fn to_value(&self) -> Value {
map_from_iter_str(self.iter().map(|(k, v)| (*k, v)))
}
}
#[cfg(feature = "std")]
impl<T: ToValue> ToValue for std::collections::HashMap<String, T> {
fn to_value(&self) -> Value {
map_from_iter_str(self.iter().map(|(k, v)| (k.as_str(), v)))
}
}
#[cfg(feature = "std")]
impl<T: ToValue> ToValue for std::collections::HashMap<&str, T> {
fn to_value(&self) -> Value {
map_from_iter_str(self.iter().map(|(k, v)| (*k, v)))
}
}
impl From<&str> for Value {
fn from(s: &str) -> Self {
Value::String(Arc::from(s))
}
}
impl From<String> for Value {
fn from(s: String) -> Self {
Value::String(Arc::from(s))
}
}
impl From<BTreeMap<String, Value>> for Value {
fn from(m: BTreeMap<String, Value>) -> Self {
Value::Map(Arc::new(
m.into_iter().map(|(k, v)| (Arc::from(k), v)).collect(),
))
}
}
impl From<BTreeMap<Arc<str>, Value>> for Value {
fn from(m: BTreeMap<Arc<str>, Value>) -> Self {
Value::Map(Arc::new(m))
}
}
impl From<Vec<Value>> for Value {
fn from(v: Vec<Value>) -> Self {
Value::List(v.into())
}
}
impl From<Arc<str>> for Value {
fn from(s: Arc<str>) -> Self {
Value::String(s)
}
}
impl From<Arc<[Value]>> for Value {
fn from(v: Arc<[Value]>) -> Self {
Value::List(v)
}
}
impl From<Arc<BTreeMap<Arc<str>, Value>>> for Value {
fn from(m: Arc<BTreeMap<Arc<str>, Value>>) -> Self {
Value::Map(m)
}
}
#[cfg(feature = "std")]
impl From<std::collections::HashMap<String, Value>> for Value {
fn from(m: std::collections::HashMap<String, Value>) -> Self {
Value::Map(Arc::new(
m.into_iter().map(|(k, v)| (Arc::from(k), v)).collect(),
))
}
}
#[macro_export]
macro_rules! tmap {
() => {
$crate::Value::from_entries([])
};
($($key:expr => $val:expr),+ $(,)?) => {
$crate::Value::from_entries([
$(($key.to_string(), $crate::ToValue::to_value(&$val)),)+
])
};
}