torg_core/
eval.rs

1//! Circuit evaluator for TØR-G graphs.
2
3use std::collections::HashMap;
4
5use crate::error::EvalError;
6use crate::graph::Graph;
7use crate::token::Source;
8
9/// Evaluate a TØR-G graph with given input values.
10///
11/// # Arguments
12///
13/// * `graph` - The graph to evaluate
14/// * `inputs` - Map from input IDs to their boolean values
15///
16/// # Returns
17///
18/// Map from output IDs to their computed boolean values.
19///
20/// # Errors
21///
22/// Returns an error if:
23/// - A required input is missing
24/// - An output references an undefined ID
25/// - Internal evaluation fails (should not happen with valid graphs)
26pub fn evaluate(
27    graph: &Graph,
28    inputs: &HashMap<u16, bool>,
29) -> Result<HashMap<u16, bool>, EvalError> {
30    // Verify all declared inputs have values
31    for &id in &graph.inputs {
32        if !inputs.contains_key(&id) {
33            return Err(EvalError::MissingInput(id));
34        }
35    }
36
37    // Values cache: stores computed values for all IDs
38    let mut values: HashMap<u16, bool> = HashMap::new();
39
40    // Load input values
41    for &id in &graph.inputs {
42        if let Some(&val) = inputs.get(&id) {
43            values.insert(id, val);
44        }
45    }
46
47    // Evaluate nodes in topological order (already sorted in graph)
48    for node in &graph.nodes {
49        let left = resolve_source(&node.left, &values)?;
50        let right = resolve_source(&node.right, &values)?;
51        let result = node.op.eval(left, right);
52        values.insert(node.id, result);
53    }
54
55    // Collect output values
56    let mut outputs = HashMap::new();
57    for &id in &graph.outputs {
58        let val = values.get(&id).ok_or(EvalError::MissingOutput(id))?;
59        outputs.insert(id, *val);
60    }
61
62    Ok(outputs)
63}
64
65/// Resolve a source operand to its boolean value.
66fn resolve_source(source: &Source, values: &HashMap<u16, bool>) -> Result<bool, EvalError> {
67    match source {
68        Source::True => Ok(true),
69        Source::False => Ok(false),
70        Source::Id(id) => values
71            .get(id)
72            .copied()
73            .ok_or(EvalError::UndefinedValue(*id)),
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::graph::Node;
81    use crate::token::BoolOp;
82
83    #[test]
84    fn test_simple_or() {
85        let graph = Graph {
86            inputs: vec![0, 1],
87            nodes: vec![Node::new(2, BoolOp::Or, Source::Id(0), Source::Id(1))],
88            outputs: vec![2],
89        };
90
91        // false OR false = false
92        let result = evaluate(&graph, &[(0, false), (1, false)].into()).unwrap();
93        assert!(!result[&2]);
94
95        // false OR true = true
96        let result = evaluate(&graph, &[(0, false), (1, true)].into()).unwrap();
97        assert!(result[&2]);
98
99        // true OR false = true
100        let result = evaluate(&graph, &[(0, true), (1, false)].into()).unwrap();
101        assert!(result[&2]);
102
103        // true OR true = true
104        let result = evaluate(&graph, &[(0, true), (1, true)].into()).unwrap();
105        assert!(result[&2]);
106    }
107
108    #[test]
109    fn test_constants() {
110        let graph = Graph {
111            inputs: vec![],
112            nodes: vec![Node::new(0, BoolOp::Or, Source::True, Source::False)],
113            outputs: vec![0],
114        };
115
116        let result = evaluate(&graph, &HashMap::new()).unwrap();
117        assert!(result[&0]); // true OR false = true
118    }
119
120    #[test]
121    fn test_chain_evaluation() {
122        // node(2) = input(0) XOR input(1)
123        // node(3) = node(2) OR True
124        let graph = Graph {
125            inputs: vec![0, 1],
126            nodes: vec![
127                Node::new(2, BoolOp::Xor, Source::Id(0), Source::Id(1)),
128                Node::new(3, BoolOp::Or, Source::Id(2), Source::True),
129            ],
130            outputs: vec![3],
131        };
132
133        // 0 XOR 0 = 0, 0 OR true = true
134        let result = evaluate(&graph, &[(0, false), (1, false)].into()).unwrap();
135        assert!(result[&3]);
136    }
137
138    #[test]
139    fn test_missing_input() {
140        let graph = Graph {
141            inputs: vec![0, 1],
142            nodes: vec![Node::new(2, BoolOp::Or, Source::Id(0), Source::Id(1))],
143            outputs: vec![2],
144        };
145
146        // Missing input 1
147        let result = evaluate(&graph, &[(0, false)].into());
148        assert_eq!(result, Err(EvalError::MissingInput(1)));
149    }
150
151    #[test]
152    fn test_nor_operation() {
153        let graph = Graph {
154            inputs: vec![0, 1],
155            nodes: vec![Node::new(2, BoolOp::Nor, Source::Id(0), Source::Id(1))],
156            outputs: vec![2],
157        };
158
159        // false NOR false = true
160        let result = evaluate(&graph, &[(0, false), (1, false)].into()).unwrap();
161        assert!(result[&2]);
162
163        // Any true = false
164        let result = evaluate(&graph, &[(0, true), (1, false)].into()).unwrap();
165        assert!(!result[&2]);
166    }
167}