This commit is contained in:
2026-02-08 22:48:51 +01:00
parent ced43b76bf
commit 134ebc6dc6
11 changed files with 1278 additions and 1 deletions

112
src/ast.rs Normal file
View File

@ -0,0 +1,112 @@
use std::fmt::Display;
pub type Variable = String;
#[derive(Clone, Debug)]
pub struct Module
{
pub clauses: Vec<Clause>,
}
#[derive(Clone, Debug)]
pub struct Clause
{
pub head: Predicate,
pub body: Option<Body>,
}
#[derive(Clone, Debug)]
pub enum Body
{
Term(Predicate),
And(Vec<Body>),
Or(Vec<Body>),
}
#[derive(Clone, Debug)]
pub enum Predicate
{
Variable(Variable), // Upercase variable like X
Fixed(String, Vec<Predicate>),
}
impl Display for Body
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
match self
{
Body::Term(predicate) => write!(f, "{}", predicate),
Body::And(items) | Body::Or(items) =>
{
let len = items.len();
for (i, item) in items.iter().enumerate()
{
write!(f, "{}", item)?;
if i != len - 1
{
write!(f, ",")?;
}
}
Ok(())
}
}
}
}
impl Display for Predicate
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
match self
{
Predicate::Variable(variable) => write!(f, "{}", variable),
Predicate::Fixed(name, predicates) =>
{
write!(f, "{}", name)?;
if !predicates.is_empty()
{
write!(f, "(")?;
let len = predicates.len();
for (i, predicate) in predicates.iter().enumerate()
{
write!(f, "{}", predicate)?;
if i != len - 1
{
write!(f, ", ")?;
}
}
write!(f, ")")
}
else
{
Ok(())
}
}
}
}
}
impl Display for Clause
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
write!(f, "{}", self.head)?;
self.body
.as_ref()
.map_or_else(|| Ok(()), |body| write!(f, ":- {}", body))?;
write!(f, ".")
}
}
impl Display for Module
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
for clause in self.clauses.iter()
{
writeln!(f, "{}", clause)?;
}
Ok(())
}
}

3
src/lib.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod ast;
pub mod parsing;
pub mod prover;

View File

@ -1,4 +1,37 @@
use picolog::ast::Body;
use picolog::ast::Module;
use picolog::ast::Predicate;
fn main()
{
println!("Hello, world!");
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.format_timestamp(None)
.init();
//println!("{:#?}", Module::parse_from_file("1.pl"));
let module: Module = "
integer(zero).
integer(s(X)) :- integer(X).
add(X, zero, X).
add(X, s(Y), Z) :- add(s(X), Y, Z).
mult(zero, X, zero).
mult(s(Y), X, Z) :- mult(Y, X, W), add(W, X, Z).
"
.into();
//let prop: Body = "integer(s(zero))".into();
let prop: Body = "mult(X, s(s(s(zero))), s(s(s(s(s(s(s(s(s(zero))))))))))".into();
for c in module.prove(&prop)
{
println!("true:");
println!("{}", c.simplified());
let _ = std::io::stdin().read_line(&mut String::new());
}
// let p: Predicate = "add(s(zero), zero, Y)".into();
// let p1: Predicate = "add(X, zero, X)".into();
// // let p: Predicate = "integer(s(zero))".into();
// // let p1: Predicate = "integer(s(X))".into();
// println!("{}", p.matches(&p1).unwrap());
}

124
src/parsing.rs Normal file
View File

