Skip to main content

lemma/evaluation/
expression.rs

1//! Iterative expression evaluation
2//!
3//! Evaluates expressions without recursion using a stack-based approach.
4//! All runtime errors (division by zero, etc.) result in Veto instead of errors.
5
6use super::conversion_explanation::build_conversion_steps;
7use super::explanation::{ExplanationNode, ValueSource};
8use super::operations::{ComputationKind, OperationKind, OperationResult, VetoType};
9use crate::computation::{
10    arithmetic_operation, comparison_operation, convert_unit, UnitResolutionContext,
11};
12use crate::planning::semantics::{
13    negated_comparison, DataPath, Expression, ExpressionKind, LiteralValue,
14    MathematicalComputation, SemanticConversionTarget, Source, ValueKind,
15};
16use crate::planning::ExecutableRule;
17use std::collections::{HashMap, HashSet};
18use std::sync::Arc;
19
20/// Get an explanation node for an expression that was already evaluated.
21/// Panics if the explanation node is missing — this indicates a bug in the evaluator,
22/// since we always set an explanation node immediately after evaluating an expression.
23fn get_explanation_node_required(
24    context: &crate::evaluation::EvaluationContext,
25    expr: &Expression,
26    operand_name: &str,
27) -> ExplanationNode {
28    let loc = expr
29        .source_location
30        .as_ref()
31        .expect("BUG: expression missing source in evaluation");
32    context
33        .get_explanation_node(expr)
34        .cloned()
35        .unwrap_or_else(|| {
36            unreachable!(
37                "BUG: {} was evaluated but has no explanation node ({}:{}:{})",
38                operand_name, loc.source_type, loc.span.line, loc.span.col
39            )
40        })
41}
42
43fn expr_ptr(expr: &Expression) -> usize {
44    expr as *const Expression as usize
45}
46
47/// Convert a value to `target`, record operations, and build a computation explanation node.
48pub(crate) fn finish_unit_conversion(
49    value: &LiteralValue,
50    target: &SemanticConversionTarget,
51    operand_explanation: ExplanationNode,
52    data_ref: Option<&DataPath>,
53    source_location: Option<Source>,
54    context: &mut crate::evaluation::EvaluationContext,
55) -> (OperationResult, ExplanationNode) {
56    let conversion_result = convert_unit(
57        value,
58        target,
59        UnitResolutionContext::WithIndex(&context.unit_index),
60    );
61
62    match &conversion_result {
63        OperationResult::Value(converted) => {
64            let converted = converted.as_ref();
65            let kind = ComputationKind::UnitConversion {
66                target: target.clone(),
67            };
68            let conversion_steps = build_conversion_steps(
69                value,
70                target,
71                converted,
72                data_ref,
73                UnitResolutionContext::WithIndex(&context.unit_index),
74            );
75            assert!(
76                !conversion_steps.is_empty(),
77                "BUG: unit conversion succeeded but explanation steps are empty"
78            );
79            context.push_operation(OperationKind::Computation {
80                kind: kind.clone(),
81                inputs: vec![value.clone()],
82                result: converted.clone(),
83            });
84            let explanation_node = ExplanationNode::Computation {
85                kind,
86                conversion_steps,
87                original_expression: String::new(),
88                expression: String::new(),
89                result: converted.clone(),
90                source_location: source_location.clone(),
91                operands: vec![operand_explanation],
92            };
93            (conversion_result, explanation_node)
94        }
95        OperationResult::Veto(_) => (conversion_result, operand_explanation),
96    }
97}
98
99fn data_ref_for_conversion_operand<'a>(
100    value_expr: &'a Expression,
101    operand_explanation: &'a ExplanationNode,
102) -> Option<&'a DataPath> {
103    if let ExpressionKind::DataPath(data_path) = &value_expr.kind {
104        return Some(data_path);
105    }
106    match operand_explanation {
107        ExplanationNode::Value {
108            source: ValueSource::Data { data_ref },
109            ..
110        } => Some(data_ref),
111        _ => None,
112    }
113}
114
115/// Get the result of an operand expression that was already evaluated.
116fn get_operand_result(
117    results: &HashMap<usize, OperationResult>,
118    expr: &Expression,
119    operand_name: &str,
120) -> OperationResult {
121    let loc = expr
122        .source_location
123        .as_ref()
124        .expect("BUG: expression missing source in evaluation");
125    results.get(&expr_ptr(expr)).cloned().unwrap_or_else(|| {
126        unreachable!(
127            "BUG: {} operand was marked ready but result is missing ({}:{}:{})",
128            operand_name, loc.source_type, loc.span.line, loc.span.col
129        )
130    })
131}
132
133/// Extract the value from an OperationResult that is known to not be Veto.
134/// Called only after an explicit Veto check has already returned early.
135/// Panics if the result has no value — this is unreachable after the Veto guard.
136fn unwrap_value_after_veto_check<'a>(
137    result: &'a OperationResult,
138    operand_name: &str,
139    source_location: &Option<crate::planning::semantics::Source>,
140) -> &'a LiteralValue {
141    result.value().unwrap_or_else(|| {
142        let loc = source_location
143            .as_ref()
144            .expect("BUG: expression missing source in evaluation");
145        unreachable!(
146            "BUG: {} passed Veto check but has no value ({}:{}:{})",
147            operand_name, loc.source_type, loc.span.line, loc.span.col
148        )
149    })
150}
151
152/// Propagate veto explanation from operand to current expression
153fn propagate_veto_explanation(
154    context: &mut crate::evaluation::EvaluationContext,
155    current: &Expression,
156    vetoed_operand: &Expression,
157    veto_result: OperationResult,
158    operand_name: &str,
159) -> OperationResult {
160    let node = get_explanation_node_required(context, vetoed_operand, operand_name);
161    context.set_explanation_node(current, node);
162    veto_result
163}
164
165/// Evaluate branch: original expression for explanation trace, normalized for authoritative result.
166fn evaluate_branch_dual(
167    branch: &crate::planning::execution_plan::Branch,
168    context: &mut crate::evaluation::EvaluationContext,
169    explanation_operand_name: &str,
170) -> (OperationResult, ExplanationNode) {
171    let authoritative = evaluate_expression(&branch.normalized_result, context);
172    let _ = evaluate_expression(&branch.result, context);
173    let explanation_node =
174        get_explanation_node_required(context, &branch.result, explanation_operand_name);
175    (authoritative, explanation_node)
176}
177
178/// Evaluate a rule to produce its final result and explanation.
179/// After planning, evaluation is guaranteed to complete — this function never returns
180/// a Error. It produces an OperationResult (Value or Veto) and an Explanation tree.
181pub(crate) fn evaluate_rule(
182    exec_rule: &ExecutableRule,
183    context: &mut crate::evaluation::EvaluationContext,
184) -> (OperationResult, crate::evaluation::explanation::Explanation) {
185    use crate::evaluation::explanation::{Branch, NonMatchedBranch};
186
187    // If rule has no unless clauses, just evaluate the default expression
188    if exec_rule.branches.len() == 1 {
189        return evaluate_rule_without_unless(exec_rule, context);
190    }
191
192    // Rule has unless clauses - collect all branch evaluations for Branches explanation node
193    let mut non_matched_branches: Vec<NonMatchedBranch> = Vec::new();
194
195    // Evaluate branches in reverse order (last matching wins)
196    for branch_index in (1..exec_rule.branches.len()).rev() {
197        let branch = &exec_rule.branches[branch_index];
198        if let Some(condition) = branch
199            .normalized_condition
200            .as_ref()
201            .or(branch.condition.as_ref())
202        {
203            let condition_result = evaluate_expression(condition, context);
204            let condition_explanation =
205                get_explanation_node_required(context, condition, "condition");
206
207            let matched = match condition_result {
208                OperationResult::Veto(ref reason) => {
209                    // Condition vetoed - this becomes the result
210                    let unless_clause_index = branch_index - 1;
211                    context.push_operation(OperationKind::RuleBranchEvaluated {
212                        index: Some(unless_clause_index),
213                        matched: true,
214                        result_value: Some(OperationResult::Veto(reason.clone())),
215                    });
216
217                    // Build Branches node with this as the matched branch
218                    let matched_branch = Branch {
219                        condition: Some(Box::new(condition_explanation)),
220                        result: Box::new(ExplanationNode::Veto {
221                            message: Some(reason.to_string()),
222                            source_location: branch.result.source_location.clone(),
223                        }),
224                        clause_index: Some(unless_clause_index),
225                        source_location: Some(branch.source.clone()),
226                    };
227
228                    let branches_node = ExplanationNode::Branches {
229                        matched: Box::new(matched_branch),
230                        non_matched: non_matched_branches,
231                        source_location: Some(exec_rule.source.clone()),
232                    };
233
234                    let explanation = crate::evaluation::explanation::Explanation {
235                        rule_path: exec_rule.path.clone(),
236                        source: Some(exec_rule.source.clone()),
237                        result: OperationResult::Veto(reason.clone()),
238                        tree: Arc::new(branches_node),
239                    };
240                    return (OperationResult::Veto(reason.clone()), explanation);
241                }
242                OperationResult::Value(lit) => match &lit.value {
243                    ValueKind::Boolean(b) => *b,
244                    _ => {
245                        let veto = OperationResult::Veto(VetoType::computation(
246                            "Unless condition must evaluate to boolean",
247                        ));
248                        let explanation = crate::evaluation::explanation::Explanation {
249                            rule_path: exec_rule.path.clone(),
250                            source: Some(exec_rule.source.clone()),
251                            result: veto.clone(),
252                            tree: Arc::new(ExplanationNode::Veto {
253                                message: Some(
254                                    "Unless condition must evaluate to boolean".to_string(),
255                                ),
256                                source_location: Some(exec_rule.source.clone()),
257                            }),
258                        };
259                        return (veto, explanation);
260                    }
261                },
262            };
263
264            let unless_clause_index = branch_index - 1;
265
266            if matched {
267                let (result, result_explanation) = evaluate_branch_dual(branch, context, "result");
268
269                context.push_operation(OperationKind::RuleBranchEvaluated {
270                    index: Some(unless_clause_index),
271                    matched: true,
272                    result_value: Some(result.clone()),
273                });
274
275                // Build Branches node with this as the matched branch
276                let matched_branch = Branch {
277                    condition: Some(Box::new(condition_explanation)),
278                    result: Box::new(result_explanation),
279                    clause_index: Some(unless_clause_index),
280                    source_location: Some(branch.source.clone()),
281                };
282
283                let branches_node = ExplanationNode::Branches {
284                    matched: Box::new(matched_branch),
285                    non_matched: non_matched_branches,
286                    source_location: Some(exec_rule.source.clone()),
287                };
288
289                let explanation = crate::evaluation::explanation::Explanation {
290                    rule_path: exec_rule.path.clone(),
291                    source: Some(exec_rule.source.clone()),
292                    result: result.clone(),
293                    tree: Arc::new(branches_node),
294                };
295                return (result, explanation);
296            }
297            // Branch didn't match - record it as non-matched.
298            context.push_operation(OperationKind::RuleBranchEvaluated {
299                index: Some(unless_clause_index),
300                matched: false,
301                result_value: None,
302            });
303
304            non_matched_branches.push(NonMatchedBranch {
305                condition: Box::new(condition_explanation),
306                result: None,
307                clause_index: Some(unless_clause_index),
308                source_location: Some(branch.source.clone()),
309            });
310        }
311    }
312
313    let default_branch = &exec_rule.branches[0];
314    let (default_result, default_result_explanation) =
315        evaluate_branch_dual(default_branch, context, "default result");
316
317    context.push_operation(OperationKind::RuleBranchEvaluated {
318        index: None,
319        matched: true,
320        result_value: Some(default_result.clone()),
321    });
322
323    // Default branch has no condition
324    let matched_branch = Branch {
325        condition: None,
326        result: Box::new(default_result_explanation),
327        clause_index: None,
328        source_location: Some(default_branch.source.clone()),
329    };
330
331    let branches_node = ExplanationNode::Branches {
332        matched: Box::new(matched_branch),
333        non_matched: non_matched_branches,
334        source_location: Some(exec_rule.source.clone()),
335    };
336
337    let explanation = crate::evaluation::explanation::Explanation {
338        rule_path: exec_rule.path.clone(),
339        source: Some(exec_rule.source.clone()),
340        result: default_result.clone(),
341        tree: Arc::new(branches_node),
342    };
343
344    (default_result, explanation)
345}
346
347/// Evaluate a rule that has no unless clauses (simple case)
348fn evaluate_rule_without_unless(
349    exec_rule: &ExecutableRule,
350    context: &mut crate::evaluation::EvaluationContext,
351) -> (OperationResult, crate::evaluation::explanation::Explanation) {
352    let default_branch = &exec_rule.branches[0];
353    let (default_result, root_explanation_node) =
354        evaluate_branch_dual(default_branch, context, "default result");
355
356    context.push_operation(OperationKind::RuleBranchEvaluated {
357        index: None,
358        matched: true,
359        result_value: Some(default_result.clone()),
360    });
361
362    let explanation = crate::evaluation::explanation::Explanation {
363        rule_path: exec_rule.path.clone(),
364        source: Some(exec_rule.source.clone()),
365        result: default_result.clone(),
366        tree: Arc::new(root_explanation_node),
367    };
368
369    (default_result, explanation)
370}
371
372/// Evaluate an expression iteratively without recursion.
373/// Uses a work list approach: collect all expressions first, then evaluate in dependency order.
374/// After planning, expression evaluation is guaranteed to complete — this function never
375/// returns a Error. It produces an OperationResult (Value or Veto).
376/// Iterative post-order traversal: collects expression nodes children-before-parents.
377/// Uses pointer identity for dedup (no Hash/Eq on Expression needed).
378fn collect_postorder(root: &Expression) -> Vec<&Expression> {
379    enum Visit<'a> {
380        Enter(&'a Expression),
381        Exit(&'a Expression),
382    }
383
384    let mut stack: Vec<Visit<'_>> = vec![Visit::Enter(root)];
385    let mut seen: HashSet<usize> = HashSet::new();
386    let mut nodes: Vec<&Expression> = Vec::new();
387
388    while let Some(visit) = stack.pop() {
389        match visit {
390            Visit::Enter(e) => {
391                if !seen.insert(expr_ptr(e)) {
392                    continue;
393                }
394                stack.push(Visit::Exit(e));
395                match &e.kind {
396                    ExpressionKind::Arithmetic(left, _, right)
397                    | ExpressionKind::Comparison(left, _, right)
398                    | ExpressionKind::LogicalAnd(left, right)
399                    | ExpressionKind::LogicalOr(left, right)
400                    | ExpressionKind::RangeLiteral(left, right)
401                    | ExpressionKind::RangeContainment(left, right) => {
402                        stack.push(Visit::Enter(right));
403                        stack.push(Visit::Enter(left));
404                    }
405                    ExpressionKind::LogicalNegation(operand, _)
406                    | ExpressionKind::UnitConversion(operand, _)
407                    | ExpressionKind::MathematicalComputation(_, operand)
408                    | ExpressionKind::ResultIsVeto(operand)
409                    | ExpressionKind::DateCalendar(_, _, operand)
410                    | ExpressionKind::PastFutureRange(_, operand) => {
411                        stack.push(Visit::Enter(operand));
412                    }
413                    ExpressionKind::DateRelative(_, date_expr) => {
414                        stack.push(Visit::Enter(date_expr));
415                    }
416                    _ => {}
417                }
418            }
419            Visit::Exit(e) => {
420                nodes.push(e);
421            }
422        }
423    }
424
425    nodes
426}
427
428fn evaluate_expression(
429    expr: &Expression,
430    context: &mut crate::evaluation::EvaluationContext,
431) -> OperationResult {
432    let nodes = collect_postorder(expr);
433    let mut results: HashMap<usize, OperationResult> = HashMap::with_capacity(nodes.len());
434
435    for node in &nodes {
436        let result = evaluate_single_expression(node, &results, context);
437        results.insert(expr_ptr(node), result);
438    }
439
440    results.remove(&expr_ptr(expr)).unwrap_or_else(|| {
441        let loc = expr
442            .source_location
443            .as_ref()
444            .expect("BUG: expression missing source in evaluation");
445        unreachable!(
446            "BUG: expression was processed but has no result ({}:{}:{})",
447            loc.source_type, loc.span.start, loc.span.end
448        )
449    })
450}
451
452/// Evaluate a single expression given its dependencies are already evaluated.
453/// After planning, this function is guaranteed to complete — it produces an OperationResult
454/// (Value or Veto) without ever returning a Error.
455fn evaluate_single_expression(
456    current: &Expression,
457    results: &HashMap<usize, OperationResult>,
458    context: &mut crate::evaluation::EvaluationContext,
459) -> OperationResult {
460    match &current.kind {
461        ExpressionKind::Literal(lit) => {
462            let value = lit.as_ref().clone();
463            let explanation_node = ExplanationNode::Value {
464                value: value.clone(),
465                source: ValueSource::Literal,
466                source_location: current.source_location.clone(),
467            };
468            context.set_explanation_node(current, explanation_node);
469            OperationResult::Value(Box::new(value))
470        }
471
472        ExpressionKind::DataPath(data_path) => {
473            // Data lookup: a data can legitimately be missing (schema-only declaration without a
474            // provided value at runtime). Returning None → Veto("Missing data: ...") is
475            // correct domain behavior.
476            //
477            // Rule-target references are not pre-populated in `data_values`; on
478            // first read we lazily resolve them from the (already-evaluated,
479            // by topological-sort guarantee) target rule's result. A target
480            // rule veto propagates with the exact same reason — copying the
481            // veto type without rewrapping in `MissingData`.
482            let data_path_clone = data_path.clone();
483            let mut value = context.get_data(data_path).cloned();
484            if value.is_none() {
485                if let Some(resolved) = context.lazy_rule_reference_resolve(data_path) {
486                    match resolved {
487                        Ok(v) => value = Some(v),
488                        Err(veto) => {
489                            let explanation_node = ExplanationNode::Veto {
490                                message: Some(veto.to_string()),
491                                source_location: current.source_location.clone(),
492                            };
493                            context.set_explanation_node(current, explanation_node);
494                            return OperationResult::Veto(veto);
495                        }
496                    }
497                } else if let Some(veto) = context.get_reference_veto(data_path) {
498                    let veto = veto.clone();
499                    let explanation_node = ExplanationNode::Veto {
500                        message: Some(veto.to_string()),
501                        source_location: current.source_location.clone(),
502                    };
503                    context.set_explanation_node(current, explanation_node);
504                    return OperationResult::Veto(veto);
505                }
506            }
507            match value {
508                Some(v) => {
509                    context.push_operation(OperationKind::DataUsed {
510                        data_ref: data_path_clone.clone(),
511                        value: v.clone(),
512                    });
513                    let explanation_node = ExplanationNode::Value {
514                        value: v.clone(),
515                        source: ValueSource::Data {
516                            data_ref: data_path_clone,
517                        },
518                        source_location: current.source_location.clone(),
519                    };
520                    context.set_explanation_node(current, explanation_node);
521                    OperationResult::Value(Box::new(v))
522                }
523                None => {
524                    let reason = VetoType::MissingData {
525                        data: data_path_clone.clone(),
526                    };
527                    let explanation_node = ExplanationNode::Veto {
528                        message: Some(reason.to_string()),
529                        source_location: current.source_location.clone(),
530                    };
531                    context.set_explanation_node(current, explanation_node);
532                    OperationResult::Veto(reason)
533                }
534            }
535        }
536
537        ExpressionKind::RulePath(rule_path) => {
538            // Rule lookup: rules are evaluated in topological order. If a referenced rule's
539            // result is not in the map, planning guaranteed no cycles and topological sort
540            // ensured the dependency was evaluated first. Missing result is a bug.
541            let rule_path_clone = rule_path.clone();
542            let loc = current
543                .source_location
544                .as_ref()
545                .expect("BUG: expression missing source in evaluation");
546            let r = context.rule_results.get(rule_path).cloned().unwrap_or_else(|| {
547                unreachable!(
548                    "BUG: Rule '{}' not found in results during topological-order evaluation ({}:{}:{})",
549                    rule_path.rule, loc.source_type, loc.span.line, loc.span.col
550                )
551            });
552
553            context.push_operation(OperationKind::RuleUsed {
554                rule_path: rule_path_clone.clone(),
555                result: r.clone(),
556            });
557
558            // Share expansion via Arc instead of cloning (avoids O(n²) for deep chains)
559            let expansion = match context.get_rule_explanation(rule_path) {
560                Some(existing_explanation) => Arc::clone(&existing_explanation.tree),
561                None => Arc::new(ExplanationNode::Value {
562                    value: match &r {
563                        OperationResult::Value(v) => v.as_ref().clone(),
564                        OperationResult::Veto(_) => LiteralValue::from_bool(false),
565                    },
566                    source: ValueSource::Computed,
567                    source_location: current.source_location.clone(),
568                }),
569            };
570
571            let explanation_node = ExplanationNode::RuleReference {
572                rule_path: rule_path_clone,
573                result: r.clone(),
574                source_location: current.source_location.clone(),
575                expansion,
576            };
577            context.set_explanation_node(current, explanation_node);
578            r
579        }
580
581        ExpressionKind::Arithmetic(left, op, right) => {
582            let left_result = get_operand_result(results, left, "left");
583            let right_result = get_operand_result(results, right, "right");
584
585            if let OperationResult::Veto(_) = left_result {
586                return propagate_veto_explanation(
587                    context,
588                    current,
589                    left,
590                    left_result,
591                    "left operand",
592                );
593            }
594            if let OperationResult::Veto(_) = right_result {
595                return propagate_veto_explanation(
596                    context,
597                    current,
598                    right,
599                    right_result,
600                    "right operand",
601                );
602            }
603
604            let left_val = unwrap_value_after_veto_check(
605                &left_result,
606                "left operand",
607                &current.source_location,
608            );
609            let right_val = unwrap_value_after_veto_check(
610                &right_result,
611                "right operand",
612                &current.source_location,
613            );
614
615            let result = arithmetic_operation(left_val, op, right_val);
616
617            let left_explanation = get_explanation_node_required(context, left, "left operand");
618            let right_explanation = get_explanation_node_required(context, right, "right operand");
619
620            if let OperationResult::Value(ref val) = result {
621                let original_expr = format!("{} {} {}", left_val, op, right_val);
622                let substituted_expr = format!("{} {} {}", left_val, op, right_val);
623                context.push_operation(OperationKind::Computation {
624                    kind: ComputationKind::Arithmetic(op.clone()),
625                    inputs: vec![left_val.clone(), right_val.clone()],
626                    result: val.as_ref().clone(),
627                });
628                let explanation_node = ExplanationNode::Computation {
629                    kind: ComputationKind::Arithmetic(op.clone()),
630                    conversion_steps: vec![],
631                    original_expression: original_expr,
632                    expression: substituted_expr,
633                    result: val.as_ref().clone(),
634                    source_location: current.source_location.clone(),
635                    operands: vec![left_explanation, right_explanation],
636                };
637                context.set_explanation_node(current, explanation_node);
638            } else if let OperationResult::Veto(_) = result {
639                context.set_explanation_node(current, left_explanation);
640            }
641            result
642        }
643
644        ExpressionKind::Comparison(left, op, right) => {
645            let left_result = get_operand_result(results, left, "left");
646            let right_result = get_operand_result(results, right, "right");
647
648            if let OperationResult::Veto(_) = left_result {
649                return propagate_veto_explanation(
650                    context,
651                    current,
652                    left,
653                    left_result,
654                    "left operand",
655                );
656            }
657            if let OperationResult::Veto(_) = right_result {
658                return propagate_veto_explanation(
659                    context,
660                    current,
661                    right,
662                    right_result,
663                    "right operand",
664                );
665            }
666
667            let left_val = unwrap_value_after_veto_check(
668                &left_result,
669                "left operand",
670                &current.source_location,
671            );
672            let right_val = unwrap_value_after_veto_check(
673                &right_result,
674                "right operand",
675                &current.source_location,
676            );
677
678            let result = comparison_operation(
679                left_val,
680                op,
681                right_val,
682                UnitResolutionContext::WithIndex(&context.unit_index),
683            );
684
685            let left_explanation = get_explanation_node_required(context, left, "left operand");
686            let right_explanation = get_explanation_node_required(context, right, "right operand");
687
688            if let OperationResult::Value(ref val) = result {
689                let is_false = matches!(val.as_ref().value, ValueKind::Boolean(false));
690                let (display_op, original_expr, substituted_expr, display_result) = if is_false {
691                    let negated_op = negated_comparison(op.clone());
692                    let orig = format!("{} {} {}", left_val, negated_op, right_val);
693                    let sub = format!("{} {} {}", left_val, negated_op, right_val);
694                    (negated_op, orig, sub, LiteralValue::from_bool(true))
695                } else {
696                    let original_expr = format!("{} {} {}", left_val, op, right_val);
697                    let substituted_expr = format!("{} {} {}", left_val, op, right_val);
698                    (
699                        op.clone(),
700                        original_expr,
701                        substituted_expr,
702                        val.as_ref().clone(),
703                    )
704                };
705                context.push_operation(OperationKind::Computation {
706                    kind: ComputationKind::Comparison(op.clone()),
707                    inputs: vec![left_val.clone(), right_val.clone()],
708                    result: val.as_ref().clone(),
709                });
710                let explanation_node = ExplanationNode::Computation {
711                    kind: ComputationKind::Comparison(display_op),
712                    conversion_steps: vec![],
713                    original_expression: original_expr,
714                    expression: substituted_expr,
715                    result: display_result,
716                    source_location: current.source_location.clone(),
717                    operands: vec![left_explanation, right_explanation],
718                };
719                context.set_explanation_node(current, explanation_node);
720            } else if let OperationResult::Veto(_) = result {
721                context.set_explanation_node(current, left_explanation);
722            }
723            result
724        }
725
726        ExpressionKind::LogicalAnd(left, right) => {
727            let left_result = get_operand_result(results, left, "left");
728            if let OperationResult::Veto(_) = left_result {
729                return propagate_veto_explanation(
730                    context,
731                    current,
732                    left,
733                    left_result,
734                    "left operand",
735                );
736            }
737
738            let left_val = unwrap_value_after_veto_check(
739                &left_result,
740                "left operand",
741                &current.source_location,
742            );
743            let left_bool = match &left_val.value {
744                ValueKind::Boolean(b) => b,
745                _ => unreachable!(
746                    "BUG: logical AND with non-boolean operand; planning should have rejected this"
747                ),
748            };
749
750            if !*left_bool {
751                let left_explanation = get_explanation_node_required(context, left, "left operand");
752                context.set_explanation_node(current, left_explanation);
753                OperationResult::Value(Box::new(LiteralValue::from_bool(false)))
754            } else {
755                let right_result = get_operand_result(results, right, "right");
756                let right_explanation =
757                    get_explanation_node_required(context, right, "right operand");
758                context.set_explanation_node(current, right_explanation);
759                right_result
760            }
761        }
762
763        ExpressionKind::LogicalOr(left, right) => {
764            let left_result = get_operand_result(results, left, "left");
765            if matches!(&left_result, OperationResult::Veto(_)) {
766                let right_result = get_operand_result(results, right, "right");
767                let right_explanation =
768                    get_explanation_node_required(context, right, "right operand");
769                context.set_explanation_node(current, right_explanation);
770                return right_result;
771            }
772
773            let left_val = unwrap_value_after_veto_check(
774                &left_result,
775                "left operand",
776                &current.source_location,
777            );
778            if matches!(&left_val.value, ValueKind::Boolean(false)) {
779                let right_result = get_operand_result(results, right, "right");
780                let right_explanation =
781                    get_explanation_node_required(context, right, "right operand");
782                context.set_explanation_node(current, right_explanation);
783                return right_result;
784            }
785
786            let left_explanation = get_explanation_node_required(context, left, "left operand");
787            context.set_explanation_node(current, left_explanation);
788            left_result
789        }
790
791        ExpressionKind::LogicalNegation(operand, _) => {
792            let result = get_operand_result(results, operand, "operand");
793            if let OperationResult::Veto(_) = result {
794                return propagate_veto_explanation(context, current, operand, result, "operand");
795            }
796
797            let value = unwrap_value_after_veto_check(&result, "operand", &current.source_location);
798            let operand_explanation = get_explanation_node_required(context, operand, "operand");
799            match &value.value {
800                ValueKind::Boolean(b) => {
801                    let result_bool = !*b;
802                    context.set_explanation_node(current, operand_explanation);
803                    OperationResult::Value(Box::new(LiteralValue::from_bool(result_bool)))
804                }
805                _ => unreachable!(
806                    "BUG: logical NOT with non-boolean operand; planning should have rejected this"
807                ),
808            }
809        }
810
811        ExpressionKind::UnitConversion(value_expr, target) => {
812            let result = get_operand_result(results, value_expr, "operand");
813            if let OperationResult::Veto(_) = result {
814                return propagate_veto_explanation(context, current, value_expr, result, "operand");
815            }
816
817            let value = unwrap_value_after_veto_check(&result, "operand", &current.source_location);
818            let operand_explanation = get_explanation_node_required(context, value_expr, "operand");
819            let data_ref = data_ref_for_conversion_operand(value_expr, &operand_explanation);
820            let (conversion_result, explanation_node) = finish_unit_conversion(
821                value,
822                target,
823                operand_explanation.clone(),
824                data_ref,
825                current.source_location.clone(),
826                context,
827            );
828            context.set_explanation_node(current, explanation_node);
829            conversion_result
830        }
831
832        ExpressionKind::MathematicalComputation(op, operand) => {
833            let result = get_operand_result(results, operand, "operand");
834            if let OperationResult::Veto(_) = result {
835                return propagate_veto_explanation(context, current, operand, result, "operand");
836            }
837
838            let value = unwrap_value_after_veto_check(&result, "operand", &current.source_location);
839            let operand_explanation = get_explanation_node_required(context, operand, "operand");
840            let math_result = evaluate_mathematical_operator(op, value, context);
841            context.set_explanation_node(current, operand_explanation);
842            math_result
843        }
844
845        ExpressionKind::ResultIsVeto(operand) => {
846            let operand_result = get_operand_result(results, operand, "operand");
847            let is_vetoed = operand_result.vetoed();
848            let result = OperationResult::Value(Box::new(LiteralValue::from_bool(is_vetoed)));
849
850            let operand_explanation = get_explanation_node_required(context, operand, "operand");
851            let operand_display = match &operand_result {
852                OperationResult::Value(value) => value.to_string(),
853                OperationResult::Veto(veto) => veto.to_string(),
854            };
855            let (original_expression, expression, display_result) = if is_vetoed {
856                (
857                    format!("{operand_display} is veto"),
858                    format!("{operand_display} is veto"),
859                    LiteralValue::from_bool(true),
860                )
861            } else {
862                (
863                    format!("{operand_display} is veto"),
864                    format!("{operand_display} is not veto"),
865                    LiteralValue::from_bool(true),
866                )
867            };
868            let computation_result = result
869                .value()
870                .expect("BUG: ResultIsVeto always produces Value")
871                .clone();
872            context.push_operation(OperationKind::Computation {
873                kind: ComputationKind::ResultIsVeto,
874                inputs: vec![],
875                result: computation_result,
876            });
877            let explanation_node = ExplanationNode::Computation {
878                kind: ComputationKind::ResultIsVeto,
879                conversion_steps: vec![],
880                original_expression,
881                expression,
882                result: display_result,
883                source_location: current.source_location.clone(),
884                operands: vec![operand_explanation],
885            };
886            context.set_explanation_node(current, explanation_node);
887            result
888        }
889
890        ExpressionKind::Veto(veto_expr) => {
891            let explanation_node = ExplanationNode::Veto {
892                message: veto_expr.message.clone(),
893                source_location: current.source_location.clone(),
894            };
895            context.set_explanation_node(current, explanation_node);
896            OperationResult::Veto(VetoType::UserDefined {
897                message: veto_expr.message.clone(),
898            })
899        }
900
901        ExpressionKind::Now => {
902            let value = context.now().clone();
903            let explanation_node = ExplanationNode::Value {
904                value: value.clone(),
905                source: ValueSource::Computed,
906                source_location: current.source_location.clone(),
907            };
908            context.set_explanation_node(current, explanation_node);
909            OperationResult::Value(Box::new(value))
910        }
911
912        ExpressionKind::DateRelative(kind, date_expr) => {
913            let date_result = get_operand_result(results, date_expr, "date operand");
914            if let OperationResult::Veto(_) = date_result {
915                return propagate_veto_explanation(
916                    context,
917                    current,
918                    date_expr,
919                    date_result,
920                    "date operand",
921                );
922            }
923
924            let date_val = unwrap_value_after_veto_check(
925                &date_result,
926                "date operand",
927                &current.source_location,
928            );
929
930            let date_semantic = match &date_val.value {
931                ValueKind::Date(dt) => dt,
932                _ => unreachable!(
933                    "BUG: date sugar with non-date operand; planning should have rejected this"
934                ),
935            };
936
937            let now_val = context.now();
938            let now_semantic = match &now_val.value {
939                ValueKind::Date(dt) => dt,
940                _ => unreachable!("BUG: context.now() must be a Date value"),
941            };
942
943            let result = crate::computation::datetime::compute_date_relative(
944                kind,
945                date_semantic,
946                now_semantic,
947            );
948
949            let date_explanation =
950                get_explanation_node_required(context, date_expr, "date operand");
951            context.set_explanation_node(current, date_explanation);
952            result
953        }
954
955        ExpressionKind::DateCalendar(kind, unit, date_expr) => {
956            let date_result = get_operand_result(results, date_expr, "date operand");
957            if let OperationResult::Veto(_) = date_result {
958                return propagate_veto_explanation(
959                    context,
960                    current,
961                    date_expr,
962                    date_result,
963                    "date operand",
964                );
965            }
966
967            let date_val = unwrap_value_after_veto_check(
968                &date_result,
969                "date operand",
970                &current.source_location,
971            );
972
973            let date_semantic = match &date_val.value {
974                ValueKind::Date(dt) => dt,
975                _ => unreachable!(
976                    "BUG: calendar sugar with non-date operand; planning should have rejected this"
977                ),
978            };
979
980            let now_val = context.now();
981            let now_semantic = match &now_val.value {
982                ValueKind::Date(dt) => dt,
983                _ => unreachable!("BUG: context.now() must be a Date value"),
984            };
985
986            let result = crate::computation::datetime::compute_date_calendar(
987                kind,
988                unit,
989                date_semantic,
990                now_semantic,
991            );
992
993            let date_explanation =
994                get_explanation_node_required(context, date_expr, "date operand");
995            context.set_explanation_node(current, date_explanation);
996            result
997        }
998
999        ExpressionKind::RangeLiteral(left, right) => {
1000            let left_result = get_operand_result(results, left, "left endpoint");
1001            let right_result = get_operand_result(results, right, "right endpoint");
1002
1003            if let OperationResult::Veto(_) = left_result {
1004                return propagate_veto_explanation(
1005                    context,
1006                    current,
1007                    left,
1008                    left_result,
1009                    "left endpoint",
1010                );
1011            }
1012            if let OperationResult::Veto(_) = right_result {
1013                return propagate_veto_explanation(
1014                    context,
1015                    current,
1016                    right,
1017                    right_result,
1018                    "right endpoint",
1019                );
1020            }
1021
1022            let left_value = unwrap_value_after_veto_check(
1023                &left_result,
1024                "left endpoint",
1025                &current.source_location,
1026            );
1027            let right_value = unwrap_value_after_veto_check(
1028                &right_result,
1029                "right endpoint",
1030                &current.source_location,
1031            );
1032
1033            let range_value = LiteralValue::range(left_value.clone(), right_value.clone());
1034            let explanation_node = ExplanationNode::Value {
1035                value: range_value.clone(),
1036                source: ValueSource::Computed,
1037                source_location: current.source_location.clone(),
1038            };
1039            context.set_explanation_node(current, explanation_node);
1040            OperationResult::Value(Box::new(range_value))
1041        }
1042
1043        ExpressionKind::PastFutureRange(kind, offset_expr) => {
1044            let offset_result = get_operand_result(results, offset_expr, "offset operand");
1045            if let OperationResult::Veto(_) = offset_result {
1046                return propagate_veto_explanation(
1047                    context,
1048                    current,
1049                    offset_expr,
1050                    offset_result,
1051                    "offset operand",
1052                );
1053            }
1054
1055            let offset_value = unwrap_value_after_veto_check(
1056                &offset_result,
1057                "offset operand",
1058                &current.source_location,
1059            );
1060
1061            let now_value = context.now();
1062            let now_semantic = match &now_value.value {
1063                ValueKind::Date(date_time) => date_time,
1064                _ => unreachable!("BUG: context.now() must be a Date value"),
1065            };
1066
1067            let result = crate::computation::datetime::evaluate_past_future_range(
1068                kind,
1069                offset_value,
1070                now_semantic,
1071            );
1072
1073            match &result {
1074                OperationResult::Value(range_value) => {
1075                    let explanation_node = ExplanationNode::Value {
1076                        value: range_value.as_ref().clone(),
1077                        source: ValueSource::Computed,
1078                        source_location: current.source_location.clone(),
1079                    };
1080                    context.set_explanation_node(current, explanation_node);
1081                }
1082                OperationResult::Veto(_) => {
1083                    let offset_explanation =
1084                        get_explanation_node_required(context, offset_expr, "offset operand");
1085                    context.set_explanation_node(current, offset_explanation);
1086                }
1087            }
1088            result
1089        }
1090
1091        ExpressionKind::RangeContainment(value_expr, range_expr) => {
1092            let value_result = get_operand_result(results, value_expr, "value operand");
1093            let range_result = get_operand_result(results, range_expr, "range operand");
1094
1095            if let OperationResult::Veto(_) = value_result {
1096                return propagate_veto_explanation(
1097                    context,
1098                    current,
1099                    value_expr,
1100                    value_result,
1101                    "value operand",
1102                );
1103            }
1104            if let OperationResult::Veto(_) = range_result {
1105                return propagate_veto_explanation(
1106                    context,
1107                    current,
1108                    range_expr,
1109                    range_result,
1110                    "range operand",
1111                );
1112            }
1113
1114            let value_literal = unwrap_value_after_veto_check(
1115                &value_result,
1116                "value operand",
1117                &current.source_location,
1118            );
1119            let range_literal = unwrap_value_after_veto_check(
1120                &range_result,
1121                "range operand",
1122                &current.source_location,
1123            );
1124
1125            let contained = match &range_literal.value {
1126                ValueKind::Range(range_left, range_right) => crate::computation::range::check_containment(
1127                    value_literal,
1128                    range_left.as_ref(),
1129                    range_right.as_ref(),
1130                ),
1131                _ => unreachable!(
1132                    "BUG: range containment reached runtime with non-range operand; planning should have rejected this"
1133                ),
1134            };
1135
1136            let result_value = LiteralValue::from_bool(contained);
1137            let explanation_node = ExplanationNode::Value {
1138                value: result_value.clone(),
1139                source: ValueSource::Computed,
1140                source_location: current.source_location.clone(),
1141            };
1142            context.set_explanation_node(current, explanation_node);
1143            OperationResult::Value(Box::new(result_value))
1144        }
1145    }
1146}
1147
1148fn evaluate_mathematical_operator(
1149    op: &MathematicalComputation,
1150    value: &LiteralValue,
1151    context: &mut crate::evaluation::EvaluationContext,
1152) -> OperationResult {
1153    use crate::computation::decimal_math::{decimal_acos, decimal_asin, decimal_atan};
1154    use rust_decimal::MathematicalOps;
1155
1156    match &value.value {
1157        ValueKind::Number(stored_rational) => {
1158            use crate::computation::rational::{commit_rational_to_decimal, decimal_to_rational};
1159            let stored_decimal = match commit_rational_to_decimal(stored_rational) {
1160                Ok(decimal) => decimal,
1161                Err(_) => {
1162                    return OperationResult::Veto(VetoType::computation(
1163                        "Mathematical operation requires a decimal-representable input",
1164                    ));
1165                }
1166            };
1167            let decimal_result: Option<rust_decimal::Decimal> = match op {
1168                MathematicalComputation::Abs => Some(stored_decimal.abs()),
1169                MathematicalComputation::Floor => Some(stored_decimal.floor()),
1170                MathematicalComputation::Ceil => Some(stored_decimal.ceil()),
1171                MathematicalComputation::Round => Some(stored_decimal.round()),
1172                MathematicalComputation::Sqrt => stored_decimal.sqrt(),
1173                MathematicalComputation::Sin => stored_decimal.checked_sin(),
1174                MathematicalComputation::Cos => stored_decimal.checked_cos(),
1175                MathematicalComputation::Tan => stored_decimal.checked_tan(),
1176                MathematicalComputation::Log => stored_decimal.checked_ln(),
1177                MathematicalComputation::Exp => stored_decimal.checked_exp(),
1178                MathematicalComputation::Asin => decimal_asin(stored_decimal),
1179                MathematicalComputation::Acos => decimal_acos(stored_decimal),
1180                MathematicalComputation::Atan => decimal_atan(stored_decimal),
1181            };
1182
1183            let committed_decimal = match decimal_result {
1184                Some(committed) => committed,
1185                None => {
1186                    return OperationResult::Veto(VetoType::computation(
1187                        "Mathematical operation result is undefined for this input",
1188                    ));
1189                }
1190            };
1191
1192            let result_rational = decimal_to_rational(committed_decimal)
1193                .expect("BUG: transcendental result must lift back to stored rational");
1194            let result_value =
1195                LiteralValue::number_with_type(result_rational, value.lemma_type.clone());
1196            context.push_operation(OperationKind::Computation {
1197                kind: ComputationKind::Mathematical(op.clone()),
1198                inputs: vec![value.clone()],
1199                result: result_value.clone(),
1200            });
1201            OperationResult::Value(Box::new(result_value))
1202        }
1203        _ => unreachable!(
1204            "BUG: mathematical operator with non-number operand; planning should have rejected this"
1205        ),
1206    }
1207}