use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum LpTerm {
Atom(String),
Var(String),
Compound {
functor: String,
args: Vec<LpTerm>,
},
Integer(i64),
Float(f64),
List(Vec<LpTerm>, Option<Box<LpTerm>>),
}
impl LpTerm {
pub fn atom(s: impl Into<String>) -> Self {
Self::Atom(s.into())
}
pub fn var(s: impl Into<String>) -> Self {
Self::Var(s.into())
}
pub fn compound(functor: impl Into<String>, args: Vec<LpTerm>) -> Self {
Self::Compound {
functor: functor.into(),
args,
}
}
pub fn list(items: Vec<LpTerm>) -> Self {
Self::List(items, None)
}
pub fn list_with_tail(items: Vec<LpTerm>, tail: LpTerm) -> Self {
Self::List(items, Some(Box::new(tail)))
}
pub fn is_ground(&self) -> bool {
match self {
Self::Var(_) => false,
Self::Atom(_) | Self::Integer(_) | Self::Float(_) => true,
Self::Compound { args, .. } => args.iter().all(|a| a.is_ground()),
Self::List(items, tail) => {
items.iter().all(|a| a.is_ground()) && tail.as_ref().map_or(true, |t| t.is_ground())
}
}
}
pub fn variables(&self) -> Vec<String> {
let mut vars = Vec::new();
self.collect_vars(&mut vars);
vars
}
fn collect_vars(&self, out: &mut Vec<String>) {
match self {
Self::Var(v) => out.push(v.clone()),
Self::Compound { args, .. } => {
for a in args {
a.collect_vars(out);
}
}
Self::List(items, tail) => {
for a in items {
a.collect_vars(out);
}
if let Some(t) = tail {
t.collect_vars(out);
}
}
_ => {}
}
}
}
#[derive(Debug, Clone)]
pub struct LpClause {
pub head: LpTerm,
pub body: Vec<LpTerm>,
}
impl LpClause {
pub fn fact(head: LpTerm) -> Self {
Self { head, body: vec![] }
}
pub fn rule(head: LpTerm, body: Vec<LpTerm>) -> Self {
Self { head, body }
}
pub fn is_fact(&self) -> bool {
self.body.is_empty()
}
}
#[derive(Debug, Clone, Default)]
pub struct LpDatabase {
pub clauses: Vec<LpClause>,
}
impl LpDatabase {
pub fn new() -> Self {
Self { clauses: vec![] }
}
pub fn add_fact(&mut self, head: LpTerm) {
self.clauses.push(LpClause::fact(head));
}
pub fn add_rule(&mut self, head: LpTerm, body: Vec<LpTerm>) {
self.clauses.push(LpClause::rule(head, body));
}
pub fn matching_clauses(&self, goal: &LpTerm) -> Vec<&LpClause> {
let (goal_f, goal_a) = functor_arity(goal);
self.clauses
.iter()
.filter(|c| {
let (hf, ha) = functor_arity(&c.head);
hf == goal_f && ha == goal_a
})
.collect()
}
}
pub(super) fn functor_arity(t: &LpTerm) -> (&str, usize) {
match t {
LpTerm::Atom(s) => (s.as_str(), 0),
LpTerm::Compound { functor, args } => (functor.as_str(), args.len()),
LpTerm::Integer(_) => ("$int", 0),
LpTerm::Float(_) => ("$float", 0),
LpTerm::Var(_) => ("$var", 0),
LpTerm::List(items, None) => ("$list", items.len()),
LpTerm::List(items, Some(_)) => ("$list_partial", items.len()),
}
}
#[derive(Debug, Clone, Default)]
pub struct Substitution {
pub bindings: HashMap<String, LpTerm>,
}
impl Substitution {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
}
}
pub fn bind(&mut self, var: impl Into<String>, term: LpTerm) {
self.bindings.insert(var.into(), term);
}
pub fn extend(&self, other: &Substitution) -> Substitution {
let mut result = self.clone();
for (k, v) in &other.bindings {
result.bindings.insert(k.clone(), v.clone());
}
result
}
pub fn lookup(&self, var: &str) -> Option<&LpTerm> {
self.bindings.get(var)
}
pub fn deref(&self, var: &str) -> LpTerm {
match self.bindings.get(var) {
None => LpTerm::Var(var.to_string()),
Some(LpTerm::Var(v2)) if v2 != var => self.deref(v2),
Some(t) => t.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct Query {
pub goals: Vec<LpTerm>,
}
impl Query {
pub fn new(goals: Vec<LpTerm>) -> Self {
Self { goals }
}
pub fn single(goal: LpTerm) -> Self {
Self { goals: vec![goal] }
}
}
#[derive(Debug, Clone)]
pub enum ResolutionResult {
Success(Substitution),
Failure,
Error(String),
}
#[derive(Debug, Clone)]
pub struct SolveConfig {
pub max_depth: usize,
pub max_solutions: usize,
pub occurs_check: bool,
}
impl Default for SolveConfig {
fn default() -> Self {
Self {
max_depth: 512,
max_solutions: 1024,
occurs_check: false,
}
}
}
impl SolveConfig {
pub fn new() -> Self {
Self::default()
}
}