anthem 2.0.0

A command-line application for assisting in the verification of answer set programs
Documentation
use {
    crate::syntax_tree::asp::mini_gringo::{Head, Predicate, Program},
    indexmap::IndexSet,
    petgraph::{algo::is_cyclic_directed, graph::DiGraph},
    std::collections::HashMap,
};

pub trait PrivateRecursion {
    type Predicates;

    fn has_private_recursion(&self, private_predicates: &Self::Predicates) -> bool;
}

impl PrivateRecursion for Program {
    type Predicates = IndexSet<Predicate>;

    fn has_private_recursion(&self, private_predicates: &Self::Predicates) -> bool {
        for rule in &self.rules {
            match rule.head {
                Head::Choice(ref a) => {
                    if private_predicates.contains(&a.predicate()) {
                        return true;
                    }
                }
                Head::Basic(_) | Head::Falsity => (),
            }
        }

        let mut dependency_graph = DiGraph::<(), ()>::new();
        let mut mapping = HashMap::new();

        for predicate in self.predicates() {
            if private_predicates.contains(&predicate) {
                let node = dependency_graph.add_node(());
                mapping.insert(predicate, node);
            }
        }

        for rule in &self.rules {
            if let Some(head_predicate) = rule.head.predicate() {
                if private_predicates.contains(&head_predicate) {
                    for body_predicate in rule.body.predicates() {
                        if private_predicates.contains(&body_predicate) {
                            dependency_graph.update_edge(
                                mapping[&head_predicate],
                                mapping[&body_predicate],
                                (),
                            );
                        }
                    }
                }
            }
        }

        is_cyclic_directed(&dependency_graph)
    }
}

#[cfg(test)]
mod tests {
    use {
        crate::{
            analyzing::private_recursion::PrivateRecursion,
            syntax_tree::asp::mini_gringo::{Predicate, Program},
        },
        indexmap::IndexSet,
        std::str::FromStr,
    };

    #[test]
    fn test_private_recursion() {
        let private_predicates: IndexSet<Predicate> = IndexSet::from_iter(
            ["a/0", "b/0", "p/1"]
                .into_iter()
                .map(|p| p.parse().unwrap()),
        );

        for program in ["a :- not c. c :- not a.", "a :- p(1). p(X) :- q(X)."] {
            assert!(
                !Program::from_str(program)
                    .unwrap()
                    .has_private_recursion(&private_predicates)
            )
        }

        for program in [
            "{a}.",
            "a :- not a.",
            "a :- not not a.",
            "a :- not b. b :- not a.",
            "{p(X)} :- not not p(X). b :- a.",
        ] {
            assert!(
                Program::from_str(program)
                    .unwrap()
                    .has_private_recursion(&private_predicates)
            )
        }
    }
}