1pub mod explanation;
8pub mod expression;
9pub mod operations;
10pub mod response;
11
12use crate::evaluation::explanation::{ExplanationNode, ValueSource};
13use crate::evaluation::response::EvaluatedRule;
14use crate::planning::semantics::{Expression, Fact, FactPath, FactValue, LiteralValue, RulePath};
15use crate::planning::ExecutionPlan;
16use indexmap::IndexMap;
17pub use operations::{ComputationKind, OperationKind, OperationRecord, OperationResult};
18pub use response::{Facts, Response, RuleResult};
19use std::collections::HashMap;
20
21pub(crate) struct EvaluationContext {
23 fact_values: HashMap<FactPath, LiteralValue>,
24 pub(crate) rule_results: HashMap<RulePath, OperationResult>,
25 rule_explanations: HashMap<RulePath, crate::evaluation::explanation::Explanation>,
26 operations: Option<Vec<crate::OperationRecord>>,
27 explanation_nodes: HashMap<usize, crate::evaluation::explanation::ExplanationNode>,
28 now: LiteralValue,
29}
30
31impl EvaluationContext {
32 fn new(plan: &ExecutionPlan, now: LiteralValue, record_operations: bool) -> Self {
33 let fact_values: HashMap<FactPath, LiteralValue> = plan
34 .facts
35 .iter()
36 .filter_map(|(path, d)| d.value().map(|v| (path.clone(), v.clone())))
37 .collect();
38 Self {
39 fact_values,
40 rule_results: HashMap::new(),
41 rule_explanations: HashMap::new(),
42 operations: if record_operations {
43 Some(Vec::new())
44 } else {
45 None
46 },
47 explanation_nodes: HashMap::new(),
48 now,
49 }
50 }
51
52 pub(crate) fn now(&self) -> &LiteralValue {
53 &self.now
54 }
55
56 fn get_fact(&self, fact_path: &FactPath) -> Option<&LiteralValue> {
57 self.fact_values.get(fact_path)
58 }
59
60 fn push_operation(&mut self, kind: OperationKind) {
61 if let Some(ref mut ops) = self.operations {
62 ops.push(OperationRecord { kind });
63 }
64 }
65
66 fn set_explanation_node(
67 &mut self,
68 expression: &Expression,
69 node: crate::evaluation::explanation::ExplanationNode,
70 ) {
71 self.explanation_nodes
72 .insert(expression as *const Expression as usize, node);
73 }
74
75 fn get_explanation_node(
76 &self,
77 expression: &Expression,
78 ) -> Option<&crate::evaluation::explanation::ExplanationNode> {
79 self.explanation_nodes
80 .get(&(expression as *const Expression as usize))
81 }
82
83 fn get_rule_explanation(
84 &self,
85 rule_path: &RulePath,
86 ) -> Option<&crate::evaluation::explanation::Explanation> {
87 self.rule_explanations.get(rule_path)
88 }
89
90 fn set_rule_explanation(
91 &mut self,
92 rule_path: RulePath,
93 explanation: crate::evaluation::explanation::Explanation,
94 ) {
95 self.rule_explanations.insert(rule_path, explanation);
96 }
97}
98
99fn collect_used_facts_from_explanation(
100 node: &ExplanationNode,
101 out: &mut HashMap<FactPath, LiteralValue>,
102) {
103 match node {
104 ExplanationNode::Value {
105 value,
106 source: ValueSource::Fact { fact_ref },
107 ..
108 } => {
109 out.entry(fact_ref.clone()).or_insert_with(|| value.clone());
110 }
111 ExplanationNode::Value { .. } => {}
112 ExplanationNode::RuleReference { expansion, .. } => {
113 collect_used_facts_from_explanation(expansion.as_ref(), out);
114 }
115 ExplanationNode::Computation { operands, .. } => {
116 for op in operands {
117 collect_used_facts_from_explanation(op, out);
118 }
119 }
120 ExplanationNode::Branches {
121 matched,
122 non_matched,
123 ..
124 } => {
125 if let Some(ref cond) = matched.condition {
126 collect_used_facts_from_explanation(cond, out);
127 }
128 collect_used_facts_from_explanation(&matched.result, out);
129 for nm in non_matched {
130 collect_used_facts_from_explanation(&nm.condition, out);
131 if let Some(ref res) = nm.result {
132 collect_used_facts_from_explanation(res, out);
133 }
134 }
135 }
136 ExplanationNode::Condition { operands, .. } => {
137 for op in operands {
138 collect_used_facts_from_explanation(op, out);
139 }
140 }
141 ExplanationNode::Veto { .. } => {}
142 }
143}
144
145#[derive(Default)]
147pub(crate) struct Evaluator;
148
149impl Evaluator {
150 pub(crate) fn evaluate(
163 &self,
164 plan: &ExecutionPlan,
165 now: LiteralValue,
166 record_operations: bool,
167 ) -> Response {
168 let mut context = EvaluationContext::new(plan, now, record_operations);
169
170 let mut response = Response {
171 spec_name: plan.spec_name.clone(),
172 spec_hash: None,
173 spec_effective_from: None,
174 spec_effective_to: None,
175 facts: Vec::new(),
176 results: IndexMap::new(),
177 };
178
179 for exec_rule in &plan.rules {
181 if let Some(ref mut ops) = context.operations {
182 ops.clear();
183 }
184 context.explanation_nodes.clear();
185
186 let (result, explanation) = expression::evaluate_rule(exec_rule, &mut context);
187
188 context
189 .rule_results
190 .insert(exec_rule.path.clone(), result.clone());
191 context.set_rule_explanation(exec_rule.path.clone(), explanation.clone());
192
193 let rule_operations = context.operations.clone().unwrap_or_default();
194
195 if !exec_rule.path.segments.is_empty() {
196 continue;
197 }
198
199 let unless_branches: Vec<(Option<Expression>, Expression)> = exec_rule.branches[1..]
200 .iter()
201 .map(|b| (b.condition.clone(), b.result.clone()))
202 .collect();
203
204 response.add_result(RuleResult {
205 rule: EvaluatedRule {
206 name: exec_rule.name.clone(),
207 path: exec_rule.path.clone(),
208 default_expression: exec_rule.branches[0].result.clone(),
209 unless_branches,
210 source_location: exec_rule.source.clone(),
211 rule_type: exec_rule.rule_type.clone(),
212 },
213 result,
214 facts: vec![],
215 operations: rule_operations,
216 explanation: Some(explanation),
217 rule_type: exec_rule.rule_type.clone(),
218 });
219 }
220
221 let mut used_facts: HashMap<FactPath, LiteralValue> = HashMap::new();
222 for rule_result in response.results.values() {
223 if let Some(ref explanation) = rule_result.explanation {
224 collect_used_facts_from_explanation(explanation.tree.as_ref(), &mut used_facts);
225 }
226 }
227
228 let fact_list: Vec<Fact> = plan
230 .facts
231 .keys()
232 .filter_map(|path| {
233 used_facts.remove(path).map(|value| Fact {
234 path: path.clone(),
235 value: FactValue::Literal(value),
236 source: None,
237 })
238 })
239 .collect();
240
241 if !fact_list.is_empty() {
242 response.facts = vec![Facts {
243 fact_path: String::new(),
244 referencing_fact_name: String::new(),
245 facts: fact_list,
246 }];
247 }
248
249 response
250 }
251}