Skip to content

Commit

Permalink
Merge pull request #141 from lixitrixi/rule-registry
Browse files Browse the repository at this point in the history
Rule registry
  • Loading branch information
ozgurakgun authored Jan 26, 2024
2 parents a0532d0 + e89211b commit e1259f2
Show file tree
Hide file tree
Showing 10 changed files with 214 additions and 11 deletions.
40 changes: 40 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ resolver = "2"
members = [
"conjure_oxide",
"crates/conjure_core",
"crates/conjure_rules_proc_macro",
"crates/conjure_rules",
"solvers/kissat",
"solvers/minion",
"solvers/chuffed",
Expand All @@ -11,3 +13,9 @@ members = [
[workspace.lints.clippy]
unwrap_used = "warn"
expect_used = "warn"

[profile.dev]
codegen-units = 1

[profile.release]
codegen-units = 1
2 changes: 2 additions & 0 deletions conjure_oxide/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ walkdir = "2.4.0"

[dependencies]
conjure_core = {path = "../crates/conjure_core" }
conjure_rules = {path = "../crates/conjure_rules" }

serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
Expand All @@ -21,6 +22,7 @@ clap = { version = "4.4.16", features = ["derive"] }
strum_macros = "0.25.3"
strum = "0.25.0"
versions = "6.1.0"
linkme = "0.3.22"

[lints]
workspace = true
6 changes: 2 additions & 4 deletions conjure_oxide/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ struct Cli {
}

pub fn main() -> AnyhowResult<()> {
println!("Rules: {:?}", conjure_rules::get_rules());

let cli = Cli::parse();
println!("Input file: {}", cli.input_file.display());
let input_file: &str = cli.input_file.to_str().ok_or(anyhow!(
Expand Down Expand Up @@ -48,9 +50,5 @@ pub fn main() -> AnyhowResult<()> {
let model = model_from_json(&astjson)?;
println!("{:?}", model);

// for rule in get_rules_by_kind() {
// println!("Applying rule {:?}", rule);
// }

Ok(())
}
10 changes: 3 additions & 7 deletions conjure_oxide/src/rules/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use conjure_core::ast::Expression;
use conjure_core::rule::{Rule, RuleApplicationError};
use conjure_core::{ast::Expression, rule::RuleApplicationError};
use conjure_rules::register_rule;

#[register_rule]
fn identity(expr: &Expression) -> Result<Expression, RuleApplicationError> {
Ok(expr.clone())
}

pub static IDENTITY_RULE: Rule = Rule {
name: "identity",
application: identity,
};
6 changes: 6 additions & 0 deletions conjure_oxide/tests/rewrite_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ use core::panic;

use conjure_oxide::ast::*;

#[test]
fn rules_present() {
let rules = conjure_rules::get_rules();
assert!(rules.len() > 0);
}

#[test]
fn sum_of_constants() {
let valid_sum_expression = Expression::Sum(vec![
Expand Down
12 changes: 12 additions & 0 deletions crates/conjure_rules/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "conjure_rules"
version = "0.1.0"
edition = "2021"

[dependencies]
conjure_rules_proc_macro = { path = "../conjure_rules_proc_macro" }
conjure_core = { path = "../conjure_core" }
linkme = "0.3.22"

[lints]
workspace = true
101 changes: 101 additions & 0 deletions crates/conjure_rules/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
//! ### A decentralised rule registry for Conjure Oxide
//!
//! This crate allows registering valid functions as expression-reduction rules.
//! Functions can be decorated with the `register_rule` macro in any downstream crate and be used by Conjure Oxide's rule engine.
//! To achieve compile-time linking, we make use of the [`linkme`](https://docs.rs/linkme/latest/linkme/) crate.
//!

// Why all the re-exports and wierdness?
// ============================
//
// Procedural macros are unhygenic - they directly subsitute into source code, and do not have
// their own scope, imports, and so on.
//
// See [https://doc.rust-lang.org/reference/procedural-macros.html#procedural-macro-hygiene].
//
// Therefore, we cannot assume the user has any dependencies apart from the one they imported the
// macro from. (Also, note Rust does not bring transitive dependencies into scope, so we cannot
// assume the presence of a dependency of the crate.)
//
// To solve this, the crate the macro is in must re-export everything the macro needs to run.
//
// However, proc-macro crates can only export proc-macros. Therefore, we must use a "front end
// crate" (i.e. this one) to re-export both the macro and all the things it may need.

use conjure_core::rule::Rule;
use linkme::distributed_slice;

#[doc(hidden)]
pub mod _dependencies {
pub use conjure_core::rule::Rule;
pub use linkme::distributed_slice;
}

#[doc(hidden)]
#[distributed_slice]
pub static RULES_DISTRIBUTED_SLICE: [Rule<'static>];

/// Returns a copied `Vec` of all rules registered with the `register_rule` macro.
///
/// Rules are not guaranteed to be in any particular order.
///
/// # Example
/// ```rust
/// # use conjure_rules::register_rule;
/// # use conjure_core::rule::{Rule, RuleApplicationError};
/// # use conjure_core::ast::Expression;
/// #
/// #[register_rule]
/// fn identity(expr: &Expression) -> Result<Expression, RuleApplicationError> {
/// Ok(expr.clone())
/// }
///
/// fn main() {
/// println!("Rules: {:?}", conjure_rules::get_rules());
/// }
/// ```
///
/// This will print (if no other rules are registered):
/// ```text
/// Rules: [Rule { name: "identity", application: MEM }]
/// ```
/// Where `MEM` is the memory address of the `identity` function.
pub fn get_rules() -> Vec<Rule<'static>> {
RULES_DISTRIBUTED_SLICE.to_vec()
}

/// This procedural macro registers a decorated function with `conjure_rules`' global registry.
/// It may be used in any downstream crate. For more information on linker magic, see the [`linkme`](https://docs.rs/linkme/latest/linkme/) crate.
///
/// **IMPORTANT**: Since the resulting rule may not be explicitly referenced, it may be removed by the compiler's dead code elimination.
/// To prevent this, you must ensure that either:
/// 1. codegen-units is set to 1, i.e. in Cargo.toml:
/// ```toml
/// [profile.release]
/// codegen-units = 1
/// ```
/// 2. The function is included somewhere else in the code
///
/// <hr>
///
/// Functions must have the signature `fn(&Expr) -> Result<Expr, RuleApplicationError>`.
/// The created rule will have the same name as the function.
///
/// Intermediary static variables are created to allow for the decentralized registry, with the prefix `CONJURE_GEN_`.
/// Please ensure that other variable names in the same scope do not conflict with these.
///
/// <hr>
///
/// For example:
/// ```rust
/// # use conjure_core::ast::Expression;
/// # use conjure_core::rule::RuleApplicationError;
/// # use conjure_rules::register_rule;
/// #
/// #[register_rule]
/// fn identity(expr: &Expression) -> Result<Expression, RuleApplicationError> {
/// Ok(expr.clone())
/// }
/// ```
#[doc(inline)]
pub use conjure_rules_proc_macro::register_rule;
15 changes: 15 additions & 0 deletions crates/conjure_rules_proc_macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "conjure_rules_proc_macro"
version = "0.1.0"
edition = "2021"

[lib]
proc_macro = true

[dependencies]
quote = "1.0.34"
conjure_core = {path= "../conjure_core"}
syn = { version = "2.0.43", features = ["full"] }

[lints]
workspace = true
25 changes: 25 additions & 0 deletions crates/conjure_rules_proc_macro/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! This is the backend procedural macro crate for `conjure_rules`. USE THAT INSTEAD!

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Ident, ItemFn};

#[proc_macro_attribute]
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!("CONJURE_GEN_RULE_{}", rule_ident).to_uppercase();
let static_ident = Ident::new(&static_name, rule_ident.span());

let expanded = quote! {
#func

#[::conjure_rules::_dependencies::distributed_slice(::conjure_rules::RULES_DISTRIBUTED_SLICE)]
pub static #static_ident: ::conjure_rules::_dependencies::Rule = ::conjure_rules::_dependencies::Rule {
name: stringify!(#rule_ident),
application: #rule_ident,
};
};

TokenStream::from(expanded)
}

0 comments on commit e1259f2

Please sign in to comment.