Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ members = ["crates/*"]
any-intern = { version = "0.1.5", path = "crates/any-intern" }
logic-eval-util = { version = "0.1.5", path = "crates/logic-eval-util" }
indexmap = "2.2.1"
smallvec = "1"
smallvec = "1.15.1"
bumpalo = "3.10"
fxhash = "0.2.1"
hashbrown = "0.16.1"
Expand Down
2 changes: 1 addition & 1 deletion crates/logic-eval-util/src/reference.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{marker::PhantomData, ptr::NonNull};
use core::{marker::PhantomData, ptr::NonNull};

/// A lightweight wrapper around a non-null pointer for a shared reference.
///
Expand Down
5 changes: 3 additions & 2 deletions crates/logic-eval/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[package]
name = "logic-eval"
version = "0.1.5"
version = "0.2.0"
edition = "2021"
rust-version = "1.65.0"
description = "A simple logic evaluator"
description = "A prolog-like logic evaluator"
readme = "README.md"
repository = "https://github.com/ecoricemon/logic-eval"
license = "Apache-2.0 OR MIT"
Expand All @@ -14,3 +14,4 @@ any-intern = { workspace = true }
logic-eval-util = { workspace = true }
indexmap = { workspace = true }
fxhash = { workspace = true }
smallvec = { workspace = true }
15 changes: 6 additions & 9 deletions crates/logic-eval/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,16 @@
[codecov-badge]: https://codecov.io/gh/ecoricemon/logic-eval/graph/badge.svg?flag=logic-eval
[codecov-url]: https://app.codecov.io/gh/ecoricemon/logic-eval?flags%5B0%5D=logic-eval

A simple logic evaluator.
A prolog-like logic evaluator.

## Example

```rust
use logic_eval::{Database, parse_str};
use logic_eval::{Database, StrInterner, parse_str};

// Creates a DB with default interner.
// Creates a DB.
let mut db = Database::new();
let interner = StrInterner::new();

// Initializes the DB with a little bit of logic.
let dataset = "
Expand All @@ -28,12 +29,12 @@ let dataset = "
descend($X, $Y) :- child($X, $Y).
descend($X, $Z) :- child($X, $Y), descend($Y, $Z).
";
db.insert_dataset(parse_str(dataset, db.interner()).unwrap());
db.insert_dataset(parse_str(dataset, &interner).unwrap());
db.commit();

// Queries the DB.
let query = "descend($X, $Y).";
let mut cx = db.query(parse_str(query, db.interner()).unwrap());
let mut cx = db.query(parse_str(query, &interner).unwrap());

let mut answer = Vec::new();
while let Some(eval) = cx.prove_next() {
Expand All @@ -46,8 +47,4 @@ assert_eq!(answer, [
"$X = b, $Y = c",
"$X = a, $Y = c",
]);

// If the database was created with default interner, it should be deallocated.
drop(cx);
db.dealloc();
```
138 changes: 21 additions & 117 deletions crates/logic-eval/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ mod prove;
// === Re-exports ===

pub use parse::{
common::{Intern, InternedStr, StrCanonicalizer, StrInterner},
inner::VAR_PREFIX,
inner::{parse_str, Parse},
repr::{Clause, ClauseDataset, Expr, Predicate, Term},
text::Name,
};
pub use prove::{
common::Atom,
db::{ClauseIter, ClauseRef, Database},
prover::ProveCx,
};
Expand All @@ -20,19 +22,30 @@ pub mod intern {
pub use any_intern::*;
}

// === Re-exports for this crate ===

pub(crate) use intern_alias::*;
#[allow(unused)]
mod intern_alias {
use crate::{parse, prove, Intern};
pub(crate) type NameIn<'int, Int> = parse::text::Name<<Int as Intern>::Interned<'int>>;
pub(crate) type TermIn<'int, Int> = parse::repr::Term<NameIn<'int, Int>>;
pub(crate) type ExprIn<'int, Int> = parse::repr::Expr<NameIn<'int, Int>>;
pub(crate) type ClauseIn<'int, Int> = parse::repr::Clause<NameIn<'int, Int>>;
pub(crate) type UniqueTermArrayIn<'int, Int> = prove::repr::UniqueTermArray<NameIn<'int, Int>>;
pub(crate) type TermStorageIn<'int, Int> = prove::repr::TermStorage<NameIn<'int, Int>>;
pub(crate) type ClauseDatasetIn<'int, Int> = parse::repr::ClauseDataset<NameIn<'int, Int>>;
}

