use crate::expr::NodeExpr;
use crate::path::Path;
use crate::severity::Severity;
use crate::sparql::SparqlConstraint;
use crate::term::{NamedNode, NodeKindSet, Term};
use crate::value_type::ValueType;
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct ShapeId(pub u32);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Shape {
Annotated {
severity: Severity,
#[serde(default, skip_serializing_if = "<[Term]>::is_empty")]
messages: Arc<[Term]>,
shape: ShapeId,
},
Top,
Pending,
TestConst(Term),
TestType(ValueType),
TestKind(NodeKindSet),
Closed(BTreeSet<NamedNode>),
Eq(Path, NamedNode),
Disj(Path, NamedNode),
Lt(Path, NamedNode),
Le(Path, NamedNode),
UniqueLang(Path),
Not(ShapeId),
And(Vec<ShapeId>),
Or(Vec<ShapeId>),
Count {
path: Path,
min: Option<u64>,
max: Option<u64>,
qualifier: ShapeId,
},
Sparql(SparqlConstraint),
Expression(NodeExpr),
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct ShapeArena {
shapes: Vec<Shape>,
}
impl ShapeArena {
pub fn new() -> Self {
Self::default()
}
pub fn len(&self) -> usize {
self.shapes.len()
}
pub fn is_empty(&self) -> bool {
self.shapes.is_empty()
}
pub fn get(&self, id: ShapeId) -> &Shape {
&self.shapes[id.0 as usize]
}
pub fn insert(&mut self, shape: Shape) -> ShapeId {
let id = ShapeId(self.shapes.len() as u32);
self.shapes.push(shape);
id
}
pub fn reserve(&mut self) -> ShapeId {
self.insert(Shape::Pending)
}
pub fn set(&mut self, id: ShapeId, shape: Shape) {
self.shapes[id.0 as usize] = shape;
}
#[track_caller]
pub fn debug_assert_finalized(&self) {
if cfg!(debug_assertions) {
for (i, shape) in self.shapes.iter().enumerate() {
assert!(
!matches!(shape, Shape::Pending),
"finalized shape arena still holds Shape::Pending at ShapeId({i}): \
a slot reserved during construction was never filled",
);
}
}
}
pub fn top(&mut self) -> ShapeId {
self.insert(Shape::Top)
}
pub fn bottom(&mut self) -> ShapeId {
let t = self.insert(Shape::Top);
self.insert(Shape::Not(t))
}
pub fn not(&mut self, inner: ShapeId) -> ShapeId {
if let Shape::Not(x) = self.get(inner) {
return *x;
}
self.insert(Shape::Not(inner))
}
pub fn and(&mut self, parts: Vec<ShapeId>) -> ShapeId {
let mut flat = Vec::with_capacity(parts.len());
for p in parts {
match self.get(p) {
Shape::Top => {}
Shape::And(inner) => flat.extend(inner.iter().copied()),
_ => flat.push(p),
}
}
match flat.len() {
0 => self.insert(Shape::Top),
1 => flat[0],
_ => self.insert(Shape::And(flat)),
}
}
pub fn or(&mut self, parts: Vec<ShapeId>) -> ShapeId {
let mut flat = Vec::with_capacity(parts.len());
for p in parts {
match self.get(p) {
Shape::Or(inner) => flat.extend(inner.iter().copied()),
_ => flat.push(p),
}
}
match flat.len() {
0 => self.bottom(),
1 => flat[0],
_ => self.insert(Shape::Or(flat)),
}
}
pub fn xone(&mut self, parts: Vec<ShapeId>) -> ShapeId {
let mut terms = Vec::with_capacity(parts.len());
for (i, &pi) in parts.iter().enumerate() {
let mut conj = Vec::with_capacity(parts.len());
conj.push(pi);
for (j, &pj) in parts.iter().enumerate() {
if i != j {
let neg = self.not(pj);
conj.push(neg);
}
}
let term = self.and(conj);
terms.push(term);
}
self.or(terms)
}
pub fn count(
&mut self,
path: Path,
min: Option<u64>,
max: Option<u64>,
qualifier: ShapeId,
) -> ShapeId {
if max.is_none() && matches!(min, None | Some(0)) {
return self.insert(Shape::Top);
}
self.insert(Shape::Count {
path,
min,
max,
qualifier,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn not_is_involution() {
let mut a = ShapeArena::new();
let t = a.top();
let n = a.not(t);
let nn = a.not(n);
assert_eq!(nn, t);
}
#[test]
fn debug_assert_finalized_accepts_filled_arena() {
let mut a = ShapeArena::new();
let id = a.reserve();
a.set(id, Shape::Top);
a.debug_assert_finalized(); }
#[test]
#[cfg_attr(not(debug_assertions), ignore = "assertion is debug-only")]
#[should_panic(expected = "Shape::Pending")]
fn debug_assert_finalized_rejects_pending() {
let mut a = ShapeArena::new();
a.reserve(); a.debug_assert_finalized();
}
#[test]
fn and_flattens_and_drops_top() {
let mut a = ShapeArena::new();
let t = a.top();
let k = a.insert(Shape::TestKind(NodeKindSet::IRI));
let inner = a.and(vec![k, t]); assert_eq!(inner, k);
let l = a.insert(Shape::TestKind(NodeKindSet::LITERAL));
let outer = a.and(vec![inner, l]);
match a.get(outer) {
Shape::And(parts) => assert_eq!(parts, &vec![k, l]),
other => panic!("expected And, got {other:?}"),
}
}
#[test]
fn empty_or_is_bottom() {
let mut a = ShapeArena::new();
let b = a.or(vec![]);
match a.get(b) {
Shape::Not(inner) => assert!(matches!(a.get(*inner), Shape::Top)),
other => panic!("expected ¬⊤, got {other:?}"),
}
}
#[test]
fn count_zero_lower_bound_is_top() {
let mut a = ShapeArena::new();
let t = a.top();
let p = Path::Pred(NamedNode::new("http://ex/p").unwrap());
let c = a.count(p, Some(0), None, t);
assert!(matches!(a.get(c), Shape::Top));
}
#[test]
fn recursive_shape_via_reserve_set() {
let mut a = ShapeArena::new();
let knows = NamedNode::new("http://ex/knows").unwrap();
let s = a.reserve();
a.set(
s,
Shape::Count {
path: Path::Pred(knows),
min: Some(1),
max: None,
qualifier: s,
},
);
match a.get(s) {
Shape::Count { qualifier, .. } => assert_eq!(*qualifier, s),
other => panic!("expected self-referential Count, got {other:?}"),
}
}
}