use super::*;
use crate::ast;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ConstraintExpr {
Reference(Path),
AndOr(Vec<ConstraintExpr>),
And(Vec<ConstraintExpr>),
OneOf(Vec<ConstraintExpr>),
}
impl ConstraintExpr {
pub fn is_in(&self, path: &Path) -> bool {
use ConstraintExpr::*;
match self {
Reference(p) => p == path,
AndOr(exprs) | And(exprs) | OneOf(exprs) => exprs.iter().any(|expr| expr.is_in(path)),
}
}
pub fn andor(mut self, rhs: Self) -> Self {
self.andor_mut(rhs);
self
}
pub fn andor_mut(&mut self, rhs: Self) {
use ConstraintExpr::*;
match (self, rhs) {
(AndOr(ref mut a), AndOr(mut b)) => {
a.append(&mut b);
}
(AndOr(ref mut a), b) => {
a.push(b);
}
(a, b @ AndOr(_)) => {
let s = std::mem::replace(a, b);
match a {
AndOr(factors) => factors.insert(0, s),
_ => unreachable!(),
}
}
(a, b) => {
let s = std::mem::replace(a, AndOr(vec![b]));
match a {
AndOr(factors) => factors.insert(0, s),
_ => unreachable!(),
}
}
}
}
pub fn from_ast_expr(
ns: &Namespace,
scope: &Scope,
expr: &ast::SuperTypeExpression,
) -> Result<Self, SemanticError> {
use ast::SuperTypeExpression::*;
Ok(match expr {
Reference(name) => Self::Reference(ns.resolve(scope, name)?.0),
AndOr { factors } => Self::AndOr(
factors
.iter()
.map(|f| Self::from_ast_expr(ns, scope, f))
.collect::<Result<Vec<Self>, SemanticError>>()?,
),
And { terms } => Self::And(
terms
.iter()
.map(|f| Self::from_ast_expr(ns, scope, f))
.collect::<Result<Vec<Self>, SemanticError>>()?,
),
OneOf { exprs } => Self::OneOf(
exprs
.iter()
.map(|f| Self::from_ast_expr(ns, scope, f))
.collect::<Result<Vec<Self>, SemanticError>>()?,
),
})
}
pub fn as_instantiables(&self, ns: &Namespace) -> Result<Vec<Vec<Path>>, SemanticError> {
let is = Instantiables::from_constraint_expr(ns, self)?;
Ok(is.as_path(ns))
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Constraints {
pub instantiables: HashMap<Path, Vec<Vec<Path>>>,
}
pub fn gather_constraint_expr(
ns: &Namespace,
st: &SyntaxTree,
) -> Result<HashMap<Path, ConstraintExpr>, SemanticError> {
let root = Scope::root();
let mut exprs: HashMap<Path, ConstraintExpr> = HashMap::new();
for schema in &st.schemas {
let scope = root.schema(&schema.name);
for entity in &schema.entities {
match &entity.constraint {
Some(ast::Constraint::SuperTypeRule(expr)) => {
let result = exprs.insert(
Path::entity(&scope, &entity.name),
ConstraintExpr::from_ast_expr(ns, &scope, expr)?,
);
if result.is_some() {
return Err(SemanticError::DuplicatedDeclaration(Path::entity(
&scope,
&entity.name,
)));
}
}
_ => continue,
}
}
}
for schema in &st.schemas {
let scope = root.schema(&schema.name);
for constraint in &schema.subtype_constraints {
if let Some(expr) = &constraint.expr {
let (path, _index) = ns.resolve(&scope, &constraint.entity)?;
let expr = ConstraintExpr::from_ast_expr(ns, &scope, expr)?;
match exprs.entry(path) {
Entry::Occupied(mut e) => {
e.get_mut().andor_mut(expr);
}
Entry::Vacant(e) => {
e.insert(expr);
}
}
}
}
}
let mut super_to_sub: HashMap<Path , Vec<Path> > = HashMap::new();
for schema in &st.schemas {
let scope = root.schema(&schema.name);
for entity in &schema.entities {
if let Some(subtype_decl) = &entity.subtype_of {
for sup_name in &subtype_decl.entity_references {
let (sup, _) = ns.resolve(&scope, sup_name)?;
let subs = super_to_sub.entry(sup).or_default();
let sub = Path::entity(&scope, &entity.name);
subs.push(sub);
}
}
}
}
for (sup, subs) in super_to_sub {
match exprs.entry(sup) {
Entry::Occupied(mut e) => {
let subs: Vec<ConstraintExpr> = subs
.into_iter()
.filter_map(|sub| {
if e.get().is_in(&sub) {
None
} else {
Some(ConstraintExpr::Reference(sub))
}
})
.collect();
if !subs.is_empty() {
e.get_mut().andor_mut(ConstraintExpr::AndOr(subs));
}
}
Entry::Vacant(e) => {
let subs = subs.into_iter().map(ConstraintExpr::Reference).collect();
e.insert(ConstraintExpr::AndOr(subs));
}
}
}
Ok(exprs)
}
impl Constraints {
pub fn new(ns: &Namespace, st: &SyntaxTree) -> Result<Self, SemanticError> {
let exprs = gather_constraint_expr(ns, st)?;
Ok(Constraints {
instantiables: exprs
.into_iter()
.map(|(path, expr)| Ok((path, expr.as_instantiables(ns)?)))
.collect::<Result<_, SemanticError>>()?,
})
}
pub fn is_supertype(&self, path: &Path) -> bool {
self.instantiables.contains_key(path)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn constraint_expr_is_in() {
let root = Scope::root();
let a = ConstraintExpr::Reference(Path::entity(&root, "a"));
assert!(a.is_in(&Path::entity(&root, "a")));
assert!(!a.is_in(&Path::entity(&root, "b")));
}
#[test]
fn constraint_expr_andor() {
let root = Scope::root();
let a = ConstraintExpr::Reference(Path::entity(&root, "a"));
let b = ConstraintExpr::Reference(Path::entity(&root, "b"));
assert_eq!(
a.clone().andor(b.clone()),
ConstraintExpr::AndOr(vec![a, b])
);
let a = ConstraintExpr::Reference(Path::entity(&root, "a"));
let b = ConstraintExpr::Reference(Path::entity(&root, "b"));
let c = ConstraintExpr::Reference(Path::entity(&root, "c"));
let ab = a.clone().andor(b.clone());
assert_eq!(ab.andor(c.clone()), ConstraintExpr::AndOr(vec![a, b, c]))
}
const PET: &str = r#"
SCHEMA test_schema;
ENTITY pet;
name : pet_name;
END_ENTITY;
SUBTYPE_CONSTRAINT separate_species FOR pet;
ABSTRACT SUPERTYPE;
ONEOF(cat, rabbit, dog);
END_SUBTYPE_CONSTRAINT;
ENTITY cat SUBTYPE OF (pet);
END_ENTITY;
ENTITY rabbit SUBTYPE OF (pet);
END_ENTITY;
ENTITY dog SUBTYPE OF (pet);
END_ENTITY;
END_SCHEMA;
"#;
const PERSON_ANDOR: &str = r#"
SCHEMA test_schema;
ENTITY person SUPERTYPE OF (employee ANDOR student);
END_ENTITY;
ENTITY employee SUBTYPE OF (person);
END_ENTITY;
ENTITY student SUBTYPE OF (person);
END_ENTITY;
END_SCHEMA;
"#;
const PERSON_AND: &str = r#"
SCHEMA test_schema;
ENTITY person
SUPERTYPE OF (
ONEOF(male,female) AND ONEOF(citizen,alien)
);
END_ENTITY;
ENTITY male SUBTYPE OF (person);
END_ENTITY;
ENTITY female SUBTYPE OF (person);
END_ENTITY;
ENTITY citizen SUBTYPE OF (person);
END_ENTITY;
ENTITY alien SUBTYPE OF (person);
END_ENTITY;
END_SCHEMA;
"#;
const PERSON_AND_SEPARATE: &str = r#"
SCHEMA test_schema;
ENTITY person;
END_ENTITY;
ENTITY male SUBTYPE OF (person);
END_ENTITY;
ENTITY female SUBTYPE OF (person);
END_ENTITY;
ENTITY citizen SUBTYPE OF (person);
END_ENTITY;
ENTITY alien SUBTYPE OF (person);
END_ENTITY;
SUBTYPE_CONSTRAINT person_prop FOR person;
ONEOF(male, female) AND ONEOF(citizen, alien);
END_SUBTYPE_CONSTRAINT;
END_SCHEMA;
"#;
const PERSON_DEFAULT: &str = r#"
SCHEMA test_schema;
ENTITY person;
END_ENTITY;
ENTITY employee SUBTYPE OF (person);
END_ENTITY;
ENTITY student SUBTYPE OF (person);
END_ENTITY;
END_SCHEMA;
"#;
const PERSON_ANDOR_SEPARATE: &str = r#"
SCHEMA test_schema;
ENTITY person;
END_ENTITY;
ENTITY employee SUBTYPE OF (person);
END_ENTITY;
ENTITY student SUBTYPE OF (person);
END_ENTITY;
SUBTYPE_CONSTRAINT person_prop FOR person;
employee ANDOR student;
END_SUBTYPE_CONSTRAINT;
END_SCHEMA;
"#;
const SUPERTYPE_OF: &str = r#"
SCHEMA test_schema;
ENTITY base SUPERTYPE OF (ONEOF (sub1, sub2));
x: REAL;
END_ENTITY;
ENTITY sub1 SUBTYPE OF (base);
y1: REAL;
END_ENTITY;
ENTITY sub2 SUBTYPE OF (base);
y2: REAL;
END_ENTITY;
END_SCHEMA;
"#;
#[test]
fn gather_constraint_expr_pet() {
let st = ast::SyntaxTree::parse(PET).unwrap();
let ns = Namespace::new(&st);
let exprs = gather_constraint_expr(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
let pet = Path::entity(&scope, "pet");
assert_eq!(
dbg!(exprs),
maplit::hashmap! {
pet => ConstraintExpr::OneOf(vec![
ConstraintExpr::Reference(Path::entity(&scope, "cat")),
ConstraintExpr::Reference(Path::entity(&scope, "rabbit")),
ConstraintExpr::Reference(Path::entity(&scope, "dog")),
])
}
);
}
#[test]
fn gather_constraint_expr_person_andor() {
let st = ast::SyntaxTree::parse(PERSON_ANDOR).unwrap();
let ns = Namespace::new(&st);
let exprs = gather_constraint_expr(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
let person = Path::entity(&scope, "person");
assert_eq!(
dbg!(exprs),
maplit::hashmap! {
person => ConstraintExpr::AndOr(vec![
ConstraintExpr::Reference(Path::entity(&scope, "employee")),
ConstraintExpr::Reference(Path::entity(&scope, "student")),
])
}
);
}
#[test]
fn gather_constraint_expr_person_explicit() {
let st = ast::SyntaxTree::parse(PERSON_ANDOR_SEPARATE).unwrap();
let ns = Namespace::new(&st);
let exprs = gather_constraint_expr(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
let person = Path::entity(&scope, "person");
assert_eq!(
dbg!(exprs),
maplit::hashmap! {
person => ConstraintExpr::AndOr(vec![
ConstraintExpr::Reference(Path::entity(&scope, "employee")),
ConstraintExpr::Reference(Path::entity(&scope, "student")),
])
}
);
}
#[test]
fn gather_constraint_expr_person_default() {
let st = ast::SyntaxTree::parse(PERSON_DEFAULT).unwrap();
let ns = Namespace::new(&st);
let exprs = gather_constraint_expr(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
let person = Path::entity(&scope, "person");
assert_eq!(
dbg!(exprs),
maplit::hashmap! {
person => ConstraintExpr::AndOr(vec![
ConstraintExpr::Reference(Path::entity(&scope, "employee")),
ConstraintExpr::Reference(Path::entity(&scope, "student")),
])
}
);
}
#[test]
fn gather_constraint_expr_person_and() {
let st = ast::SyntaxTree::parse(PERSON_AND).unwrap();
let ns = Namespace::new(&st);
let exprs = gather_constraint_expr(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
let person = Path::entity(&scope, "person");
assert_eq!(
dbg!(exprs),
maplit::hashmap! {
person => ConstraintExpr::And(vec![
ConstraintExpr::OneOf(vec![
ConstraintExpr::Reference(Path::entity(&scope, "male")),
ConstraintExpr::Reference(Path::entity(&scope, "female")),
]),
ConstraintExpr::OneOf(vec![
ConstraintExpr::Reference(Path::entity(&scope, "citizen")),
ConstraintExpr::Reference(Path::entity(&scope, "alien")),
]),
])
}
);
}
#[test]
fn gather_constraint_expr_person_and_separate() {
let st = ast::SyntaxTree::parse(PERSON_AND_SEPARATE).unwrap();
let ns = Namespace::new(&st);
let exprs = gather_constraint_expr(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
let person = Path::entity(&scope, "person");
assert_eq!(
dbg!(exprs),
maplit::hashmap! {
person => ConstraintExpr::And(vec![
ConstraintExpr::OneOf(vec![
ConstraintExpr::Reference(Path::entity(&scope, "male")),
ConstraintExpr::Reference(Path::entity(&scope, "female")),
]),
ConstraintExpr::OneOf(vec![
ConstraintExpr::Reference(Path::entity(&scope, "citizen")),
ConstraintExpr::Reference(Path::entity(&scope, "alien")),
]),
])
}
);
}
#[test]
fn constraint_oneof() {
let st = ast::SyntaxTree::parse(PET).unwrap();
let ns = Namespace::new(&st);
let c = Constraints::new(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
assert_eq!(
dbg!(c),
Constraints {
instantiables: maplit::hashmap! {
Path::entity(&scope, "pet") => vec![
vec![Path::entity(&scope, "cat")],
vec![Path::entity(&scope, "rabbit")],
vec![Path::entity(&scope, "dog")],
]
}
}
);
}
#[test]
fn supertype_of_oneof() {
let st = ast::SyntaxTree::parse(SUPERTYPE_OF).unwrap();
let ns = Namespace::new(&st);
let c = Constraints::new(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
assert_eq!(
dbg!(c),
Constraints {
instantiables: maplit::hashmap! {
Path::entity(&scope, "base") => vec![
vec![Path::entity(&scope, "sub1")],
vec![Path::entity(&scope, "sub2")],
]
}
}
);
}
#[test]
fn supertype_of_andor() {
let st = ast::SyntaxTree::parse(PERSON_ANDOR).unwrap();
let ns = Namespace::new(&st);
let c = Constraints::new(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
assert_eq!(
dbg!(c),
Constraints {
instantiables: maplit::hashmap! {
Path::entity(&scope, "person") => vec![
vec![Path::entity(&scope, "employee")],
vec![Path::entity(&scope, "student")],
vec![Path::entity(&scope, "employee"), Path::entity(&scope, "student")],
]
}
}
);
}
#[test]
fn supertype_of_and() {
let st = ast::SyntaxTree::parse(PERSON_AND).unwrap();
let ns = Namespace::new(&st);
let c = Constraints::new(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
assert_eq!(
dbg!(c),
Constraints {
instantiables: maplit::hashmap! {
Path::entity(&scope, "person") => vec![
vec![Path::entity(&scope, "male"), Path::entity(&scope, "citizen")],
vec![Path::entity(&scope, "male"), Path::entity(&scope, "alien")],
vec![Path::entity(&scope, "female"), Path::entity(&scope, "citizen")],
vec![Path::entity(&scope, "female"), Path::entity(&scope, "alien")],
]
}
}
);
}
#[test]
fn default_constraint() {
let st = ast::SyntaxTree::parse(PERSON_DEFAULT).unwrap();
let ns = Namespace::new(&st);
let c = Constraints::new(&ns, &st).unwrap();
let scope = Scope::root().schema("test_schema");
assert_eq!(
dbg!(c),
Constraints {
instantiables: maplit::hashmap! {
Path::entity(&scope, "person") => vec![
vec![Path::entity(&scope, "employee")],
vec![Path::entity(&scope, "student")],
vec![Path::entity(&scope, "employee"), Path::entity(&scope, "student")],
]
}
}
);
}
}