Skip to main content

lemma/computation/
comparison.rs

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