@ -0,0 +1,124 @@
use std::path::Path;
use winnow::Parser;
use winnow::Result;
use winnow::ascii::alphanumeric0;
use winnow::ascii::multispace0;
use winnow::combinator::alt;
use winnow::combinator::delimited;
use winnow::combinator::opt;
use winnow::combinator::separated;
use winnow::combinator::seq;
use winnow::error::ContextError;
use crate::ast::Body;
use crate::ast::Clause;
use crate::ast::Module;
use crate::ast::Predicate;
pub fn predicate_parse(input: &mut &str) -> Result<Predicate>
{
let ident = alphanumeric0.parse_next(input)?;
// Check if output is a variable
if ident.chars().next().is_some_and(|char| char.is_uppercase())
{
Ok(Predicate::Variable(String::from(ident)))
}
else
{
let arguments = delimited(
("(", multispace0),
separated(1.., predicate_parse, (multispace0, ",", multispace0)),
(multispace0, ")"),
)
.parse_next(input)
.unwrap_or(Vec::new());
Ok(Predicate::Fixed(String::from(ident), arguments))
}
}
fn body_parse_or(input: &mut &str) -> Result<Body>
{
separated(
1..,
alt((
delimited("(", body_parse, ")"),
predicate_parse.map(Body::Term),
)),
(multispace0, ";", multispace0),
)
.map(Body::Or)
.parse_next(input)
}
pub fn body_parse(input: &mut &str) -> Result<Body>
{
// Parse and
separated(1.., body_parse_or, (multispace0, ",", multispace0))
.map(Body::And)
.parse_next(input)
}
pub fn clause_parse(input: &mut &str) -> Result<Clause>
{
seq! {
Clause
{
head: predicate_parse,
body: opt((multispace0, ":-", multispace0, body_parse).map(|(_, _, _, b)| b)),
_: multispace0,
_: "."
}
}
.parse_next(input)
}
pub fn module_parse(input: &mut &str) -> Result<Module>
{
let _: Result<&str, ContextError> = multispace0.parse_next(input);
separated(0.., clause_parse, multispace0)
.map(|clauses| Module { clauses })
.parse_next(input)
}
impl<T> From<T> for Module
where
T: AsRef<str>,
{
fn from(value: T) -> Self
{
let mut str: &str = value.as_ref();
module_parse.parse_next(&mut str).unwrap()
}
}
impl Module
{
pub fn parse_from_file<P: AsRef<Path>>(path: P) -> Self
{
std::fs::read_to_string(path).unwrap().into()
}
}
impl<T> From<T> for Predicate
where
T: AsRef<str>,
{
fn from(value: T) -> Self
{
let mut str: &str = value.as_ref();
predicate_parse.parse_next(&mut str).unwrap()
}
}
impl<T> From<T> for Body
where
T: AsRef<str>,
{
fn from(value: T) -> Self
{
let mut str: &str = value.as_ref();
body_parse.parse_next(&mut str).unwrap()
}
}

382
src/prover.rs Normal file
View File

