use std::sync::Arc;
use crate::error::GatError;
use crate::theory::Theory;
pub fn colimit(t1: &Theory, t2: &Theory, shared: &Theory) -> Result<Theory, GatError> {
let mut sorts = t1.sorts.clone();
for sort in &t2.sorts {
if t1.has_sort(&sort.name) {
if shared.has_sort(&sort.name) {
continue;
}
let t1_sort = t1
.find_sort(&sort.name)
.ok_or_else(|| GatError::SortConflict {
name: sort.name.to_string(),
})?;
if t1_sort.params != sort.params {
return Err(GatError::SortConflict {
name: sort.name.to_string(),
});
}
} else {
sorts.push(sort.clone());
}
}
let mut ops = t1.ops.clone();
for op in &t2.ops {
if t1.has_op(&op.name) {
if shared.has_op(&op.name) {
continue;
}
let t1_op = t1.find_op(&op.name).ok_or_else(|| GatError::OpConflict {
name: op.name.to_string(),
})?;
if t1_op.inputs != op.inputs || t1_op.output != op.output {
return Err(GatError::OpConflict {
name: op.name.to_string(),
});
}
} else {
ops.push(op.clone());
}
}
let mut eqs = t1.eqs.clone();
for eq in &t2.eqs {
if let Some(t1_eq) = t1.find_eq(&eq.name) {
if shared.find_eq(&eq.name).is_some() {
continue;
}
if t1_eq.lhs != eq.lhs || t1_eq.rhs != eq.rhs {
return Err(GatError::EqConflict {
name: eq.name.to_string(),
});
}
} else {
eqs.push(eq.clone());
}
}
let name: Arc<str> = format!("{}_{}_colimit", t1.name, t2.name).into();
Ok(Theory::new(name, sorts, ops, eqs))
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::eq::{Equation, Term};
use crate::op::Operation;
use crate::sort::{Sort, SortParam};
#[test]
fn graph_constraint_colimit() {
let shared = Theory::new(
"ThVertex",
vec![Sort::simple("Vertex")],
Vec::new(),
Vec::new(),
);
let th_graph = Theory::new(
"ThGraph",
vec![Sort::simple("Vertex"), Sort::simple("Edge")],
vec![
Operation::unary("src", "e", "Edge", "Vertex"),
Operation::unary("tgt", "e", "Edge", "Vertex"),
],
Vec::new(),
);
let th_constraint = Theory::new(
"ThConstraint",
vec![Sort::simple("Vertex"), Sort::simple("Constraint")],
vec![Operation::unary("target", "c", "Constraint", "Vertex")],
Vec::new(),
);
let result = colimit(&th_graph, &th_constraint, &shared).unwrap();
assert_eq!(&*result.name, "ThGraph_ThConstraint_colimit");
assert_eq!(result.sorts.len(), 3); assert_eq!(result.ops.len(), 3);
assert!(result.find_sort("Vertex").is_some());
assert!(result.find_sort("Edge").is_some());
assert!(result.find_sort("Constraint").is_some());
assert!(result.find_op("src").is_some());
assert!(result.find_op("tgt").is_some());
assert!(result.find_op("target").is_some());
}
#[test]
fn sort_conflict_detected() {
let shared = Theory::new("Empty", Vec::new(), Vec::new(), Vec::new());
let t1 = Theory::new("T1", vec![Sort::simple("X")], Vec::new(), Vec::new());
let t2 = Theory::new(
"T2",
vec![Sort::dependent("X", vec![SortParam::new("a", "S")])],
Vec::new(),
Vec::new(),
);
let result = colimit(&t1, &t2, &shared);
assert!(matches!(result, Err(GatError::SortConflict { .. })));
}
#[test]
fn op_conflict_detected() {
let shared = Theory::new("Empty", Vec::new(), Vec::new(), Vec::new());
let t1 = Theory::new(
"T1",
vec![Sort::simple("A"), Sort::simple("B")],
vec![Operation::unary("f", "x", "A", "B")],
Vec::new(),
);
let t2 = Theory::new(
"T2",
vec![Sort::simple("A"), Sort::simple("B")],
vec![Operation::unary("f", "x", "B", "A")], Vec::new(),
);
let result = colimit(&t1, &t2, &shared);
assert!(matches!(result, Err(GatError::OpConflict { .. })));
}
#[test]
fn eq_conflict_detected() {
let shared = Theory::new("Empty", Vec::new(), Vec::new(), Vec::new());
let t1 = Theory::new(
"T1",
Vec::new(),
Vec::new(),
vec![Equation::new("ax", Term::var("x"), Term::var("y"))],
);
let t2 = Theory::new(
"T2",
Vec::new(),
Vec::new(),
vec![Equation::new("ax", Term::var("a"), Term::var("b"))], );
let result = colimit(&t1, &t2, &shared);
assert!(matches!(result, Err(GatError::EqConflict { .. })));
}
#[test]
fn compatible_non_shared_duplicates_allowed() {
let shared = Theory::new("Empty", Vec::new(), Vec::new(), Vec::new());
let t1 = Theory::new("T1", vec![Sort::simple("X")], Vec::new(), Vec::new());
let t2 = Theory::new("T2", vec![Sort::simple("X")], Vec::new(), Vec::new());
let result = colimit(&t1, &t2, &shared).unwrap();
assert_eq!(result.sorts.len(), 1);
}
#[test]
fn shared_declarations_not_duplicated() {
let shared = Theory::new(
"Shared",
vec![Sort::simple("S")],
vec![Operation::nullary("c", "S")],
vec![Equation::new("e", Term::var("x"), Term::var("x"))],
);
let t1 = Theory::new(
"T1",
vec![Sort::simple("S"), Sort::simple("A")],
vec![Operation::nullary("c", "S")],
vec![Equation::new("e", Term::var("x"), Term::var("x"))],
);
let t2 = Theory::new(
"T2",
vec![Sort::simple("S"), Sort::simple("B")],
vec![Operation::nullary("c", "S")],
vec![Equation::new("e", Term::var("x"), Term::var("x"))],
);
let result = colimit(&t1, &t2, &shared).unwrap();
assert_eq!(result.sorts.len(), 3); assert_eq!(result.ops.len(), 1); assert_eq!(result.eqs.len(), 1); }
}