use crate::algebraic_value::de::{ValueDeserializeError, ValueDeserializer};
use crate::algebraic_value::ser::value_serialize;
use crate::de::Deserialize;
use crate::meta_type::MetaType;
use crate::raw_identifier::RawIdentifier;
use crate::{AlgebraicType, AlgebraicValue, SpacetimeType, SumTypeVariant, SumValue, Typespace};
pub const SCHEDULE_AT_INTERVAL_TAG: &str = "Interval";
pub const SCHEDULE_AT_TIME_TAG: &str = "Time";
pub const OPTION_SOME_TAG: &str = "some";
pub const OPTION_NONE_TAG: &str = "none";
pub const RESULT_OK_TAG: &str = "ok";
pub const RESULT_ERR_TAG: &str = "err";
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, SpacetimeType)]
#[sats(crate = crate)]
pub struct SumType {
pub variants: Box<[SumTypeVariant]>,
}
impl std::fmt::Debug for SumType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("SumType ")?;
f.debug_map()
.entries(
self.variants
.iter()
.map(|variant| (crate::dbg_aggregate_name(&variant.name), &variant.algebraic_type)),
)
.finish()
}
}
impl SumType {
pub const fn new(variants: Box<[SumTypeVariant]>) -> Self {
Self { variants }
}
pub fn new_unnamed(types: Box<[AlgebraicType]>) -> Self {
let variants = Vec::from(types).into_iter().map(|ty| ty.into()).collect();
Self { variants }
}
pub fn as_option(&self) -> Option<&AlgebraicType> {
match &*self.variants {
[first, second] if Self::are_variants_option(first, second) => Some(&first.algebraic_type),
_ => None,
}
}
pub fn as_option_mut(&mut self) -> Option<&mut AlgebraicType> {
match &mut *self.variants {
[first, second] if Self::are_variants_option(first, second) => Some(&mut first.algebraic_type),
_ => None,
}
}
fn are_variants_option(first: &SumTypeVariant, second: &SumTypeVariant) -> bool {
second.is_unit() && first.has_name(OPTION_SOME_TAG)
&& second.has_name(OPTION_NONE_TAG)
}
pub fn is_option(&self) -> bool {
self.as_option().is_some()
}
pub fn as_result(&self) -> Option<(&AlgebraicType, &AlgebraicType)> {
match &*self.variants {
[first, second] if Self::are_variants_result(first, second) => {
Some((&first.algebraic_type, &second.algebraic_type))
}
_ => None,
}
}
pub fn as_result_mut(&mut self) -> Option<(&mut AlgebraicType, &mut AlgebraicType)> {
match &mut *self.variants {
[first, second] if Self::are_variants_result(first, second) => {
Some((&mut first.algebraic_type, &mut second.algebraic_type))
}
_ => None,
}
}
fn are_variants_result(first: &SumTypeVariant, second: &SumTypeVariant) -> bool {
first.has_name(RESULT_OK_TAG) && second.has_name(RESULT_ERR_TAG)
}
pub fn is_result(&self) -> bool {
self.as_result().is_some()
}
pub fn is_empty(&self) -> bool {
self.variants.is_empty()
}
pub fn is_schedule_at(&self) -> bool {
match &*self.variants {
[first, second] => {
first.has_name(SCHEDULE_AT_INTERVAL_TAG)
&& first.algebraic_type.is_time_duration()
&& second.has_name(SCHEDULE_AT_TIME_TAG)
&& second.algebraic_type.is_timestamp()
}
_ => false,
}
}
pub fn is_special(&self) -> bool {
self.is_option() || self.is_schedule_at() || self.is_result()
}
pub fn is_simple_enum(&self) -> bool {
self.variants.iter().all(SumTypeVariant::is_unit)
}
pub fn get_variant(&self, tag_name: &str) -> Option<(u8, &SumTypeVariant)> {
self.variants.iter().enumerate().find_map(|(pos, x)| {
if x.name.as_deref() == Some(tag_name) {
Some((pos as u8, x))
} else {
None
}
})
}
pub fn get_variant_simple(&self, tag_name: &str) -> Option<(u8, &SumTypeVariant)> {
if self.is_simple_enum() {
self.get_variant(tag_name)
} else {
None
}
}
pub fn get_variant_by_tag(&self, tag: u8) -> Option<&SumTypeVariant> {
self.variants.get(tag as usize)
}
pub fn type_check(&self, sv: &SumValue, typespace: &Typespace) -> bool {
self.get_variant_by_tag(sv.tag)
.is_some_and(|var| var.algebraic_type.type_check(&sv.value, typespace))
}
}
impl From<Box<[SumTypeVariant]>> for SumType {
fn from(fields: Box<[SumTypeVariant]>) -> Self {
SumType::new(fields)
}
}
impl<const N: usize> From<[SumTypeVariant; N]> for SumType {
fn from(fields: [SumTypeVariant; N]) -> Self {
SumType::new(fields.into())
}
}
impl<const N: usize> From<[(Option<&'static str>, AlgebraicType); N]> for SumType {
fn from(fields: [(Option<&'static str>, AlgebraicType); N]) -> Self {
fields.map(|(s, t)| SumTypeVariant::new(t, s.map(Into::into))).into()
}
}
impl<Id: Into<RawIdentifier>, const N: usize> From<[(Id, AlgebraicType); N]> for SumType {
fn from(fields: [(Id, AlgebraicType); N]) -> Self {
fields.map(|(s, t)| SumTypeVariant::new_named(t, s)).into()
}
}
impl<const N: usize> From<[AlgebraicType; N]> for SumType {
fn from(fields: [AlgebraicType; N]) -> Self {
fields.map(SumTypeVariant::from).into()
}
}
impl MetaType for SumType {
fn meta_type() -> AlgebraicType {
AlgebraicType::product([("variants", AlgebraicType::array(SumTypeVariant::meta_type()))])
}
}
impl SumType {
pub fn as_value(&self) -> AlgebraicValue {
value_serialize(self)
}
pub fn from_value(value: &AlgebraicValue) -> Result<SumType, ValueDeserializeError> {
Self::deserialize(ValueDeserializer::from_ref(value))
}
}