Skip to main content

lemma/evaluation/
operations.rs

1//! Operation types and result handling for evaluation
2
3use std::fmt;
4use std::sync::Arc;
5
6use crate::planning::semantics::{
7    ArithmeticComputation, ComparisonComputation, DataPath, LemmaType, LiteralValue,
8    LogicalComputation, MathematicalComputation, SemanticConversionTarget, SemanticDateTime,
9    SemanticTime, TypeSpecification,
10};
11use serde::{Deserialize, Serialize};
12
13/// Why an operation yielded no value (domain veto).
14///
15/// JSON serialization is a single string (see [`fmt::Display`]). There is intentionally no
16/// `Deserialize` implementation: veto payloads are engine output only.
17#[derive(Debug, Clone, PartialEq)]
18pub enum VetoType {
19    /// Evaluation needed a data that was not provided
20    MissingData { data: DataPath },
21    /// Explicit `veto "reason"` in Lemma source
22    UserDefined { message: Option<String> },
23    /// Runtime domain failure (division by zero, date overflow, etc.)
24    Computation { message: String },
25}
26
27impl fmt::Display for VetoType {
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        match self {
30            VetoType::MissingData { data } => write!(f, "Missing data: {}", data),
31            VetoType::UserDefined { message: Some(msg) } => write!(f, "{msg}"),
32            VetoType::UserDefined { message: None } => write!(f, "Vetoed"),
33            VetoType::Computation { message } => write!(f, "{message}"),
34        }
35    }
36}
37
38impl VetoType {
39    #[must_use]
40    pub fn computation(message: impl Into<String>) -> Self {
41        VetoType::Computation {
42            message: message.into(),
43        }
44    }
45}
46
47impl Serialize for VetoType {
48    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
49    where
50        S: serde::Serializer,
51    {
52        serializer.serialize_str(&self.to_string())
53    }
54}
55
56/// Result of an operation (evaluating a rule or expression)
57#[derive(Debug, Clone, PartialEq, Serialize)]
58#[serde(rename_all = "snake_case")]
59pub enum OperationResult {
60    /// Operation produced a value
61    Value(Arc<LiteralValue>),
62    /// Operation was vetoed (valid result, no value)
63    Veto(VetoType),
64}
65
66impl OperationResult {
67    pub fn from_literal(value: LiteralValue) -> Self {
68        Self::Value(Arc::new(value))
69    }
70
71    pub fn from_literal_arc(value: Arc<LiteralValue>) -> Self {
72        Self::Value(value)
73    }
74
75    pub fn vetoed(&self) -> bool {
76        matches!(self, OperationResult::Veto(_))
77    }
78
79    #[must_use]
80    pub fn value(&self) -> Option<&LiteralValue> {
81        match self {
82            OperationResult::Value(value) => Some(value.as_ref()),
83            OperationResult::Veto(_) => None,
84        }
85    }
86
87    #[must_use]
88    pub fn value_arc(&self) -> Option<&Arc<LiteralValue>> {
89        match self {
90            OperationResult::Value(value) => Some(value),
91            OperationResult::Veto(_) => None,
92        }
93    }
94
95    pub fn number(number: rust_decimal::Decimal) -> Self {
96        Self::from_literal(LiteralValue::number_from_decimal(number))
97    }
98
99    pub fn measure(
100        value: rust_decimal::Decimal,
101        unit: impl Into<String>,
102        lemma_type: Option<LemmaType>,
103    ) -> Self {
104        use crate::computation::rational::checked_mul;
105        let lemma_type = std::sync::Arc::new(
106            lemma_type.unwrap_or_else(|| LemmaType::primitive(TypeSpecification::measure())),
107        );
108        let unit_name = unit.into();
109        let rational = crate::literals::rational_from_parsed_decimal(value)
110            .expect("BUG: operation result measure must lift at boundary");
111        let factor = if let TypeSpecification::Measure { units, .. } = &lemma_type.specifications {
112            units
113                .get(&unit_name)
114                .map(|u| u.factor.clone())
115                .unwrap_or_else(|_| {
116                    panic!(
117                        "BUG: OperationResult::measure unit '{}' not declared on type",
118                        unit_name
119                    )
120                })
121        } else {
122            crate::computation::rational::rational_one()
123        };
124        let canonical = checked_mul(&rational, &factor)
125            .expect("BUG: measure canonicalization overflow in OperationResult::measure");
126        Self::from_literal(LiteralValue::measure_with_type(
127            canonical, unit_name, lemma_type,
128        ))
129    }
130
131    pub fn text(text: impl Into<String>) -> Self {
132        Self::from_literal(LiteralValue::text(text.into()))
133    }
134
135    pub fn date(date: impl Into<SemanticDateTime>) -> Self {
136        Self::from_literal(LiteralValue::date(date.into()))
137    }
138
139    pub fn time(time: impl Into<SemanticTime>) -> Self {
140        Self::from_literal(LiteralValue::time(time.into()))
141    }
142
143    pub fn boolean(boolean: bool) -> Self {
144        Self::from_literal(LiteralValue::from_bool(boolean))
145    }
146
147    pub fn ratio(rational: rust_decimal::Decimal) -> Self {
148        Self::from_literal(LiteralValue::ratio_from_decimal(rational, None))
149    }
150
151    pub fn veto(veto: impl Into<String>) -> Self {
152        Self::Veto(VetoType::UserDefined {
153            message: Some(veto.into()),
154        })
155    }
156}
157
158/// The kind of computation performed
159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160#[serde(tag = "type", content = "computation", rename_all = "snake_case")]
161pub enum ComputationKind {
162    Arithmetic(ArithmeticComputation),
163    Comparison(ComparisonComputation),
164    Mathematical(MathematicalComputation),
165    Logical(LogicalComputation),
166    UnitConversion {
167        target: SemanticConversionTarget,
168    },
169    /// Operand result was tested for veto (`is veto` syntax).
170    ResultIsVeto,
171}
172
173#[cfg(test)]
174mod computation_kind_serde_tests {
175    use super::ComputationKind;
176    use crate::parsing::ast::{
177        ArithmeticComputation, ComparisonComputation, MathematicalComputation,
178    };
179
180    #[test]
181    fn computation_kind_arithmetic_round_trip() {
182        let k = ComputationKind::Arithmetic(ArithmeticComputation::Add);
183        let json = serde_json::to_string(&k).expect("serialize");
184        assert!(json.contains("\"type\"") && json.contains("\"computation\""));
185        let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
186        assert_eq!(back, k);
187    }
188
189    #[test]
190    fn computation_kind_comparison_round_trip() {
191        let k = ComputationKind::Comparison(ComparisonComputation::GreaterThan);
192        let json = serde_json::to_string(&k).expect("serialize");
193        let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
194        assert_eq!(back, k);
195    }
196
197    #[test]
198    fn computation_kind_mathematical_round_trip() {
199        let k = ComputationKind::Mathematical(MathematicalComputation::Sqrt);
200        let json = serde_json::to_string(&k).expect("serialize");
201        let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
202        assert_eq!(back, k);
203    }
204
205    #[test]
206    fn computation_kind_unit_conversion_round_trip() {
207        use crate::parsing::ast::PrimitiveKind;
208        use crate::planning::semantics::SemanticConversionTarget;
209        let k = ComputationKind::UnitConversion {
210            target: SemanticConversionTarget::Type(PrimitiveKind::Number),
211        };
212        let json = serde_json::to_string(&k).expect("serialize");
213        let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
214        assert_eq!(back, k);
215    }
216
217    #[test]
218    fn veto_type_serializes_as_display_string() {
219        use super::VetoType;
220        use crate::planning::semantics::DataPath;
221        let v = VetoType::MissingData {
222            data: DataPath::new(vec![], "product".to_string()),
223        };
224        let json = serde_json::to_string(&v).expect("serialize");
225        assert_eq!(json, "\"Missing data: product\"");
226    }
227}