1use crate::computation::rational::{
14 checked_div, checked_mul, decimal_to_display_str, NumericFailure, RationalInteger,
15};
16use crate::literals::rational_from_parsed_decimal;
17use crate::planning::semantics::{
18 format_decimal_for_api, range_element_type_specification, ratio_element_type_for_api,
19 semantic_calendar_unit_from_measure_type, LemmaType, LiteralUnitMapFailure, LiteralValue,
20 SemanticDateTime, SemanticTime, SpecInstance, SpecValue, TypeSpecification, TypedLiteral,
21 UnitFactorSource, ValueKind,
22};
23use crate::planning::unit_family::{declared_bare_names_only, FamilyUnitCatalog, FamilyUnitEntry};
24use indexmap::IndexMap;
25use rust_decimal::Decimal;
26use serde::{Deserialize, Serialize};
27use std::borrow::Cow;
28use std::collections::{BTreeMap, HashMap};
29use std::fmt;
30use std::sync::Arc;
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct CalendarResult {
35 pub value: Decimal,
36 pub unit: String,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46pub struct RangeResult {
47 pub from: RuleResultValue,
48 pub to: RuleResultValue,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
61pub struct RuleResultValue {
62 #[serde(default, skip_serializing_if = "Option::is_none")]
65 pub result: Option<String>,
66 #[serde(default, skip_serializing_if = "Option::is_none")]
67 pub measure: Option<BTreeMap<String, Decimal>>,
68 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub ratio: Option<BTreeMap<String, Decimal>>,
70 #[serde(default, skip_serializing_if = "Option::is_none")]
71 pub number: Option<Decimal>,
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub boolean: Option<bool>,
74 #[serde(default, skip_serializing_if = "Option::is_none")]
75 pub text: Option<String>,
76 #[serde(default, skip_serializing_if = "Option::is_none")]
77 pub date: Option<SemanticDateTime>,
78 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub time: Option<SemanticTime>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
81 pub calendar: Option<CalendarResult>,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub unit: Option<String>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub range: Option<Box<RangeResult>>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub spec: Option<Box<SpecResult>>,
93 #[serde(default, skip_serializing_if = "Option::is_none")]
95 pub row: Option<RowReference>,
96 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub list: Option<Vec<RuleResultValue>>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub fields: Option<IndexMap<String, RuleResultValue>>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
105 pub reference: Option<String>,
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
110pub struct SpecResult {
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub repository: Option<String>,
113 pub spec: String,
114 pub effective: String,
116 pub instance: String,
118 pub path: Vec<crate::api::PathSegment>,
120 pub data: IndexMap<String, SpecRuleResult>,
124 pub rules: IndexMap<String, SpecRuleResult>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
131pub struct RowReference {
132 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub repository: Option<String>,
134 pub spec: String,
135 pub effective: String,
137 #[serde(default, skip_serializing_if = "Option::is_none")]
142 pub instance: Option<String>,
143 pub index: usize,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
148pub struct RowResult {
149 #[serde(default, skip_serializing_if = "Option::is_none")]
150 pub repository: Option<String>,
151 pub spec: String,
152 pub effective: String,
154 pub data: IndexMap<String, SpecRuleResult>,
157 pub rules: IndexMap<String, SpecRuleResult>,
159}
160
161#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
165pub struct SpecRuleResult {
166 pub vetoed: bool,
167 #[serde(default, skip_serializing_if = "Option::is_none")]
168 pub veto_reason: Option<String>,
169 pub rule_type: String,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
171 pub result: Option<RuleResultValue>,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq)]
176pub enum RuleResultValueFailure {
177 DecimalLimit,
178 NumericOverflow,
179 OutOfMemory,
180}
181
182pub(crate) enum UnitExpansion<'a> {
184 Declared,
186 Family(&'a FamilyUnitEntry),
188}
189
190pub fn rule_result_value_failure_message(failure: RuleResultValueFailure) -> &'static str {
192 match failure {
193 RuleResultValueFailure::DecimalLimit => {
194 "The calculated result exceeds the decimal value limit."
195 }
196 RuleResultValueFailure::NumericOverflow => "The numeric result overflowed.",
197 RuleResultValueFailure::OutOfMemory => "The calculation ran out of memory.",
198 }
199}
200
201fn map_numeric_to_rule_result_value_failure(failure: NumericFailure) -> RuleResultValueFailure {
202 match failure {
203 NumericFailure::Overflow => RuleResultValueFailure::DecimalLimit,
204 NumericFailure::OutOfMemory => RuleResultValueFailure::OutOfMemory,
205 NumericFailure::DivisionByZero => {
206 panic!(
207 "BUG: decimal commit encountered division by zero while building RuleResultValue"
208 )
209 }
210 NumericFailure::Irrational => {
211 panic!(
212 "BUG: decimal commit encountered irrational result while building RuleResultValue"
213 )
214 }
215 NumericFailure::MagnitudeLimit => {
216 panic!("BUG: decimal commit does not apply the arithmetic magnitude limit")
217 }
218 }
219}
220
221fn map_unit_conversion_failure(failure: NumericFailure) -> RuleResultValueFailure {
222 match failure {
223 NumericFailure::Overflow => RuleResultValueFailure::NumericOverflow,
224 NumericFailure::OutOfMemory => RuleResultValueFailure::OutOfMemory,
225 NumericFailure::DivisionByZero => {
226 panic!("BUG: unit conversion division by zero while building RuleResultValue")
227 }
228 NumericFailure::Irrational => {
229 panic!("BUG: unit conversion irrational while building RuleResultValue")
230 }
231 NumericFailure::MagnitudeLimit => {
232 panic!("BUG: unit conversion does not apply the arithmetic magnitude limit")
233 }
234 }
235}
236
237fn map_literal_unit_map_failure(failure: LiteralUnitMapFailure) -> RuleResultValueFailure {
238 match failure {
239 LiteralUnitMapFailure::Commit(failure) => map_numeric_to_rule_result_value_failure(failure),
240 LiteralUnitMapFailure::UnitConversion(failure) => map_unit_conversion_failure(failure),
241 }
242}
243
244fn expansion_for_type<'a>(
245 rule_type: &LemmaType,
246 catalog: Option<&'a FamilyUnitCatalog>,
247) -> UnitExpansion<'a> {
248 let Some(catalog) = catalog else {
249 return UnitExpansion::Declared;
250 };
251 match catalog.entry_for_type(rule_type) {
252 Some(entry) => UnitExpansion::Family(entry),
253 None => UnitExpansion::Declared,
254 }
255}
256
257fn unit_names_for_expansion(
258 result_type: &LemmaType,
259 expansion: &UnitExpansion<'_>,
260 catalog: Option<&FamilyUnitCatalog>,
261) -> Arc<[String]> {
262 match expansion {
263 UnitExpansion::Declared => Arc::from(declared_bare_names_only(result_type)),
264 UnitExpansion::Family(_) => catalog
265 .expect("BUG: family expansion requires FamilyUnitCatalog")
266 .ordered_bare_names_for_type(result_type),
267 }
268}
269
270fn measure_factor_source<'a>(
271 result_type: &'a LemmaType,
272 expansion: &'a UnitExpansion<'a>,
273) -> UnitFactorSource<'a> {
274 match expansion {
275 UnitExpansion::Declared => UnitFactorSource::DeclaredOn(result_type),
276 UnitExpansion::Family(entry) => UnitFactorSource::Merged {
277 measure: entry.merged_measure_units.as_ref(),
278 ratio: entry.merged_ratio_units.as_ref(),
279 },
280 }
281}
282
283fn ratio_factor_source<'a>(
284 result_type: &'a LemmaType,
285 expansion: &'a UnitExpansion<'a>,
286) -> UnitFactorSource<'a> {
287 measure_factor_source(result_type, expansion)
288}
289
290enum NodeType<'a> {
293 Borrowed(&'a LemmaType),
294 Shared(Arc<LemmaType>),
295}
296
297impl NodeType<'_> {
298 fn get(&self) -> &LemmaType {
299 match self {
300 Self::Borrowed(lemma_type) => lemma_type,
301 Self::Shared(lemma_type) => lemma_type.as_ref(),
302 }
303 }
304}
305
306enum ResultValueTask<'a> {
307 Visit {
308 literal: Cow<'a, LiteralValue>,
309 lemma_type: NodeType<'a>,
310 },
311 AssembleList {
312 display: String,
313 rows: usize,
314 },
315}
316
317fn result_value_from_literal(
318 literal: &LiteralValue,
319 lemma_type: &LemmaType,
320 expansion: &UnitExpansion<'_>,
321 catalog: Option<&FamilyUnitCatalog>,
322 runtime_scales: Option<&HashMap<String, RationalInteger>>,
323) -> Result<RuleResultValue, RuleResultValueFailure> {
324 let mut tasks = vec![ResultValueTask::Visit {
325 literal: Cow::Borrowed(literal),
326 lemma_type: NodeType::Borrowed(lemma_type),
327 }];
328 let mut converted: Vec<RuleResultValue> = Vec::new();
329 while let Some(task) = tasks.pop() {
330 match task {
331 ResultValueTask::Visit {
332 literal,
333 lemma_type,
334 } => {
335 let lemma_type = lemma_type.get();
336 match &literal.value {
337 ValueKind::Range(from, to) => {
338 let from_value = result_value_from_range_endpoint(
339 &from.to_literal(),
340 from.lemma_type.as_ref(),
341 expansion,
342 catalog,
343 runtime_scales,
344 )?;
345 let to_value = result_value_from_range_endpoint(
346 &to.to_literal(),
347 to.lemma_type.as_ref(),
348 expansion,
349 catalog,
350 runtime_scales,
351 )?;
352 converted.push(RuleResultValue {
353 result: Some(literal.display_value_with_type(lemma_type)),
354 range: Some(Box::new(RangeResult {
355 from: from_value,
356 to: to_value,
357 })),
358 ..RuleResultValue::default()
359 });
360 }
361 ValueKind::List(rows) => {
362 tasks.push(ResultValueTask::AssembleList {
363 display: literal.display_value_with_type(lemma_type),
364 rows: rows.len(),
365 });
366 for row in rows.iter().rev() {
367 tasks.push(ResultValueTask::Visit {
368 literal: Cow::Owned(row.to_literal()),
369 lemma_type: NodeType::Shared(Arc::clone(&row.lemma_type)),
370 });
371 }
372 }
373 _ => converted.push(result_value_from_non_range_literal(
374 &literal,
375 lemma_type,
376 expansion,
377 catalog,
378 runtime_scales,
379 )?),
380 }
381 }
382 ResultValueTask::AssembleList { display, rows } => {
383 let list = converted.split_off(converted.len() - rows);
384 converted.push(RuleResultValue {
385 result: Some(display),
386 list: Some(list),
387 ..RuleResultValue::default()
388 });
389 }
390 }
391 }
392 Ok(converted
393 .pop()
394 .expect("BUG: literal conversion yields exactly one RuleResultValue"))
395}
396
397pub(crate) fn rule_result_value_from_literal(
401 literal: &LiteralValue,
402 rule_type: &LemmaType,
403 catalog: &FamilyUnitCatalog,
404 runtime_scales: Option<&HashMap<String, RationalInteger>>,
405) -> Result<RuleResultValue, RuleResultValueFailure> {
406 let expansion = expansion_for_type(rule_type, Some(catalog));
407 result_value_from_literal(
408 literal,
409 rule_type,
410 &expansion,
411 Some(catalog),
412 runtime_scales,
413 )
414}
415
416pub fn type_scoped_result_value_from_literal(
418 literal: &LiteralValue,
419 lemma_type: &LemmaType,
420) -> Result<RuleResultValue, RuleResultValueFailure> {
421 let expansion = UnitExpansion::Declared;
422 result_value_from_literal(literal, lemma_type, &expansion, None, None)
423}
424
425fn result_value_from_range_endpoint(
426 endpoint: &LiteralValue,
427 endpoint_type: &LemmaType,
428 expansion: &UnitExpansion<'_>,
429 catalog: Option<&FamilyUnitCatalog>,
430 runtime_scales: Option<&HashMap<String, RationalInteger>>,
431) -> Result<RuleResultValue, RuleResultValueFailure> {
432 if matches!(&endpoint.value, ValueKind::Range(_, _)) {
433 panic!("BUG: range endpoint must not itself be a range");
434 }
435 result_value_from_non_range_literal(endpoint, endpoint_type, expansion, catalog, runtime_scales)
436}
437
438fn result_value_from_non_range_literal(
439 literal: &LiteralValue,
440 result_type: &LemmaType,
441 expansion: &UnitExpansion<'_>,
442 catalog: Option<&FamilyUnitCatalog>,
443 runtime_scales: Option<&HashMap<String, RationalInteger>>,
444) -> Result<RuleResultValue, RuleResultValueFailure> {
445 match &literal.value {
446 ValueKind::Measure(rational) if result_type.is_calendar_like() => {
447 let unit = semantic_calendar_unit_from_measure_type(result_type);
448 let value = result_type
449 .try_rational_as_decimal(rational)
450 .map_err(map_numeric_to_rule_result_value_failure)?;
451 let result = Some(literal.display_value_with_type(result_type));
452 Ok(RuleResultValue {
453 result,
454 calendar: Some(CalendarResult {
455 value,
456 unit: unit.to_string(),
457 }),
458 ..RuleResultValue::default()
459 })
460 }
461 ValueKind::UnscaledMeasure { magnitude, unit } => {
462 let decimal = magnitude
463 .try_to_decimal()
464 .map_err(map_numeric_to_rule_result_value_failure)?;
465 let api_decimal = format_decimal_for_api(decimal);
466 let display = format!("{} {unit}", decimal_to_display_str(&decimal));
467 let mut measure = BTreeMap::new();
468 measure.insert(unit.to_string(), api_decimal);
469 Ok(RuleResultValue {
470 result: Some(display),
471 measure: Some(measure),
472 unit: Some(unit.to_string()),
473 ..RuleResultValue::default()
474 })
475 }
476 ValueKind::Measure(_) => {
477 let unit_names = unit_names_for_expansion(result_type, expansion, catalog);
478 let unit_name_refs: Vec<&str> = unit_names.iter().map(String::as_str).collect();
479 let fallback = measure_factor_source(result_type, expansion);
480 let source = match runtime_scales {
481 Some(scales) => UnitFactorSource::Runtime {
482 scales,
483 fallback: &fallback,
484 },
485 None => fallback,
486 };
487 let measure = result_type
488 .measure_literal_unit_map(literal, &unit_name_refs, source)
489 .map_err(map_literal_unit_map_failure)?;
490 let result = Some(literal.display_value_with_scales(result_type, runtime_scales));
491 let unit = result_type.unit().map(|_| {
492 result_type
493 .measure_display_unit_name(runtime_scales)
494 .expect("BUG: a measure with a written unit has a unit table")
495 .to_string()
496 });
497 Ok(RuleResultValue {
498 result,
499 measure: Some(measure),
500 unit,
501 ..RuleResultValue::default()
502 })
503 }
504 ValueKind::Ratio(_) => {
505 let result = Some(literal.display_value_with_type(result_type));
506 let unit_names = unit_names_for_expansion(result_type, expansion, catalog);
507 let unit_name_refs: Vec<&str> = unit_names.iter().map(String::as_str).collect();
508 Ok(RuleResultValue {
509 result,
510 ratio: Some(
511 result_type
512 .ratio_literal_unit_map(
513 literal,
514 &unit_name_refs,
515 ratio_factor_source(result_type, expansion),
516 )
517 .map_err(map_literal_unit_map_failure)?,
518 ),
519 unit: result_type.unit().map(str::to_string),
520 ..RuleResultValue::default()
521 })
522 }
523 other => scalar_result_value(result_type, other),
524 }
525}
526
527fn scalar_result_value(
528 result_type: &LemmaType,
529 value: &ValueKind,
530) -> Result<RuleResultValue, RuleResultValueFailure> {
531 match value {
532 ValueKind::Number(rational) => {
533 let decimal = rational
534 .try_to_decimal()
535 .map_err(map_numeric_to_rule_result_value_failure)?;
536 let api_decimal = format_decimal_for_api(decimal);
537 let display_string = decimal_to_display_str(&decimal);
538 Ok(RuleResultValue {
539 result: Some(display_string),
540 number: Some(api_decimal),
541 ..RuleResultValue::default()
542 })
543 }
544 ValueKind::Boolean(b) => {
545 let result = Some(value.to_string());
546 Ok(RuleResultValue {
547 result,
548 boolean: Some(*b),
549 ..RuleResultValue::default()
550 })
551 }
552 ValueKind::Text(_) | ValueKind::Date(_) | ValueKind::Time(_) => {
553 let result = Some(value.to_string());
554 Ok(scalar_result_value_non_numeric(result, value))
555 }
556 ValueKind::Measure(_) | ValueKind::Ratio(_) | ValueKind::UnscaledMeasure { .. } => {
557 unreachable!("BUG: measure, ratio and unscaled measure must be handled by caller")
558 }
559 ValueKind::Range(_, _) => {
560 unreachable!("BUG: range must be handled by result_value_from_literal")
561 }
562 ValueKind::List(_) => {
563 unreachable!("BUG: list must be handled by result_value_from_literal")
564 }
565 ValueKind::Spec(SpecValue::Row(key)) => match key.segments() {
566 Some(path) => Ok(RuleResultValue {
567 result: Some(key.to_string()),
568 spec: Some(Box::new(SpecResult {
569 repository: key.row.identity.repository.clone(),
570 spec: key.row.identity.spec.clone(),
571 effective: key.row.identity.effective.to_string(),
572 instance: key.to_string(),
573 path: path.iter().map(crate::api::PathSegment::from).collect(),
574 data: IndexMap::new(),
575 rules: IndexMap::new(),
576 })),
577 ..RuleResultValue::default()
578 }),
579 None => Ok(RuleResultValue {
580 result: Some(value.to_string()),
581 ..RuleResultValue::default()
582 }),
583 },
584 ValueKind::Spec(SpecValue::Fixed(instance)) => {
585 let identity = result_type
586 .spec_identity()
587 .expect("BUG: spec value whose type is not a spec");
588 Ok(RuleResultValue {
589 result: Some(instance.to_string()),
590 spec: Some(Box::new(SpecResult {
591 repository: identity.repository.clone(),
592 spec: identity.spec.clone(),
593 effective: identity.effective.to_string(),
594 instance: instance.to_string(),
595 path: instance
596 .prefix
597 .iter()
598 .map(crate::api::PathSegment::from)
599 .collect(),
600 data: IndexMap::new(),
601 rules: IndexMap::new(),
602 })),
603 ..RuleResultValue::default()
604 })
605 }
606 }
607}
608
609fn scalar_result_value_non_numeric(result: Option<String>, value: &ValueKind) -> RuleResultValue {
610 match value {
611 ValueKind::Text(s) => RuleResultValue {
612 result,
613 text: Some(s.to_string()),
614 ..RuleResultValue::default()
615 },
616 ValueKind::Date(d) => RuleResultValue {
617 result,
618 date: Some(d.clone()),
619 ..RuleResultValue::default()
620 },
621 ValueKind::Time(t) => RuleResultValue {
622 result,
623 time: Some(t.clone()),
624 ..RuleResultValue::default()
625 },
626 _ => unreachable!("BUG: scalar_result_value_non_numeric called with non-scalar type"),
627 }
628}
629
630fn literal_from_measure_map(
631 measure: &BTreeMap<String, Decimal>,
632 rule_type: &LemmaType,
633) -> LiteralValue {
634 let unit = rule_type
635 .unit()
636 .and_then(|name| rule_type.measure_units_table()?.get(name).ok())
637 .or_else(|| rule_type.measure_fallback_unit())
638 .expect(
639 "BUG: reconstructing a measure from a unit map requires a declared unit on the type",
640 );
641 let magnitude = measure
642 .get(&unit.name)
643 .unwrap_or_else(|| panic!("BUG: measure map missing unit '{}'", unit.name));
644 let rational = rational_from_parsed_decimal(*magnitude)
645 .expect("BUG: measure rule result value must lift to rational");
646 match unit.literal_factor() {
647 Some(factor) => {
648 let canonical = checked_mul(&rational, factor).unwrap_or_else(|failure| {
649 panic!(
650 "BUG: measure canonicalization from RuleResultValue fields failed: {failure}"
651 )
652 });
653 LiteralValue::measure(canonical)
654 }
655 None => LiteralValue::new(ValueKind::UnscaledMeasure {
656 magnitude: rational,
657 unit: Arc::from(unit.name.as_str()),
658 }),
659 }
660}
661
662fn literal_from_ratio_map(
663 ratio: &BTreeMap<String, Decimal>,
664 rule_type: &LemmaType,
665) -> LiteralValue {
666 let ratio_type = ratio_element_type_for_api(rule_type);
667 let units = match &ratio_type.specifications {
668 TypeSpecification::Ratio { units, .. } => units,
669 _ => panic!(
670 "BUG: ratio rule result type must be Ratio, got {}",
671 rule_type.name()
672 ),
673 };
674 let unit = rule_type
675 .unit()
676 .and_then(|name| units.get(name).ok())
677 .or_else(|| units.iter().next())
678 .expect("BUG: ratio rule result type must declare at least one unit");
679 let magnitude = ratio
680 .get(&unit.name)
681 .unwrap_or_else(|| panic!("BUG: ratio map missing unit '{}'", unit.name));
682 let display_rational = rational_from_parsed_decimal(*magnitude)
683 .expect("BUG: ratio rule result value must lift to rational");
684 let canonical = checked_div(&display_rational, &unit.value).unwrap_or_else(|failure| {
685 panic!("BUG: ratio canonicalization from RuleResultValue fields failed: {failure}")
686 });
687 LiteralValue::ratio(canonical)
688}
689
690impl RuleResultValue {
691 pub fn to_literal(&self, rule_type: &LemmaType) -> LiteralValue {
698 if let Some(row) = &self.row {
699 panic!(
700 "BUG: list row '{}' cannot be rebuilt from its API value",
701 row.instance.as_deref().unwrap_or("<appended>")
702 );
703 }
704 if let Some(spec) = &self.spec {
705 assert!(
706 !spec.path.is_empty(),
707 "BUG: spec result '{}' has no uses hop",
708 spec.instance
709 );
710 return LiteralValue::new(ValueKind::Spec(SpecValue::Fixed(SpecInstance {
711 prefix: spec
712 .path
713 .iter()
714 .map(|segment| crate::planning::semantics::PathSegment {
715 uses: Arc::from(segment.uses.as_str()),
716 row: segment.row,
717 repository: segment.repository.as_deref().map(Arc::from),
718 spec: Arc::from(segment.spec.as_str()),
719 })
720 .collect(),
721 })));
722 }
723 if let Some(rows) = &self.list {
724 let TypeSpecification::List { element, .. } = &rule_type.specifications else {
725 panic!(
726 "BUG: list result whose rule type is not a list ({})",
727 rule_type.name()
728 );
729 };
730 return LiteralValue::new(ValueKind::List(
731 rows.iter()
732 .map(|row| {
733 let row_type =
734 endpoint_type_with_written_unit(element, row.unit.as_deref());
735 TypedLiteral {
736 value: row.to_literal(&row_type).value,
737 lemma_type: Arc::new(row_type),
738 }
739 })
740 .collect(),
741 ));
742 }
743 if let Some(range) = &self.range {
744 if range.from.range.is_some() || range.to.range.is_some() {
745 panic!("BUG: range endpoint must not itself be a range");
746 }
747 let endpoint_schema =
748 element_type_from_range_rule(rule_type).unwrap_or_else(|| rule_type.clone());
749 let left_type =
750 endpoint_type_with_written_unit(&endpoint_schema, range.from.unit.as_deref());
751 let right_type =
752 endpoint_type_with_written_unit(&endpoint_schema, range.to.unit.as_deref());
753 let left = range.from.to_literal(&left_type);
754 let right = range.to.to_literal(&right_type);
755 return LiteralValue::range(
756 TypedLiteral {
757 value: left.value,
758 lemma_type: Arc::new(left_type),
759 },
760 TypedLiteral {
761 value: right.value,
762 lemma_type: Arc::new(right_type),
763 },
764 );
765 }
766
767 if let Some(b) = self.boolean {
768 return LiteralValue::from_bool(b);
769 }
770 let owned_rule_type = match self.unit.as_deref() {
771 Some(unit)
772 if matches!(
773 &rule_type.specifications,
774 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
775 ) && rule_type.unit() != Some(unit) =>
776 {
777 Arc::new(rule_type.clone().with_unit(unit))
778 }
779 _ => Arc::new(rule_type.clone()),
780 };
781 if let Some(number) = self.number {
782 return LiteralValue::number_from_decimal(number);
783 }
784 if let Some(calendar) = &self.calendar {
785 let rational = rational_from_parsed_decimal(calendar.value)
786 .expect("BUG: calendar rule result value must lift to rational");
787 return LiteralValue::measure(rational);
788 }
789 if let Some(measure) = &self.measure {
790 return literal_from_measure_map(measure, owned_rule_type.as_ref());
791 }
792 if let Some(ratio) = &self.ratio {
793 return literal_from_ratio_map(ratio, owned_rule_type.as_ref());
794 }
795 if let Some(date) = &self.date {
796 return LiteralValue::date(date.clone());
797 }
798 if let Some(time) = &self.time {
799 return LiteralValue::time(time.clone());
800 }
801 if let Some(text) = &self.text {
802 return LiteralValue::text(text.clone());
803 }
804 panic!("BUG: rule result value fields cannot reconstruct literal");
805 }
806}
807
808fn endpoint_type_with_written_unit(endpoint_schema: &LemmaType, unit: Option<&str>) -> LemmaType {
809 match unit {
810 Some(unit)
811 if matches!(
812 &endpoint_schema.specifications,
813 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
814 ) && endpoint_schema.unit() != Some(unit) =>
815 {
816 endpoint_schema.clone().with_unit(unit)
817 }
818 _ => endpoint_schema.clone(),
819 }
820}
821
822fn element_type_from_range_rule(rule_type: &LemmaType) -> Option<LemmaType> {
823 range_element_type_specification(&rule_type.specifications).map(LemmaType::primitive)
824}
825
826fn format_unit_map(map: &BTreeMap<String, Decimal>) -> String {
827 map.iter()
828 .map(|(unit, value)| format!("{value} {unit}"))
829 .collect::<Vec<_>>()
830 .join(", ")
831}
832
833impl fmt::Display for RuleResultValue {
834 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835 if let Some(range) = &self.range {
836 return write!(f, "{}...{}", range.from, range.to);
837 }
838 if let Some(measure) = &self.measure {
839 return write!(f, "{}", format_unit_map(measure));
840 }
841 if let Some(ratio) = &self.ratio {
842 return write!(f, "{}", format_unit_map(ratio));
843 }
844 if let Some(number) = &self.number {
845 return write!(f, "{number}");
846 }
847 if let Some(b) = self.boolean {
848 return write!(f, "{b}");
849 }
850 if let Some(text) = &self.text {
851 return write!(f, "{text}");
852 }
853 if let Some(date) = &self.date {
854 return write!(f, "{date}");
855 }
856 if let Some(time) = &self.time {
857 return write!(f, "{time}");
858 }
859 if let Some(calendar) = &self.calendar {
860 return write!(f, "{} {}", calendar.value, calendar.unit);
861 }
862 if let Some(display) = &self.result {
863 return write!(f, "{display}");
864 }
865 if let Some(reference) = &self.reference {
866 return write!(f, "{reference}");
867 }
868 write!(f, "")
869 }
870}