use std::collections::{BTreeMap, BTreeSet};
use crate::spec::law::canonical_law_id;
use crate::spec::law::AlgebraicLaw;
use crate::spec::OpSpec;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum Arity {
Unary,
Binary,
Ternary,
Other(usize),
}
impl Arity {
fn of(spec: &OpSpec) -> Self {
match spec.signature.inputs.len() {
1 => Self::Unary,
2 => Self::Binary,
3 => Self::Ternary,
n => Self::Other(n),
}
}
}
fn law_fingerprint(law: &AlgebraicLaw) -> String {
canonical_law_id(law)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CharacterizationEntry {
pub op_id: String,
pub declared_laws: BTreeSet<String>,
pub verdict: Distinctiveness,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Distinctiveness {
Unique,
Collision {
partners: Vec<String>,
suggestion: String,
},
EmptyLawSet,
}
#[derive(Debug, Clone, Default)]
pub struct CharacterizationReport {
pub entries: Vec<CharacterizationEntry>,
}
impl CharacterizationReport {
#[inline]
pub fn unique(&self) -> Vec<&CharacterizationEntry> {
self.entries
.iter()
.filter(|e| matches!(e.verdict, Distinctiveness::Unique))
.collect()
}
#[inline]
pub fn collisions(&self) -> Vec<&CharacterizationEntry> {
self.entries
.iter()
.filter(|e| matches!(e.verdict, Distinctiveness::Collision { .. }))
.collect()
}
#[inline]
pub fn empty(&self) -> Vec<&CharacterizationEntry> {
self.entries
.iter()
.filter(|e| matches!(e.verdict, Distinctiveness::EmptyLawSet))
.collect()
}
}
#[inline]
pub fn characterize(specs: &[OpSpec]) -> CharacterizationReport {
let mut by_arity: BTreeMap<Arity, Vec<(usize, BTreeSet<String>)>> = BTreeMap::new();
for (index, spec) in specs.iter().enumerate() {
let arity = Arity::of(spec);
let fingerprints: BTreeSet<String> = spec.laws.iter().map(law_fingerprint).collect();
by_arity
.entry(arity)
.or_default()
.push((index, fingerprints));
}
let mut entries = Vec::with_capacity(specs.len());
for (index, spec) in specs.iter().enumerate() {
let arity = Arity::of(spec);
let my_laws: BTreeSet<String> = spec.laws.iter().map(law_fingerprint).collect();
if my_laws.is_empty() {
entries.push(CharacterizationEntry {
op_id: spec.id.to_string(),
declared_laws: my_laws,
verdict: Distinctiveness::EmptyLawSet,
});
continue;
}
let mut partners: Vec<String> = Vec::new();
let bucket = by_arity.get(&arity).expect("arity bucket exists");
for (other_index, other_laws) in bucket {
if *other_index == index {
continue;
}
if my_laws.is_subset(other_laws) {
partners.push(specs[*other_index].id.to_string());
}
}
partners.sort();
partners.dedup();
let verdict = if partners.is_empty() {
Distinctiveness::Unique
} else {
let suggestion = suggest_discriminator(&my_laws);
Distinctiveness::Collision {
partners,
suggestion,
}
};
entries.push(CharacterizationEntry {
op_id: spec.id.to_string(),
declared_laws: my_laws,
verdict,
});
}
CharacterizationReport { entries }
}
fn suggest_discriminator(laws: &BTreeSet<String>) -> String {
let has = |needle: &str| laws.iter().any(|l| l.starts_with(needle));
if !has("identity") && !has("absorbing") {
"add an Identity or Absorbing law with a concrete element".to_string()
} else if !has("self-inverse") && !has("idempotent") {
"add a SelfInverse or Idempotent law; these constrain diagonal behaviour".to_string()
} else if !has("bounded") {
"add a Bounded law to pin the output range".to_string()
} else if !has("de-morgan") && !has("complement") && !has("distributive") {
"add a cross-op law (DeMorgan, Complement, DistributiveOver) to tie this op to another"
.to_string()
} else {
"consider a Custom law or parameterized Identity with a value specific to this op"
.to_string()
}
}
pub struct CharacterizeEnforcer;
impl crate::enforce::EnforceGate for CharacterizeEnforcer {
fn id(&self) -> &'static str {
"characterize"
}
fn name(&self) -> &'static str {
"characterize"
}
fn run(&self, ctx: &crate::enforce::EnforceCtx<'_>) -> Vec<crate::enforce::Finding> {
let report = characterize(ctx.specs);
let messages = report
.entries
.into_iter()
.filter_map(|entry| match entry.verdict {
Distinctiveness::Unique => None,
Distinctiveness::EmptyLawSet => Some(format!(
"characterize({}): empty law set. Fix: add discriminating algebraic laws.",
entry.op_id
)),
Distinctiveness::Collision {
partners,
suggestion,
} => Some(format!(
"characterize({}): law set collides with {:?}. Fix: {}.",
entry.op_id, partners, suggestion
)),
})
.collect::<Vec<_>>();
crate::enforce::finding_result(self.id(), messages)
}
}
pub const REGISTERED: CharacterizeEnforcer = CharacterizeEnforcer;