1#![warn(missing_docs)]
43
44pub mod ast;
46mod bitset;
47mod compiled;
48pub mod engine;
50pub mod facts;
52mod functions;
53pub mod knowledge_base;
55mod methods;
56mod operators;
57pub mod parser;
59pub mod rule;
61mod time_functions;
62
63pub use engine::{EngineError, EngineListener, ExecutionResult, RuleEngine, RulePhase};
64pub use facts::{serde_time, Fact, FactValue};
65pub use knowledge_base::KnowledgeBase;
66pub use rule::Rule;
67
68#[cfg(doctest)]
71mod guide {
72 #[doc = include_str!("../README.md")]
73 struct Readme;
74 #[doc = include_str!("../docs/src/introduction.md")]
75 struct Introduction;
76 #[doc = include_str!("../docs/src/getting-started.md")]
77 struct GettingStarted;
78 #[doc = include_str!("../docs/src/facts.md")]
79 struct Facts;
80 #[doc = include_str!("../docs/src/language.md")]
81 struct Language;
82 #[doc = include_str!("../docs/src/functions.md")]
83 struct Functions;
84 #[doc = include_str!("../docs/src/execution.md")]
85 struct Execution;
86 #[doc = include_str!("../docs/src/extending.md")]
87 struct Extending;
88 #[doc = include_str!("../docs/src/migrating.md")]
89 struct Migrating;
90 #[doc = include_str!("../docs/src/performance.md")]
91 struct Performance;
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::ast::Expression;
98 use std::collections::HashMap;
99
100 #[test]
101 fn test_rule_engine_basic() {
102 let mut engine = RuleEngine::new();
103 let mut facts = HashMap::new();
104
105 let rule = Rule::new(
107 "test_rule".to_string(),
108 0,
109 Expression::GreaterThan(
110 Box::new(Expression::Variable("x".to_string())),
111 Box::new(Expression::Integer(5)),
112 ),
113 vec![
114 Expression::assign(&["y"], Expression::Integer(10)),
115 Expression::call("Retract", vec![Expression::String("test_rule".to_string())]),
116 ],
117 );
118
119 engine.add_rule(rule).unwrap();
120 facts.insert("x".to_string(), Fact::number_fact("x".to_string(), 7.0));
121
122 let result = engine.execute(&mut facts).unwrap();
123
124 assert_eq!(result.rules_fired.len(), 1);
125 assert_eq!(result.rules_fired[0], "test_rule");
126 assert_eq!(facts.get("y").unwrap().value, FactValue::Integer(10));
127 }
128
129 #[test]
130 fn test_fact_manipulation() {
131 let mut fact = Fact::from_object(
132 "test".to_string(),
133 HashMap::from([
134 (
135 "field1".to_string(),
136 FactValue::String("value1".to_string()),
137 ),
138 ("field2".to_string(), FactValue::Integer(42)),
139 ]),
140 );
141
142 assert_eq!(
143 fact.get_field("field1"),
144 Some(&FactValue::String("value1".to_string()))
145 );
146 assert_eq!(fact.get_field("field2"), Some(&FactValue::Integer(42)));
147
148 fact.set_field("field3".to_string(), FactValue::Boolean(true))
149 .unwrap();
150 assert_eq!(fact.get_field("field3"), Some(&FactValue::Boolean(true)));
151 }
152
153 #[test]
154 fn test_knowledge_base() {
155 let mut kb = KnowledgeBase::new();
156
157 let rule1 = Rule::new("rule1".to_string(), 10, Expression::Boolean(true), vec![]);
158 let rule2 = Rule::new("rule2".to_string(), 5, Expression::Boolean(true), vec![]);
159
160 kb.add_rule(rule1).unwrap();
161 kb.add_rule(rule2).unwrap();
162
163 assert_eq!(kb.len(), 2);
164
165 let sorted_rules = kb.get_rules_sorted_by_salience();
166 assert_eq!(sorted_rules[0].name, "rule1"); assert_eq!(sorted_rules[1].name, "rule2");
168 }
169
170 #[test]
171 fn test_grl_parser_simple_rule() {
172 let parser = parser::GrlParser::new();
173
174 let grl_text = r#"
175 rule SimpleRule "Test simple rule" salience 5 {
176 when
177 x == 10
178 then
179 y = 20;
180 }
181 "#;
182
183 let rule = parser.parse_rule(grl_text).unwrap();
184
185 assert_eq!(rule.name, "SimpleRule");
186 assert_eq!(rule.description, Some("Test simple rule".to_string()));
187 assert_eq!(rule.salience, 5);
188
189 match &rule.when_condition {
191 Expression::Equal(left, right) => match (&**left, &**right) {
192 (Expression::Variable(var), Expression::Integer(val)) => {
193 assert_eq!(var, "x");
194 assert_eq!(*val, 10);
195 }
196 _ => panic!("Unexpected expression structure in condition"),
197 },
198 _ => panic!("Expected Equal expression for condition"),
199 }
200
201 assert_eq!(
203 rule.then_actions,
204 vec![Expression::assign(&["y"], Expression::Integer(20))]
205 );
206 }
207
208 #[test]
209 fn test_grl_parser_complex_rule() {
210 let parser = parser::GrlParser::new();
211
212 let rule_age = Rule::new(
213 "AgeRule".to_string(),
214 10,
215 Expression::GreaterEqual(
216 Box::new(Expression::FieldAccess(
217 Box::new(Expression::Variable("customer".to_string())),
218 "age".to_string(),
219 )),
220 Box::new(Expression::Integer(18)),
221 ),
222 vec![
223 Expression::assign(&["customer", "eligible"], Expression::Boolean(true)),
224 Expression::assign(
225 &["message"],
226 Expression::String("Customer is eligible for premium service".to_string()),
227 ),
228 ],
229 );
230
231 let grl_text = r#"
233 rule EligibilityRule "Tests eligibility" salience 10 {
234 when
235 customer.eligible == true
236 then
237 message = "Confirmed eligible";
238 }
239 "#;
240
241 let rule = parser.parse_rule(grl_text).unwrap();
242
243 assert_eq!(rule.name, "EligibilityRule");
244 assert_eq!(rule.description, Some("Tests eligibility".to_string()));
245 assert_eq!(rule.salience, 10);
246
247 match &rule.when_condition {
249 Expression::Equal(left, right) => match (&**left, &**right) {
250 (Expression::FieldAccess(obj, field), Expression::Boolean(val)) => match &**obj {
251 Expression::Variable(obj_name) => {
252 assert_eq!(obj_name, "customer");
253 assert_eq!(field, "eligible");
254 assert!(*val);
255 }
256 _ => panic!("Expected Variable expression for object"),
257 },
258 _ => panic!("Unexpected expression structure in condition"),
259 },
260 _ => panic!("Expected Equal expression for condition"),
261 }
262
263 assert_eq!(
265 rule.then_actions,
266 vec![Expression::assign(
267 &["message"],
268 Expression::String("Confirmed eligible".to_string()),
269 )]
270 );
271
272 assert_eq!(rule_age.name, "AgeRule");
274 assert_eq!(rule_age.salience, 10);
275
276 match &rule_age.when_condition {
278 Expression::GreaterEqual(left_field, left_val) => match (&**left_field, &**left_val) {
279 (Expression::FieldAccess(obj, field), Expression::Integer(val)) => match &**obj {
280 Expression::Variable(obj_name) => {
281 assert_eq!(obj_name, "customer");
282 assert_eq!(field, "age");
283 assert_eq!(*val, 18);
284 }
285 _ => panic!("Expected Variable expression for object"),
286 },
287 _ => panic!("Unexpected expression structure in age condition"),
288 },
289 _ => panic!("Expected GreaterEqual expression for age condition"),
290 }
291
292 assert_eq!(
294 rule_age.then_actions,
295 vec![
296 Expression::assign(&["customer", "eligible"], Expression::Boolean(true)),
297 Expression::assign(
298 &["message"],
299 Expression::String("Customer is eligible for premium service".to_string()),
300 ),
301 ]
302 );
303 }
304
305 #[test]
306 fn test_grl_parser_arithmetic_expressions() {
307 let parser = parser::GrlParser::new();
308
309 let grl_text = r#"
310 rule ArithmeticRule "Test arithmetic operations" {
311 when
312 x > 5
313 then
314 y = x + 10;
315 }
316 "#;
317
318 let rule = parser.parse_rule(grl_text).unwrap();
319
320 assert_eq!(rule.name, "ArithmeticRule");
321
322 assert_eq!(
324 rule.then_actions,
325 vec![Expression::assign(
326 &["y"],
327 Expression::Add(
328 Box::new(Expression::Variable("x".to_string())),
329 Box::new(Expression::Integer(10)),
330 ),
331 )]
332 );
333 }
334}