File size: 6,638 Bytes
224e773
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
use crate::ast::ConstraintProgram;
use std::collections::HashMap;

/// Evaluator for constraint programs.
///
/// The Evaluator executes a compiled ConstraintProgram against a set of variable bindings.
/// Each constraint is tested: if all its requirements pass, evaluation succeeds. If any
/// requirement fails, the "otherwise" action determines the constraint's result (reject = false,
/// accept = true).
///
/// NAND Lowering (Future Enhancement):
/// The current evaluator uses boolean algebra directly. Future versions will lower constraints
/// to NAND gates for cryptographic finality. Mapping:
///   - Predicate(x) → lookup x in bindings
///   - Check(name) → built-in gate or binding lookup
///   - AND of requirements → cascaded NAND implementations
///   - Constraint evaluation → NAND commitment to immutable ledger (WORM)
pub struct Evaluator {
    pub program: ConstraintProgram,
}

impl Evaluator {
    pub fn new(program: ConstraintProgram) -> Self {
        Self { program }
    }

    /// Evaluate the constraint program against provided bindings.
    ///
    /// Returns Ok(true) if all constraints pass, Ok(false) if any fails.
    /// Returns Err only on internal evaluation errors.
    pub fn evaluate(&self, bindings: &HashMap<String, bool>) -> hyperkitty_core::Result<bool> {
        Ok(self.program.evaluate(bindings))
    }

    /// Evaluate a single constraint by name.
    ///
    /// Useful for debugging or selective constraint testing.
    pub fn evaluate_constraint(
        &self,
        name: &str,
        bindings: &HashMap<String, bool>,
    ) -> hyperkitty_core::Result<bool> {
        for constraint in &self.program.constraints {
            if constraint.name == name {
                return Ok(constraint.evaluate(bindings));
            }
        }
        Err(hyperkitty_core::Error::Custom(
            format!("Constraint not found: {}", name)
        ))
    }

    /// Count how many constraints pass.
    pub fn count_passing(&self, bindings: &HashMap<String, bool>) -> usize {
        self.program
            .constraints
            .iter()
            .filter(|c| c.evaluate(bindings))
            .count()
    }

    /// Get detailed evaluation report for debugging.
    pub fn report(&self, bindings: &HashMap<String, bool>) -> String {
        let mut report = String::new();
        report.push_str(&format!(
            "Constraint Evaluation Report ({} constraints)\n",
            self.program.constraints.len()
        ));
        report.push_str(&"=".repeat(60));
        report.push('\n');

        for constraint in &self.program.constraints {
            let result = constraint.evaluate(bindings);
            let status = if result { "PASS" } else { "FAIL" };
            report.push_str(&format!(
                "  [{}] {}: {} (otherwise: {:?})\n",
                status, constraint.name, constraint.param, constraint.otherwise
            ));
            for req in &constraint.requires {
                report.push_str(&format!("      - {:?}\n", req));
            }
        }

        report.push_str(&"=".repeat(60));
        report.push('\n');
        report.push_str(&format!(
            "Overall: {}/{} constraints passed\n",
            self.count_passing(bindings),
            self.program.constraints.len()
        ));

        report
    }
}

impl Default for Evaluator {
    fn default() -> Self {
        Self {
            program: ConstraintProgram::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ast::{Constraint, OtherwiseAction, Requirement};

    #[test]
    fn test_evaluator_all_pass() {
        let mut prog = ConstraintProgram::new();
        let mut c = Constraint::new("test".to_string(), "msg".to_string(), OtherwiseAction::Reject);
        c.add_requirement(Requirement::Check("always_true".to_string()));
        prog.add_constraint(c);

        let evaluator = Evaluator::new(prog);
        let bindings = HashMap::new();
        assert!(evaluator.evaluate(&bindings).unwrap());
    }

    #[test]
    fn test_evaluator_one_fails() {
        let mut prog = ConstraintProgram::new();

        let mut c1 = Constraint::new("c1".to_string(), "msg".to_string(), OtherwiseAction::Reject);
        c1.add_requirement(Requirement::Check("always_true".to_string()));
        prog.add_constraint(c1);

        let mut c2 = Constraint::new("c2".to_string(), "msg".to_string(), OtherwiseAction::Reject);
        c2.add_requirement(Requirement::Check("always_false".to_string()));
        prog.add_constraint(c2);

        let evaluator = Evaluator::new(prog);
        let bindings = HashMap::new();
        assert!(!evaluator.evaluate(&bindings).unwrap());
    }

    #[test]
    fn test_evaluate_single_constraint() {
        let mut prog = ConstraintProgram::new();

        let mut c1 = Constraint::new("first".to_string(), "msg".to_string(), OtherwiseAction::Reject);
        c1.add_requirement(Requirement::Check("always_true".to_string()));
        prog.add_constraint(c1);

        let mut c2 = Constraint::new("second".to_string(), "msg".to_string(), OtherwiseAction::Reject);
        c2.add_requirement(Requirement::Check("always_false".to_string()));
        prog.add_constraint(c2);

        let evaluator = Evaluator::new(prog);
        let bindings = HashMap::new();

        assert!(evaluator.evaluate_constraint("first", &bindings).unwrap());
        assert!(!evaluator.evaluate_constraint("second", &bindings).unwrap());
    }

    #[test]
    fn test_count_passing() {
        let mut prog = ConstraintProgram::new();

        for i in 0..3 {
            let mut c = Constraint::new(
                format!("c{}", i),
                "msg".to_string(),
                OtherwiseAction::Reject,
            );
            c.add_requirement(Requirement::Check("always_true".to_string()));
            prog.add_constraint(c);
        }

        let evaluator = Evaluator::new(prog);
        let bindings = HashMap::new();
        assert_eq!(evaluator.count_passing(&bindings), 3);
    }

    #[test]
    fn test_evaluator_report() {
        let mut prog = ConstraintProgram::new();
        let mut c = Constraint::new("test".to_string(), "msg".to_string(), OtherwiseAction::Reject);
        c.add_requirement(Requirement::Check("always_true".to_string()));
        prog.add_constraint(c);

        let evaluator = Evaluator::new(prog);
        let bindings = HashMap::new();
        let report = evaluator.report(&bindings);

        assert!(report.contains("Constraint Evaluation Report"));
        assert!(report.contains("test"));
        assert!(report.contains("PASS"));
    }
}