use std::collections::{BTreeMap, BTreeSet};
use super::tarjan::TarjanState;
use super::types::HeadGraphClass;
use crate::ir::{Atom, ConstantSymbol, Literal, PredicateSymbol, Program, Term};
pub(super) type NodeKey = (PredicateSymbol, HeadGraphClass);
#[derive(Debug)]
pub(super) struct DependencyGraph {
pub(super) edges: BTreeMap<NodeKey, Vec<DependencyEdge>>,
pub(super) nodes: BTreeSet<NodeKey>,
}
#[derive(Clone, Debug)]
pub(super) struct DependencyEdge {
pub(super) target: NodeKey,
pub(super) negated: bool,
}
impl DependencyGraph {
pub(super) fn build(program: &Program) -> Self {
let mut head_nodes: BTreeMap<PredicateSymbol, BTreeSet<HeadGraphClass>> = BTreeMap::new();
for rule in &program.rules {
head_nodes
.entry(rule.head.predicate.clone())
.or_default()
.insert(HeadGraphClass::classify(&rule.head));
}
let mut edges: BTreeMap<NodeKey, Vec<DependencyEdge>> = BTreeMap::new();
let mut nodes: BTreeSet<NodeKey> = BTreeSet::new();
for (pred, classes) in &head_nodes {
for class in classes {
nodes.insert((pred.clone(), class.clone()));
}
}
for rule in &program.rules {
let head_class = HeadGraphClass::classify(&rule.head);
let head_key = (rule.head.predicate.clone(), head_class);
nodes.insert(head_key.clone());
for lit in &rule.body {
match lit {
Literal::Positive(atom) => add_body_atom_edges(
atom,
&head_key,
false,
&head_nodes,
&mut nodes,
&mut edges,
),
Literal::Negative(atom) => add_body_atom_edges(
atom,
&head_key,
true,
&head_nodes,
&mut nodes,
&mut edges,
),
Literal::Aggregate(agg) => super::entry::add_aggregate_edges(
agg,
&head_key,
&head_nodes,
&mut nodes,
&mut edges,
),
Literal::Filter(_) | Literal::Bind { .. } => continue,
}
}
}
Self { edges, nodes }
}
pub(super) fn strongly_connected_components(&self) -> Vec<BTreeSet<NodeKey>> {
let mut state = TarjanState::new();
for node in &self.nodes {
if !state.indices.contains_key(node) {
state.strong_connect(node, self);
}
}
state.result
}
}
pub(super) enum BodyGraphFilter {
Any,
Constant(ConstantSymbol),
Narrow,
}
impl BodyGraphFilter {
fn classify(atom: &Atom) -> Self {
match atom.terms.last() {
None => Self::Narrow,
Some(Term::Constant(c)) => Self::Constant(c.clone()),
Some(Term::Variable(_) | Term::TripleTerm(_) | Term::Skolem { .. }) => Self::Any,
}
}
fn matches(&self, class: &HeadGraphClass) -> bool {
match (self, class) {
(Self::Any, _) => true,
(Self::Constant(_), HeadGraphClass::Inherit) => true,
(Self::Constant(c), HeadGraphClass::Literal(d)) => c == d,
(Self::Narrow, HeadGraphClass::Narrow) => true,
_ => false,
}
}
}
pub(super) fn add_body_atom_edges(
atom: &Atom,
head_key: &NodeKey,
negated: bool,
head_nodes: &BTreeMap<PredicateSymbol, BTreeSet<HeadGraphClass>>,
nodes: &mut BTreeSet<NodeKey>,
edges: &mut BTreeMap<NodeKey, Vec<DependencyEdge>>,
) {
let filter = BodyGraphFilter::classify(atom);
let producer_classes = head_nodes.get(&atom.predicate);
let mut targets: Vec<HeadGraphClass> = producer_classes
.into_iter()
.flat_map(|classes| classes.iter().cloned())
.filter(|class| filter.matches(class))
.collect();
if targets.is_empty() {
targets.push(default_body_class(&filter));
}
for class in targets {
let source = (atom.predicate.clone(), class);
nodes.insert(source.clone());
edges.entry(source).or_default().push(DependencyEdge {
target: head_key.clone(),
negated,
});
}
}
fn default_body_class(filter: &BodyGraphFilter) -> HeadGraphClass {
match filter {
BodyGraphFilter::Any => HeadGraphClass::Inherit,
BodyGraphFilter::Constant(c) => HeadGraphClass::Literal(c.clone()),
BodyGraphFilter::Narrow => HeadGraphClass::Narrow,
}
}