@ -0,0 +1,382 @@
pub mod constraints;
pub mod trace;
pub mod unification;
use std::cell::Cell;
use std::collections::VecDeque;
use std::rc::Rc;
use litemap::LiteMap;
use log::info;
use crate::ast::Body;
use crate::ast::Clause;
use crate::ast::Module;
use crate::ast::Predicate;
use crate::prover::constraints::Constraints;
#[derive(Clone, Copy, Debug, Default)]
pub struct Counter(usize);
#[derive(Clone, Debug, Default)]
pub struct GlobalCounter(Rc<Cell<usize>>);
impl GlobalCounter
{
pub fn new() -> GlobalCounter
{
GlobalCounter(Rc::new(Cell::new(0)))
}
pub fn get(&self) -> usize
{
let val = self.0.get();
self.0.set(val + 1);
val
}
pub fn snapshot(&self) -> Counter
{
Counter(self.0.get())
}
pub fn restore(&mut self, snapshot: Counter)
{
self.0.set(snapshot.0);
}
}
pub struct BodyProver<'a>
{
module: &'a Module,
constraints: Constraints,
prover: Box<dyn Iterator<Item = Constraints> + 'a>,
}
pub struct PredicateProver<'a>
{
module: &'a Module,
predicate: Predicate,
constraints: Constraints,
counter_snapshot: Counter,
global_counter: GlobalCounter,
current_clause: usize,
sub_proof: Option<BodyProver<'a>>,
}
pub struct AndProver<'a>
{
module: &'a Module,
bodies: Vec<Body>,
constraints: Constraints,
global_counter: GlobalCounter,
sub_proofs: VecDeque<(Counter, BodyProver<'a>)>,
}
impl BodyProver<'_>
{
pub fn new<'a>(
module: &'a Module,
global_counter: GlobalCounter,
body: Body,
constraints: Constraints,
) -> BodyProver<'a>
{
let prover: Box<dyn Iterator<Item = Constraints>> = match &body
{
Body::Term(predicate) => Box::new(PredicateProver::new(
module,
global_counter,
predicate.clone(),
constraints.clone(),
)),
Body::And(items) => Box::new(AndProver::new(
module,
global_counter,
items.clone(),
constraints.clone(),
)),
Body::Or(items) => Box::new(BodyProver::new(
module,
global_counter,
items[0].clone(),
constraints.clone(),
)),
};
info!(target: "BodyProver", "Proving {}", body);
BodyProver {
module,
constraints,
prover,
}
}
}
impl PredicateProver<'_>
{
pub fn new<'a>(
module: &'a Module,
global_counter: GlobalCounter,
predicate: Predicate,
constraints: Constraints,
) -> PredicateProver<'a>
{
info!(target: "PredicateProver", "Proving {}", predicate);
PredicateProver {
module,
predicate,
constraints,
current_clause: 0,
sub_proof: None,
counter_snapshot: global_counter.snapshot(),
global_counter,
}
}
}
impl AndProver<'_>
{
pub fn new<'a>(
module: &'a Module,
global_counter: GlobalCounter,
bodies: Vec<Body>,
constraints: Constraints,
) -> AndProver<'a>
{
assert!(!bodies.is_empty());
AndProver {
module,
sub_proofs: VecDeque::from(vec![(
global_counter.snapshot(),
BodyProver::new(
module,
global_counter.clone(),
bodies[0].clone(),
constraints.clone(),
),
)]),
bodies,
constraints,
global_counter,
}
}
}
impl Iterator for PredicateProver<'_>
{
type Item = Constraints;
fn next(&mut self) -> Option<Self::Item>
{
match self.sub_proof.as_mut()
{
None =>
{
if self.current_clause == self.module.clauses.len()
{
None
}
else
{
let clause = &self.module.clauses[self.current_clause]
.make_unique(self.global_counter.clone());
info!(target: "PredicateProver", "Unifying '{}' against '{}'", self.predicate, clause);
let uni = self.predicate.matches(&clause.head);
let full_constraints = uni.and_then(|x| x.and(&self.constraints));
if let Some(c) = &full_constraints
{
info!(target: "PredicateProver", " => {}", c);
}
else
{
info!(target: "PredicateProver", " => (Can't unify)");
}
match full_constraints
{
Some(constraints) if clause.body.is_none() =>
{
self.current_clause += 1;
Some(constraints)
}
Some(constraints) =>
{
self.current_clause += 1;
self.counter_snapshot = self.global_counter.snapshot();
self.sub_proof = Some(BodyProver::new(
self.module,
self.global_counter.clone(),
clause.body.clone().unwrap(),
constraints,
));
self.next()
}
None =>
{
self.current_clause += 1;
self.next()
}
}
}
}
Some(prover) =>
{
let next = prover.next();
match next
{
Some(constraints) => Some(constraints),
None =>
{
self.global_counter.restore(self.counter_snapshot);
self.sub_proof = None;
self.next()
}
}
}
}
}
}
impl Iterator for BodyProver<'_>
{
type Item = Constraints;
fn next(&mut self) -> Option<Constraints>
{
self.prover.next()
}
}
impl Iterator for AndProver<'_>
{
type Item = Constraints;
fn next(&mut self) -> Option<Self::Item>
{
if self.sub_proofs.is_empty()
{
return None;
}
let (current_proof_snap, mut current_proof) = self.sub_proofs.pop_back().unwrap();
match current_proof.next()
{
Some(constraints) =>
{
if self.sub_proofs.len() == self.bodies.len() - 1
{
self.sub_proofs
.push_back((current_proof_snap, current_proof));
Some(constraints)
}
else
{
self.sub_proofs
.push_back((current_proof_snap, current_proof));
self.sub_proofs.push_back((
self.global_counter.snapshot(),
BodyProver::new(
self.module,
self.global_counter.clone(),
self.bodies[self.sub_proofs.len()].clone(),
constraints,
),
));
self.next()
}
}
None =>
{
self.global_counter.restore(current_proof_snap);
self.next()
}
}
}
}
impl Module
{
pub fn prove<'a>(&'a self, body: &'a Body) -> BodyProver<'a>
{
BodyProver::new(
self,
GlobalCounter::new(),
body.clone(),
Constraints::none(),
)
}
}
impl Clause
{
pub fn make_unique(&self, counter: GlobalCounter) -> Clause
{
let mut unique_map = LiteMap::new();
Clause {
head: self.head.make_unique(counter.clone(), &mut unique_map),
body: self
.body
.as_ref()
.map(|body| body.make_unique(counter.clone(), &mut unique_map)),
}
}
}
impl Body
{
pub fn make_unique(
&self,
counter: GlobalCounter,
unique_map: &mut LiteMap<String, usize>,
) -> Body
{
match self
{
Body::Term(predicate) => Body::Term(predicate.make_unique(counter.clone(), unique_map)),
Body::And(items) => Body::And(
items
.iter()
.map(|x| x.make_unique(counter.clone(), unique_map))
.collect(),
),
Body::Or(items) => Body::Or(
items
.iter()
.map(|x| x.make_unique(counter.clone(), unique_map))
.collect(),
),
}
}
}
impl Predicate
{
pub fn make_unique(
&self,
counter: GlobalCounter,
unique_map: &mut LiteMap<String, usize>,
) -> Self
{
match self
{
Predicate::Variable(name) => Predicate::Variable(format!(
"_{}[{}]",
name,
unique_map
.entry(name.clone())
.or_insert_with(|| counter.get())
)),
Predicate::Fixed(name, predicates) => Predicate::Fixed(
name.clone(),
predicates
.iter()
.map(|x| x.make_unique(counter.clone(), unique_map))
.collect(),
),
}
}
}