// === Hash map and set used within this crate ===

use std::{
borrow::Borrow,
collections::{HashMap, HashSet},
error::Error as StdError,
fmt::{self, Debug, Display},
hash::{BuildHasherDefault, Hash, Hasher},
hash::{BuildHasherDefault, Hasher},
result::Result as StdResult,
sync::{Arc, Mutex},
};

// === Hash map and set used within this crate ===

pub(crate) type Map<K, V> = HashMap<K, V, fxhash::FxBuildHasher>;
pub(crate) type Map<K, V> = fxhash::FxHashMap<K, V>;

#[derive(Default, Clone, Copy)]
struct PassThroughHasher {
Expand All @@ -59,112 +72,3 @@ pub(crate) type PassThroughState = BuildHasherDefault<PassThroughHasher>;

pub(crate) type Result<T> = StdResult<T, Error>;
pub(crate) type Error = Box<dyn StdError + Send + Sync>;

// === Interning dependency ===

pub trait Intern {
type InternedStr<'a>: AsRef<str> + Borrow<str> + Clone + Eq + Ord + Hash + Debug + Display
where
Self: 'a;

fn intern_formatted_str<T: Display + ?Sized>(
&self,
value: &T,
upper_size: usize,
) -> StdResult<Self::InternedStr<'_>, fmt::Error>;

fn intern_str(&self, text: &str) -> Self::InternedStr<'_> {
self.intern_formatted_str(text, text.len()).unwrap()
}
}

type DefaultInternerInner = HashSet<Arc<str>, fxhash::FxBuildHasher>;
pub struct DefaultInterner(Mutex<DefaultInternerInner>);

impl Default for DefaultInterner {
fn default() -> Self {
let set = HashSet::default();
Self(Mutex::new(set))
}
}

impl Intern for DefaultInterner {
type InternedStr<'a>
= Arc<str>
where
Self: 'a;

fn intern_formatted_str<T: Display + ?Sized>(
&self,
value: &T,
_: usize,
) -> StdResult<Self::InternedStr<'_>, fmt::Error> {
let mut set = self.0.lock().unwrap();

let value = value.to_string();
if let Some(existing_value) = set.get(&*value) {
Ok(existing_value.clone())
} else {
let value: Arc<str> = value.into();
set.insert(value.clone());
Ok(value)
}
}
}

impl Intern for any_intern::DroplessInterner {
type InternedStr<'a>
= any_intern::Interned<'a, str>
where
Self: 'a;

fn intern_formatted_str<T: Display + ?Sized>(
&self,
value: &T,
upper_size: usize,
) -> StdResult<Self::InternedStr<'_>, fmt::Error> {
self.intern_formatted_str(value, upper_size)
}

fn intern_str(&self, text: &str) -> Self::InternedStr<'_> {
self.intern(text)
}
}

impl Intern for any_intern::Interner {
type InternedStr<'a>
= any_intern::Interned<'a, str>
where
Self: 'a;

fn intern_formatted_str<T: Display + ?Sized>(
&self,
value: &T,
upper_size: usize,
) -> StdResult<Self::InternedStr<'_>, fmt::Error> {
self.intern_formatted_str(value, upper_size)
}

fn intern_str(&self, text: &str) -> Self::InternedStr<'_> {
self.intern_dropless(text)
}
}

// === Type aliases for complex `Intern` related types ===

pub(crate) use intern_alias::*;
#[allow(unused)]
mod intern_alias {
use super::{parse, prove, Intern};
pub(crate) type NameIn<'int, Int> = parse::text::Name<<Int as Intern>::InternedStr<'int>>;
pub(crate) type TermIn<'int, Int> = parse::repr::Term<NameIn<'int, Int>>;
pub(crate) type ExprIn<'int, Int> = parse::repr::Expr<NameIn<'int, Int>>;
pub(crate) type ClauseIn<'int, Int> = parse::repr::Clause<NameIn<'int, Int>>;
pub(crate) type UniqueTermArrayIn<'int, Int> = prove::repr::UniqueTermArray<NameIn<'int, Int>>;
pub(crate) type TermStorageIn<'int, Int> = prove::repr::TermStorage<NameIn<'int, Int>>;
pub(crate) type ClauseDatasetIn<'int, Int> = parse::repr::ClauseDataset<NameIn<'int, Int>>;
pub(crate) type Name2Int<'int, Int> =
prove::prover::IdxMap<'int, NameIn<'int, Int>, prove::prover::Integer, Int>;
pub(crate) type Int2Name<'int, Int> =
prove::prover::IdxMap<'int, prove::prover::Integer, NameIn<'int, Int>, Int>;
}
123 changes: 123 additions & 0 deletions crates/logic-eval/src/parse/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use crate::{Atom, Clause, Expr, Term};
use core::fmt::{self, Display};
use smallvec::SmallVec;

