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);
impl Default for ShapeId {
fn default() -> Self {
Self(u32::MAX)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub enum ConstraintKind {
#[default]
Unknown,
Top,
Constant,
ClassMembership,
ValueType,
NodeKind,
Closed,
Equals,
Disjoint,
LessThan,
LessThanOrEquals,
UniqueLang,
Negation,
Conjunction,
Disjunction,
Cardinality,
Sparql,
Expression,
}
#[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,
})
}
}
impl ConstraintKind {
pub fn of(arena: &ShapeArena, id: ShapeId) -> Self {
match arena.get(id) {
Shape::Annotated { shape, .. } => Self::of(arena, *shape),
Shape::Top => Self::Top,
Shape::Pending => Self::Unknown,
Shape::TestConst(_) => Self::Constant,
Shape::TestType(_) => Self::ValueType,
Shape::TestKind(_) => Self::NodeKind,
Shape::Closed(_) => Self::Closed,
Shape::Eq(..) => Self::Equals,
Shape::Disj(..) => Self::Disjoint,
Shape::Lt(..) => Self::LessThan,
Shape::Le(..) => Self::LessThanOrEquals,
Shape::UniqueLang(_) => Self::UniqueLang,
Shape::Not(_) => Self::Negation,
Shape::And(_) => Self::Conjunction,
Shape::Or(_) => Self::Disjunction,
Shape::Count {
path,
min,
max,
qualifier,
} if is_class_membership(path, *min, *max, arena, *qualifier) => Self::ClassMembership,
Shape::Count { .. } => Self::Cardinality,
Shape::Sparql(_) => Self::Sparql,
Shape::Expression(_) => Self::Expression,
}
}
}
fn is_class_membership(
path: &Path,
min: Option<u64>,
max: Option<u64>,
arena: &ShapeArena,
qualifier: ShapeId,
) -> bool {
const RDF_TYPE: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
const RDFS_SUBCLASS_OF: &str = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
if !matches!((min, max), (Some(1), None) | (None, Some(0))) {
return false;
}
let Path::Seq(parts) = path else { return false };
let is_class_path = matches!(
parts.as_slice(),
[Path::Pred(ty), Path::Star(sub)]
if ty.as_str() == RDF_TYPE
&& matches!(sub.as_ref(), Path::Pred(s) if s.as_str() == RDFS_SUBCLASS_OF)
);
is_class_path && matches!(arena.get(qualifier), Shape::TestConst(_))
}
#[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:?}"),
}
}
}