183
src/prover/constraints.rs Normal file
View File

@ -0,0 +1,183 @@
use std::fmt::Display;
use litemap::LiteMap;
use winnow::Str;
use crate::ast::Predicate;
use crate::ast::Variable;
use crate::prover::constraints;
#[derive(Clone, Debug)]
pub struct Constraints
{
set: LiteMap<Variable, Predicate>,
}
impl Constraints
{
pub fn none() -> Self
{
Constraints {
set: LiteMap::new(),
}
}
pub fn with(variable: Variable, predicate: Predicate) -> Self
{
let mut c = Constraints::none();
c.set.insert(variable, predicate);
c
}
pub fn try_append(&mut self, variable: &Variable, predicate: &Predicate) -> bool
{
if let Some(other_predicate) = self.set.get(variable)
{
// If variable is already contrained, we need to check if both contraints are
// contradictory
// Try to unify both predicates
let unification = predicate.matches(other_predicate);
if let Some(unification_contraints) = unification
{
// Instead of adding the variable = predicates contraint,
// We can try adding the unification contraints which is implicitely the same
if self.try_merge(&unification_contraints)
{
self.set.insert(variable.clone(), predicate.clone());
true
}
else
{
false
}
}
else
{
// Unification is impossible, variable = predicates is contradictory under self
false
}
}
else
{
// No constraint
self.set.insert(variable.clone(), predicate.clone());
true
}
}
pub fn try_merge(&mut self, constraints: &Constraints) -> bool
{
// Trying to merge, is just trying to add all of the constraints into self
let mut ok = self.clone();
for (var, pred) in constraints.set.iter()
{
if !ok.try_append(var, pred)
{
return false;
}
}
*self = ok;
true
}
pub fn and(&self, constraints: &Constraints) -> Option<Constraints>
{
let mut new_contraints = self.clone();
let valid = new_contraints.try_merge(constraints);
if valid { Some(new_contraints) } else { None }
}
pub fn simplified(&self) -> Constraints
{
let mut max_sub = Constraints::none();
for (var, pred) in self.set.iter()
{
max_sub.set.insert(var.clone(), pred.substitute(self));
}
let mut stripped = max_sub.clone();
'outer: for (var, _) in max_sub.set.iter()
{
if var.chars().next().is_some_and(|x| x == '_')
{
for (_, other_pred) in max_sub.set.iter()
{
if other_pred.contains_variable(var)
{
continue 'outer;
}
}
stripped.set.remove(var);
}
}
stripped
}
}
impl Predicate
{
pub fn substitute(&self, constraints: &Constraints) -> Predicate
{
match self
{
Predicate::Variable(name) =>
{
if let Some(pred) = constraints.set.get(name)
{
let max_sub = pred.substitute(constraints);
max_sub
}
else
{
Predicate::Variable(name.clone())
}
}
Predicate::Fixed(name, predicates) => Predicate::Fixed(
name.clone(),
predicates
.iter()
.map(|x| x.substitute(constraints))
.collect(),
),
}
}
pub fn contains_variable(&self, name: &String) -> bool
{
match self
{
Predicate::Variable(var_name) => name == var_name,
Predicate::Fixed(_, predicates) => predicates
.iter()
.find(|x| x.contains_variable(name))
.is_some(),
}
}
}
impl Default for Constraints
{
fn default() -> Self
{
Self::none()
}
}
impl Display for Constraints
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
{
let len = self.set.len();
for (i, (var, pred)) in self.set.iter().enumerate()
{
write!(f, "{} = {}", var, pred)?;
if i != len - 1
{
write!(f, ", ")?;
}
}
Ok(())
}
}

