use crate::rete::alpha::AlphaNode;
use std::sync::Arc;
pub fn build_rete_ul_from_condition_group(group: &crate::rete::auto_network::ConditionGroup) -> ReteUlNode {
use crate::rete::auto_network::ConditionGroup;
match group {
ConditionGroup::Single(cond) => {
ReteUlNode::UlAlpha(AlphaNode {
field: cond.field.clone(),
operator: cond.operator.clone(),
value: cond.value.clone(),
})
}
ConditionGroup::Compound { left, operator, right } => {
match operator.as_str() {
"AND" => ReteUlNode::UlAnd(
Box::new(build_rete_ul_from_condition_group(left)),
Box::new(build_rete_ul_from_condition_group(right)),
),
"OR" => ReteUlNode::UlOr(
Box::new(build_rete_ul_from_condition_group(left)),
Box::new(build_rete_ul_from_condition_group(right)),
),
_ => ReteUlNode::UlAnd(
Box::new(build_rete_ul_from_condition_group(left)),
Box::new(build_rete_ul_from_condition_group(right)),
),
}
}
ConditionGroup::Not(inner) => {
ReteUlNode::UlNot(Box::new(build_rete_ul_from_condition_group(inner)))
}
ConditionGroup::Exists(inner) => {
ReteUlNode::UlExists(Box::new(build_rete_ul_from_condition_group(inner)))
}
ConditionGroup::Forall(inner) => {
ReteUlNode::UlForall(Box::new(build_rete_ul_from_condition_group(inner)))
}
}
}
use std::collections::HashMap;
fn evaluate_condition_string(condition: &str, facts: &HashMap<String, String>) -> bool {
let condition = condition.trim();
let operators = ["==", "!=", ">=", "<=", ">", "<"];
for op in &operators {
if let Some(pos) = condition.find(op) {
let field = condition[..pos].trim();
let value_str = condition[pos + op.len()..]
.trim()
.trim_matches('"')
.trim_matches('\'');
if let Some(field_value) = facts.get(field) {
return compare_string_values(field_value, op, value_str);
} else {
return false;
}
}
}
false
}
fn compare_string_values(field_value: &str, operator: &str, value_str: &str) -> bool {
if let (Ok(field_num), Ok(val_num)) = (field_value.parse::<f64>(), value_str.parse::<f64>()) {
match operator {
"==" => (field_num - val_num).abs() < f64::EPSILON,
"!=" => (field_num - val_num).abs() >= f64::EPSILON,
">" => field_num > val_num,
"<" => field_num < val_num,
">=" => field_num >= val_num,
"<=" => field_num <= val_num,
_ => false,
}
} else {
match operator {
"==" => field_value == value_str,
"!=" => field_value != value_str,
_ => false,
}
}
}
pub fn evaluate_rete_ul_node(node: &ReteUlNode, facts: &HashMap<String, String>) -> bool {
match node {
ReteUlNode::UlAlpha(alpha) => {
let val = if alpha.field.contains('.') {
let parts: Vec<&str> = alpha.field.split('.').collect();
if parts.len() == 2 {
let prefix = parts[0];
let suffix = parts[1];
facts.get(&format!("{}.{}", prefix, suffix)).or_else(|| facts.get(&format!("{}:{}", prefix, suffix)))
} else {
facts.get(&alpha.field)
}
} else {
facts.get(&alpha.field)
};
if let Some(val) = val {
match alpha.operator.as_str() {
"==" => val == &alpha.value,
"!=" => val != &alpha.value,
">" => val.parse::<f64>().unwrap_or(0.0) > alpha.value.parse::<f64>().unwrap_or(0.0),
"<" => val.parse::<f64>().unwrap_or(0.0) < alpha.value.parse::<f64>().unwrap_or(0.0),
">=" => val.parse::<f64>().unwrap_or(0.0) >= alpha.value.parse::<f64>().unwrap_or(0.0),
"<=" => val.parse::<f64>().unwrap_or(0.0) <= alpha.value.parse::<f64>().unwrap_or(0.0),
_ => false,
}
} else {
false
}
}
ReteUlNode::UlAnd(left, right) => {
evaluate_rete_ul_node(left, facts) && evaluate_rete_ul_node(right, facts)
}
ReteUlNode::UlOr(left, right) => {
evaluate_rete_ul_node(left, facts) || evaluate_rete_ul_node(right, facts)
}
ReteUlNode::UlNot(inner) => {
!evaluate_rete_ul_node(inner, facts)
}
ReteUlNode::UlExists(inner) => {
let target_field = match &**inner {
ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
_ => "".to_string(),
};
if target_field.contains('.') {
let parts: Vec<&str> = target_field.split('.').collect();
if parts.len() == 2 {
let prefix = parts[0];
let suffix = parts[1];
let filtered: Vec<_> = facts.iter()
.filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
.collect();
filtered.iter().any(|(_, value)| {
let mut sub_facts = HashMap::new();
sub_facts.insert(target_field.clone(), (*value).clone());
evaluate_rete_ul_node(inner, &sub_facts)
})
} else {
facts.iter().any(|(field, value)| {
let mut sub_facts = HashMap::new();
sub_facts.insert(field.clone(), value.clone());
evaluate_rete_ul_node(inner, &sub_facts)
})
}
} else {
facts.iter().any(|(field, value)| {
let mut sub_facts = HashMap::new();
sub_facts.insert(field.clone(), value.clone());
evaluate_rete_ul_node(inner, &sub_facts)
})
}
}
ReteUlNode::UlForall(inner) => {
let target_field = match &**inner {
ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
_ => "".to_string(),
};
if target_field.contains('.') {
let parts: Vec<&str> = target_field.split('.').collect();
if parts.len() == 2 {
let prefix = parts[0];
let suffix = parts[1];
let filtered: Vec<_> = facts.iter()
.filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
.collect();
if filtered.is_empty() {
return true; }
filtered.iter().all(|(_, value)| {
let mut sub_facts = HashMap::new();
sub_facts.insert(target_field.clone(), (*value).clone());
evaluate_rete_ul_node(inner, &sub_facts)
})
} else {
facts.iter().all(|(field, value)| {
let mut sub_facts = HashMap::new();
sub_facts.insert(field.clone(), value.clone());
evaluate_rete_ul_node(inner, &sub_facts)
})
}
} else {
facts.iter().all(|(field, value)| {
let mut sub_facts = HashMap::new();
sub_facts.insert(field.clone(), value.clone());
evaluate_rete_ul_node(inner, &sub_facts)
})
}
}
ReteUlNode::UlAccumulate {
source_pattern,
extract_field,
source_conditions,
function,
..
} => {
use super::accumulate::*;
let pattern_prefix = format!("{}.", source_pattern);
let mut matching_values = Vec::new();
let mut instances: std::collections::HashMap<String, std::collections::HashMap<String, String>> =
std::collections::HashMap::new();
for (key, value) in facts {
if key.starts_with(&pattern_prefix) {
let parts: Vec<&str> = key.strip_prefix(&pattern_prefix).unwrap().split('.').collect();
if parts.len() >= 2 {
let instance_id = parts[0];
let field_name = parts[1..].join(".");
instances
.entry(instance_id.to_string())
.or_insert_with(std::collections::HashMap::new)
.insert(field_name, value.clone());
} else if parts.len() == 1 {
instances
.entry("default".to_string())
.or_insert_with(std::collections::HashMap::new)
.insert(parts[0].to_string(), value.clone());
}
}
}
for (_instance_id, instance_facts) in instances {
let mut matches = true;
for condition_str in source_conditions {
if !evaluate_condition_string(condition_str, &instance_facts) {
matches = false;
break;
}
}
if matches {
if let Some(value_str) = instance_facts.get(extract_field) {
let fact_value = if let Ok(i) = value_str.parse::<i64>() {
super::facts::FactValue::Integer(i)
} else if let Ok(f) = value_str.parse::<f64>() {
super::facts::FactValue::Float(f)
} else if let Ok(b) = value_str.parse::<bool>() {
super::facts::FactValue::Boolean(b)
} else {
super::facts::FactValue::String(value_str.clone())
};
matching_values.push(fact_value);
}
}
}
let has_results = !matching_values.is_empty();
match function.as_str() {
"count" => has_results, "sum" | "average" | "min" | "max" => {
has_results
}
_ => true, }
}
ReteUlNode::UlMultiField { field, operation, value, operator, compare_value } => {
let field_value = facts.get(field);
match operation.as_str() {
"empty" => {
field_value.map(|v| v.is_empty() || v == "[]").unwrap_or(true)
}
"not_empty" => {
field_value.map(|v| !v.is_empty() && v != "[]").unwrap_or(false)
}
"count" => {
if let Some(val) = field_value {
let count = if val.starts_with('[') && val.ends_with(']') {
let inner = &val[1..val.len()-1];
if inner.trim().is_empty() {
0
} else {
inner.split(',').count()
}
} else {
0
};
if let (Some(op), Some(cmp_val)) = (operator, compare_value) {
let cmp_num = cmp_val.parse::<i64>().unwrap_or(0);
match op.as_str() {
">" => (count as i64) > cmp_num,
"<" => (count as i64) < cmp_num,
">=" => (count as i64) >= cmp_num,
"<=" => (count as i64) <= cmp_num,
"==" => (count as i64) == cmp_num,
"!=" => (count as i64) != cmp_num,
_ => false,
}
} else {
count > 0
}
} else {
false
}
}
"contains" => {
if let (Some(val), Some(search)) = (field_value, value) {
val.contains(search)
} else {
false
}
}
_ => {
false
}
}
}
ReteUlNode::UlTerminal(_) => true }
}
#[derive(Debug, Clone)]
pub enum ReteUlNode {
UlAlpha(AlphaNode),
UlAnd(Box<ReteUlNode>, Box<ReteUlNode>),
UlOr(Box<ReteUlNode>, Box<ReteUlNode>),
UlNot(Box<ReteUlNode>),
UlExists(Box<ReteUlNode>),
UlForall(Box<ReteUlNode>),
UlAccumulate {
result_var: String,
source_pattern: String,
extract_field: String,
source_conditions: Vec<String>,
function: String,
function_arg: String,
},
UlMultiField {
field: String,
operation: String, value: Option<String>, operator: Option<String>, compare_value: Option<String>, },
UlTerminal(String), }
impl ReteUlNode {
pub fn evaluate_typed(&self, facts: &super::facts::TypedFacts) -> bool {
evaluate_rete_ul_node_typed(self, facts)
}
}
pub struct ReteUlRule {
pub name: String,
pub node: ReteUlNode,
pub priority: i32,
pub no_loop: bool,
pub action: Arc<dyn Fn(&mut std::collections::HashMap<String, String>) + Send + Sync>,
}
pub fn fire_rete_ul_rules(
rules: &mut [(String, ReteUlNode, Box<dyn FnMut(&mut std::collections::HashMap<String, String>)>)],
facts: &mut std::collections::HashMap<String, String>,
) -> Vec<String> {
let mut fired_rules = Vec::new();
let mut changed = true;
while changed {
changed = false;
for (rule_name, node, action) in rules.iter_mut() {
let fired_flag = format!("{}_fired", rule_name);
if facts.get(&fired_flag) == Some(&"true".to_string()) {
continue;
}
if evaluate_rete_ul_node(node, facts) {
action(facts);
facts.insert(fired_flag.clone(), "true".to_string());
fired_rules.push(rule_name.clone());
changed = true;
}
}
}
fired_rules
}
pub fn fire_rete_ul_rules_with_agenda(
rules: &mut [ReteUlRule],
facts: &mut std::collections::HashMap<String, String>,
) -> Vec<String> {
let mut fired_rules = Vec::new();
let mut fired_flags = std::collections::HashSet::new();
let max_iterations = 100; let mut iterations = 0;
loop {
iterations += 1;
if iterations > max_iterations {
eprintln!("Warning: RETE engine reached max iterations ({})", max_iterations);
break;
}
let mut agenda: Vec<usize> = rules
.iter()
.enumerate()
.filter(|(_, rule)| {
if fired_flags.contains(&rule.name) {
return false;
}
evaluate_rete_ul_node(&rule.node, facts)
})
.map(|(i, _)| i)
.collect();
if agenda.is_empty() {
break;
}
agenda.sort_by_key(|&i| -rules[i].priority);
for &i in &agenda {
let rule = &mut rules[i];
(rule.action)(facts);
fired_rules.push(rule.name.clone());
fired_flags.insert(rule.name.clone());
let fired_flag = format!("{}_fired", rule.name);
facts.insert(fired_flag, "true".to_string());
}
if rules.iter().all(|r| r.no_loop) {
break;
}
}
fired_rules
}
pub struct ReteUlEngine {
rules: Vec<ReteUlRule>,
facts: std::collections::HashMap<String, String>,
}
impl ReteUlEngine {
pub fn new() -> Self {
Self {
rules: Vec::new(),
facts: std::collections::HashMap::new(),
}
}
pub fn add_rule_with_action<F>(
&mut self,
name: String,
node: ReteUlNode,
priority: i32,
no_loop: bool,
action: F,
) where
F: Fn(&mut std::collections::HashMap<String, String>) + Send + Sync + 'static,
{
self.rules.push(ReteUlRule {
name,
node,
priority,
no_loop,
action: Arc::new(action),
});
}
pub fn add_rule_from_definition(
&mut self,
rule: &crate::rete::auto_network::Rule,
priority: i32,
no_loop: bool,
) {
let node = build_rete_ul_from_condition_group(&rule.conditions);
let rule_name = rule.name.clone();
let action = Arc::new(move |facts: &mut std::collections::HashMap<String, String>| {
facts.insert(format!("{}_executed", rule_name), "true".to_string());
});
self.rules.push(ReteUlRule {
name: rule.name.clone(),
node,
priority,
no_loop,
action,
});
}
pub fn set_fact(&mut self, key: String, value: String) {
self.facts.insert(key, value);
}
pub fn get_fact(&self, key: &str) -> Option<&String> {
self.facts.get(key)
}
pub fn remove_fact(&mut self, key: &str) -> Option<String> {
self.facts.remove(key)
}
pub fn get_all_facts(&self) -> &std::collections::HashMap<String, String> {
&self.facts
}
pub fn clear_facts(&mut self) {
self.facts.clear();
}
pub fn fire_all(&mut self) -> Vec<String> {
fire_rete_ul_rules_with_agenda(&mut self.rules, &mut self.facts)
}
pub fn matches(&self, rule_name: &str) -> bool {
self.rules
.iter()
.find(|r| r.name == rule_name)
.map(|r| evaluate_rete_ul_node(&r.node, &self.facts))
.unwrap_or(false)
}
pub fn get_matching_rules(&self) -> Vec<&str> {
self.rules
.iter()
.filter(|r| evaluate_rete_ul_node(&r.node, &self.facts))
.map(|r| r.name.as_str())
.collect()
}
pub fn reset_fired_flags(&mut self) {
let keys_to_remove: Vec<_> = self.facts
.keys()
.filter(|k| k.ends_with("_fired") || k.ends_with("_executed"))
.cloned()
.collect();
for key in keys_to_remove {
self.facts.remove(&key);
}
}
}
use super::facts::{FactValue, TypedFacts};
pub fn evaluate_rete_ul_node_typed(node: &ReteUlNode, facts: &TypedFacts) -> bool {
match node {
ReteUlNode::UlAlpha(alpha) => {
alpha.matches_typed(facts)
}
ReteUlNode::UlAnd(left, right) => {
evaluate_rete_ul_node_typed(left, facts) && evaluate_rete_ul_node_typed(right, facts)
}
ReteUlNode::UlOr(left, right) => {
evaluate_rete_ul_node_typed(left, facts) || evaluate_rete_ul_node_typed(right, facts)
}
ReteUlNode::UlNot(inner) => {
!evaluate_rete_ul_node_typed(inner, facts)
}
ReteUlNode::UlExists(inner) => {
let target_field = match &**inner {
ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
_ => "".to_string(),
};
if target_field.contains('.') {
let parts: Vec<&str> = target_field.split('.').collect();
if parts.len() == 2 {
let prefix = parts[0];
let suffix = parts[1];
let filtered: Vec<_> = facts.get_all().iter()
.filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
.collect();
filtered.iter().any(|(_, _)| {
evaluate_rete_ul_node_typed(inner, facts)
})
} else {
evaluate_rete_ul_node_typed(inner, facts)
}
} else {
evaluate_rete_ul_node_typed(inner, facts)
}
}
ReteUlNode::UlForall(inner) => {
let target_field = match &**inner {
ReteUlNode::UlAlpha(alpha) => alpha.field.clone(),
_ => "".to_string(),
};
if target_field.contains('.') {
let parts: Vec<&str> = target_field.split('.').collect();
if parts.len() == 2 {
let prefix = parts[0];
let suffix = parts[1];
let filtered: Vec<_> = facts.get_all().iter()
.filter(|(k, _)| k.starts_with(prefix) && k.ends_with(suffix))
.collect();
if filtered.is_empty() {
return true; }
filtered.iter().all(|(_, _)| {
evaluate_rete_ul_node_typed(inner, facts)
})
} else {
if facts.get_all().is_empty() {
return true; }
evaluate_rete_ul_node_typed(inner, facts)
}
} else {
if facts.get_all().is_empty() {
return true; }
evaluate_rete_ul_node_typed(inner, facts)
}
}
ReteUlNode::UlAccumulate {
source_pattern,
extract_field,
source_conditions,
function,
..
} => {
use super::accumulate::*;
let pattern_prefix = format!("{}.", source_pattern);
let mut matching_values = Vec::new();
let mut instances: std::collections::HashMap<String, std::collections::HashMap<String, FactValue>> =
std::collections::HashMap::new();
for (key, value) in facts.get_all() {
if key.starts_with(&pattern_prefix) {
let parts: Vec<&str> = key.strip_prefix(&pattern_prefix).unwrap().split('.').collect();
if parts.len() >= 2 {
let instance_id = parts[0];
let field_name = parts[1..].join(".");
instances
.entry(instance_id.to_string())
.or_insert_with(std::collections::HashMap::new)
.insert(field_name, value.clone());
} else if parts.len() == 1 {
instances
.entry("default".to_string())
.or_insert_with(std::collections::HashMap::new)
.insert(parts[0].to_string(), value.clone());
}
}
}
for (_instance_id, instance_facts) in instances {
let mut matches = true;
for condition_str in source_conditions {
let string_facts: HashMap<String, String> = instance_facts
.iter()
.map(|(k, v)| (k.clone(), format!("{:?}", v)))
.collect();
if !evaluate_condition_string(condition_str, &string_facts) {
matches = false;
break;
}
}
if matches {
if let Some(value) = instance_facts.get(extract_field) {
matching_values.push(value.clone());
}
}
}
let has_results = !matching_values.is_empty();
match function.as_str() {
"count" => has_results,
"sum" | "average" | "min" | "max" => has_results,
_ => true,
}
}
ReteUlNode::UlMultiField { field, operation, value, operator, compare_value } => {
use super::facts::FactValue;
let field_value = facts.get(field);
match operation.as_str() {
"empty" => {
if let Some(FactValue::Array(arr)) = field_value {
arr.is_empty()
} else {
true
}
}
"not_empty" => {
if let Some(FactValue::Array(arr)) = field_value {
!arr.is_empty()
} else {
false
}
}
"count" => {
if let Some(FactValue::Array(arr)) = field_value {
let count = arr.len() as i64;
if let (Some(op), Some(cmp_val)) = (operator, compare_value) {
let cmp_num = cmp_val.parse::<i64>().unwrap_or(0);
match op.as_str() {
">" => count > cmp_num,
"<" => count < cmp_num,
">=" => count >= cmp_num,
"<=" => count <= cmp_num,
"==" => count == cmp_num,
"!=" => count != cmp_num,
_ => false,
}
} else {
count > 0
}
} else {
false
}
}
"contains" => {
if let (Some(FactValue::Array(arr)), Some(search)) = (field_value, value) {
arr.iter().any(|item| {
match item {
FactValue::String(s) => s == search,
FactValue::Integer(i) => i.to_string() == *search,
FactValue::Float(f) => f.to_string() == *search,
FactValue::Boolean(b) => b.to_string() == *search,
_ => false,
}
})
} else {
false
}
}
"first" => {
if let Some(FactValue::Array(arr)) = field_value {
!arr.is_empty()
} else {
false
}
}
"last" => {
if let Some(FactValue::Array(arr)) = field_value {
!arr.is_empty()
} else {
false
}
}
"collect" => {
matches!(field_value, Some(FactValue::Array(_)))
}
_ => {
false
}
}
}
ReteUlNode::UlTerminal(_) => true
}
}
pub struct TypedReteUlRule {
pub name: String,
pub node: ReteUlNode,
pub priority: i32,
pub no_loop: bool,
pub action: Arc<dyn Fn(&mut TypedFacts, &mut super::ActionResults) + Send + Sync>,
}
pub struct TypedReteUlEngine {
rules: Vec<TypedReteUlRule>,
facts: TypedFacts,
}
impl TypedReteUlEngine {
pub fn new() -> Self {
Self {
rules: Vec::new(),
facts: TypedFacts::new(),
}
}
pub fn add_rule_with_action<F>(
&mut self,
name: String,
node: ReteUlNode,
priority: i32,
no_loop: bool,
action: F,
) where
F: Fn(&mut TypedFacts, &mut super::ActionResults) + Send + Sync + 'static,
{
self.rules.push(TypedReteUlRule {
name,
node,
priority,
no_loop,
action: Arc::new(action),
});
}
pub fn add_rule_from_definition(
&mut self,
rule: &crate::rete::auto_network::Rule,
priority: i32,
no_loop: bool,
) {
let node = build_rete_ul_from_condition_group(&rule.conditions);
let rule_name = rule.name.clone();
let action = Arc::new(move |facts: &mut TypedFacts, _results: &mut super::ActionResults| {
facts.set(format!("{}_executed", rule_name), true);
});
self.rules.push(TypedReteUlRule {
name: rule.name.clone(),
node,
priority,
no_loop,
action,
});
}
pub fn set_fact<K: Into<String>, V: Into<FactValue>>(&mut self, key: K, value: V) {
self.facts.set(key, value);
}
pub fn get_fact(&self, key: &str) -> Option<&FactValue> {
self.facts.get(key)
}
pub fn remove_fact(&mut self, key: &str) -> Option<FactValue> {
self.facts.remove(key)
}
pub fn get_all_facts(&self) -> &TypedFacts {
&self.facts
}
pub fn clear_facts(&mut self) {
self.facts.clear();
}
pub fn fire_all(&mut self) -> Vec<String> {
let mut fired_rules = Vec::new();
let mut agenda: Vec<usize>;
let mut changed = true;
let mut fired_flags = std::collections::HashSet::new();
while changed {
changed = false;
agenda = self.rules.iter().enumerate()
.filter(|(_, rule)| {
let fired_flag = format!("{}_fired", rule.name);
let already_fired = fired_flags.contains(&rule.name) ||
self.facts.get(&fired_flag).and_then(|v| v.as_boolean()) == Some(true);
!rule.no_loop || !already_fired
})
.filter(|(_, rule)| evaluate_rete_ul_node_typed(&rule.node, &self.facts))
.map(|(i, _)| i)
.collect();
agenda.sort_by_key(|&i| -self.rules[i].priority);
for &i in &agenda {
let rule = &mut self.rules[i];
let fired_flag = format!("{}_fired", rule.name);
let already_fired = fired_flags.contains(&rule.name) ||
self.facts.get(&fired_flag).and_then(|v| v.as_boolean()) == Some(true);
if rule.no_loop && already_fired {
continue;
}
let mut action_results = super::ActionResults::new();
(rule.action)(&mut self.facts, &mut action_results);
fired_rules.push(rule.name.clone());
fired_flags.insert(rule.name.clone());
self.facts.set(fired_flag, true);
changed = true;
}
}
fired_rules
}
pub fn matches(&self, rule_name: &str) -> bool {
self.rules
.iter()
.find(|r| r.name == rule_name)
.map(|r| evaluate_rete_ul_node_typed(&r.node, &self.facts))
.unwrap_or(false)
}
pub fn get_matching_rules(&self) -> Vec<&str> {
self.rules
.iter()
.filter(|r| evaluate_rete_ul_node_typed(&r.node, &self.facts))
.map(|r| r.name.as_str())
.collect()
}
pub fn reset_fired_flags(&mut self) {
let keys_to_remove: Vec<_> = self.facts.get_all()
.keys()
.filter(|k| k.ends_with("_fired") || k.ends_with("_executed"))
.cloned()
.collect();
for key in keys_to_remove {
self.facts.remove(&key);
}
}
}
impl Default for TypedReteUlEngine {
fn default() -> Self {
Self::new()
}
}