1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
pub use linkme::distributed_slice;

/// This procedural macro registers a decorated function with `conjure_rules`' global registry, and
/// adds the rule to one or more `RuleSet`'s.
///
/// 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) -> ApplicationResult`.
/// 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.
///
/// This macro must decorate a function with the given signature.
/// As arguments, it excepts a tuple of 2-tuples in the format:
/// `((<RuleSet name>, <Priority in RuleSet>), ...)`
///
/// <hr>
///
/// For example:
/// ```rust
/// use conjure_core::ast::Expression;
/// use conjure_core::model::Model;
/// use conjure_core::rule_engine::{ApplicationError, ApplicationResult, Reduction};
/// use conjure_core::rule_engine::register_rule;
///
/// #[register_rule(("RuleSetName", 10))]
/// fn identity(expr: &Expression, mdl: &Model) -> ApplicationResult {
///   Ok(Reduction::pure(expr.clone()))
/// }
/// ```
pub use conjure_macros::register_rule;

/// This procedural macro registers a rule set with the 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.
///
/// This macro uses the following syntax:
///
/// ```text
/// register_rule_set!(<RuleSet name>, <RuleSet order>, (<DependencyRuleSet1>, <DependencyRuleSet2>, ...));
/// ```
///
/// # Example
///
/// ```rust
/// use conjure_core::rule_engine::register_rule_set;
///
/// register_rule_set!("MyRuleSet", 10, ("DependencyRuleSet", "AnotherRuleSet"));
/// ```
#[doc(inline)]
pub use conjure_macros::register_rule_set;
pub use resolve_rules::{get_rule_priorities, get_rules_vec, resolve_rule_sets};
pub use rewrite::{rewrite_model, RewriteError};
pub use rule::{ApplicationError, ApplicationResult, Reduction, Rule};
pub use rule_set::RuleSet;

use crate::solver::SolverFamily;

mod resolve_rules;
mod rewrite;
mod rule;
mod rule_set;

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

#[doc(hidden)]
#[distributed_slice]
pub static RULE_SETS_DISTRIBUTED_SLICE: [RuleSet<'static>];

pub mod _dependencies {
    pub use linkme;
    pub use linkme::distributed_slice;
}

/// 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_core::rule_engine::{ApplicationResult, Reduction, get_rules};
/// # use conjure_core::ast::Expression;
/// # use conjure_core::model::Model;
/// # use conjure_core::rule_engine::register_rule;
///
/// #[register_rule]
/// fn identity(expr: &Expression, mdl: &Model) -> ApplicationResult {
///   Ok(Reduction::pure(expr.clone()))
/// }
///
/// fn main() {
///   println!("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<&'static Rule<'static>> {
    RULES_DISTRIBUTED_SLICE.iter().collect()
}

/// Get a rule by name.
/// Returns the rule with the given name or None if it doesn't exist.
///
/// # Example
/// ```rust
/// use conjure_core::rule_engine::register_rule;
/// use conjure_core::rule_engine::{Rule, ApplicationResult, Reduction, get_rule_by_name};
/// use conjure_core::ast::Expression;
/// use conjure_core::model::Model;
///
/// #[register_rule]
/// fn identity(expr: &Expression, mdl: &Model) -> ApplicationResult {
///  Ok(Reduction::pure(expr.clone()))
/// }
///
/// fn main() {
/// println!("Rule: {:?}", get_rule_by_name("identity"));
/// }
/// ```
///
/// This will print:
/// ```text
/// Rule: Some(Rule { name: "identity", application: MEM })
/// ```
pub fn get_rule_by_name(name: &str) -> Option<&'static Rule<'static>> {
    get_rules().iter().find(|rule| rule.name == name).cloned()
}

/// Get all rule sets
/// Returns a `Vec` of static references to all rule sets registered with the `register_rule_set` macro.
/// Rule sets are not guaranteed to be in any particular order.
///
/// # Example
/// ```rust
/// use conjure_core::rule_engine::register_rule_set;
/// use conjure_core::rule_engine::get_rule_sets;
///
/// register_rule_set!("MyRuleSet", 10, ("AnotherRuleSet"));
/// register_rule_set!("AnotherRuleSet", 5, ());
///
/// println!("Rule sets: {:?}", get_rule_sets());
/// ```
///
/// This will print (if no other rule sets are registered):
/// ```text
/// Rule sets: [
///   RuleSet { name: "MyRuleSet", order: 10, rules: OnceLock { state: Uninitialized }, dependencies: ["AnotherRuleSet"] },
///   RuleSet { name: "AnotherRuleSet", order: 5, rules: OnceLock { state: Uninitialized }, dependencies: [] }
/// ]
/// ```
///
pub fn get_rule_sets() -> Vec<&'static RuleSet<'static>> {
    RULE_SETS_DISTRIBUTED_SLICE.iter().collect()
}

/// Get a rule set by name.
/// Returns the rule set with the given name or None if it doesn't exist.
///
/// # Example
/// ```rust
/// use conjure_core::rule_engine::register_rule_set;
/// use conjure_core::rule_engine::get_rule_set_by_name;
///
/// register_rule_set!("MyRuleSet", 10, ("DependencyRuleSet", "AnotherRuleSet"));
///
/// println!("Rule set: {:?}", get_rule_set_by_name("MyRuleSet"));
/// ```
///
/// This will print:
/// ```text
/// Rule set: Some(RuleSet { name: "MyRuleSet", order: 10, rules: OnceLock { state: Uninitialized }, dependencies: ["DependencyRuleSet", "AnotherRuleSet"] })
/// ```
pub fn get_rule_set_by_name(name: &str) -> Option<&'static RuleSet<'static>> {
    get_rule_sets()
        .iter()
        .find(|rule_set| rule_set.name == name)
        .cloned()
}

/// Get all rule sets for a given solver family.
/// Returns a `Vec` of static references to all rule sets that are applicable to the given solver family.
///
/// # Example
///
/// ```rust
/// use conjure_core::solver::SolverFamily;
/// use conjure_core::rule_engine::get_rule_sets_for_solver_family;
///
/// let rule_sets = get_rule_sets_for_solver_family(SolverFamily::SAT);
/// assert_eq!(rule_sets.len(), 1);
/// assert_eq!(rule_sets[0].name, "CNF");
/// ```
pub fn get_rule_sets_for_solver_family(
    solver_family: SolverFamily,
) -> Vec<&'static RuleSet<'static>> {
    get_rule_sets()
        .iter()
        .filter(|rule_set| {
            rule_set
                .solver_families
                .iter()
                .any(|family| family.eq(&solver_family))
        })
        .cloned()
        .collect()
}