extern crate alloc;
use alloc::boxed::Box;
use core::any::TypeId;
use core::fmt;
use core::marker::PhantomData;
use crate::hlist::{Coniunctio, HList, Nihil};
pub struct Variatio<R> {
tag: TypeId,
value: Box<dyn core::any::Any + Send + Sync>,
_row: PhantomData<R>,
}
pub trait HabetCasum<Label, Value, Index>: Sized {}
pub struct CasusHic;
pub struct CasusIbi<T>(PhantomData<T>);
impl<Label, Value, Tail: HList> HabetCasum<Label, Value, CasusHic>
for Coniunctio<(Label, Value), Tail>
{
}
impl<Label, Value, Head, Tail, TailIndex> HabetCasum<Label, Value, CasusIbi<TailIndex>>
for Coniunctio<Head, Tail>
where
Tail: HabetCasum<Label, Value, TailIndex>,
{
}
#[inline]
pub fn inject<Label: 'static, Value: Send + Sync + 'static, R, Index>(value: Value) -> Variatio<R>
where
R: HabetCasum<Label, Value, Index>,
{
Variatio {
tag: TypeId::of::<Label>(),
value: Box::new(value),
_row: PhantomData,
}
}
impl<R> Variatio<R> {
#[inline]
pub fn tag(&self) -> TypeId {
self.tag
}
#[inline]
pub fn is<Label: 'static>(&self) -> bool {
self.tag == TypeId::of::<Label>()
}
#[inline]
pub fn try_get<Label: 'static, Value: 'static>(&self) -> Option<&Value> {
if self.is::<Label>() {
self.value.downcast_ref()
} else {
None
}
}
#[inline]
pub fn match_on(self) -> MatchBuilder<R, Nihil> {
MatchBuilder {
variant: self,
_handled: PhantomData,
}
}
#[inline]
pub fn on<Label, Value, F, T, Index>(self, f: F) -> CaseResult<R, T>
where
Label: 'static,
Value: 'static,
F: FnOnce(Value) -> T,
R: HabetCasum<Label, Value, Index>,
{
if self.is::<Label>() {
let value = *self
.value
.downcast::<Value>()
.expect("unreachable: HabetCasum bound guarantees the value type for this label");
CaseResult::Matched(f(value))
} else {
CaseResult::Unmatched(Variatio {
tag: self.tag,
value: self.value,
_row: PhantomData,
})
}
}
}
pub struct MatchBuilder<R, Handled> {
variant: Variatio<R>,
_handled: PhantomData<Handled>,
}
impl<R, Handled> MatchBuilder<R, Handled> {
#[inline]
pub fn case<Label, Value, F, T, Index>(self, f: F) -> CaseResult<R, T>
where
Label: 'static,
Value: 'static,
F: FnOnce(Value) -> T,
R: HabetCasum<Label, Value, Index>,
{
self.variant.on::<Label, Value, F, T, Index>(f)
}
}
pub enum CaseResult<R, T> {
Matched(T),
Unmatched(Variatio<R>),
}
impl<R, T> CaseResult<R, T> {
#[inline]
pub fn on<Label, Value, F, Index>(self, f: F) -> CaseResult<R, T>
where
Label: 'static,
Value: 'static,
F: FnOnce(Value) -> T,
R: HabetCasum<Label, Value, Index>,
{
match self {
CaseResult::Matched(t) => CaseResult::Matched(t),
CaseResult::Unmatched(v) => v.on::<Label, Value, F, T, Index>(f),
}
}
#[inline]
pub fn otherwise(self, default: T) -> T {
match self {
CaseResult::Matched(t) => t,
CaseResult::Unmatched(_) => default,
}
}
#[inline]
pub fn otherwise_with<F: FnOnce() -> T>(self, f: F) -> T {
match self {
CaseResult::Matched(t) => t,
CaseResult::Unmatched(_) => f(),
}
}
#[inline]
pub fn exhaust(self) -> T {
match self {
CaseResult::Matched(t) => t,
CaseResult::Unmatched(_) => crate::cold_panic!("non-exhaustive match on Variatio"),
}
}
}
pub trait ExtendoCasum<Label, Value>: Sized {
type Output;
fn widen(self) -> Variatio<Self::Output>;
}
impl<R, Label, Value> ExtendoCasum<Label, Value> for Variatio<R> {
type Output = Coniunctio<(Label, Value), R>;
#[inline]
fn widen(self) -> Variatio<Self::Output> {
Variatio {
tag: self.tag,
value: self.value,
_row: PhantomData,
}
}
}
impl<R> fmt::Debug for Variatio<R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Variatio {{ tag: {:?}, ... }}", self.tag)
}
}
pub type Casus<Label, Value> = (Label, Value);
#[cfg(test)]
mod tests {
use super::*;
use crate::labelled::chars::*;
use alloc::string::String;
type Success = (Ls, Lu, Lc, Lc, Le, Ls, Ls);
type Error = (Le, Lr, Lr, Lo, Lr);
type ResultRow = Coniunctio<Casus<Success, i32>, Coniunctio<Casus<Error, String>, Nihil>>;
#[test]
fn test_inject() {
let v: Variatio<ResultRow> = inject::<Success, _, _, _>(42);
assert!(v.is::<Success>());
assert!(!v.is::<Error>());
}
#[test]
fn test_try_get() {
let v: Variatio<ResultRow> = inject::<Success, _, _, _>(42i32);
assert_eq!(v.try_get::<Success, i32>(), Some(&42));
assert_eq!(v.try_get::<Error, String>(), None);
}
#[test]
fn test_on_matched() {
let v: Variatio<ResultRow> = inject::<Success, _, _, _>(42i32);
let result = v.on::<Success, i32, _, _, _>(|n| n * 2).otherwise(0);
assert_eq!(result, 84);
}
#[test]
fn test_on_unmatched() {
let v: Variatio<ResultRow> = inject::<Error, _, _, _>(String::from("oops"));
let result = v.on::<Success, i32, _, _, _>(|n| n * 2).otherwise(-1);
assert_eq!(result, -1);
}
#[test]
fn test_multiple_cases() {
let v1: Variatio<ResultRow> = inject::<Success, _, _, _>(42i32);
let v2: Variatio<ResultRow> = inject::<Error, _, _, _>(String::from("oops"));
let r1 = v1
.on::<Success, i32, _, _, _>(|n| alloc::format!("ok: {n}"))
.on::<Error, String, _, _>(|e| alloc::format!("err: {e}"))
.exhaust();
let r2 = v2
.on::<Success, i32, _, _, _>(|n| alloc::format!("ok: {n}"))
.on::<Error, String, _, _>(|e| alloc::format!("err: {e}"))
.exhaust();
assert_eq!(r1, "ok: 42");
assert_eq!(r2, "err: oops");
}
#[test]
fn test_widen() {
type NewError = (Ln, Le, Lw, DoubleUnderscore, Le, Lr, Lr);
let v: Variatio<ResultRow> = inject::<Success, _, _, _>(42i32);
let widened: Variatio<Coniunctio<Casus<NewError, bool>, ResultRow>> = v.widen();
assert!(widened.is::<Success>());
}
#[test]
fn test_otherwise_with() {
let v: Variatio<ResultRow> = inject::<Error, _, _, _>(String::from("oops"));
let result = v.on::<Success, i32, _, _, _>(|n| n).otherwise_with(|| 999);
assert_eq!(result, 999);
}
}