use std::{
collections::BTreeMap,
fmt::{self,Display},
mem,
};
use core_extensions::{
matches,
prelude::*,
};
use crate::{
StableAbi,
std_types::{
StaticStr,
StaticSlice,
RVec,
ROption,
RSome,
RNone,
RBox,
},
traits::IntoReprC,
utils::FmtPadding,
};
#[repr(C)]
#[derive(Debug,Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,StableAbi)]
#[sabi(inside_abi_stable_crate)]
pub struct Tag{
variant:TagVariant,
}
#[repr(C)]
#[derive(Debug,Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,StableAbi)]
#[sabi(inside_abi_stable_crate)]
pub enum TagVariant{
Primitive(Primitive),
Ignored(&'static Tag),
Array(StaticSlice<Tag>),
Set(StaticSlice<Tag>),
Map(StaticSlice<KeyValue<Tag>>),
}
#[repr(C)]
#[derive(Debug,Clone,Copy,PartialEq,Eq,PartialOrd,Ord,Hash,StableAbi)]
#[sabi(inside_abi_stable_crate)]
pub enum Primitive{
Null,
Bool(bool),
Int(i64),
UInt(u64),
String_(StaticStr),
}
#[repr(C)]
#[derive(Debug,Clone,PartialEq,Eq,PartialOrd,Ord,Hash,StableAbi)]
#[sabi(inside_abi_stable_crate)]
pub struct CheckableTag{
variant:CTVariant,
}
#[repr(C)]
#[derive(Debug,Clone,PartialEq,Eq,PartialOrd,Ord,Hash,StableAbi)]
#[sabi(inside_abi_stable_crate)]
pub enum CTVariant{
Primitive(Primitive),
Ignored(RBox<CheckableTag>),
Array(RVec<CheckableTag>),
Set(RVec<KeyValue<CheckableTag>>),
Map(RVec<KeyValue<CheckableTag>>),
}
#[repr(C)]
#[derive(Debug,Copy,Clone,PartialEq,Eq,PartialOrd,Ord,Hash,StableAbi)]
#[sabi(inside_abi_stable_crate)]
pub struct KeyValue<T>{
pub key:T,
pub value:T,
}
#[doc(hidden)]
pub trait TagTrait{
fn is_null(&self)->bool;
}
impl TagTrait for Tag{
fn is_null(&self)->bool{
self.variant==TagVariant::Primitive(Primitive::Null)
}
}
impl TagTrait for CheckableTag{
fn is_null(&self)->bool{
self.variant==CTVariant::Primitive(Primitive::Null)
}
}
impl<'a> TagTrait for &'a CheckableTag{
fn is_null(&self)->bool{
*self==&Tag::null().to_checkable()
}
}
impl Tag{
const fn new(variant:TagVariant)->Self{
Self{
variant,
}
}
pub const fn null()->Self{
Self::new(TagVariant::Primitive(Primitive::Null))
}
pub const fn bool_(b:bool)->Self{
Self::new(TagVariant::Primitive(Primitive::Bool(b)))
}
pub const fn int(n:i64)->Self{
Self::new(TagVariant::Primitive(Primitive::Int(n)))
}
pub const fn uint(n:u64)->Self{
Self::new(TagVariant::Primitive(Primitive::UInt(n)))
}
pub const fn str(s:&'static str)->Self{
Self::new(TagVariant::Primitive(Primitive::String_(StaticStr::new(s))))
}
pub const fn ignored(ignored:&'static Tag)->Self{
Self::new(TagVariant::Ignored(ignored))
}
pub const fn arr(s:&'static [Tag])->Self{
Self::new(TagVariant::Array(StaticSlice::new(s)))
}
pub const fn set(s:&'static [Tag])->Self{
Self::new(TagVariant::Set(StaticSlice::new(s)))
}
pub const fn kv(key:Tag,value:Tag)->KeyValue<Tag>{
KeyValue{key,value}
}
pub const fn map(s:&'static [KeyValue<Tag>])->Self{
Self::new(TagVariant::Map(StaticSlice::new(s)))
}
}
impl Tag{
pub fn to_checkable(self)->CheckableTag{
let variant=match self.variant {
TagVariant::Primitive(prim)=>
CTVariant::Primitive(prim),
TagVariant::Ignored(ignored)=>{
(*ignored).to_checkable()
.piped(RBox::new)
.piped(CTVariant::Ignored)
}
TagVariant::Array(arr)=>{
arr.iter().cloned()
.filter(|x| *x!=Tag::null() )
.map(Self::to_checkable)
.collect::<RVec<CheckableTag>>()
.piped(CTVariant::Array)
}
TagVariant::Set(arr)=>{
arr.iter().cloned()
.filter(|x| !x.is_null() )
.map(|x| (x.to_checkable(),Tag::null().to_checkable()) )
.piped(sorted_ct_vec_from_iter)
.piped(CTVariant::Set)
}
TagVariant::Map(arr)=>{
arr.iter().cloned()
.filter(|kv| !kv.key.is_null() )
.map(|x| x.map(|y|y.to_checkable()).into_pair() )
.piped(sorted_ct_vec_from_iter)
.piped(CTVariant::Map)
}
};
CheckableTag{
variant,
}
}
}
fn sorted_ct_vec_from_iter<I>(iter:I)->RVec<KeyValue<CheckableTag>>
where I:IntoIterator<Item= (CheckableTag,CheckableTag) >
{
iter.into_iter()
.collect::<BTreeMap<CheckableTag,CheckableTag>>()
.into_iter()
.map(KeyValue::from_pair)
.collect::<RVec<KeyValue<CheckableTag>>>()
}
impl CheckableTag{
pub fn check_compatible(&self,other:&Self)->Result<(),TagErrors>{
use self::CTVariant as CTV;
let err_with_variant=|vari:TagErrorVariant|{
TagErrors{
expected:self.clone(),
found:other.clone(),
backtrace:vec![].into(),
errors:vec![vari].into(),
}
};
let mismatched_val_err=|cond:bool|{
if cond {
Ok(())
}else{
Err(err_with_variant(TagErrorVariant::MismatchedValue))
}
};
let same_variant=match (&self.variant,&other.variant) {
(CTV::Primitive(Primitive::Null),_)=>
return Ok(()),
(CTV::Primitive(l),CTV::Primitive(r))=>
mem::discriminant(l)==mem::discriminant(r),
(l,r)=>
mem::discriminant(l)==mem::discriminant(r),
};
if !same_variant {
return Err(err_with_variant(TagErrorVariant::MismatchedDiscriminant))
}
let is_map=matches!(CTV::Map{..}=self.variant);
match (&self.variant,&other.variant) {
(CTV::Primitive(l),CTV::Primitive(r))=>{
match (l,r) {
(Primitive::Null,Primitive::Null)=>(),
(Primitive::Null,_)=>(),
(Primitive::Bool(l_cond),Primitive::Bool(r_cond))=>
mismatched_val_err(l_cond==r_cond)?,
(Primitive::Bool(_),_)=>{},
(Primitive::Int(l_num),Primitive::Int(r_num))=>
mismatched_val_err(l_num==r_num)?,
(Primitive::Int(_),_)=>{},
(Primitive::UInt(l_num),Primitive::UInt(r_num))=>
mismatched_val_err(l_num==r_num)?,
(Primitive::UInt(_),_)=>{},
(Primitive::String_(l_str),Primitive::String_(r_str))=>
mismatched_val_err(l_str.as_str()==r_str.as_str())?,
(Primitive::String_(_),_)=>{},
}
},
(CTV::Primitive(_),_)=>{}
(CTV::Ignored(_),_)=>{}
(CTV::Array(l_arr),CTV::Array(r_arr))=>{
let l_arr=l_arr.as_slice();
let r_arr=r_arr.as_slice();
if l_arr.len()!=r_arr.len() {
let e=TagErrorVariant::MismatchedArrayLength{
expected:l_arr.len(),
found:r_arr.len(),
};
return Err(err_with_variant(e));
}
for (l_elem,r_elem) in l_arr.iter().zip(r_arr.iter()) {
l_elem.check_compatible(r_elem)
.map_err(|errs| errs.context(l_elem.clone()) )?;
}
}
(CTV::Array(_),_)=>{},
(CTV::Set(l_map),CTV::Set(r_map))
|(CTV::Map(l_map),CTV::Map(r_map))=>{
if l_map.len() > r_map.len() {
let e=TagErrorVariant::MismatchedAssocLength{
expected:l_map.len(),
found:r_map.len(),
};
return Err(err_with_variant(e));
}
let mut r_iter=r_map.iter().map(KeyValue::as_pair);
'outer:for (l_key,l_elem) in l_map.iter().map(KeyValue::as_pair) {
let mut first_err=None::<KeyValue<&CheckableTag>>;
'inner:loop {
let (r_key,r_elem)=match r_iter.next() {
Some(x)=>x,
None=>break 'inner,
};
match l_key.check_compatible(r_key)
.and_then(|_| l_elem.check_compatible(r_elem) )
{
Ok(_)=>continue 'outer,
Err(_)=>{
first_err.get_or_insert( KeyValue::new(r_key,r_elem) );
},
}
}
let e=if is_map {
TagErrorVariant::MismatchedMapEntry{
expected:KeyValue::new(l_key.clone(),l_elem.clone()),
found:first_err.map(|x|x.map(Clone::clone)).into_c(),
}
}else{
TagErrorVariant::MissingSetValue{
expected:l_key.clone(),
found:first_err.map(|x|x.key).cloned().into_c(),
}
};
return Err(err_with_variant(e));
}
}
(CTV::Set(_),_)=>{},
(CTV::Map(_),_)=>{},
}
Ok(())
}
}
impl<T> KeyValue<T>{
pub const fn new(key:T,value:T)->Self{
Self{ key,value }
}
pub fn map<F,U>(self,mut f:F)->KeyValue<U>
where F:FnMut(T)->U
{
KeyValue{
key:f(self.key),
value:f(self.value),
}
}
pub fn into_pair(self)->(T,T){
(self.key,self.value)
}
pub fn as_pair(&self)->(&T,&T){
(
&self.key,
&self.value,
)
}
pub fn from_pair((key,value):(T,T))->Self{
Self{ key,value }
}
}
impl<T> Display for KeyValue<T>
where T:Display+TagTrait
{
fn fmt(&self,f:&mut fmt::Formatter<'_>)->fmt::Result{
write!(f,"{}",self.key)?;
if !self.value.is_null() {
write!(f,"=>{}",self.value)?;
}
Ok(())
}
}
pub struct FromLiteral<T>(pub T);
impl FromLiteral<bool>{
pub const fn to_tag(self)->Tag{
Tag::bool_(self.0)
}
}
impl FromLiteral<&'static str>{
pub const fn to_tag(self)->Tag{
Tag::str(self.0)
}
}
impl FromLiteral<i64>{
pub const fn to_tag(self)->Tag{
Tag::int(self.0)
}
}
impl FromLiteral<Tag>{
pub const fn to_tag(self)->Tag{
self.0
}
}
fn display_iter<I>(iter:I,f:&mut fmt::Formatter<'_>,indent:usize)->fmt::Result
where
I:IntoIterator,
I::Item:Display,
{
let mut buffer=String::new();
for elem in iter {
Display::fmt(&buffer.display_pad(indent,&elem)?,f)?;
writeln!(f,",")?;
}
Ok(())
}
impl Display for Primitive {
fn fmt(&self,f:&mut fmt::Formatter<'_>)->fmt::Result {
match *self {
Primitive::Null=>{
write!(f,"null")?;
},
Primitive::Bool(cond)=>{
write!(f,"{}",cond)?;
},
Primitive::Int(num)=>{
write!(f,"{}",num)?;
},
Primitive::UInt(num)=>{
write!(f,"{}",num)?;
},
Primitive::String_(s)=>{
write!(f,"'{}'",s)?;
},
}
Ok(())
}
}
impl Display for CheckableTag {
fn fmt(&self,f:&mut fmt::Formatter<'_>)->fmt::Result {
match &self.variant {
CTVariant::Primitive(prim)=>{
Display::fmt(prim,f)?;
},
CTVariant::Ignored(ignored)=>{
Display::fmt(ignored,f)?;
}
CTVariant::Array(arr)=>{
writeln!(f,"[")?;
display_iter(&*arr,f,4)?;
write!(f,"]")?;
},
CTVariant::Set(map)|CTVariant::Map(map)=>{
writeln!(f,"{{")?;
display_iter(map.iter(),f,4)?;
write!(f,"}}")?;
},
}
Ok(())
}
}
#[derive(Debug,Clone,PartialEq)]
pub struct TagErrors{
expected:CheckableTag,
found:CheckableTag,
backtrace:RVec<CheckableTag>,
errors:RVec<TagErrorVariant>,
}
impl TagErrors{
pub fn context(mut self,current:CheckableTag)->Self{
self.backtrace.push(current);
self
}
}
impl Display for TagErrors {
fn fmt(&self,f:&mut fmt::Formatter<'_>)->fmt::Result{
let mut buffer=String::new();
writeln!(f,"Stacktrace:")?;
if self.backtrace.is_empty() {
writeln!(f," Empty.")?;
}else{
for stack in self.backtrace.iter().rev() {
writeln!(f," Inside:\n{},",buffer.display_pad(8,stack)? )?;
}
}
writeln!(f,"Expected:\n{}",buffer.display_pad(4,&self.expected)? )?;
writeln!(f,"Found:\n{}",buffer.display_pad(4,&self.found)? )?;
writeln!(f,"Errors:\n")?;
for err in self.errors.iter().rev() {
writeln!(f,"\n{},",buffer.display_pad(4,err)? )?;
}
Ok(())
}
}
#[repr(C)]
#[derive(Debug,Clone,PartialEq,StableAbi)]
#[sabi(inside_abi_stable_crate)]
pub enum TagErrorVariant{
MismatchedDiscriminant,
MismatchedValue,
MismatchedArrayLength{
expected:usize,
found:usize,
},
MismatchedAssocLength{
expected:usize,
found:usize,
},
MissingSetValue{
expected:CheckableTag,
found:ROption<CheckableTag>,
},
MismatchedMapEntry{
expected:KeyValue<CheckableTag>,
found:ROption<KeyValue<CheckableTag>>,
},
}
impl Display for TagErrorVariant {
fn fmt(&self,f:&mut fmt::Formatter<'_>)->fmt::Result {
match self {
TagErrorVariant::MismatchedDiscriminant=>{
writeln!(f,"Mismatched Tag variant.")?;
}
TagErrorVariant::MismatchedValue=>{
writeln!(f,"Mitmatched Value.")?;
}
TagErrorVariant::MismatchedArrayLength{expected,found}=>{
writeln!(f,"Mismatched length expected:{} found:{}",expected,found)?;
}
TagErrorVariant::MismatchedAssocLength{expected,found}=>{
writeln!(
f,
"Mismatched length expected at least:{} found:{}",
expected,
found,
)?;
}
TagErrorVariant::MissingSetValue{expected,found}=>{
let mut buffer=String::new();
writeln!(
f,
"Mismatched value in set\nExpected:\n{}",
buffer.display_pad(4,&expected)?
)?;
match found {
RSome(found) => writeln!(f,"Found:\n{}",buffer.display_pad(4,&found)?),
RNone => writeln!(f,"Found:\n Nothing",),
}?;
},
TagErrorVariant::MismatchedMapEntry{expected,found}=>{
let mut buffer=String::new();
writeln!(
f,
"Mismatched entry in map\nExpected:\n{}",
buffer.display_pad(4,&expected)?
)?;
match found {
RSome(found) => writeln!(f,"Found:\n{}",buffer.display_pad(4,&found)?),
RNone => writeln!(f,"Found:\n Nothing",),
}?;
},
}
Ok(())
}
}
#[cfg(all(test,not(feature="only_new_tests")))]
mod test;