lemma/evaluator/
context.rs

1//! Evaluation context for executing Lemma rules
2//!
3//! Contains all state needed during evaluation of a single document.
4
5use crate::{
6    FactType, FactValue, LemmaDoc, LemmaFact, LiteralValue, OperationRecord, OperationResult,
7};
8use std::collections::HashMap;
9
10/// Context for evaluating a Lemma document
11///
12/// Contains all state needed for a single evaluation:
13/// - Facts (inputs)
14/// - Rule results (computed values)
15/// - Operation records (execution log)
16pub struct EvaluationContext<'a> {
17    /// Document being evaluated
18    pub current_doc: &'a LemmaDoc,
19
20    /// All loaded documents (for cross-document references)
21    pub all_documents: &'a HashMap<String, LemmaDoc>,
22
23    /// Source text for all documents (for error reporting)
24    /// Maps source_id -> source text
25    pub sources: &'a HashMap<String, String>,
26
27    /// Fact values (from document + overrides)
28    /// Maps fact name -> concrete value
29    /// Only contains facts that have actual values (not TypeAnnotations)
30    pub facts: HashMap<String, LiteralValue>,
31
32    /// Rule results computed so far (populated during execution)
33    /// Maps rule name -> operation result (either Value or Veto)
34    pub rule_results: HashMap<String, OperationResult>,
35
36    /// Operation records - records every operation
37    pub operations: Vec<OperationRecord>,
38}
39
40impl<'a> EvaluationContext<'a> {
41    /// Create a new evaluation context
42    pub fn new(
43        current_doc: &'a LemmaDoc,
44        all_documents: &'a HashMap<String, LemmaDoc>,
45        sources: &'a HashMap<String, String>,
46        facts: HashMap<String, LiteralValue>,
47    ) -> Self {
48        Self {
49            current_doc,
50            all_documents,
51            sources,
52            facts,
53            rule_results: HashMap::new(),
54            operations: Vec::new(),
55        }
56    }
57}
58
59/// Build a fact map from document facts and overrides
60///
61/// Includes facts with concrete values (FactValue::Literal) and expands
62/// DocumentReference facts by importing all facts from the referenced document.
63/// Facts with TypeAnnotation are missing and will cause evaluation errors.
64pub fn build_fact_map(
65    doc_facts: &[LemmaFact],
66    overrides: &[LemmaFact],
67    all_documents: &HashMap<String, LemmaDoc>,
68) -> HashMap<String, LiteralValue> {
69    let mut facts = HashMap::new();
70
71    // Add document facts
72    for fact in doc_facts {
73        match &fact.value {
74            FactValue::Literal(lit) => {
75                let name = get_fact_name(fact);
76                facts.insert(name, lit.clone());
77            }
78            FactValue::DocumentReference(doc_name) => {
79                // Resolve document reference by importing all facts from referenced doc
80                if let Some(referenced_doc) = all_documents.get(doc_name) {
81                    let fact_prefix = get_fact_name(fact);
82                    for ref_fact in &referenced_doc.facts {
83                        if let FactValue::Literal(lit) = &ref_fact.value {
84                            let ref_fact_name = get_fact_name(ref_fact);
85                            let qualified_name = format!("{}.{}", fact_prefix, ref_fact_name);
86                            facts.insert(qualified_name, lit.clone());
87                        }
88                    }
89                }
90            }
91            FactValue::TypeAnnotation(_) => {
92                // Skip type annotations
93            }
94        }
95    }
96
97    // Apply overrides
98    for fact in overrides {
99        if let FactValue::Literal(lit) = &fact.value {
100            let name = get_fact_name(fact);
101            facts.insert(name, lit.clone());
102        }
103    }
104
105    facts
106}
107
108/// Get the display name for a fact (handles local and foreign facts)
109fn get_fact_name(fact: &LemmaFact) -> String {
110    match &fact.fact_type {
111        FactType::Local(name) => name.clone(),
112        FactType::Foreign(foreign_ref) => foreign_ref.reference.join("."),
113    }
114}