1use crate::{DomainId, ToRelationDatum};
2use sim_kernel::{Datum, NumberLiteral, Ref, Symbol};
3use std::{collections::BTreeMap, fmt};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
7pub enum StorageRepr {
8 Bool,
10 I64,
12 F64,
14 Text,
16 Bytes,
18}
19
20#[derive(Clone, Debug, PartialEq)]
22pub enum StorageValue {
23 Bool(bool),
25 I64(i64),
27 F64(f64),
29 Text(String),
31 Bytes(Vec<u8>),
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub enum DomainTrait {
38 Equatable,
40 Ordered,
42}
43
44#[derive(Clone, Debug, PartialEq, Eq)]
46pub enum DomainError {
47 DuplicateId(DomainId),
49 InvalidShapeRef,
51 IncoherentTraits,
53 StorageMismatch,
55 NonFiniteFloat,
57 DatumMismatch,
59}
60impl fmt::Display for DomainError {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "{self:?}")
63 }
64}
65impl std::error::Error for DomainError {}
66
67#[derive(Clone, Debug, PartialEq, Eq)]
69pub struct DomainSpec {
70 id: DomainId,
71 storage: StorageRepr,
72 shape: Ref,
73 traits: Vec<DomainTrait>,
74}
75impl DomainSpec {
76 pub fn new(
78 id: DomainId,
79 storage: StorageRepr,
80 shape: Ref,
81 traits: impl IntoIterator<Item = DomainTrait>,
82 ) -> Result<Self, DomainError> {
83 if matches!(shape, Ref::Handle(_) | Ref::Coord(_)) {
84 return Err(DomainError::InvalidShapeRef);
85 }
86 let mut traits: Vec<_> = traits.into_iter().collect();
87 traits.sort();
88 traits.dedup();
89 if traits.contains(&DomainTrait::Ordered) && !traits.contains(&DomainTrait::Equatable) {
90 return Err(DomainError::IncoherentTraits);
91 }
92 Ok(Self {
93 id,
94 storage,
95 shape,
96 traits,
97 })
98 }
99 pub fn id(&self) -> &DomainId {
101 &self.id
102 }
103 pub const fn storage(&self) -> StorageRepr {
105 self.storage
106 }
107 pub const fn shape(&self) -> &Ref {
109 &self.shape
110 }
111 pub fn traits(&self) -> &[DomainTrait] {
113 &self.traits
114 }
115}
116
117#[derive(Clone, Debug, Default, PartialEq, Eq)]
119pub struct DomainCatalog(BTreeMap<DomainId, DomainSpec>);
120impl DomainCatalog {
121 pub fn new(specs: impl IntoIterator<Item = DomainSpec>) -> Result<Self, DomainError> {
123 let mut map = BTreeMap::new();
124 for spec in specs {
125 let id = spec.id.clone();
126 if map.insert(id.clone(), spec).is_some() {
127 return Err(DomainError::DuplicateId(id));
128 }
129 }
130 Ok(Self(map))
131 }
132 pub fn get(&self, id: &DomainId) -> Option<&DomainSpec> {
134 self.0.get(id)
135 }
136 pub fn iter(&self) -> impl Iterator<Item = &DomainSpec> {
138 self.0.values()
139 }
140}
141
142impl ToRelationDatum for DomainCatalog {
143 fn to_datum(&self) -> Datum {
144 Datum::Node {
145 tag: Symbol::qualified("relation", "domain-catalog"),
146 fields: vec![(
147 Symbol::new("domains"),
148 Datum::Vector(self.iter().map(ToRelationDatum::to_datum).collect()),
149 )],
150 }
151 }
152}
153
154#[derive(Clone, Copy, Debug, PartialEq, Eq)]
156pub enum BaseDomain {
157 Bool,
159 I64,
161 F64,
163 Text,
165 Bytes,
167}
168impl BaseDomain {
169 pub fn id(self) -> DomainId {
171 DomainId::new(Symbol::qualified(
172 "relation",
173 match self {
174 Self::Bool => "bool",
175 Self::I64 => "i64",
176 Self::F64 => "f64",
177 Self::Text => "text",
178 Self::Bytes => "bytes",
179 },
180 ))
181 .expect("built-in id")
182 }
183 pub fn spec(self) -> DomainSpec {
185 let storage = match self {
186 Self::Bool => StorageRepr::Bool,
187 Self::I64 => StorageRepr::I64,
188 Self::F64 => StorageRepr::F64,
189 Self::Text => StorageRepr::Text,
190 Self::Bytes => StorageRepr::Bytes,
191 };
192 DomainSpec::new(
193 self.id(),
194 storage,
195 Ref::Symbol(Symbol::qualified(
196 "relation",
197 match self {
198 Self::Bool => "BoolShape",
199 Self::I64 => "I64Shape",
200 Self::F64 => "FiniteF64Shape",
201 Self::Text => "TextShape",
202 Self::Bytes => "BytesShape",
203 },
204 )),
205 [DomainTrait::Equatable, DomainTrait::Ordered],
206 )
207 .expect("built-in domain declaration is coherent")
208 }
209 pub fn to_datum(self, value: StorageValue) -> Result<Datum, DomainError> {
211 match (self, value) {
212 (Self::Bool, StorageValue::Bool(v)) => Ok(Datum::Bool(v)),
213 (Self::I64, StorageValue::I64(v)) => Ok(Datum::Number(NumberLiteral {
214 domain: Symbol::qualified("core", "i64"),
215 canonical: v.to_string(),
216 })),
217 (Self::F64, StorageValue::F64(v)) if v.is_finite() => {
218 let v = if v == 0.0 { 0.0 } else { v };
219 Ok(Datum::Number(NumberLiteral {
220 domain: Symbol::qualified("core", "f64"),
221 canonical: v.to_string(),
222 }))
223 }
224 (Self::F64, StorageValue::F64(_)) => Err(DomainError::NonFiniteFloat),
225 (Self::Text, StorageValue::Text(v)) => Ok(Datum::String(v)),
226 (Self::Bytes, StorageValue::Bytes(v)) => Ok(Datum::Bytes(v)),
227 _ => Err(DomainError::StorageMismatch),
228 }
229 }
230 pub fn from_datum(self, datum: &Datum) -> Result<StorageValue, DomainError> {
232 match (self, datum) {
233 (Self::Bool, Datum::Bool(v)) => Ok(StorageValue::Bool(*v)),
234 (Self::I64, Datum::Number(v)) if v.domain == Symbol::qualified("core", "i64") => v
235 .canonical
236 .parse()
237 .map(StorageValue::I64)
238 .map_err(|_| DomainError::DatumMismatch),
239 (Self::F64, Datum::Number(v)) if v.domain == Symbol::qualified("core", "f64") => {
240 let n: f64 = v
241 .canonical
242 .parse()
243 .map_err(|_| DomainError::DatumMismatch)?;
244 if !n.is_finite() {
245 Err(DomainError::NonFiniteFloat)
246 } else {
247 Ok(StorageValue::F64(if n == 0.0 { 0.0 } else { n }))
248 }
249 }
250 (Self::Text, Datum::String(v)) => Ok(StorageValue::Text(v.clone())),
251 (Self::Bytes, Datum::Bytes(v)) => Ok(StorageValue::Bytes(v.clone())),
252 _ => Err(DomainError::DatumMismatch),
253 }
254 }
255}
256
257impl ToRelationDatum for DomainSpec {
258 fn to_datum(&self) -> Datum {
259 Datum::Node {
260 tag: Symbol::qualified("relation", "domain"),
261 fields: vec![
262 (Symbol::new("id"), Datum::Symbol(self.id.symbol().clone())),
263 (
264 Symbol::new("storage"),
265 Datum::Symbol(Symbol::qualified(
266 "relation",
267 match self.storage {
268 StorageRepr::Bool => "bool",
269 StorageRepr::I64 => "i64",
270 StorageRepr::F64 => "f64",
271 StorageRepr::Text => "text",
272 StorageRepr::Bytes => "bytes",
273 },
274 )),
275 ),
276 (Symbol::new("shape"), ref_datum(&self.shape)),
277 (
278 Symbol::new("traits"),
279 Datum::Vector(
280 self.traits
281 .iter()
282 .map(|v| {
283 Datum::Symbol(Symbol::qualified(
284 "relation",
285 match v {
286 DomainTrait::Equatable => "equatable",
287 DomainTrait::Ordered => "ordered",
288 },
289 ))
290 })
291 .collect(),
292 ),
293 ),
294 ],
295 }
296 }
297}
298fn ref_datum(value: &Ref) -> Datum {
299 match value {
300 Ref::Symbol(v) => Datum::Node {
301 tag: Symbol::qualified("core", "ref-symbol"),
302 fields: vec![(Symbol::new("symbol"), Datum::Symbol(v.clone()))],
303 },
304 Ref::Content(v) => Datum::Node {
305 tag: Symbol::qualified("core", "ref-content"),
306 fields: vec![
307 (Symbol::new("algorithm"), Datum::Symbol(v.algorithm.clone())),
308 (Symbol::new("bytes"), Datum::Bytes(v.bytes.to_vec())),
309 ],
310 },
311 _ => unreachable!("validated durable ref"),
312 }
313}