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::explanation::{ExplanationNode, ValueSource};
7use super::operations::{ComputationKind, OperationKind, OperationResult, VetoType};
8use crate::computation::{arithmetic_operation, comparison_operation};
9use crate::planning::semantics::{
10    negated_comparison, Expression, ExpressionKind, LiteralValue, MathematicalComputation,
11    ValueKind,
12};
13use crate::planning::ExecutableRule;
14use std::collections::{HashMap, HashSet};
15use std::sync::Arc;
16
17/// Get an explanation node for an expression that was already evaluated.
18/// Panics if the explanation node is missing — this indicates a bug in the evaluator,
19/// since we always set an explanation node immediately after evaluating an expression.
20fn get_explanation_node_required(
21    context: &crate::evaluation::EvaluationContext,
22    expr: &Expression,
23    operand_name: &str,
24) -> ExplanationNode {
25    let loc = expr
26        .source_location
27        .as_ref()
28        .expect("BUG: expression missing source in evaluation");
29    context
30        .get_explanation_node(expr)
31        .cloned()
32        .unwrap_or_else(|| {
33            unreachable!(
34                "BUG: {} was evaluated but has no explanation node ({}:{}:{})",
35                operand_name, loc.attribute, loc.span.line, loc.span.col
36            )
37        })
38}
39
40fn expr_ptr(expr: &Expression) -> usize {
41    expr as *const Expression as usize
42}
43
44/// Get the result of an operand expression that was already evaluated.
45fn get_operand_result(
46    results: &HashMap<usize, OperationResult>,
47    expr: &Expression,
48    operand_name: &str,
49) -> OperationResult {
50    let loc = expr
51        .source_location
52        .as_ref()
53        .expect("BUG: expression missing source in evaluation");
54    results.get(&expr_ptr(expr)).cloned().unwrap_or_else(|| {
55        unreachable!(
56            "BUG: {} operand was marked ready but result is missing ({}:{}:{})",
57            operand_name, loc.attribute, loc.span.line, loc.span.col
58        )
59    })
60}
61
62/// Extract the value from an OperationResult that is known to not be Veto.
63/// Called only after an explicit Veto check has already returned early.
64/// Panics if the result has no value — this is unreachable after the Veto guard.
65fn unwrap_value_after_veto_check<'a>(
66    result: &'a OperationResult,
67    operand_name: &str,
68    source_location: &Option<crate::planning::semantics::Source>,
69) -> &'a LiteralValue {
70    result.value().unwrap_or_else(|| {
71        let loc = source_location
72            .as_ref()
73            .expect("BUG: expression missing source in evaluation");
74        unreachable!(
75            "BUG: {} passed Veto check but has no value ({}:{}:{})",
76            operand_name, loc.attribute, loc.span.line, loc.span.col
77        )
78    })
79}
80
81/// Propagate veto explanation from operand to current expression
82fn propagate_veto_explanation(
83    context: &mut crate::evaluation::EvaluationContext,
84    current: &Expression,
85    vetoed_operand: &Expression,
86    veto_result: OperationResult,
87    operand_name: &str,
88) -> OperationResult {
89    let node = get_explanation_node_required(context, vetoed_operand, operand_name);
90    context.set_explanation_node(current, node);
91    veto_result
92}
93
94/// Evaluate a rule to produce its final result and explanation.
95/// After planning, evaluation is guaranteed to complete — this function never returns
96/// a Error. It produces an OperationResult (Value or Veto) and an Explanation tree.
97pub(crate) fn evaluate_rule(
98    exec_rule: &ExecutableRule,
99    context: &mut crate::evaluation::EvaluationContext,
100) -> (OperationResult, crate::evaluation::explanation::Explanation) {
101    use crate::evaluation::explanation::{Branch, NonMatchedBranch};
102
103    // If rule has no unless clauses, just evaluate the default expression
104    if exec_rule.branches.len() == 1 {
105        return evaluate_rule_without_unless(exec_rule, context);
106    }
107
108    // Rule has unless clauses - collect all branch evaluations for Branches explanation node
109    let mut non_matched_branches: Vec<NonMatchedBranch> = Vec::new();
110
111    // Evaluate branches in reverse order (last matching wins)
112    for branch_index in (1..exec_rule.branches.len()).rev() {
113        let branch = &exec_rule.branches[branch_index];
114        if let Some(ref condition) = branch.condition {
115            let condition_result = evaluate_expression(condition, context);
116            let condition_explanation =
117                get_explanation_node_required(context, condition, "condition");
118
119            let matched = match condition_result {
120                OperationResult::Veto(ref reason) => {
121                    // Condition vetoed - this becomes the result
122                    let unless_clause_index = branch_index - 1;
123                    context.push_operation(OperationKind::RuleBranchEvaluated {
124                        index: Some(unless_clause_index),
125                        matched: true,
126                        result_value: Some(OperationResult::Veto(reason.clone())),
127                    });
128
129                    // Build Branches node with this as the matched branch
130                    let matched_branch = Branch {
131                        condition: Some(Box::new(condition_explanation)),
132                        result: Box::new(ExplanationNode::Veto {
133                            message: Some(reason.to_string()),
134                            source_location: branch.result.source_location.clone(),
135                        }),
136                        clause_index: Some(unless_clause_index),
137                        source_location: Some(branch.source.clone()),
138                    };
139
140                    let branches_node = ExplanationNode::Branches {
141                        matched: Box::new(matched_branch),
142                        non_matched: non_matched_branches,
143                        source_location: Some(exec_rule.source.clone()),
144                    };
145
146                    let explanation = crate::evaluation::explanation::Explanation {
147                        rule_path: exec_rule.path.clone(),
148                        source: Some(exec_rule.source.clone()),
149                        result: OperationResult::Veto(reason.clone()),
150                        tree: Arc::new(branches_node),
151                    };
152                    return (OperationResult::Veto(reason.clone()), explanation);
153                }
154                OperationResult::Value(lit) => match &lit.value {
155                    ValueKind::Boolean(b) => *b,
156                    _ => {
157                        let veto = OperationResult::Veto(VetoType::computation(
158                            "Unless condition must evaluate to boolean",
159                        ));
160                        let explanation = crate::evaluation::explanation::Explanation {
161                            rule_path: exec_rule.path.clone(),
162                            source: Some(exec_rule.source.clone()),
163                            result: veto.clone(),
164                            tree: Arc::new(ExplanationNode::Veto {
165                                message: Some(
166                                    "Unless condition must evaluate to boolean".to_string(),
167                                ),
168                                source_location: Some(exec_rule.source.clone()),
169                            }),
170                        };
171                        return (veto, explanation);
172                    }
173                },
174            };
175
176            let unless_clause_index = branch_index - 1;
177
178            if matched {
179                // This unless clause matched - evaluate its result
180                let result = evaluate_expression(&branch.result, context);
181
182                context.push_operation(OperationKind::RuleBranchEvaluated {
183                    index: Some(unless_clause_index),
184                    matched: true,
185                    result_value: Some(result.clone()),
186                });
187
188                let result_explanation =
189                    get_explanation_node_required(context, &branch.result, "result");
190
191                // Build Branches node with this as the matched branch
192                let matched_branch = Branch {
193                    condition: Some(Box::new(condition_explanation)),
194                    result: Box::new(result_explanation),
195                    clause_index: Some(unless_clause_index),
196                    source_location: Some(branch.source.clone()),
197                };
198
199                let branches_node = ExplanationNode::Branches {
200                    matched: Box::new(matched_branch),
201                    non_matched: non_matched_branches,
202                    source_location: Some(exec_rule.source.clone()),
203                };
204
205                let explanation = crate::evaluation::explanation::Explanation {
206                    rule_path: exec_rule.path.clone(),
207                    source: Some(exec_rule.source.clone()),
208                    result: result.clone(),
209                    tree: Arc::new(branches_node),
210                };
211                return (result, explanation);
212            }
213            // Branch didn't match - record it as non-matched.
214            context.push_operation(OperationKind::RuleBranchEvaluated {
215                index: Some(unless_clause_index),
216                matched: false,
217                result_value: None,
218            });
219
220            non_matched_branches.push(NonMatchedBranch {
221                condition: Box::new(condition_explanation),
222                result: None,
223                clause_index: Some(unless_clause_index),
224                source_location: Some(branch.source.clone()),
225            });
226        }
227    }
228
229    // No unless clause matched - evaluate default expression (first branch)
230    let default_branch = &exec_rule.branches[0];
231    let default_result = evaluate_expression(&default_branch.result, context);
232
233    context.push_operation(OperationKind::RuleBranchEvaluated {
234        index: None,
235        matched: true,
236        result_value: Some(default_result.clone()),
237    });
238
239    let default_result_explanation =
240        get_explanation_node_required(context, &default_branch.result, "default result");
241
242    // Default branch has no condition
243    let matched_branch = Branch {
244        condition: None,
245        result: Box::new(default_result_explanation),
246        clause_index: None,
247        source_location: Some(default_branch.source.clone()),
248    };
249
250    let branches_node = ExplanationNode::Branches {
251        matched: Box::new(matched_branch),
252        non_matched: non_matched_branches,
253        source_location: Some(exec_rule.source.clone()),
254    };
255
256    let explanation = crate::evaluation::explanation::Explanation {
257        rule_path: exec_rule.path.clone(),
258        source: Some(exec_rule.source.clone()),
259        result: default_result.clone(),
260        tree: Arc::new(branches_node),
261    };
262
263    (default_result, explanation)
264}
265
266/// Evaluate a rule that has no unless clauses (simple case)
267fn evaluate_rule_without_unless(
268    exec_rule: &ExecutableRule,
269    context: &mut crate::evaluation::EvaluationContext,
270) -> (OperationResult, crate::evaluation::explanation::Explanation) {
271    let default_branch = &exec_rule.branches[0];
272    let default_result = evaluate_expression(&default_branch.result, context);
273
274    context.push_operation(OperationKind::RuleBranchEvaluated {
275        index: None,
276        matched: true,
277        result_value: Some(default_result.clone()),
278    });
279
280    let root_explanation_node =
281        get_explanation_node_required(context, &default_branch.result, "default result");
282
283    let explanation = crate::evaluation::explanation::Explanation {
284        rule_path: exec_rule.path.clone(),
285        source: Some(exec_rule.source.clone()),
286        result: default_result.clone(),
287        tree: Arc::new(root_explanation_node),
288    };
289
290    (default_result, explanation)
291}
292
293/// Evaluate an expression iteratively without recursion.
294/// Uses a work list approach: collect all expressions first, then evaluate in dependency order.
295/// After planning, expression evaluation is guaranteed to complete — this function never
296/// returns a Error. It produces an OperationResult (Value or Veto).
297/// Iterative post-order traversal: collects expression nodes children-before-parents.
298/// Uses pointer identity for dedup (no Hash/Eq on Expression needed).
299fn collect_postorder(root: &Expression) -> Vec<&Expression> {
300    enum Visit<'a> {
301        Enter(&'a Expression),
302        Exit(&'a Expression),
303    }
304
305    let mut stack: Vec<Visit<'_>> = vec![Visit::Enter(root)];
306    let mut seen: HashSet<usize> = HashSet::new();
307    let mut nodes: Vec<&Expression> = Vec::new();
308
309    while let Some(visit) = stack.pop() {
310        match visit {
311            Visit::Enter(e) => {
312                if !seen.insert(expr_ptr(e)) {
313                    continue;
314                }
315                stack.push(Visit::Exit(e));
316                match &e.kind {
317                    ExpressionKind::Arithmetic(left, _, right)
318                    | ExpressionKind::Comparison(left, _, right)
319                    | ExpressionKind::LogicalAnd(left, right) => {
320                        stack.push(Visit::Enter(right));
321                        stack.push(Visit::Enter(left));
322                    }
323                    ExpressionKind::LogicalNegation(operand, _)
324                    | ExpressionKind::UnitConversion(operand, _)
325                    | ExpressionKind::MathematicalComputation(_, operand)
326                    | ExpressionKind::DateCalendar(_, _, operand) => {
327                        stack.push(Visit::Enter(operand));
328                    }
329                    ExpressionKind::DateRelative(_, date_expr, tolerance_expr) => {
330                        if let Some(tol) = tolerance_expr {
331                            stack.push(Visit::Enter(tol));
332                        }
333                        stack.push(Visit::Enter(date_expr));
334                    }
335                    _ => {}
336                }
337            }
338            Visit::Exit(e) => {
339                nodes.push(e);
340            }
341        }
342    }
343
344    nodes
345}
346
347fn evaluate_expression(
348    expr: &Expression,
349    context: &mut crate::evaluation::EvaluationContext,
350) -> OperationResult {
351    let nodes = collect_postorder(expr);
352    let mut results: HashMap<usize, OperationResult> = HashMap::with_capacity(nodes.len());
353
354    for node in &nodes {
355        let result = evaluate_single_expression(node, &results, context);
356        results.insert(expr_ptr(node), result);
357    }
358
359    results.remove(&expr_ptr(expr)).unwrap_or_else(|| {
360        let loc = expr
361            .source_location
362            .as_ref()
363            .expect("BUG: expression missing source in evaluation");
364        unreachable!(
365            "BUG: expression was processed but has no result ({}:{}:{})",
366            loc.attribute, loc.span.start, loc.span.end
367        )
368    })
369}
370
371/// Evaluate a single expression given its dependencies are already evaluated.
372/// After planning, this function is guaranteed to complete — it produces an OperationResult
373/// (Value or Veto) without ever returning a Error.
374fn evaluate_single_expression(
375    current: &Expression,
376    results: &HashMap<usize, OperationResult>,
377    context: &mut crate::evaluation::EvaluationContext,
378) -> OperationResult {
379    match &current.kind {
380        ExpressionKind::Literal(lit) => {
381            let value = lit.as_ref().clone();
382            let explanation_node = ExplanationNode::Value {
383                value: value.clone(),
384                source: ValueSource::Literal,
385                source_location: current.source_location.clone(),
386            };
387            context.set_explanation_node(current, explanation_node);
388            OperationResult::Value(Box::new(value))
389        }
390
391        ExpressionKind::DataPath(data_path) => {
392            // Data lookup: a data can legitimately be missing (TypeDeclaration without a
393            // provided value at runtime). Returning None → Veto("Missing data: ...") is
394            // correct domain behavior.
395            //
396            // Rule-target references are not pre-populated in `data_values`; on
397            // first read we lazily resolve them from the (already-evaluated,
398            // by topological-sort guarantee) target rule's result. A target
399            // rule veto propagates with the exact same reason — copying the
400            // veto type without rewrapping in `MissingData`.
401            let data_path_clone = data_path.clone();
402            let mut value = context.get_data(data_path).cloned();
403            if value.is_none() {
404                if let Some(resolved) = context.lazy_rule_reference_resolve(data_path) {
405                    match resolved {
406                        Ok(v) => value = Some(v),
407                        Err(veto) => {
408                            let explanation_node = ExplanationNode::Veto {
409                                message: Some(veto.to_string()),
410                                source_location: current.source_location.clone(),
411                            };
412                            context.set_explanation_node(current, explanation_node);
413                            return OperationResult::Veto(veto);
414                        }
415                    }
416                } else if let Some(veto) = context.get_reference_veto(data_path) {
417                    let veto = veto.clone();
418                    let explanation_node = ExplanationNode::Veto {
419                        message: Some(veto.to_string()),
420                        source_location: current.source_location.clone(),
421                    };
422                    context.set_explanation_node(current, explanation_node);
423                    return OperationResult::Veto(veto);
424                }
425            }
426            match value {
427                Some(v) => {
428                    context.push_operation(OperationKind::DataUsed {
429                        data_ref: data_path_clone.clone(),
430                        value: v.clone(),
431                    });
432                    let explanation_node = ExplanationNode::Value {
433                        value: v.clone(),
434                        source: ValueSource::Data {
435                            data_ref: data_path_clone,
436                        },
437                        source_location: current.source_location.clone(),
438                    };
439                    context.set_explanation_node(current, explanation_node);
440                    OperationResult::Value(Box::new(v))
441                }
442                None => {
443                    let reason = VetoType::MissingData {
444                        data: data_path_clone.clone(),
445                    };
446                    let explanation_node = ExplanationNode::Veto {
447                        message: Some(reason.to_string()),
448                        source_location: current.source_location.clone(),
449                    };
450                    context.set_explanation_node(current, explanation_node);
451                    OperationResult::Veto(reason)
452                }
453            }
454        }
455
456        ExpressionKind::RulePath(rule_path) => {
457            // Rule lookup: rules are evaluated in topological order. If a referenced rule's
458            // result is not in the map, planning guaranteed no cycles and topological sort
459            // ensured the dependency was evaluated first. Missing result is a bug.
460            let rule_path_clone = rule_path.clone();
461            let loc = current
462                .source_location
463                .as_ref()
464                .expect("BUG: expression missing source in evaluation");
465            let r = context.rule_results.get(rule_path).cloned().unwrap_or_else(|| {
466                unreachable!(
467                    "BUG: Rule '{}' not found in results during topological-order evaluation ({}:{}:{})",
468                    rule_path.rule, loc.attribute, loc.span.line, loc.span.col
469                )
470            });
471
472            context.push_operation(OperationKind::RuleUsed {
473                rule_path: rule_path_clone.clone(),
474                result: r.clone(),
475            });
476
477            // Share expansion via Arc instead of cloning (avoids O(n²) for deep chains)
478            let expansion = match context.get_rule_explanation(rule_path) {
479                Some(existing_explanation) => Arc::clone(&existing_explanation.tree),
480                None => Arc::new(ExplanationNode::Value {
481                    value: match &r {
482                        OperationResult::Value(v) => v.as_ref().clone(),
483                        OperationResult::Veto(_) => LiteralValue::from_bool(false),
484                    },
485                    source: ValueSource::Computed,
486                    source_location: current.source_location.clone(),
487                }),
488            };
489
490            let explanation_node = ExplanationNode::RuleReference {
491                rule_path: rule_path_clone,
492                result: r.clone(),
493                source_location: current.source_location.clone(),
494                expansion,
495            };
496            context.set_explanation_node(current, explanation_node);
497            r
498        }
499
500        ExpressionKind::Arithmetic(left, op, right) => {
501            let left_result = get_operand_result(results, left, "left");
502            let right_result = get_operand_result(results, right, "right");
503
504            if let OperationResult::Veto(_) = left_result {
505                return propagate_veto_explanation(
506                    context,
507                    current,
508                    left,
509                    left_result,
510                    "left operand",
511                );
512            }
513            if let OperationResult::Veto(_) = right_result {
514                return propagate_veto_explanation(
515                    context,
516                    current,
517                    right,
518                    right_result,
519                    "right operand",
520                );
521            }
522
523            let left_val = unwrap_value_after_veto_check(
524                &left_result,
525                "left operand",
526                &current.source_location,
527            );
528            let right_val = unwrap_value_after_veto_check(
529                &right_result,
530                "right operand",
531                &current.source_location,
532            );
533
534            let result = arithmetic_operation(left_val, op, right_val);
535
536            let left_explanation = get_explanation_node_required(context, left, "left operand");
537            let right_explanation = get_explanation_node_required(context, right, "right operand");
538
539            if let OperationResult::Value(ref val) = result {
540                let original_expr = format!("{} {} {}", left_val, op, right_val);
541                let substituted_expr = format!("{} {} {}", left_val, op, right_val);
542                context.push_operation(OperationKind::Computation {
543                    kind: ComputationKind::Arithmetic(op.clone()),
544                    inputs: vec![left_val.clone(), right_val.clone()],
545                    result: val.as_ref().clone(),
546                });
547                let explanation_node = ExplanationNode::Computation {
548                    kind: ComputationKind::Arithmetic(op.clone()),
549                    original_expression: original_expr,
550                    expression: substituted_expr,
551                    result: val.as_ref().clone(),
552                    source_location: current.source_location.clone(),
553                    operands: vec![left_explanation, right_explanation],
554                };
555                context.set_explanation_node(current, explanation_node);
556            } else if let OperationResult::Veto(_) = result {
557                context.set_explanation_node(current, left_explanation);
558            }
559            result
560        }
561
562        ExpressionKind::Comparison(left, op, right) => {
563            let left_result = get_operand_result(results, left, "left");
564            let right_result = get_operand_result(results, right, "right");
565
566            if let OperationResult::Veto(_) = left_result {
567                return propagate_veto_explanation(
568                    context,
569                    current,
570                    left,
571                    left_result,
572                    "left operand",
573                );
574            }
575            if let OperationResult::Veto(_) = right_result {
576                return propagate_veto_explanation(
577                    context,
578                    current,
579                    right,
580                    right_result,
581                    "right operand",
582                );
583            }
584
585            let left_val = unwrap_value_after_veto_check(
586                &left_result,
587                "left operand",
588                &current.source_location,
589            );
590            let right_val = unwrap_value_after_veto_check(
591                &right_result,
592                "right operand",
593                &current.source_location,
594            );
595
596            let result = comparison_operation(left_val, op, right_val);
597
598            let left_explanation = get_explanation_node_required(context, left, "left operand");
599            let right_explanation = get_explanation_node_required(context, right, "right operand");
600
601            if let OperationResult::Value(ref val) = result {
602                let is_false = matches!(val.as_ref().value, ValueKind::Boolean(false));
603                let (display_op, original_expr, substituted_expr, display_result) = if is_false {
604                    let negated_op = negated_comparison(op.clone());
605                    let orig = format!("{} {} {}", left_val, negated_op, right_val);
606                    let sub = format!("{} {} {}", left_val, negated_op, right_val);
607                    (negated_op, orig, sub, LiteralValue::from_bool(true))
608                } else {
609                    let original_expr = format!("{} {} {}", left_val, op, right_val);
610                    let substituted_expr = format!("{} {} {}", left_val, op, right_val);
611                    (
612                        op.clone(),
613                        original_expr,
614                        substituted_expr,
615                        val.as_ref().clone(),
616                    )
617                };
618                context.push_operation(OperationKind::Computation {
619                    kind: ComputationKind::Comparison(op.clone()),
620                    inputs: vec![left_val.clone(), right_val.clone()],
621                    result: val.as_ref().clone(),
622                });
623                let explanation_node = ExplanationNode::Computation {
624                    kind: ComputationKind::Comparison(display_op),
625                    original_expression: original_expr,
626                    expression: substituted_expr,
627                    result: display_result,
628                    source_location: current.source_location.clone(),
629                    operands: vec![left_explanation, right_explanation],
630                };
631                context.set_explanation_node(current, explanation_node);
632            } else if let OperationResult::Veto(_) = result {
633                context.set_explanation_node(current, left_explanation);
634            }
635            result
636        }
637
638        ExpressionKind::LogicalAnd(left, right) => {
639            let left_result = get_operand_result(results, left, "left");
640            if let OperationResult::Veto(_) = left_result {
641                return propagate_veto_explanation(
642                    context,
643                    current,
644                    left,
645                    left_result,
646                    "left operand",
647                );
648            }
649
650            let left_val = unwrap_value_after_veto_check(
651                &left_result,
652                "left operand",
653                &current.source_location,
654            );
655            let left_bool = match &left_val.value {
656                ValueKind::Boolean(b) => b,
657                _ => unreachable!(
658                    "BUG: logical AND with non-boolean operand; planning should have rejected this"
659                ),
660            };
661
662            if !*left_bool {
663                let left_explanation = get_explanation_node_required(context, left, "left operand");
664                context.set_explanation_node(current, left_explanation);
665                OperationResult::Value(Box::new(LiteralValue::from_bool(false)))
666            } else {
667                let right_result = get_operand_result(results, right, "right");
668                let right_explanation =
669                    get_explanation_node_required(context, right, "right operand");
670                context.set_explanation_node(current, right_explanation);
671                right_result
672            }
673        }
674
675        ExpressionKind::LogicalNegation(operand, _) => {
676            let result = get_operand_result(results, operand, "operand");
677            if let OperationResult::Veto(_) = result {
678                return propagate_veto_explanation(context, current, operand, result, "operand");
679            }
680
681            let value = unwrap_value_after_veto_check(&result, "operand", &current.source_location);
682            let operand_explanation = get_explanation_node_required(context, operand, "operand");
683            match &value.value {
684                ValueKind::Boolean(b) => {
685                    let result_bool = !*b;
686                    context.set_explanation_node(current, operand_explanation);
687                    OperationResult::Value(Box::new(LiteralValue::from_bool(result_bool)))
688                }
689                _ => unreachable!(
690                    "BUG: logical NOT with non-boolean operand; planning should have rejected this"
691                ),
692            }
693        }
694
695        ExpressionKind::UnitConversion(value_expr, target) => {
696            let result = get_operand_result(results, value_expr, "operand");
697            if let OperationResult::Veto(_) = result {
698                return propagate_veto_explanation(context, current, value_expr, result, "operand");
699            }
700
701            let value = unwrap_value_after_veto_check(&result, "operand", &current.source_location);
702            let operand_explanation = get_explanation_node_required(context, value_expr, "operand");
703
704            let conversion_result = crate::computation::convert_unit(value, target);
705
706            context.set_explanation_node(current, operand_explanation);
707            conversion_result
708        }
709
710        ExpressionKind::MathematicalComputation(op, operand) => {
711            let result = get_operand_result(results, operand, "operand");
712            if let OperationResult::Veto(_) = result {
713                return propagate_veto_explanation(context, current, operand, result, "operand");
714            }
715
716            let value = unwrap_value_after_veto_check(&result, "operand", &current.source_location);
717            let operand_explanation = get_explanation_node_required(context, operand, "operand");
718            let math_result = evaluate_mathematical_operator(op, value, context);
719            context.set_explanation_node(current, operand_explanation);
720            math_result
721        }
722
723        ExpressionKind::Veto(veto_expr) => {
724            let explanation_node = ExplanationNode::Veto {
725                message: veto_expr.message.clone(),
726                source_location: current.source_location.clone(),
727            };
728            context.set_explanation_node(current, explanation_node);
729            OperationResult::Veto(VetoType::UserDefined {
730                message: veto_expr.message.clone(),
731            })
732        }
733
734        ExpressionKind::Now => {
735            let value = context.now().clone();
736            let explanation_node = ExplanationNode::Value {
737                value: value.clone(),
738                source: ValueSource::Computed,
739                source_location: current.source_location.clone(),
740            };
741            context.set_explanation_node(current, explanation_node);
742            OperationResult::Value(Box::new(value))
743        }
744
745        ExpressionKind::DateRelative(kind, date_expr, tolerance_expr) => {
746            let date_result = get_operand_result(results, date_expr, "date operand");
747            if let OperationResult::Veto(_) = date_result {
748                return propagate_veto_explanation(
749                    context,
750                    current,
751                    date_expr,
752                    date_result,
753                    "date operand",
754                );
755            }
756
757            let date_val = unwrap_value_after_veto_check(
758                &date_result,
759                "date operand",
760                &current.source_location,
761            );
762
763            let date_semantic = match &date_val.value {
764                ValueKind::Date(dt) => dt,
765                _ => unreachable!(
766                    "BUG: date sugar with non-date operand; planning should have rejected this"
767                ),
768            };
769
770            let now_val = context.now();
771            let now_semantic = match &now_val.value {
772                ValueKind::Date(dt) => dt,
773                _ => unreachable!("BUG: context.now() must be a Date value"),
774            };
775
776            let tolerance = match tolerance_expr {
777                Some(tol_expr) => {
778                    let tol_result = get_operand_result(results, tol_expr, "tolerance operand");
779                    if let OperationResult::Veto(_) = tol_result {
780                        return propagate_veto_explanation(
781                            context,
782                            current,
783                            tol_expr,
784                            tol_result,
785                            "tolerance operand",
786                        );
787                    }
788                    let tol_val = unwrap_value_after_veto_check(
789                        &tol_result,
790                        "tolerance operand",
791                        &current.source_location,
792                    );
793                    match &tol_val.value {
794                        ValueKind::Duration(amount, unit) => Some((*amount, unit.clone())),
795                        _ => unreachable!(
796                            "BUG: date sugar tolerance with non-duration; planning should have rejected this"
797                        ),
798                    }
799                }
800                None => None,
801            };
802
803            let result = crate::computation::datetime::compute_date_relative(
804                kind,
805                date_semantic,
806                tolerance.as_ref().map(|(a, u)| (a, u)),
807                now_semantic,
808            );
809
810            let date_explanation =
811                get_explanation_node_required(context, date_expr, "date operand");
812            context.set_explanation_node(current, date_explanation);
813            result
814        }
815
816        ExpressionKind::DateCalendar(kind, unit, date_expr) => {
817            let date_result = get_operand_result(results, date_expr, "date operand");
818            if let OperationResult::Veto(_) = date_result {
819                return propagate_veto_explanation(
820                    context,
821                    current,
822                    date_expr,
823                    date_result,
824                    "date operand",
825                );
826            }
827
828            let date_val = unwrap_value_after_veto_check(
829                &date_result,
830                "date operand",
831                &current.source_location,
832            );
833
834            let date_semantic = match &date_val.value {
835                ValueKind::Date(dt) => dt,
836                _ => unreachable!(
837                    "BUG: calendar sugar with non-date operand; planning should have rejected this"
838                ),
839            };
840
841            let now_val = context.now();
842            let now_semantic = match &now_val.value {
843                ValueKind::Date(dt) => dt,
844                _ => unreachable!("BUG: context.now() must be a Date value"),
845            };
846
847            let result = crate::computation::datetime::compute_date_calendar(
848                kind,
849                unit,
850                date_semantic,
851                now_semantic,
852            );
853
854            let date_explanation =
855                get_explanation_node_required(context, date_expr, "date operand");
856            context.set_explanation_node(current, date_explanation);
857            result
858        }
859    }
860}
861
862fn evaluate_mathematical_operator(
863    op: &MathematicalComputation,
864    value: &LiteralValue,
865    context: &mut crate::evaluation::EvaluationContext,
866) -> OperationResult {
867    match &value.value {
868        ValueKind::Number(n) => {
869            use rust_decimal::prelude::ToPrimitive;
870            let float_val = match n.to_f64() {
871                Some(v) => v,
872                None => {
873                    return OperationResult::Veto(VetoType::computation(
874                        "Cannot convert to float for mathematical operation",
875                    ));
876                }
877            };
878
879            let math_result = match op {
880                MathematicalComputation::Sqrt => float_val.sqrt(),
881                MathematicalComputation::Sin => float_val.sin(),
882                MathematicalComputation::Cos => float_val.cos(),
883                MathematicalComputation::Tan => float_val.tan(),
884                MathematicalComputation::Asin => float_val.asin(),
885                MathematicalComputation::Acos => float_val.acos(),
886                MathematicalComputation::Atan => float_val.atan(),
887                MathematicalComputation::Log => float_val.ln(),
888                MathematicalComputation::Exp => float_val.exp(),
889                MathematicalComputation::Abs => {
890                    return OperationResult::Value(Box::new(LiteralValue::number_with_type(
891                        n.abs(),
892                        value.lemma_type.clone(),
893                    )));
894                }
895                MathematicalComputation::Floor => {
896                    return OperationResult::Value(Box::new(LiteralValue::number_with_type(
897                        n.floor(),
898                        value.lemma_type.clone(),
899                    )));
900                }
901                MathematicalComputation::Ceil => {
902                    return OperationResult::Value(Box::new(LiteralValue::number_with_type(
903                        n.ceil(),
904                        value.lemma_type.clone(),
905                    )));
906                }
907                MathematicalComputation::Round => {
908                    return OperationResult::Value(Box::new(LiteralValue::number_with_type(
909                        n.round(),
910                        value.lemma_type.clone(),
911                    )));
912                }
913            };
914
915            let decimal_result = match rust_decimal::Decimal::from_f64_retain(math_result) {
916                Some(d) => d,
917                None => {
918                    return OperationResult::Veto(VetoType::computation(
919                        "Mathematical operation result cannot be represented",
920                    ));
921                }
922            };
923
924            let result_value =
925                LiteralValue::number_with_type(decimal_result, value.lemma_type.clone());
926            context.push_operation(OperationKind::Computation {
927                kind: ComputationKind::Mathematical(op.clone()),
928                inputs: vec![value.clone()],
929                result: result_value.clone(),
930            });
931            OperationResult::Value(Box::new(result_value))
932        }
933        _ => unreachable!(
934            "BUG: mathematical operator with non-number operand; planning should have rejected this"
935        ),
936    }
937}