Skip to content

Commit

Permalink
Merge pull request #4 from lixitrixi/rule-engine
Browse files Browse the repository at this point in the history
Here I implement a decentralized rule registry which can be added to at compile time with the register_rule macro. I also implement a basic expression rewriting algorithm to iterate over rules and apply available ones until no more are found.
  • Loading branch information
lixitrixi committed Jan 2, 2024
2 parents 5289a05 + 6a0415f commit 69faf07
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 3 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ members = [
"antwort",
"antwort_core",
"antwort_solver",
"antwort_macros",
]
2 changes: 2 additions & 0 deletions antwort/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ edition = "2021"
[dependencies]
antwort_core = {path = "../antwort_core" }
antwort_solver = {path = "../antwort_solver" }
antwort_macros = {path = "../antwort_macros" }
linkme = "0.3.20"
5 changes: 5 additions & 0 deletions antwort/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ pub use antwort_core::ast::*;
pub use antwort_core::*;

pub use antwort_solver as solver;

pub use antwort_macros as macros;

pub mod rule_engine;
pub use rule_engine::_RULES_DISTRIBUTED_SLICE;
50 changes: 48 additions & 2 deletions antwort/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,55 @@
use antwort::macros::register_rule;
use antwort::rule::RuleApplicationError;
use antwort::rule_engine::{get_rules, rewrite};
use antwort::solver::{solve, Clause, Formula};
use antwort::Expr;

#[register_rule]
fn example_rule(_expr: &Expr) -> Result<Expr, RuleApplicationError> {
Err(RuleApplicationError::RuleNotApplicable)
}

#[register_rule]
fn de_morgans1(expr: &Expr) -> Result<Expr, RuleApplicationError> {
if let Expr::Negation(b) = expr {
if let Expr::Disjunction(exprs) = b.as_ref() {
return Ok(Expr::Conjunction(
exprs
.iter()
.map(|e| Expr::Negation(Box::new(e.clone())))
.collect(),
));
}
}
Err(RuleApplicationError::RuleNotApplicable)
}

#[register_rule]
fn de_morgans2(expr: &Expr) -> Result<Expr, RuleApplicationError> {
if let Expr::Negation(b) = expr {
if let Expr::Conjunction(exprs) = b.as_ref() {
return Ok(Expr::Disjunction(
exprs
.iter()
.map(|e| Expr::Negation(Box::new(e.clone())))
.collect(),
));
}
}
Err(RuleApplicationError::RuleNotApplicable)
}

