use crate::{DomainId, ToRelationDatum};
use sim_kernel::{Datum, NumberLiteral, Ref, Symbol};
use std::{collections::BTreeMap, fmt};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum StorageRepr {
Bool,
I64,
F64,
Text,
Bytes,
}
#[derive(Clone, Debug, PartialEq)]
pub enum StorageValue {
Bool(bool),
I64(i64),
F64(f64),
Text(String),
Bytes(Vec<u8>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DomainTrait {
Equatable,
Ordered,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum DomainError {
DuplicateId(DomainId),
InvalidShapeRef,
IncoherentTraits,
StorageMismatch,
NonFiniteFloat,
DatumMismatch,
}
impl fmt::Display for DomainError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for DomainError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DomainSpec {
id: DomainId,
storage: StorageRepr,
shape: Ref,
traits: Vec<DomainTrait>,
}
impl DomainSpec {
pub fn new(
id: DomainId,
storage: StorageRepr,
shape: Ref,
traits: impl IntoIterator<Item = DomainTrait>,
) -> Result<Self, DomainError> {
if matches!(shape, Ref::Handle(_) | Ref::Coord(_)) {
return Err(DomainError::InvalidShapeRef);
}
let mut traits: Vec<_> = traits.into_iter().collect();
traits.sort();
traits.dedup();
if traits.contains(&DomainTrait::Ordered) && !traits.contains(&DomainTrait::Equatable) {
return Err(DomainError::IncoherentTraits);
}
Ok(Self {
id,
storage,
shape,
traits,
})
}
pub fn id(&self) -> &DomainId {
&self.id
}
pub const fn storage(&self) -> StorageRepr {
self.storage
}
pub const fn shape(&self) -> &Ref {
&self.shape
}
pub fn traits(&self) -> &[DomainTrait] {
&self.traits
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct DomainCatalog(BTreeMap<DomainId, DomainSpec>);
impl DomainCatalog {
pub fn new(specs: impl IntoIterator<Item = DomainSpec>) -> Result<Self, DomainError> {
let mut map = BTreeMap::new();
for spec in specs {
let id = spec.id.clone();
if map.insert(id.clone(), spec).is_some() {
return Err(DomainError::DuplicateId(id));
}
}
Ok(Self(map))
}
pub fn get(&self, id: &DomainId) -> Option<&DomainSpec> {
self.0.get(id)
}
pub fn iter(&self) -> impl Iterator<Item = &DomainSpec> {
self.0.values()
}
}
impl ToRelationDatum for DomainCatalog {
fn to_datum(&self) -> Datum {
Datum::Node {
tag: Symbol::qualified("relation", "domain-catalog"),
fields: vec![(
Symbol::new("domains"),
Datum::Vector(self.iter().map(ToRelationDatum::to_datum).collect()),
)],
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BaseDomain {
Bool,
I64,
F64,
Text,
Bytes,
}
impl BaseDomain {
pub fn id(self) -> DomainId {
DomainId::new(Symbol::qualified(
"relation",
match self {
Self::Bool => "bool",
Self::I64 => "i64",
Self::F64 => "f64",
Self::Text => "text",
Self::Bytes => "bytes",
},
))
.expect("built-in id")
}
pub fn spec(self) -> DomainSpec {
let storage = match self {
Self::Bool => StorageRepr::Bool,
Self::I64 => StorageRepr::I64,
Self::F64 => StorageRepr::F64,
Self::Text => StorageRepr::Text,
Self::Bytes => StorageRepr::Bytes,
};
DomainSpec::new(
self.id(),
storage,
Ref::Symbol(Symbol::qualified(
"relation",
match self {
Self::Bool => "BoolShape",
Self::I64 => "I64Shape",
Self::F64 => "FiniteF64Shape",
Self::Text => "TextShape",
Self::Bytes => "BytesShape",
},
)),
[DomainTrait::Equatable, DomainTrait::Ordered],
)
.expect("built-in domain declaration is coherent")
}
pub fn to_datum(self, value: StorageValue) -> Result<Datum, DomainError> {
match (self, value) {
(Self::Bool, StorageValue::Bool(v)) => Ok(Datum::Bool(v)),
(Self::I64, StorageValue::I64(v)) => Ok(Datum::Number(NumberLiteral {
domain: Symbol::qualified("core", "i64"),
canonical: v.to_string(),
})),
(Self::F64, StorageValue::F64(v)) if v.is_finite() => {
let v = if v == 0.0 { 0.0 } else { v };
Ok(Datum::Number(NumberLiteral {
domain: Symbol::qualified("core", "f64"),
canonical: v.to_string(),
}))
}
(Self::F64, StorageValue::F64(_)) => Err(DomainError::NonFiniteFloat),
(Self::Text, StorageValue::Text(v)) => Ok(Datum::String(v)),
(Self::Bytes, StorageValue::Bytes(v)) => Ok(Datum::Bytes(v)),
_ => Err(DomainError::StorageMismatch),
}
}
pub fn from_datum(self, datum: &Datum) -> Result<StorageValue, DomainError> {
match (self, datum) {
(Self::Bool, Datum::Bool(v)) => Ok(StorageValue::Bool(*v)),
(Self::I64, Datum::Number(v)) if v.domain == Symbol::qualified("core", "i64") => v
.canonical
.parse()
.map(StorageValue::I64)
.map_err(|_| DomainError::DatumMismatch),
(Self::F64, Datum::Number(v)) if v.domain == Symbol::qualified("core", "f64") => {
let n: f64 = v
.canonical
.parse()
.map_err(|_| DomainError::DatumMismatch)?;
if !n.is_finite() {
Err(DomainError::NonFiniteFloat)
} else {
Ok(StorageValue::F64(if n == 0.0 { 0.0 } else { n }))
}
}
(Self::Text, Datum::String(v)) => Ok(StorageValue::Text(v.clone())),
(Self::Bytes, Datum::Bytes(v)) => Ok(StorageValue::Bytes(v.clone())),
_ => Err(DomainError::DatumMismatch),
}
}
}
impl ToRelationDatum for DomainSpec {
fn to_datum(&self) -> Datum {
Datum::Node {
tag: Symbol::qualified("relation", "domain"),
fields: vec![
(Symbol::new("id"), Datum::Symbol(self.id.symbol().clone())),
(
Symbol::new("storage"),
Datum::Symbol(Symbol::qualified(
"relation",
match self.storage {
StorageRepr::Bool => "bool",
StorageRepr::I64 => "i64",
StorageRepr::F64 => "f64",
StorageRepr::Text => "text",
StorageRepr::Bytes => "bytes",
},
)),
),
(Symbol::new("shape"), ref_datum(&self.shape)),
(
Symbol::new("traits"),
Datum::Vector(
self.traits
.iter()
.map(|v| {
Datum::Symbol(Symbol::qualified(
"relation",
match v {
DomainTrait::Equatable => "equatable",
DomainTrait::Ordered => "ordered",
},
))
})
.collect(),
),
),
],
}
}
}
fn ref_datum(value: &Ref) -> Datum {
match value {
Ref::Symbol(v) => Datum::Node {
tag: Symbol::qualified("core", "ref-symbol"),
fields: vec![(Symbol::new("symbol"), Datum::Symbol(v.clone()))],
},
Ref::Content(v) => Datum::Node {
tag: Symbol::qualified("core", "ref-content"),
fields: vec![
(Symbol::new("algorithm"), Datum::Symbol(v.algorithm.clone())),
(Symbol::new("bytes"), Datum::Bytes(v.bytes.to_vec())),
],
},
_ => unreachable!("validated durable ref"),
}
}