Skip to main content

lemma/computation/
comparison.rs

1//! Type-aware comparison operations
2
3use crate::evaluation::OperationResult;
4use crate::planning::semantics::{
5    primitive_boolean, ComparisonComputation, LiteralValue, SemanticConversionTarget, ValueKind,
6};
7use rust_decimal::Decimal;
8
9/// Perform type-aware comparison, returning OperationResult (Veto on error)
10pub fn comparison_operation(
11    left: &LiteralValue,
12    op: &ComparisonComputation,
13    right: &LiteralValue,
14) -> OperationResult {
15    match (&left.value, &right.value) {
16        (ValueKind::Number(l), ValueKind::Number(r)) => {
17            OperationResult::Value(Box::new(LiteralValue::from_bool(compare_decimals(*l, op, r))))
18        }
19
20        (ValueKind::Boolean(l), ValueKind::Boolean(r)) => match op {
21            ComparisonComputation::Equal | ComparisonComputation::Is => {
22                OperationResult::Value(Box::new(LiteralValue::from_bool(l == r)))
23            }
24            ComparisonComputation::NotEqual | ComparisonComputation::IsNot => {
25                OperationResult::Value(Box::new(LiteralValue {
26                    value: ValueKind::Boolean(l != r),
27                    lemma_type: primitive_boolean().clone(),
28                }))
29            }
30            _ => unreachable!(
31                "BUG: invalid boolean comparison operator {}; this should be rejected during planning",
32                op
33            ),
34        },
35
36        (ValueKind::Text(l), ValueKind::Text(r)) => match op {
37            ComparisonComputation::Equal | ComparisonComputation::Is => {
38                OperationResult::Value(Box::new(LiteralValue::from_bool(l == r)))
39            }
40            ComparisonComputation::NotEqual | ComparisonComputation::IsNot => {
41                OperationResult::Value(Box::new(LiteralValue {
42                    value: ValueKind::Boolean(l != r),
43                    lemma_type: primitive_boolean().clone(),
44                }))
45            }
46            _ => unreachable!(
47                "BUG: invalid text comparison operator {}; this should be rejected during planning",
48                op
49            ),
50        },
51
52        (ValueKind::Ratio(l, _), ValueKind::Ratio(r, _)) => {
53            OperationResult::Value(Box::new(LiteralValue::from_bool(compare_decimals(*l, op, r))))
54        }
55        (ValueKind::Scale(l, lu), ValueKind::Scale(r, ru)) => {
56            if !left.lemma_type.same_scale_family(&right.lemma_type) {
57                unreachable!(
58                    "BUG: compared different scale families ({} vs {}); this should be rejected during planning",
59                    left.lemma_type.name(),
60                    right.lemma_type.name()
61                );
62            }
63
64            if lu.eq_ignore_ascii_case(ru) {
65                return OperationResult::Value(Box::new(LiteralValue::from_bool(
66                    compare_decimals(*l, op, r),
67                )));
68            }
69
70            // Convert right to left's unit for comparison
71            let target = SemanticConversionTarget::ScaleUnit(lu.clone());
72            match super::units::convert_unit(right, &target) {
73                OperationResult::Value(converted) => match converted.as_ref().value {
74                    ValueKind::Scale(converted_value, _) => OperationResult::Value(Box::new(
75                        LiteralValue::from_bool(compare_decimals(*l, op, &converted_value)),
76                    )),
77                    _ => unreachable!("BUG: scale unit conversion returned non-scale value"),
78                },
79                OperationResult::Veto(msg) => {
80                    unreachable!("BUG: scale unit conversion vetoed unexpectedly: {:?}", msg)
81                }
82            }
83        }
84
85        (ValueKind::Date(_), ValueKind::Date(_)) => super::datetime::datetime_comparison(left, op, right),
86        (ValueKind::Time(_), ValueKind::Time(_)) => super::datetime::time_comparison(left, op, right),
87
88        // Duration comparison
89        (ValueKind::Duration(l, lu), ValueKind::Duration(r, ru)) => {
90            let left_seconds = super::units::duration_to_seconds(*l, lu);
91            let right_seconds = super::units::duration_to_seconds(*r, ru);
92            OperationResult::Value(Box::new(LiteralValue::from_bool(
93                compare_decimals(left_seconds, op, &right_seconds),
94            )))
95        }
96
97        // Duration with number
98        (ValueKind::Duration(value, _), ValueKind::Number(n)) => OperationResult::Value(Box::new(
99            LiteralValue::from_bool(compare_decimals(*value, op, n)),
100        )),
101        (ValueKind::Number(n), ValueKind::Duration(value, _)) => OperationResult::Value(Box::new(
102            LiteralValue::from_bool(compare_decimals(*n, op, value)),
103        )),
104
105        _ => unreachable!(
106            "BUG: unsupported comparison during evaluation: {} {} {}",
107            type_name(left),
108            op,
109            type_name(right)
110        ),
111    }
112}
113
114fn compare_decimals(left: Decimal, op: &ComparisonComputation, right: &Decimal) -> bool {
115    match op {
116        ComparisonComputation::GreaterThan => left > *right,
117        ComparisonComputation::LessThan => left < *right,
118        ComparisonComputation::GreaterThanOrEqual => left >= *right,
119        ComparisonComputation::LessThanOrEqual => left <= *right,
120        ComparisonComputation::Equal | ComparisonComputation::Is => left == *right,
121        ComparisonComputation::NotEqual | ComparisonComputation::IsNot => left != *right,
122    }
123}
124
125fn type_name(value: &LiteralValue) -> String {
126    value.get_type().name().to_string()
127}