0
src/prover/trace.rs Normal file
View File

65
src/prover/unification.rs Normal file
View File

@ -0,0 +1,65 @@
use std::process::Output;
use log::debug;
use crate::ast::Predicate;
use crate::prover::constraints::Constraints;
impl Predicate
{
/// Checks if `self` can be unified with `other`
///
/// returns `Some(constraints)` if the unification is valid under `constraints`
/// returns `None` if the predicate cannot be unified against the other
pub fn matches(&self, other: &Predicate) -> Option<Constraints>
{
debug!("Unifying {} against {}", self, other);
match self
{
Predicate::Variable(variable) =>
{
// We are trying to see if X (any) matches the other Predicate.
// This is always true if X = other_predicate
Some(Constraints::with(variable.clone(), other.clone()))
}
Predicate::Fixed(name, arguments) =>
{
match other
{
Predicate::Variable(var) =>
{
// We are trying to see if something like "predicate(..., ...)" matches X
// (any)
// This is always true
Some(Constraints::with(var.clone(), self.clone()))
}
// Match pred(X, Y, Z, ...) with pred(_X, _Y, _Z, ...)
Predicate::Fixed(other_name, other_arguments)
if other_name == name && other_arguments.len() == arguments.len() =>
{
// If there is no arguments, no constraints
if arguments.is_empty()
{
return Some(Constraints::none());
}
// We unify with other only if our arguments unify with theirs
arguments
.iter()
.zip(other_arguments.iter())
.map(|(ours, theirs)| ours.matches(theirs))
.reduce(|a, b| match (a, b)
{
(Some(c1), Some(c2)) => c1.and(&c2),
_ => None,
})
.unwrap_or(None)
}
_ => None,
}
}
}
}
}