lemma/evaluation/
operations.rs1use std::fmt;
4
5use crate::planning::semantics::{
6 ArithmeticComputation, ComparisonComputation, DataPath, LemmaType, LiteralValue,
7 LogicalComputation, MathematicalComputation, SemanticConversionTarget, SemanticDateTime,
8 SemanticTime, TypeSpecification,
9};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, PartialEq)]
17pub enum VetoType {
18 MissingData { data: DataPath },
20 UserDefined { message: Option<String> },
22 Computation { message: String },
24}
25
26impl fmt::Display for VetoType {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 VetoType::MissingData { data } => write!(f, "Missing data: {}", data),
30 VetoType::UserDefined { message: Some(msg) } => write!(f, "{msg}"),
31 VetoType::UserDefined { message: None } => write!(f, "Vetoed"),
32 VetoType::Computation { message } => write!(f, "{message}"),
33 }
34 }
35}
36
37impl VetoType {
38 #[must_use]
39 pub fn computation(message: impl Into<String>) -> Self {
40 VetoType::Computation {
41 message: message.into(),
42 }
43 }
44}
45
46impl Serialize for VetoType {
47 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
48 where
49 S: serde::Serializer,
50 {
51 serializer.serialize_str(&self.to_string())
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize)]
57#[serde(rename_all = "snake_case")]
58pub enum OperationResult {
59 Value(LiteralValue),
61 Veto(VetoType),
63}
64
65impl OperationResult {
66 pub fn vetoed(&self) -> bool {
67 matches!(self, OperationResult::Veto(_))
68 }
69
70 #[must_use]
71 pub fn value(&self) -> Option<&LiteralValue> {
72 match self {
73 OperationResult::Value(v) => Some(v),
74 OperationResult::Veto(_) => None,
75 }
76 }
77
78 pub fn number(number: rust_decimal::Decimal) -> Self {
79 Self::Value(LiteralValue::number_from_decimal(number))
80 }
81
82 pub fn quantity(
83 value: rust_decimal::Decimal,
84 unit: impl Into<String>,
85 lemma_type: Option<LemmaType>,
86 ) -> Self {
87 use crate::computation::rational::checked_mul;
88 let lemma_type = std::sync::Arc::new(
89 lemma_type.unwrap_or_else(|| LemmaType::primitive(TypeSpecification::quantity())),
90 );
91 let unit_name = unit.into();
92 let rational = crate::literals::rational_from_parsed_decimal(value)
93 .expect("BUG: operation result quantity must lift at boundary");
94 let factor = if let TypeSpecification::Quantity { units, .. } = &lemma_type.specifications {
95 units
96 .get(&unit_name)
97 .map(|u| u.factor.clone())
98 .unwrap_or_else(|_| {
99 panic!(
100 "BUG: OperationResult::quantity unit '{}' not declared on type",
101 unit_name
102 )
103 })
104 } else {
105 crate::computation::rational::rational_one()
106 };
107 let canonical = checked_mul(&rational, &factor)
108 .expect("BUG: quantity canonicalization overflow in OperationResult::quantity");
109 Self::Value(LiteralValue::quantity_with_type(
110 canonical, unit_name, lemma_type,
111 ))
112 }
113
114 pub fn text(text: impl Into<String>) -> Self {
115 Self::Value(LiteralValue::text(text.into()))
116 }
117
118 pub fn date(date: impl Into<SemanticDateTime>) -> Self {
119 Self::Value(LiteralValue::date(date.into()))
120 }
121
122 pub fn time(time: impl Into<SemanticTime>) -> Self {
123 Self::Value(LiteralValue::time(time.into()))
124 }
125
126 pub fn boolean(boolean: bool) -> Self {
127 Self::Value(LiteralValue::from_bool(boolean))
128 }
129
130 pub fn ratio(rational: rust_decimal::Decimal) -> Self {
131 Self::Value(LiteralValue::ratio_from_decimal(rational, None))
132 }
133
134 pub fn veto(veto: impl Into<String>) -> Self {
135 Self::Veto(VetoType::UserDefined {
136 message: Some(veto.into()),
137 })
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde(tag = "type", content = "computation", rename_all = "snake_case")]
144pub enum ComputationKind {
145 Arithmetic(ArithmeticComputation),
146 Comparison(ComparisonComputation),
147 Mathematical(MathematicalComputation),
148 Logical(LogicalComputation),
149 UnitConversion {
150 target: SemanticConversionTarget,
151 },
152 ResultIsVeto,
154}
155
156#[cfg(test)]
157mod computation_kind_serde_tests {
158 use super::ComputationKind;
159 use crate::parsing::ast::{
160 ArithmeticComputation, ComparisonComputation, MathematicalComputation,
161 };
162
163 #[test]
164 fn computation_kind_arithmetic_round_trip() {
165 let k = ComputationKind::Arithmetic(ArithmeticComputation::Add);
166 let json = serde_json::to_string(&k).expect("serialize");
167 assert!(json.contains("\"type\"") && json.contains("\"computation\""));
168 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
169 assert_eq!(back, k);
170 }
171
172 #[test]
173 fn computation_kind_comparison_round_trip() {
174 let k = ComputationKind::Comparison(ComparisonComputation::GreaterThan);
175 let json = serde_json::to_string(&k).expect("serialize");
176 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
177 assert_eq!(back, k);
178 }
179
180 #[test]
181 fn computation_kind_mathematical_round_trip() {
182 let k = ComputationKind::Mathematical(MathematicalComputation::Sqrt);
183 let json = serde_json::to_string(&k).expect("serialize");
184 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
185 assert_eq!(back, k);
186 }
187
188 #[test]
189 fn computation_kind_unit_conversion_round_trip() {
190 use crate::parsing::ast::PrimitiveKind;
191 use crate::planning::semantics::SemanticConversionTarget;
192 let k = ComputationKind::UnitConversion {
193 target: SemanticConversionTarget::Type(PrimitiveKind::Number),
194 };
195 let json = serde_json::to_string(&k).expect("serialize");
196 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
197 assert_eq!(back, k);
198 }
199
200 #[test]
201 fn veto_type_serializes_as_display_string() {
202 use super::VetoType;
203 use crate::planning::semantics::DataPath;
204 let v = VetoType::MissingData {
205 data: DataPath::new(vec![], "product".to_string()),
206 };
207 let json = serde_json::to_string(&v).expect("serialize");
208 assert_eq!(json, "\"Missing data: product\"");
209 }
210}