pub trait Intern {
type Interned<'a>: Atom
where
Self: 'a;

fn intern_str(&self, s: &str) -> Self::Interned<'_>;

fn intern_formatted_str<T: Display + ?Sized>(
&self,
value: &T,
upper_size: usize,
) -> Result<Self::Interned<'_>, fmt::Error>;
}

impl Intern for any_intern::DroplessInterner {
type Interned<'a>
= any_intern::Interned<'a, str>
where
Self: 'a;

fn intern_str(&self, s: &str) -> Self::Interned<'_> {
self.intern(s)
}

fn intern_formatted_str<T: Display + ?Sized>(
&self,
value: &T,
upper_size: usize,
) -> Result<Self::Interned<'_>, fmt::Error> {
<Self>::intern_formatted_str(self, value, upper_size)
}
}

impl Intern for any_intern::Interner {
type Interned<'a>
= any_intern::Interned<'a, str>
where
Self: 'a;

fn intern_str(&self, s: &str) -> Self::Interned<'_> {
self.intern_dropless(s)
}

fn intern_formatted_str<T: Display + ?Sized>(
&self,
value: &T,
upper_size: usize,
) -> Result<Self::Interned<'_>, fmt::Error> {
<Self>::intern_formatted_str(self, value, upper_size)
}
}

pub type StrInterner = any_intern::DroplessInterner;
pub type InternedStr<'int> = any_intern::Interned<'int, str>;

pub struct StrCanonicalizer<'int> {
interner: &'int StrInterner,
}

impl<'int> StrCanonicalizer<'int> {
pub fn new(interner: &'int StrInterner) -> Self {
Self { interner }
}

pub fn canonicalize(&self, clause: Clause<InternedStr<'int>>) -> Clause<InternedStr<'int>> {
let mut vars = SmallVec::new();
find_var_in_clause(&clause, &mut vars);

let mut clause = clause;
clause.replace_term(&mut |term| {
if !term.args.is_empty() {
return None;
}
vars.iter().enumerate().find_map(|(i, var)| {
if &term.functor == var {
Some(Term {
functor: self.interner.intern_formatted_str(&i, i % 10 + 1).unwrap(),
args: Vec::new(),
})
} else {
None
}
})
});

return clause;

// === Internal helper functions ===

fn find_var_in_clause<T: Atom>(clause: &Clause<T>, vars: &mut SmallVec<[T; 4]>) {
find_var_in_term(&clause.head, vars);
if let Some(body) = &clause.body {
find_var_in_expr(body, vars);
}
}

fn find_var_in_expr<T: Atom>(expr: &Expr<T>, vars: &mut SmallVec<[T; 4]>) {
match expr {
Expr::Term(term) => find_var_in_term(term, vars),
Expr::Not(expr) => find_var_in_expr(expr, vars),
Expr::And(expr) | Expr::Or(expr) => {
for inner_expr in expr.iter() {
find_var_in_expr(inner_expr, vars);
}
}
}
}

fn find_var_in_term<T: Atom>(term: &Term<T>, vars: &mut SmallVec<[T; 4]>) {
if term.functor.is_variable() {
vars.push(term.functor.clone());
} else {
for arg in &term.args {
find_var_in_term(arg, vars);
}
}
}
}
}
26 changes: 26 additions & 0 deletions crates/logic-eval/src/parse/inner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,29 @@ pub(crate) struct Location {
/// Exclusive
right: usize,
}

#[cfg(test)]
mod tests {
use super::*;
use crate::parse::repr::Clause;

type Interner = any_intern::DroplessInterner;

#[test]
fn test_parse() {
fn assert(text: &str, interner: &Interner) {
let clause: Clause<_> = parse_str(text, interner).unwrap();
assert_eq!(text, clause.to_string());
}

let interner = Interner::new();

assert("f.", &interner);
assert("f(a, b).", &interner);
assert("f(a, b) :- f.", &interner);
assert("f(a, b) :- f(a).", &interner);
assert("f(a, b) :- f(a), f(b).", &interner);
assert("f(a, b) :- f(a); f(b).", &interner);
assert("f(a, b) :- f(a), (f(b); f(c)).", &interner);
}
}
Loading
Loading