fn main() {
let expr = Expr::Variable("a".to_string());
println!("{:?}", expr);
let rules = get_rules();
println!("{:?}", rules);
let res = rules[0].apply(&Expr::Variable("a".to_string()));
println!("{:?}", res);

let expr = Expr::Negation(Box::new(Expr::Conjunction(vec![
Expr::Variable("a".to_string()),
Expr::Variable("b".to_string()),
])));
rewrite(&expr);

let mut f = Formula::new();
let mut c1 = Clause::new();
Expand Down
55 changes: 55 additions & 0 deletions antwort/src/rule_engine.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::rule::Rule;
use crate::Expr;
use linkme::distributed_slice;

#[distributed_slice]
pub static _RULES_DISTRIBUTED_SLICE: [Rule];

pub fn get_rules() -> Vec<Rule> {
_RULES_DISTRIBUTED_SLICE.to_vec()
}

/// Applies the first applicable rule to the expression and returns the rewritten result.
fn apply_first(expr: &Expr, rules: &Vec<Rule>) -> Option<Expr> {
for rule in rules {
match rule.apply(&expr) {
Ok(new) => {
return Some(new);
}
Err(_) => continue,
}
}
return None;
}

/// # Returns
/// - Some(<new_expression>) after applying the first applicable rule to `expr` or a sub-expression.
/// - None if no rule is applicable to the expression or any sub-expression.
fn rewrite_iteration(expr: &Expr, rules: &Vec<Rule>) -> Option<Expr> {
if let Some(new) = apply_first(expr, rules) {
return Some(new);
} else {
let mut sub = expr.sub_expressions();
for i in 0..sub.len() {
if let Some(new) = rewrite_iteration(sub[i], rules) {
sub[i] = &new;
return Some(expr.with_sub_expressions(sub));
}
}
}
None // No rules applicable to this branch of the expression
}

/// Rewrites the expression using the rules in the registry.
/// Continues until no more rules are applicable to the expression or any sub-expression.
pub fn rewrite(expr: &Expr) -> Expr {
println!("REWRITE: {:?}", expr);
let rules = get_rules();
let mut new = expr.clone();
while let Some(step) = rewrite_iteration(&new, &rules) {
new = step;
println!(" STEP: {:?}", new);
}
println!("DONE");
new
}
32 changes: 31 additions & 1 deletion antwort_core/src/ast.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub enum Expr {
Variable(String),
Negation(Box<Expr>),
Expand All @@ -7,3 +7,33 @@ pub enum Expr {
Implication(Box<Expr>, Box<Expr>),
Equivalence(Box<Expr>, Box<Expr>),
}

impl Expr {
/// Returns a vector of references to the sub-expressions of the expression.
pub fn sub_expressions(&self) -> Vec<&Expr> {
match self {
Expr::Variable(_) => vec![],
Expr::Negation(a) => vec![a.as_ref()],
Expr::Disjunction(a) => a.iter().collect(),
Expr::Conjunction(a) => a.iter().collect(),
Expr::Implication(a, b) => vec![a.as_ref(), b.as_ref()],
Expr::Equivalence(a, b) => vec![a.as_ref(), b.as_ref()],
}
}

/// Creates a clone of the expression with the sub-expressions replaced by the given vector.
pub fn with_sub_expressions(&self, sub: Vec<&Expr>) -> Expr {
match self {
Expr::Variable(_) => self.clone(),
Expr::Negation(_) => Expr::Negation(Box::new(sub[0].clone())),
Expr::Disjunction(_) => Expr::Disjunction(sub.iter().map(|e| (*e).clone()).collect()),
Expr::Conjunction(_) => Expr::Conjunction(sub.iter().map(|e| (*e).clone()).collect()),
Expr::Implication(_, _) => {
Expr::Implication(Box::new(sub[0].clone()), Box::new(sub[1].clone()))
}
Expr::Equivalence(_, _) => {
Expr::Equivalence(Box::new(sub[0].clone()), Box::new(sub[1].clone()))
}
}
}
}
1 change: 1 addition & 0 deletions antwort_core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod ast;
pub mod rule;
18 changes: 18 additions & 0 deletions antwort_core/src/rule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use crate::ast::Expr;

#[derive(Debug)]
pub enum RuleApplicationError {
RuleNotApplicable,
}

#[derive(Clone, Debug)]
pub struct Rule {
pub name: &'static str,
pub application: fn(&Expr) -> Result<Expr, RuleApplicationError>,
}

impl Rule {
pub fn apply(&self, expr: &Expr) -> Result<Expr, RuleApplicationError> {
(self.application)(expr)
}
}
12 changes: 12 additions & 0 deletions antwort_macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "antwort_macros"
version = "0.1.0"
edition = "2021"

[lib]
proc_macro = true

[dependencies]
linkme = "0.3.20"
quote = "1.0.34"
syn = { version = "2.0.43", features = ["full"] }
28 changes: 28 additions & 0 deletions antwort_macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Ident, ItemFn};

#[proc_macro_attribute]
/// This procedural macro registers a decorated function with Antwort's rule engine.
/// Functions must have the signature `fn(&Expr) -> Result<Expr, RuleApplicationError>`.
///
/// Intermediary static variables are created to allow for the decentralized registry, with the prefix `_ANTWORT_GEN_`.
/// Please ensure that other variable names do not conflict with these.
pub fn register_rule(_: TokenStream, item: TokenStream) -> TokenStream {
let func = parse_macro_input!(item as ItemFn);
let rule_ident = &func.sig.ident;
let static_name = format!("_ANTWORT_GEN_RULE_{}", rule_ident).to_uppercase();
let static_ident = Ident::new(&static_name, rule_ident.span());

let expanded = quote! {
#func

#[::linkme::distributed_slice(::antwort::_RULES_DISTRIBUTED_SLICE)]
static #static_ident: ::antwort::rule::Rule = ::antwort::rule::Rule {
name: stringify!(#rule_ident),
application: #rule_ident,
};
};

TokenStream::from(expanded)
}

0 comments on commit 69faf07

Please sign in to comment.