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    FactReference, FactType, FactValue, LemmaDoc, LemmaError, LemmaFact, LiteralValue,
7    OperationRecord, OperationResult, ResourceLimits,
8};
9use std::collections::HashMap;
10
11use super::timeout::TimeoutTracker;
12
13/// Context for evaluating a Lemma document
14///
15/// Contains all state needed for a single evaluation:
16/// - Facts (inputs)
17/// - Rule results (computed values)
18/// - Operation records (execution log)
19/// - Timeout tracking
20pub struct EvaluationContext<'a> {
21    /// Document being evaluated
22    pub current_doc: &'a LemmaDoc,
23
24    /// All loaded documents (needed when facts reference other documents)
25    pub all_documents: &'a HashMap<String, LemmaDoc>,
26
27    /// Source text for all documents (for error reporting)
28    /// Maps source_id -> source text
29    pub sources: &'a HashMap<String, String>,
30
31    /// Fact values (from document + overrides)
32    /// Maps fact path -> concrete value
33    /// Only contains facts that have actual values (not TypeAnnotations)
34    pub facts: HashMap<FactReference, LiteralValue>,
35
36    /// Timeout tracker (platform-specific)
37    pub timeout_tracker: &'a TimeoutTracker,
38
39    /// Resource limits including timeout
40    pub limits: &'a ResourceLimits,
41
42    /// Rule results computed so far (populated during execution)
43    /// Maps RulePath -> operation result (either Value or Veto)
44    pub rule_results: HashMap<crate::RulePath, OperationResult>,
45
46    /// Operation records - records every operation
47    pub operations: Vec<OperationRecord>,
48}
49
50impl<'a> EvaluationContext<'a> {
51    /// Create a new evaluation context
52    pub fn new(
53        current_doc: &'a LemmaDoc,
54        all_documents: &'a HashMap<String, LemmaDoc>,
55        sources: &'a HashMap<String, String>,
56        facts: HashMap<FactReference, LiteralValue>,
57        timeout_tracker: &'a TimeoutTracker,
58        limits: &'a ResourceLimits,
59    ) -> Self {
60        Self {
61            current_doc,
62            all_documents,
63            sources,
64            facts,
65            rule_results: HashMap::new(),
66            operations: Vec::new(),
67            timeout_tracker,
68            limits,
69        }
70    }
71
72    /// Check if evaluation has exceeded timeout
73    pub fn check_timeout(&self) -> Result<(), crate::LemmaError> {
74        self.timeout_tracker.check_timeout(self.limits)
75    }
76}
77
78/// Build a fact map from document facts and overrides
79///
80/// Includes facts with concrete values (FactValue::Literal) and expands
81/// DocumentReference facts by importing all facts from the referenced document.
82/// Facts with TypeAnnotation are missing and will cause evaluation errors.
83///
84/// Validates that fact overrides match the expected types declared in the document.
85pub fn build_fact_map(
86    doc: &LemmaDoc,
87    doc_facts: &[LemmaFact],
88    overrides: &[LemmaFact],
89    all_documents: &HashMap<String, LemmaDoc>,
90) -> Result<HashMap<FactReference, LiteralValue>, LemmaError> {
91    let mut facts = HashMap::new();
92
93    // Add document facts
94    for fact in doc_facts {
95        match &fact.value {
96            FactValue::Literal(lit) => {
97                let path = get_fact_path(fact);
98                facts.insert(path, lit.clone());
99            }
100            FactValue::DocumentReference(doc_name) => {
101                // Resolve document reference by recursively importing all facts from referenced doc
102                if let Some(referenced_doc) = all_documents.get(doc_name) {
103                    let fact_prefix = get_fact_path(fact);
104                    // Recursively build fact map for the referenced document
105                    let referenced_facts =
106                        build_fact_map(referenced_doc, &referenced_doc.facts, &[], all_documents)?;
107                    for (ref_fact_path, lit) in referenced_facts {
108                        // Prepend the prefix to create the qualified path
109                        let mut qualified_reference = fact_prefix.reference.clone();
110                        qualified_reference.extend_from_slice(&ref_fact_path.reference);
111                        let qualified_path = FactReference {
112                            reference: qualified_reference,
113                        };
114                        facts.insert(qualified_path, lit);
115                    }
116                }
117            }
118            FactValue::TypeAnnotation(_) => {
119                // Skip type annotations
120            }
121        }
122    }
123
124    // Apply overrides with type validation
125    for fact in overrides {
126        if let FactValue::Literal(lit) = &fact.value {
127            let path = get_fact_path(fact);
128
129            // Check if this fact exists in the document and validate type
130            if let Some(expected_type) = doc.get_fact_type(&path) {
131                let actual_type = lit.to_type();
132                if expected_type != actual_type {
133                    return Err(LemmaError::Engine(format!(
134                        "Type mismatch for fact '{}': expected {}, got {}",
135                        path, expected_type, actual_type
136                    )));
137                }
138            }
139
140            facts.insert(path, lit.clone());
141        }
142    }
143
144    Ok(facts)
145}
146
147/// Get the fact reference for a fact (handles local and foreign facts)
148fn get_fact_path(fact: &LemmaFact) -> FactReference {
149    match &fact.fact_type {
150        FactType::Local(name) => FactReference {
151            reference: vec![name.clone()],
152        },
153        FactType::Foreign(foreign_ref) => FactReference {
154            reference: foreign_ref.reference.clone(),
155        },
156    }
157}