1pub mod context;
10pub mod datetime;
11pub mod expression;
12pub mod operations;
13pub mod rules;
14pub mod units;
15
16use crate::{LemmaDoc, LemmaError, LemmaFact, LemmaResult, Response, RuleResult};
17use context::{build_fact_map, EvaluationContext};
18use std::collections::HashMap;
19
20pub struct Evaluator;
22
23impl Evaluator {
24 pub fn new() -> Self {
25 Self
26 }
27
28 pub fn evaluate_document(
33 &self,
34 doc_name: &str,
35 documents: &HashMap<String, LemmaDoc>,
36 sources: &HashMap<String, String>,
37 fact_overrides: Vec<LemmaFact>,
38 requested_rules: Option<Vec<String>>,
39 ) -> LemmaResult<Response> {
40 let doc = documents
41 .get(doc_name)
42 .ok_or_else(|| LemmaError::Engine(format!("Document '{}' not found", doc_name)))?;
43
44 let facts = build_fact_map(&doc.facts, &fact_overrides, documents);
46
47 let execution_order = build_execution_plan(&doc.rules)?;
49
50 let mut context = EvaluationContext::new(doc, documents, sources, facts);
52
53 let mut response = Response::new(doc_name.to_string());
55 let mut failed_rules: std::collections::HashSet<String> = std::collections::HashSet::new();
56
57 for rule_name in execution_order {
58 let rule = doc
59 .rules
60 .iter()
61 .find(|r| r.name == rule_name)
62 .ok_or_else(|| LemmaError::Engine(format!("Rule {} not found", rule_name)))?;
63
64 let mut all_rule_deps = std::collections::HashSet::new();
66
67 let refs = crate::analysis::extract_references(&rule.expression);
69 for rule_ref in refs.rules {
70 all_rule_deps.insert(rule_ref.join("."));
71 }
72
73 for uc in &rule.unless_clauses {
75 let cond_refs = crate::analysis::extract_references(&uc.condition);
76 let res_refs = crate::analysis::extract_references(&uc.result);
77 for rule_ref in cond_refs.rules.into_iter().chain(res_refs.rules) {
78 all_rule_deps.insert(rule_ref.join("."));
79 }
80 }
81
82 let mut missing_deps = Vec::new();
83 for dep_name in all_rule_deps {
84 if failed_rules.contains(&dep_name) {
85 missing_deps.push(dep_name);
86 }
87 }
88
89 if !missing_deps.is_empty() {
90 failed_rules.insert(rule.name.clone());
92 response.add_result(RuleResult::missing_facts(rule.name.clone(), missing_deps));
93 continue;
94 }
95
96 context.operations.clear();
98
99 match rules::evaluate_rule(rule, &mut context) {
101 Ok(result) => {
102 context
104 .rule_results
105 .insert(rule.name.clone(), result.clone());
106
107 match result {
109 crate::OperationResult::Value(value) => {
110 response.add_result(RuleResult::success_with_operations(
111 rule.name.clone(),
112 value.clone(),
113 HashMap::new(),
114 context.operations.clone(),
115 ));
116 }
117 crate::OperationResult::Veto(msg) => {
118 response.add_result(RuleResult::veto(rule.name.clone(), msg));
119 }
120 }
121 }
122 Err(LemmaError::Engine(msg)) if msg.starts_with("Missing fact:") => {
123 failed_rules.insert(rule.name.clone());
124 let missing = vec![msg.replace("Missing fact: ", "")];
125 response.add_result(RuleResult::missing_facts(rule.name.clone(), missing));
126 }
127 Err(e) => {
128 return Err(e);
129 }
130 }
131 }
132
133 if let Some(rule_names) = requested_rules {
135 response.filter_rules(&rule_names);
136 }
137
138 Ok(response)
139 }
140}
141
142impl Default for Evaluator {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148fn build_execution_plan(rules: &[crate::LemmaRule]) -> LemmaResult<Vec<String>> {
152 let graph = crate::analysis::build_dependency_graph(rules);
154
155 topological_sort(&graph)
157}
158
159pub(crate) fn topological_sort(
164 graph: &HashMap<String, std::collections::HashSet<String>>,
165) -> LemmaResult<Vec<String>> {
166 use std::collections::{HashSet, VecDeque};
167
168 let mut reverse_graph: HashMap<String, HashSet<String>> = HashMap::new();
170 let mut all_nodes: HashSet<String> = HashSet::new();
171
172 for (node, dependencies) in graph {
173 all_nodes.insert(node.clone());
174 reverse_graph.entry(node.clone()).or_default();
175
176 for dep in dependencies {
177 all_nodes.insert(dep.clone());
178 reverse_graph
179 .entry(dep.clone())
180 .or_default()
181 .insert(node.clone());
182 }
183 }
184
185 let mut dependency_count: HashMap<String, usize> = HashMap::new();
187 for node in &all_nodes {
188 let count = graph.get(node).map(|deps| deps.len()).unwrap_or(0);
189 dependency_count.insert(node.clone(), count);
190 }
191
192 let mut queue: VecDeque<String> = dependency_count
194 .iter()
195 .filter(|(_, &count)| count == 0)
196 .map(|(node, _)| node.clone())
197 .collect();
198
199 let mut result = Vec::new();
200
201 while let Some(node) = queue.pop_front() {
203 result.push(node.clone());
204
205 if let Some(dependents) = reverse_graph.get(&node) {
207 for dependent in dependents {
208 if let Some(count) = dependency_count.get_mut(dependent) {
210 *count -= 1;
211 if *count == 0 {
212 queue.push_back(dependent.clone());
213 }
214 }
215 }
216 }
217 }
218
219 if result.len() != all_nodes.len() {
221 return Err(LemmaError::Engine(
222 "Circular dependency detected in rules (validator should have caught this)".to_string(),
223 ));
224 }
225
226 Ok(result)
227}