pub mod evaluator;
pub mod parser;
pub mod tests;
use std::collections::{HashMap, HashSet};
use thiserror::Error;
#[derive(Debug, Error, Clone)]
pub enum DatalogError {
#[error("parse error: {0}")]
ParseError(String),
#[error("evaluation error: {0}")]
EvaluationError(String),
#[error("stratification error (cyclic negation detected): {0}")]
StratificationError(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum DatalogValue {
Str(String),
Int(i64),
Bool(bool),
}
impl std::fmt::Display for DatalogValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DatalogValue::Str(s) => write!(f, "{s}"),
DatalogValue::Int(i) => write!(f, "{i}"),
DatalogValue::Bool(b) => write!(f, "{b}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DatalogTerm {
Variable(String),
Constant(DatalogValue),
}
impl std::fmt::Display for DatalogTerm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DatalogTerm::Variable(v) => write!(f, "{v}"),
DatalogTerm::Constant(c) => write!(f, "{c}"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DatalogAtom {
pub predicate: String,
pub terms: Vec<DatalogTerm>,
}
impl std::fmt::Display for DatalogAtom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let terms: Vec<String> = self.terms.iter().map(|t| t.to_string()).collect();
write!(f, "{}({})", self.predicate, terms.join(", "))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DatalogFact {
pub predicate: String,
pub args: Vec<DatalogValue>,
}
impl std::fmt::Display for DatalogFact {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let args: Vec<String> = self.args.iter().map(|a| a.to_string()).collect();
write!(f, "{}({}).", self.predicate, args.join(", "))
}
}
#[derive(Debug, Clone)]
pub struct DatalogRule {
pub head: DatalogAtom,
pub body: Vec<DatalogAtom>,
}
impl std::fmt::Display for DatalogRule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.body.is_empty() {
write!(f, "{}.", self.head)
} else {
let body: Vec<String> = self.body.iter().map(|a| a.to_string()).collect();
write!(f, "{} :- {}.", self.head, body.join(", "))
}
}
}
#[derive(Debug, Clone, Default)]
pub struct DatalogProgram {
pub rules: Vec<DatalogRule>,
pub edb: Vec<DatalogFact>,
}
impl DatalogProgram {
pub fn new() -> Self {
Self::default()
}
pub fn add_rule(&mut self, rule: DatalogRule) {
self.rules.push(rule);
}
pub fn add_fact(&mut self, fact: DatalogFact) {
self.edb.push(fact);
}
pub fn idb_predicates(&self) -> HashSet<String> {
self.rules
.iter()
.map(|r| r.head.predicate.clone())
.collect()
}
pub fn edb_predicates(&self) -> HashSet<String> {
let idb = self.idb_predicates();
self.edb
.iter()
.map(|f| f.predicate.clone())
.filter(|p| !idb.contains(p))
.collect()
}
}
#[derive(Debug, Clone, Default)]
pub struct FactDatabase {
inner: HashMap<String, HashSet<Vec<DatalogValue>>>,
}
impl FactDatabase {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, predicate: &str, args: Vec<DatalogValue>) -> bool {
self.inner
.entry(predicate.to_string())
.or_default()
.insert(args)
}
pub fn insert_fact(&mut self, fact: &DatalogFact) -> bool {
self.insert(&fact.predicate, fact.args.clone())
}
pub fn contains(&self, predicate: &str, args: &[DatalogValue]) -> bool {
self.inner
.get(predicate)
.is_some_and(|set| set.contains(args))
}
pub fn tuples_for(&self, predicate: &str) -> impl Iterator<Item = &Vec<DatalogValue>> {
static EMPTY: std::sync::OnceLock<HashSet<Vec<DatalogValue>>> = std::sync::OnceLock::new();
let empty = EMPTY.get_or_init(HashSet::new);
self.inner.get(predicate).unwrap_or(empty).iter()
}
pub fn predicates(&self) -> impl Iterator<Item = &String> {
self.inner.keys()
}
pub fn len(&self) -> usize {
self.inner.values().map(|s| s.len()).sum()
}
pub fn is_empty(&self) -> bool {
self.inner.values().all(|s| s.is_empty())
}
pub fn get_set(&self, predicate: &str) -> Option<&HashSet<Vec<DatalogValue>>> {
self.inner.get(predicate)
}
pub fn merge(&mut self, other: &FactDatabase) -> usize {
let mut count = 0;
for (pred, tuples) in &other.inner {
let entry = self.inner.entry(pred.clone()).or_default();
for tuple in tuples {
if entry.insert(tuple.clone()) {
count += 1;
}
}
}
count
}
}
pub type Substitution = HashMap<String, DatalogValue>;
pub use evaluator::SemiNaiveEvaluator;
pub use parser::{parse_atom, parse_program, parse_rule};