1use crate::computation::rational::RationalInteger;
4use crate::literals::{
5 rational_to_serialized_str, BaseMeasureVector, DateTimeValue, MeasureUnit as DomainMeasureUnit,
6 MeasureUnits, RatioUnit as DomainRatioUnit, RatioUnits, TimeValue,
7};
8use crate::planning::semantics::{
9 LemmaType as DomainLemmaType, MeasureTrait as DomainMeasureTrait,
10 TypeDefiningSpec as DomainTypeDefiningSpec, TypeExtends as DomainTypeExtends,
11 TypeSpecification as DomainTypeSpecification,
12};
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub struct RationalFactor {
18 pub numer: String,
19 pub denom: String,
20}
21
22impl RationalFactor {
23 fn from_ratio(value: &RationalInteger) -> Self {
24 let reduced = value
25 .clone()
26 .try_reduce()
27 .expect("BUG: stored measure unit factor must reduce");
28 Self {
29 numer: reduced.numer_to_string(),
30 denom: reduced.denom_to_string(),
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
37pub struct NamedBound {
38 pub value: String,
39 pub unit: String,
40}
41
42fn planned_bound_decimal(rational: &RationalInteger) -> String {
43 rational_to_serialized_str(rational)
44 .expect("BUG: planned bound must serialize to decimal string")
45}
46
47fn named_bound_from(bound: &(RationalInteger, String)) -> NamedBound {
48 NamedBound {
49 value: planned_bound_decimal(&bound.0),
50 unit: bound.1.clone(),
51 }
52}
53
54fn optional_named_bound(bound: &Option<(RationalInteger, String)>) -> Option<NamedBound> {
55 bound.as_ref().map(named_bound_from)
56}
57
58fn optional_decimal_bound(bound: &Option<RationalInteger>) -> Option<String> {
59 bound.as_ref().map(planned_bound_decimal)
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
63#[serde(rename_all = "snake_case")]
64pub enum MeasureTrait {
65 Duration,
66 Calendar,
67}
68
69impl From<DomainMeasureTrait> for MeasureTrait {
70 fn from(value: DomainMeasureTrait) -> Self {
71 match value {
72 DomainMeasureTrait::Duration => Self::Duration,
73 DomainMeasureTrait::Calendar => Self::Calendar,
74 }
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
79pub struct MeasureUnit {
80 pub name: String,
81 pub factor: RationalFactor,
82 pub derived_measure_factors: Vec<(String, i32)>,
83 pub decomposition: BaseMeasureVector,
84 #[serde(skip_serializing_if = "Option::is_none", default)]
85 pub minimum: Option<String>,
86 #[serde(skip_serializing_if = "Option::is_none", default)]
87 pub maximum: Option<String>,
88 #[serde(
89 rename = "suggestion",
90 skip_serializing_if = "Option::is_none",
91 default
92 )]
93 pub suggestion_magnitude: Option<String>,
94}
95
96impl From<&DomainMeasureUnit> for MeasureUnit {
97 fn from(unit: &DomainMeasureUnit) -> Self {
98 Self {
99 name: unit.name.clone(),
100 factor: RationalFactor::from_ratio(&unit.factor),
101 derived_measure_factors: unit.derived_measure_factors.clone(),
102 decomposition: unit.decomposition.clone(),
103 minimum: unit.minimum.as_ref().map(|minimum| {
104 rational_to_serialized_str(minimum)
105 .expect("BUG: planned measure unit minimum must serialize to decimal string")
106 }),
107 maximum: unit.maximum.as_ref().map(|maximum| {
108 rational_to_serialized_str(maximum)
109 .expect("BUG: planned measure unit maximum must serialize to decimal string")
110 }),
111 suggestion_magnitude: unit.suggestion_magnitude.as_ref().map(|suggestion| {
112 rational_to_serialized_str(suggestion)
113 .expect("BUG: planned measure unit suggestion must serialize to decimal string")
114 }),
115 }
116 }
117}
118
119#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
120pub struct RatioUnit {
121 pub name: String,
122 pub value: RationalFactor,
123 #[serde(skip_serializing_if = "Option::is_none", default)]
124 pub minimum: Option<String>,
125 #[serde(skip_serializing_if = "Option::is_none", default)]
126 pub maximum: Option<String>,
127 #[serde(
128 rename = "suggestion",
129 skip_serializing_if = "Option::is_none",
130 default
131 )]
132 pub suggestion_magnitude: Option<String>,
133}
134
135impl From<&DomainRatioUnit> for RatioUnit {
136 fn from(unit: &DomainRatioUnit) -> Self {
137 Self {
138 name: unit.name.clone(),
139 value: RationalFactor::from_ratio(&unit.value),
140 minimum: unit.minimum.as_ref().map(|minimum| {
141 rational_to_serialized_str(minimum)
142 .expect("BUG: planned ratio unit minimum must serialize to decimal string")
143 }),
144 maximum: unit.maximum.as_ref().map(|maximum| {
145 rational_to_serialized_str(maximum)
146 .expect("BUG: planned ratio unit maximum must serialize to decimal string")
147 }),
148 suggestion_magnitude: unit.suggestion_magnitude.as_ref().map(|suggestion| {
149 rational_to_serialized_str(suggestion)
150 .expect("BUG: planned ratio unit suggestion must serialize to decimal string")
151 }),
152 }
153 }
154}
155
156fn measure_units_from(units: &MeasureUnits) -> Vec<MeasureUnit> {
157 units.iter().map(MeasureUnit::from).collect()
158}
159
160fn ratio_units_from(units: &RatioUnits) -> Vec<RatioUnit> {
161 units.iter().map(RatioUnit::from).collect()
162}
163
164#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
165#[serde(tag = "kind", rename_all = "snake_case")]
166pub enum TypeDefiningSpec {
167 Local,
168 Import,
169}
170
171impl From<&DomainTypeDefiningSpec> for TypeDefiningSpec {
172 fn from(value: &DomainTypeDefiningSpec) -> Self {
173 match value {
174 DomainTypeDefiningSpec::Local => Self::Local,
175 DomainTypeDefiningSpec::Import => Self::Import,
176 }
177 }
178}
179
180#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
181#[serde(tag = "kind", rename_all = "snake_case")]
182pub enum TypeExtends {
183 Primitive,
184 Custom {
185 parent: String,
186 family: String,
187 defining_spec: TypeDefiningSpec,
188 },
189}
190
191impl From<&DomainTypeExtends> for TypeExtends {
192 fn from(value: &DomainTypeExtends) -> Self {
193 match value {
194 DomainTypeExtends::Primitive => Self::Primitive,
195 DomainTypeExtends::Custom {
196 parent,
197 family,
198 defining_spec,
199 } => Self::Custom {
200 parent: parent.clone(),
201 family: family.clone(),
202 defining_spec: TypeDefiningSpec::from(defining_spec),
203 },
204 }
205 }
206}
207
208#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
209#[serde(tag = "kind", rename_all = "lowercase")]
210pub enum TypeSpecification {
211 Boolean {
212 help: String,
213 },
214 Measure {
215 #[serde(default)]
216 minimum: Option<NamedBound>,
217 #[serde(default)]
218 maximum: Option<NamedBound>,
219 decimals: Option<u8>,
220 units: Vec<MeasureUnit>,
221 #[serde(default)]
222 traits: Vec<MeasureTrait>,
223 #[serde(default)]
224 decomposition: Option<BaseMeasureVector>,
225 help: String,
226 },
227 Number {
228 #[serde(default)]
229 minimum: Option<String>,
230 #[serde(default)]
231 maximum: Option<String>,
232 decimals: Option<u8>,
233 help: String,
234 },
235 NumberRange {
236 #[serde(default)]
237 lower: Option<String>,
238 #[serde(default)]
239 upper: Option<String>,
240 #[serde(default)]
241 minimum: Option<String>,
242 #[serde(default)]
243 maximum: Option<String>,
244 help: String,
245 },
246 Ratio {
247 #[serde(default)]
248 minimum: Option<String>,
249 #[serde(default)]
250 maximum: Option<String>,
251 decimals: Option<u8>,
252 units: Vec<RatioUnit>,
253 help: String,
254 },
255 RatioRange {
256 #[serde(default)]
257 lower: Option<String>,
258 #[serde(default)]
259 upper: Option<String>,
260 #[serde(default)]
261 minimum: Option<String>,
262 #[serde(default)]
263 maximum: Option<String>,
264 units: Vec<RatioUnit>,
265 help: String,
266 },
267 Text {
268 length: Option<usize>,
269 options: Vec<String>,
270 help: String,
271 },
272 Date {
273 minimum: Option<DateTimeValue>,
274 maximum: Option<DateTimeValue>,
275 help: String,
276 },
277 DateRange {
278 lower: Option<DateTimeValue>,
279 upper: Option<DateTimeValue>,
280 #[serde(default)]
281 minimum: Option<NamedBound>,
282 #[serde(default)]
283 maximum: Option<NamedBound>,
284 help: String,
285 },
286 Time {
287 minimum: Option<TimeValue>,
288 maximum: Option<TimeValue>,
289 help: String,
290 },
291 TimeRange {
292 lower: Option<TimeValue>,
293 upper: Option<TimeValue>,
294 #[serde(default)]
295 minimum: Option<NamedBound>,
296 #[serde(default)]
297 maximum: Option<NamedBound>,
298 help: String,
299 },
300 MeasureRange {
301 #[serde(default)]
302 lower: Option<NamedBound>,
303 #[serde(default)]
304 upper: Option<NamedBound>,
305 #[serde(default)]
306 minimum: Option<NamedBound>,
307 #[serde(default)]
308 maximum: Option<NamedBound>,
309 units: Vec<MeasureUnit>,
310 #[serde(default)]
311 decomposition: Option<BaseMeasureVector>,
312 help: String,
313 },
314 Veto {
315 message: Option<String>,
316 },
317 Undetermined,
318}
319
320impl From<&DomainTypeSpecification> for TypeSpecification {
321 fn from(spec: &DomainTypeSpecification) -> Self {
322 match spec {
323 DomainTypeSpecification::Boolean { help } => Self::Boolean { help: help.clone() },
324 DomainTypeSpecification::Measure {
325 minimum,
326 maximum,
327 decimals,
328 units,
329 traits,
330 decomposition,
331 help,
332 } => Self::Measure {
333 minimum: optional_named_bound(minimum),
334 maximum: optional_named_bound(maximum),
335 decimals: *decimals,
336 units: measure_units_from(units),
337 traits: traits.iter().copied().map(MeasureTrait::from).collect(),
338 decomposition: decomposition.clone(),
339 help: help.clone(),
340 },
341 DomainTypeSpecification::Number {
342 minimum,
343 maximum,
344 decimals,
345 help,
346 } => Self::Number {
347 minimum: optional_decimal_bound(minimum),
348 maximum: optional_decimal_bound(maximum),
349 decimals: *decimals,
350 help: help.clone(),
351 },
352 DomainTypeSpecification::NumberRange {
353 lower,
354 upper,
355 minimum,
356 maximum,
357 help,
358 } => Self::NumberRange {
359 lower: optional_decimal_bound(lower),
360 upper: optional_decimal_bound(upper),
361 minimum: optional_decimal_bound(minimum),
362 maximum: optional_decimal_bound(maximum),
363 help: help.clone(),
364 },
365 DomainTypeSpecification::Ratio {
366 minimum,
367 maximum,
368 decimals,
369 units,
370 help,
371 } => Self::Ratio {
372 minimum: optional_decimal_bound(minimum),
373 maximum: optional_decimal_bound(maximum),
374 decimals: *decimals,
375 units: ratio_units_from(units),
376 help: help.clone(),
377 },
378 DomainTypeSpecification::RatioRange {
379 lower,
380 upper,
381 minimum,
382 maximum,
383 units,
384 help,
385 } => Self::RatioRange {
386 lower: optional_decimal_bound(lower),
387 upper: optional_decimal_bound(upper),
388 minimum: optional_decimal_bound(minimum),
389 maximum: optional_decimal_bound(maximum),
390 units: ratio_units_from(units),
391 help: help.clone(),
392 },
393 DomainTypeSpecification::Text {
394 length,
395 options,
396 help,
397 } => Self::Text {
398 length: *length,
399 options: options.clone(),
400 help: help.clone(),
401 },
402 DomainTypeSpecification::Date {
403 minimum,
404 maximum,
405 help,
406 } => Self::Date {
407 minimum: minimum.clone(),
408 maximum: maximum.clone(),
409 help: help.clone(),
410 },
411 DomainTypeSpecification::DateRange {
412 lower,
413 upper,
414 minimum,
415 maximum,
416 help,
417 } => Self::DateRange {
418 lower: lower.clone(),
419 upper: upper.clone(),
420 minimum: optional_named_bound(minimum),
421 maximum: optional_named_bound(maximum),
422 help: help.clone(),
423 },
424 DomainTypeSpecification::Time {
425 minimum,
426 maximum,
427 help,
428 } => Self::Time {
429 minimum: minimum.clone(),
430 maximum: maximum.clone(),
431 help: help.clone(),
432 },
433 DomainTypeSpecification::TimeRange {
434 lower,
435 upper,
436 minimum,
437 maximum,
438 help,
439 } => Self::TimeRange {
440 lower: lower.clone(),
441 upper: upper.clone(),
442 minimum: optional_named_bound(minimum),
443 maximum: optional_named_bound(maximum),
444 help: help.clone(),
445 },
446 DomainTypeSpecification::MeasureRange {
447 lower,
448 upper,
449 minimum,
450 maximum,
451 units,
452 decomposition,
453 help,
454 } => Self::MeasureRange {
455 lower: optional_named_bound(lower),
456 upper: optional_named_bound(upper),
457 minimum: optional_named_bound(minimum),
458 maximum: optional_named_bound(maximum),
459 units: measure_units_from(units),
460 decomposition: decomposition.clone(),
461 help: help.clone(),
462 },
463 DomainTypeSpecification::Veto { message } => Self::Veto {
464 message: message.clone(),
465 },
466 DomainTypeSpecification::Undetermined => Self::Undetermined,
467 }
468 }
469}
470
471#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
472pub struct LemmaType {
473 pub name: Option<String>,
474 #[serde(flatten)]
475 pub specifications: TypeSpecification,
476 pub extends: TypeExtends,
477 #[serde(default, skip_serializing_if = "Option::is_none")]
478 pub measure_binding_unit: Option<String>,
479}
480
481impl From<&DomainLemmaType> for LemmaType {
482 fn from(lemma_type: &DomainLemmaType) -> Self {
483 Self {
484 name: lemma_type.name.clone(),
485 specifications: TypeSpecification::from(&lemma_type.specifications),
486 extends: TypeExtends::from(&lemma_type.extends),
487 measure_binding_unit: lemma_type.measure_binding_unit.clone(),
488 }
489 }
490}