#![allow(clippy::type_complexity)]
#![allow(deprecated)]
use crate::engine::rule::{Condition, ConditionGroup, Rule};
use crate::errors::{Result, RuleEngineError};
use crate::parser::GRLParser;
use crate::rete::facts::{FactValue, TypedFacts};
use crate::rete::propagation::IncrementalEngine;
use crate::rete::{AlphaNode, ReteUlNode, TypedReteUlRule};
use crate::types::{Operator, Value};
use log::info;
use std::fs;
use std::path::Path;
#[cfg(feature = "streaming")]
use crate::rete::network::{StreamWindowSpec, StreamWindowTypeRete};
pub struct GrlReteLoader;
impl GrlReteLoader {
pub fn load_from_file<P: AsRef<Path>>(
path: P,
engine: &mut IncrementalEngine,
) -> Result<usize> {
let grl_text =
fs::read_to_string(path.as_ref()).map_err(|e| RuleEngineError::ParseError {
message: format!("Failed to read GRL file: {}", e),
})?;
Self::load_from_string(&grl_text, engine)
}
pub fn load_from_string(grl_text: &str, engine: &mut IncrementalEngine) -> Result<usize> {
let rules = GRLParser::parse_rules(grl_text)?;
let mut loaded_count = 0;
for rule in rules {
let rete_rule = Self::convert_rule_to_rete(rule)?;
let dependencies = Self::extract_dependencies(&rete_rule);
engine.add_rule(rete_rule, dependencies);
loaded_count += 1;
}
Ok(loaded_count)
}
fn convert_rule_to_rete(rule: Rule) -> Result<TypedReteUlRule> {
let node = Self::convert_condition_group(&rule.conditions)?;
let rete_rule = TypedReteUlRule {
name: rule.name.clone(),
node,
priority: rule.salience,
no_loop: rule.no_loop,
action: Self::create_action_closure(rule.actions),
};
Ok(rete_rule)
}
fn convert_condition_group(group: &ConditionGroup) -> Result<ReteUlNode> {
match group {
ConditionGroup::Single(condition) => Self::convert_condition(condition),
ConditionGroup::Compound {
left,
operator,
right,
} => {
let left_node = Self::convert_condition_group(left)?;
let right_node = Self::convert_condition_group(right)?;
match operator {
crate::types::LogicalOperator::And => {
Ok(ReteUlNode::UlAnd(Box::new(left_node), Box::new(right_node)))
}
crate::types::LogicalOperator::Or => {
Ok(ReteUlNode::UlOr(Box::new(left_node), Box::new(right_node)))
}
crate::types::LogicalOperator::Not => {
Ok(ReteUlNode::UlNot(Box::new(left_node)))
}
}
}
ConditionGroup::Not(inner) => {
let inner_node = Self::convert_condition_group(inner)?;
Ok(ReteUlNode::UlNot(Box::new(inner_node)))
}
ConditionGroup::Exists(inner) => {
let inner_node = Self::convert_condition_group(inner)?;
Ok(ReteUlNode::UlExists(Box::new(inner_node)))
}
ConditionGroup::Forall(inner) => {
let inner_node = Self::convert_condition_group(inner)?;
Ok(ReteUlNode::UlForall(Box::new(inner_node)))
}
ConditionGroup::Accumulate {
result_var,
source_pattern,
extract_field,
source_conditions,
function,
function_arg,
} => Ok(ReteUlNode::UlAccumulate {
result_var: result_var.clone(),
source_pattern: source_pattern.clone(),
extract_field: extract_field.clone(),
source_conditions: source_conditions.clone(),
function: function.clone(),
function_arg: function_arg.clone(),
}),
#[cfg(feature = "streaming")]
ConditionGroup::StreamPattern {
var_name,
event_type,
stream_name,
window,
} => {
Ok(ReteUlNode::UlStream {
var_name: var_name.clone(),
event_type: event_type.clone(),
stream_name: stream_name.clone(),
window: window.as_ref().map(|w| StreamWindowSpec {
duration: w.duration,
window_type: match &w.window_type {
crate::engine::rule::StreamWindowType::Sliding => {
StreamWindowTypeRete::Sliding
}
crate::engine::rule::StreamWindowType::Tumbling => {
StreamWindowTypeRete::Tumbling
}
crate::engine::rule::StreamWindowType::Session { timeout } => {
StreamWindowTypeRete::Session { timeout: *timeout }
}
},
}),
})
}
}
}
fn convert_condition(condition: &Condition) -> Result<ReteUlNode> {
use crate::engine::rule::ConditionExpression;
match &condition.expression {
ConditionExpression::MultiField {
field,
operation,
variable: _,
} => {
let operator_str = Self::operator_to_string(&condition.operator);
let value_str = if !matches!(condition.value, Value::Boolean(_)) {
Some(Self::value_to_string(&condition.value))
} else {
None
};
let (op, cmp_val) = if operation == "count" && operator_str != "==" {
(Some(operator_str), value_str)
} else {
(None, value_str)
};
Ok(ReteUlNode::UlMultiField {
field: field.clone(),
operation: operation.clone(),
value: if operation == "contains" {
cmp_val.clone()
} else {
None
},
operator: op,
compare_value: if operation == "count" { cmp_val } else { None },
})
}
ConditionExpression::FunctionCall { name, args } => {
Ok(ReteUlNode::UlFunctionCall {
name: name.clone(),
args: args.clone(),
operator: Self::operator_to_string(&condition.operator),
value: Self::value_to_string(&condition.value),
})
}
_ => {
let operator_str = Self::operator_to_string(&condition.operator);
let value_str = Self::value_to_string(&condition.value);
let alpha = AlphaNode {
field: condition.field.clone(),
operator: operator_str,
value: value_str,
};
Ok(ReteUlNode::UlAlpha(alpha))
}
}
}
fn operator_to_string(op: &Operator) -> String {
match op {
Operator::Equal => "==".to_string(),
Operator::NotEqual => "!=".to_string(),
Operator::GreaterThan => ">".to_string(),
Operator::GreaterThanOrEqual => ">=".to_string(),
Operator::LessThan => "<".to_string(),
Operator::LessThanOrEqual => "<=".to_string(),
Operator::Contains => "contains".to_string(),
Operator::NotContains => "!contains".to_string(),
Operator::StartsWith => "startsWith".to_string(),
Operator::EndsWith => "endsWith".to_string(),
Operator::Matches => "matches".to_string(),
Operator::In => "in".to_string(),
}
}
fn value_to_string(value: &Value) -> String {
match value {
Value::Number(n) => n.to_string(),
Value::Integer(i) => i.to_string(),
Value::String(s) => s.clone(),
Value::Boolean(b) => b.to_string(),
Value::Null => "null".to_string(),
Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(Self::value_to_string).collect();
format!("[{}]", items.join(","))
}
Value::Object(_) => {
"object".to_string()
}
Value::Expression(expr) => {
expr.clone()
}
}
}
fn create_action_closure(
actions: Vec<crate::types::ActionType>,
) -> std::sync::Arc<dyn Fn(&mut TypedFacts, &mut super::ActionResults) + Send + Sync> {
std::sync::Arc::new(
move |facts: &mut TypedFacts, results: &mut super::ActionResults| {
for action in &actions {
Self::execute_action(action, facts, results);
}
},
)
}
fn execute_action(
action: &crate::types::ActionType,
facts: &mut TypedFacts,
results: &mut super::ActionResults,
) {
use crate::types::ActionType;
match action {
ActionType::Set { field, value } => {
let evaluated_value = match value {
Value::Expression(expr) => {
Self::evaluate_expression_for_rete(expr, facts)
}
_ => value.clone(),
};
let fact_value = Self::value_to_fact_value(&evaluated_value);
facts.set(field, fact_value);
}
ActionType::Log { message } => {
info!("📝 {}", message);
}
ActionType::MethodCall {
object,
method,
args,
} => {
let mut all_args = vec![object.clone()];
all_args.extend(args.iter().map(Self::value_to_string));
results.add(super::ActionResult::CallFunction {
function_name: format!("{}.{}", object, method),
args: all_args,
});
println!("� METHOD: {}.{}", object, method);
}
ActionType::Retract { object } => {
let object_name = object.trim_matches('"');
if let Some(handle) = facts.get_fact_handle(object_name) {
results.add(super::ActionResult::Retract(handle));
println!("🗑️ RETRACT: {} (handle: {:?})", object_name, handle);
} else {
results.add(super::ActionResult::RetractByType(object_name.to_string()));
println!("🗑️ RETRACT: {} (by type, no handle found)", object_name);
}
}
ActionType::Custom {
action_type,
params,
} => {
let args: Vec<String> = params.values().map(Self::value_to_string).collect();
results.add(super::ActionResult::CallFunction {
function_name: action_type.clone(),
args,
});
println!("🔧 CUSTOM CALL: {}", action_type);
}
ActionType::ActivateAgendaGroup { group } => {
results.add(super::ActionResult::ActivateAgendaGroup(group.clone()));
println!("📋 ACTIVATE GROUP: {}", group);
}
ActionType::ScheduleRule {
rule_name,
delay_ms,
} => {
results.add(super::ActionResult::ScheduleRule {
rule_name: rule_name.clone(),
delay_ms: *delay_ms,
});
println!("⏰ SCHEDULE: {} (delay: {}ms)", rule_name, delay_ms);
}
ActionType::CompleteWorkflow { workflow_name } => {
let completion_key = format!("workflow.{}.completed", workflow_name);
facts.set(&completion_key, FactValue::Boolean(true));
let timestamp_key = format!("workflow.{}.completed_at", workflow_name);
facts.set(
×tamp_key,
FactValue::Integer(chrono::Utc::now().timestamp()),
);
println!("✔️ WORKFLOW COMPLETED: {}", workflow_name);
}
ActionType::SetWorkflowData { key, value } => {
let data_key = format!("workflow.data.{}", key);
let fact_value = Self::value_to_fact_value(value);
facts.set(&data_key, fact_value);
println!("📊 WORKFLOW DATA SET: {} = {:?}", key, value);
}
ActionType::Append { field, value } => {
let current_value = facts.get(field);
let mut array = match current_value {
Some(FactValue::Array(arr)) => arr.clone(),
Some(_) => {
log::warn!("Field {} is not an array, creating new array", field);
Vec::new()
}
None => {
Vec::new()
}
};
let evaluated_value = match value {
Value::Expression(expr) => Self::evaluate_expression_for_rete(expr, facts),
_ => value.clone(),
};
let fact_value = Self::value_to_fact_value(&evaluated_value);
array.push(fact_value);
facts.set(field, FactValue::Array(array));
info!("➕ APPEND: {} += {:?}", field, evaluated_value);
}
}
}
fn value_to_fact_value(value: &Value) -> FactValue {
match value {
Value::Number(n) => {
if n.fract() == 0.0 {
FactValue::Integer(*n as i64)
} else {
FactValue::Float(*n)
}
}
Value::Integer(i) => FactValue::Integer(*i),
Value::String(s) => FactValue::String(s.clone()),
Value::Boolean(b) => FactValue::Boolean(*b),
Value::Null => FactValue::Null,
Value::Array(arr) => {
let fact_arr: Vec<FactValue> = arr.iter().map(Self::value_to_fact_value).collect();
FactValue::Array(fact_arr)
}
Value::Object(_) => {
FactValue::String("object".to_string())
}
Value::Expression(expr) => {
FactValue::String(format!("[EXPR: {}]", expr))
}
}
}
fn extract_dependencies(rule: &TypedReteUlRule) -> Vec<String> {
let mut deps = Vec::new();
Self::extract_deps_from_node(&rule.node, &mut deps);
deps.sort();
deps.dedup();
deps
}
fn extract_deps_from_node(node: &ReteUlNode, deps: &mut Vec<String>) {
match node {
ReteUlNode::UlAlpha(alpha) => {
if let Some(dot_pos) = alpha.field.find('.') {
let fact_type = alpha.field[..dot_pos].to_string();
deps.push(fact_type);
}
}
ReteUlNode::UlMultiField { field, .. } => {
if let Some(dot_pos) = field.find('.') {
let fact_type = field[..dot_pos].to_string();
deps.push(fact_type);
}
}
ReteUlNode::UlAnd(left, right) | ReteUlNode::UlOr(left, right) => {
Self::extract_deps_from_node(left, deps);
Self::extract_deps_from_node(right, deps);
}
ReteUlNode::UlNot(inner)
| ReteUlNode::UlExists(inner)
| ReteUlNode::UlForall(inner) => {
Self::extract_deps_from_node(inner, deps);
}
ReteUlNode::UlAccumulate { source_pattern, .. } => {
deps.push(source_pattern.clone());
}
#[cfg(feature = "streaming")]
ReteUlNode::UlStream { stream_name, .. } => {
deps.push(stream_name.clone());
}
ReteUlNode::UlFunctionCall { args, .. } => {
for arg in args {
if let Some(dot_pos) = arg.find('.') {
deps.push(arg[..dot_pos].to_string());
}
}
}
ReteUlNode::UlTerminal(_) => {}
}
}
fn evaluate_expression_for_rete(expr: &str, typed_facts: &TypedFacts) -> Value {
use crate::engine::facts::Facts;
let facts = Facts::new();
for (key, value) in typed_facts.get_all() {
let converted_value = Self::fact_value_to_value(value);
facts.set(key, converted_value.clone());
if !key.contains('.') {
facts.set(&format!("Order.{}", key), converted_value);
}
}
match crate::expression::evaluate_expression(expr, &facts) {
Ok(result) => result,
Err(_e) => {
Value::String(expr.to_string())
}
}
}
fn fact_value_to_value(fact_value: &FactValue) -> Value {
match fact_value {
FactValue::String(s) => {
if let Ok(i) = s.parse::<i64>() {
Value::Integer(i)
} else if let Ok(f) = s.parse::<f64>() {
Value::Number(f)
} else if s == "true" {
Value::Boolean(true)
} else if s == "false" {
Value::Boolean(false)
} else {
Value::String(s.clone())
}
}
FactValue::Integer(i) => Value::Integer(*i),
FactValue::Float(f) => Value::Number(*f),
FactValue::Boolean(b) => Value::Boolean(*b),
FactValue::Array(arr) => {
Value::Array(arr.iter().map(Self::fact_value_to_value).collect())
}
FactValue::Null => Value::Null,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_convert_simple_rule() {
let grl = r#"
rule "TestRule" salience 10 no-loop {
when
Person.age > 18
then
Person.is_adult = true;
}
"#;
let rules = GRLParser::parse_rules(grl).unwrap();
assert_eq!(rules.len(), 1);
let rete_rule = GrlReteLoader::convert_rule_to_rete(rules[0].clone()).unwrap();
assert_eq!(rete_rule.name, "TestRule");
assert_eq!(rete_rule.priority, 10);
assert!(rete_rule.no_loop);
}
#[test]
fn test_extract_dependencies() {
let grl = r#"
rule "MultiTypeRule" {
when
Person.age > 18 && Order.amount > 1000
then
Person.premium = true;
}
"#;
let rules = GRLParser::parse_rules(grl).unwrap();
let rete_rule = GrlReteLoader::convert_rule_to_rete(rules[0].clone()).unwrap();
let deps = GrlReteLoader::extract_dependencies(&rete_rule);
assert_eq!(deps.len(), 2);
assert!(deps.contains(&"Person".to_string()));
assert!(deps.contains(&"Order".to_string()));
}
#[test]
fn test_load_from_string() {
let grl = r#"
rule "Rule1" {
when
Person.age > 18
then
Person.is_adult = true;
}
rule "Rule2" {
when
Order.amount > 1000
then
Order.high_value = true;
}
"#;
let mut engine = IncrementalEngine::new();
let count = GrlReteLoader::load_from_string(grl, &mut engine).unwrap();
assert_eq!(count, 2);
}
}