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(Box<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.as_ref()),
74 OperationResult::Veto(_) => None,
75 }
76 }
77
78 pub fn number(number: rust_decimal::Decimal) -> Self {
79 Self::Value(Box::new(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.get(&unit_name).map(|u| u.factor).unwrap_or_else(|_| {
96 panic!(
97 "BUG: OperationResult::quantity unit '{}' not declared on type",
98 unit_name
99 )
100 })
101 } else {
102 crate::computation::rational::rational_one()
103 };
104 let canonical = checked_mul(&rational, &factor)
105 .expect("BUG: quantity canonicalization overflow in OperationResult::quantity");
106 Self::Value(Box::new(LiteralValue::quantity_with_type(
107 canonical, unit_name, lemma_type,
108 )))
109 }
110
111 pub fn text(text: impl Into<String>) -> Self {
112 Self::Value(Box::new(LiteralValue::text(text.into())))
113 }
114
115 pub fn date(date: impl Into<SemanticDateTime>) -> Self {
116 Self::Value(Box::new(LiteralValue::date(date.into())))
117 }
118
119 pub fn time(time: impl Into<SemanticTime>) -> Self {
120 Self::Value(Box::new(LiteralValue::time(time.into())))
121 }
122
123 pub fn boolean(boolean: bool) -> Self {
124 Self::Value(Box::new(LiteralValue::from_bool(boolean)))
125 }
126
127 pub fn ratio(rational: rust_decimal::Decimal) -> Self {
128 Self::Value(Box::new(LiteralValue::ratio_from_decimal(rational, None)))
129 }
130
131 pub fn veto(veto: impl Into<String>) -> Self {
132 Self::Veto(VetoType::UserDefined {
133 message: Some(veto.into()),
134 })
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
140#[serde(tag = "type", content = "computation", rename_all = "snake_case")]
141pub enum ComputationKind {
142 Arithmetic(ArithmeticComputation),
143 Comparison(ComparisonComputation),
144 Mathematical(MathematicalComputation),
145 Logical(LogicalComputation),
146 UnitConversion {
147 target: SemanticConversionTarget,
148 },
149 ResultIsVeto,
151}
152
153#[cfg(test)]
154mod computation_kind_serde_tests {
155 use super::ComputationKind;
156 use crate::parsing::ast::{
157 ArithmeticComputation, ComparisonComputation, MathematicalComputation,
158 };
159
160 #[test]
161 fn computation_kind_arithmetic_round_trip() {
162 let k = ComputationKind::Arithmetic(ArithmeticComputation::Add);
163 let json = serde_json::to_string(&k).expect("serialize");
164 assert!(json.contains("\"type\"") && json.contains("\"computation\""));
165 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
166 assert_eq!(back, k);
167 }
168
169 #[test]
170 fn computation_kind_comparison_round_trip() {
171 let k = ComputationKind::Comparison(ComparisonComputation::GreaterThan);
172 let json = serde_json::to_string(&k).expect("serialize");
173 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
174 assert_eq!(back, k);
175 }
176
177 #[test]
178 fn computation_kind_mathematical_round_trip() {
179 let k = ComputationKind::Mathematical(MathematicalComputation::Sqrt);
180 let json = serde_json::to_string(&k).expect("serialize");
181 let back: ComputationKind = serde_json::from_str(&json).expect("deserialize");
182 assert_eq!(back, k);
183 }
184
185 #[test]
186 fn computation_kind_unit_conversion_round_trip() {
187 use crate::parsing::ast::PrimitiveKind;
188 use crate::planning::semantics::SemanticConversionTarget;
189 let k = ComputationKind::UnitConversion {
190 target: SemanticConversionTarget::Type(PrimitiveKind::Number),
191 };
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 veto_type_serializes_as_display_string() {
199 use super::VetoType;
200 use crate::planning::semantics::DataPath;
201 let v = VetoType::MissingData {
202 data: DataPath::new(vec![], "product".to_string()),
203 };
204 let json = serde_json::to_string(&v).expect("serialize");
205 assert_eq!(json, "\"Missing data: product\"");
206 }
207}