use std::collections::BTreeMap;
use femstache_types::{
context::TyContext,
vars::{Ty, TyId},
};
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
Null,
Bool(bool),
String(String),
List(Vec<Value>),
Dict(BTreeMap<String, Value>),
Struct(BTreeMap<String, Value>),
}
impl Value {
pub fn is_null(&self) -> bool {
matches!(self, Value::Null)
}
pub fn is_bool(&self) -> bool {
matches!(self, Value::Bool(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Value::String(_))
}
pub fn is_list(&self) -> bool {
matches!(self, Value::List(_))
}
pub fn is_dict(&self) -> bool {
matches!(self, Value::Dict(_))
}
pub fn is_struct(&self) -> bool {
matches!(self, Value::Struct(_))
}
pub fn as_bool(&self) -> Option<bool> {
if let &Value::Bool(b) = self {
Some(b)
} else {
None
}
}
pub fn as_str(&self) -> Option<&str> {
if let Value::String(s) = self {
Some(s)
} else {
None
}
}
pub fn as_list(&self) -> Option<&[Value]> {
if let Value::List(l) = self {
Some(l)
} else {
None
}
}
pub fn as_dict(&self) -> Option<&BTreeMap<String, Value>> {
if let Value::Dict(m) = self {
Some(m)
} else {
None
}
}
pub fn as_struct(&self) -> Option<&BTreeMap<String, Value>> {
if let Value::Struct(m) = self {
Some(m)
} else {
None
}
}
pub fn into_string(self) -> Option<String> {
if let Value::String(s) = self {
Some(s)
} else {
None
}
}
pub fn into_list(self) -> Option<Vec<Value>> {
if let Value::List(l) = self {
Some(l)
} else {
None
}
}
pub fn is_truthy(&self) -> bool {
match self {
Value::Null => false,
Value::Bool(b) => *b,
Value::String(s) => !s.is_empty(),
Value::List(l) => !l.is_empty(),
Value::Dict(m) => !m.is_empty(),
Value::Struct(s) => !s.is_empty(),
}
}
pub fn conforms_to(&self, ctx: &TyContext, ty_id: TyId) -> bool {
let ty = ctx.resolve(ty_id);
match (self, ty) {
(Value::Bool(_), Ty::Bool) => true,
(Value::String(_), Ty::String) => true,
(Value::Null, Ty::Optional(_)) => true,
(Value::List(items), Ty::List(item_id)) => {
items.iter().all(|i| i.conforms_to(ctx, item_id))
}
(Value::Dict(vals), Ty::Dict(val_id)) => {
vals.values().all(|v| v.conforms_to(ctx, val_id))
}
(Value::Struct(fields), Ty::Struct(ty_fields, _)) => {
for (fname, fty) in ty_fields {
let Some(value) = fields.get(&fname) else {
return false;
};
if !value.conforms_to(ctx, fty) {
return false;
}
}
true
}
(_, Ty::Var(_)) => true,
(_, _) => false,
}
}
}
pub trait ToValue {
fn to_value(&self) -> Value;
}
impl ToValue for Value {
fn to_value(&self) -> Value {
self.clone()
}
}
impl<T: ToValue> ToValue for &T {
fn to_value(&self) -> Value {
<T as ToValue>::to_value(self)
}
}
impl<T: ToValue> ToValue for &[T] {
fn to_value(&self) -> Value {
Value::List(self.iter().map(|e| e.to_value()).collect())
}
}
impl ToValue for &str {
fn to_value(&self) -> Value {
Value::String((*self).to_owned())
}
}
impl ToValue for String {
fn to_value(&self) -> Value {
Value::String(self.clone())
}
}
macro_rules! impl_to_value_via_to_string {
($name:ident) => {
impl ToValue for $name {
fn to_value(&self) -> Value {
Value::String(<$name as ::std::string::ToString>::to_string(self))
}
}
};
($($name:ident,)*) => {
$(impl_to_value_via_to_string! { $name })*
}
}
impl_to_value_via_to_string! {
u8, u16, u32, u64, u128,
i8, i16, i32, i64, i128,
bool,
}