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().and_then(|_| {
494 result_type
495 .measure_display_unit_name(runtime_scales)
496 .map(str::to_string)
497 });
498 Ok(RuleResultValue {
499 result,
500 measure: Some(measure),
501 unit,
502 ..RuleResultValue::default()
503 })
504 }
505 ValueKind::Ratio(_) => {
506 let result = Some(literal.display_value_with_type(result_type));
507 let unit_names = unit_names_for_expansion(result_type, expansion, catalog);
508 let unit_name_refs: Vec<&str> = unit_names.iter().map(String::as_str).collect();
509 Ok(RuleResultValue {
510 result,
511 ratio: Some(
512 result_type
513 .ratio_literal_unit_map(
514 literal,
515 &unit_name_refs,
516 ratio_factor_source(result_type, expansion),
517 )
518 .map_err(map_literal_unit_map_failure)?,
519 ),
520 unit: result_type.unit().map(str::to_string),
521 ..RuleResultValue::default()
522 })
523 }
524 other => scalar_result_value(result_type, other),
525 }
526}
527
528fn scalar_result_value(
529 result_type: &LemmaType,
530 value: &ValueKind,
531) -> Result<RuleResultValue, RuleResultValueFailure> {
532 match value {
533 ValueKind::Number(rational) => {
534 let decimal = rational
535 .try_to_decimal()
536 .map_err(map_numeric_to_rule_result_value_failure)?;
537 let api_decimal = format_decimal_for_api(decimal);
538 let display_string = decimal_to_display_str(&decimal);
539 Ok(RuleResultValue {
540 result: Some(display_string),
541 number: Some(api_decimal),
542 ..RuleResultValue::default()
543 })
544 }
545 ValueKind::Boolean(b) => {
546 let result = Some(value.to_string());
547 Ok(RuleResultValue {
548 result,
549 boolean: Some(*b),
550 ..RuleResultValue::default()
551 })
552 }
553 ValueKind::Text(_) | ValueKind::Date(_) | ValueKind::Time(_) => {
554 let result = Some(value.to_string());
555 Ok(scalar_result_value_non_numeric(result, value))
556 }
557 ValueKind::Measure(_) | ValueKind::Ratio(_) | ValueKind::UnscaledMeasure { .. } => {
558 unreachable!("BUG: measure, ratio and unscaled measure must be handled by caller")
559 }
560 ValueKind::Range(_, _) => {
561 unreachable!("BUG: range must be handled by result_value_from_literal")
562 }
563 ValueKind::List(_) => {
564 unreachable!("BUG: list must be handled by result_value_from_literal")
565 }
566 ValueKind::Spec(SpecValue::Row(key)) => match key.segments() {
567 Some(path) => Ok(RuleResultValue {
568 result: Some(key.to_string()),
569 spec: Some(Box::new(SpecResult {
570 repository: key.row.identity.repository.clone(),
571 spec: key.row.identity.spec.clone(),
572 effective: key.row.identity.effective.to_string(),
573 instance: key.to_string(),
574 path: path.iter().map(crate::api::PathSegment::from).collect(),
575 data: IndexMap::new(),
576 rules: IndexMap::new(),
577 })),
578 ..RuleResultValue::default()
579 }),
580 None => Ok(RuleResultValue {
581 result: Some(value.to_string()),
582 ..RuleResultValue::default()
583 }),
584 },
585 ValueKind::Spec(SpecValue::Fixed(instance)) => {
586 let identity = result_type
587 .spec_identity()
588 .expect("BUG: spec value whose type is not a spec");
589 Ok(RuleResultValue {
590 result: Some(instance.to_string()),
591 spec: Some(Box::new(SpecResult {
592 repository: identity.repository.clone(),
593 spec: identity.spec.clone(),
594 effective: identity.effective.to_string(),
595 instance: instance.to_string(),
596 path: instance
597 .prefix
598 .iter()
599 .map(crate::api::PathSegment::from)
600 .collect(),
601 data: IndexMap::new(),
602 rules: IndexMap::new(),
603 })),
604 ..RuleResultValue::default()
605 })
606 }
607 }
608}
609
610fn scalar_result_value_non_numeric(result: Option<String>, value: &ValueKind) -> RuleResultValue {
611 match value {
612 ValueKind::Text(s) => RuleResultValue {
613 result,
614 text: Some(s.to_string()),
615 ..RuleResultValue::default()
616 },
617 ValueKind::Date(d) => RuleResultValue {
618 result,
619 date: Some(d.clone()),
620 ..RuleResultValue::default()
621 },
622 ValueKind::Time(t) => RuleResultValue {
623 result,
624 time: Some(t.clone()),
625 ..RuleResultValue::default()
626 },
627 _ => unreachable!("BUG: scalar_result_value_non_numeric called with non-scalar type"),
628 }
629}
630
631fn literal_from_measure_map(
632 measure: &BTreeMap<String, Decimal>,
633 rule_type: &LemmaType,
634) -> LiteralValue {
635 let unit = rule_type
636 .unit()
637 .and_then(|name| rule_type.measure_units_table()?.get(name).ok())
638 .or_else(|| rule_type.measure_fallback_unit())
639 .expect(
640 "BUG: reconstructing a measure from a unit map requires a declared unit on the type",
641 );
642 let magnitude = measure
643 .get(&unit.name)
644 .unwrap_or_else(|| panic!("BUG: measure map missing unit '{}'", unit.name));
645 let rational = rational_from_parsed_decimal(*magnitude)
646 .expect("BUG: measure rule result value must lift to rational");
647 match unit.literal_factor() {
648 Some(factor) => {
649 let canonical = checked_mul(&rational, factor).unwrap_or_else(|failure| {
650 panic!(
651 "BUG: measure canonicalization from RuleResultValue fields failed: {failure}"
652 )
653 });
654 LiteralValue::measure(canonical)
655 }
656 None => LiteralValue::new(ValueKind::UnscaledMeasure {
657 magnitude: rational,
658 unit: Arc::from(unit.name.as_str()),
659 }),
660 }
661}
662
663fn literal_from_ratio_map(
664 ratio: &BTreeMap<String, Decimal>,
665 rule_type: &LemmaType,
666) -> LiteralValue {
667 let ratio_type = ratio_element_type_for_api(rule_type);
668 let units = match &ratio_type.specifications {
669 TypeSpecification::Ratio { units, .. } => units,
670 _ => panic!(
671 "BUG: ratio rule result type must be Ratio, got {}",
672 rule_type.name()
673 ),
674 };
675 let unit = rule_type
676 .unit()
677 .and_then(|name| units.get(name).ok())
678 .or_else(|| units.iter().next())
679 .expect("BUG: ratio rule result type must declare at least one unit");
680 let magnitude = ratio
681 .get(&unit.name)
682 .unwrap_or_else(|| panic!("BUG: ratio map missing unit '{}'", unit.name));
683 let display_rational = rational_from_parsed_decimal(*magnitude)
684 .expect("BUG: ratio rule result value must lift to rational");
685 let canonical = checked_div(&display_rational, &unit.value).unwrap_or_else(|failure| {
686 panic!("BUG: ratio canonicalization from RuleResultValue fields failed: {failure}")
687 });
688 LiteralValue::ratio(canonical)
689}
690
691impl RuleResultValue {
692 pub fn to_literal(&self, rule_type: &LemmaType) -> LiteralValue {
699 if let Some(row) = &self.row {
700 panic!(
701 "BUG: list row '{}' cannot be rebuilt from its API value",
702 row.instance.as_deref().unwrap_or("<appended>")
703 );
704 }
705 if let Some(spec) = &self.spec {
706 assert!(
707 !spec.path.is_empty(),
708 "BUG: spec result '{}' has no uses hop",
709 spec.instance
710 );
711 return LiteralValue::new(ValueKind::Spec(SpecValue::Fixed(SpecInstance {
712 prefix: spec
713 .path
714 .iter()
715 .map(|segment| crate::planning::semantics::PathSegment {
716 uses: Arc::from(segment.uses.as_str()),
717 row: segment.row,
718 repository: segment.repository.as_deref().map(Arc::from),
719 spec: Arc::from(segment.spec.as_str()),
720 })
721 .collect(),
722 })));
723 }
724 if let Some(rows) = &self.list {
725 let TypeSpecification::List { element, .. } = &rule_type.specifications else {
726 panic!(
727 "BUG: list result whose rule type is not a list ({})",
728 rule_type.name()
729 );
730 };
731 return LiteralValue::new(ValueKind::List(
732 rows.iter()
733 .map(|row| {
734 let row_type =
735 endpoint_type_with_written_unit(element, row.unit.as_deref());
736 TypedLiteral {
737 value: row.to_literal(&row_type).value,
738 lemma_type: Arc::new(row_type),
739 }
740 })
741 .collect(),
742 ));
743 }
744 if let Some(range) = &self.range {
745 if range.from.range.is_some() || range.to.range.is_some() {
746 panic!("BUG: range endpoint must not itself be a range");
747 }
748 let endpoint_schema =
749 element_type_from_range_rule(rule_type).unwrap_or_else(|| rule_type.clone());
750 let left_type =
751 endpoint_type_with_written_unit(&endpoint_schema, range.from.unit.as_deref());
752 let right_type =
753 endpoint_type_with_written_unit(&endpoint_schema, range.to.unit.as_deref());
754 let left = range.from.to_literal(&left_type);
755 let right = range.to.to_literal(&right_type);
756 return LiteralValue::range(
757 TypedLiteral {
758 value: left.value,
759 lemma_type: Arc::new(left_type),
760 },
761 TypedLiteral {
762 value: right.value,
763 lemma_type: Arc::new(right_type),
764 },
765 );
766 }
767
768 if let Some(b) = self.boolean {
769 return LiteralValue::from_bool(b);
770 }
771 let owned_rule_type = match self.unit.as_deref() {
772 Some(unit)
773 if matches!(
774 &rule_type.specifications,
775 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
776 ) && rule_type.unit() != Some(unit) =>
777 {
778 Arc::new(rule_type.clone().with_unit(unit))
779 }
780 _ => Arc::new(rule_type.clone()),
781 };
782 if let Some(number) = self.number {
783 return LiteralValue::number_from_decimal(number);
784 }
785 if let Some(calendar) = &self.calendar {
786 let rational = rational_from_parsed_decimal(calendar.value)
787 .expect("BUG: calendar rule result value must lift to rational");
788 return LiteralValue::measure(rational);
789 }
790 if let Some(measure) = &self.measure {
791 return literal_from_measure_map(measure, owned_rule_type.as_ref());
792 }
793 if let Some(ratio) = &self.ratio {
794 return literal_from_ratio_map(ratio, owned_rule_type.as_ref());
795 }
796 if let Some(date) = &self.date {
797 return LiteralValue::date(date.clone());
798 }
799 if let Some(time) = &self.time {
800 return LiteralValue::time(time.clone());
801 }
802 if let Some(text) = &self.text {
803 return LiteralValue::text(text.clone());
804 }
805 panic!("BUG: rule result value fields cannot reconstruct literal");
806 }
807}
808
809fn endpoint_type_with_written_unit(endpoint_schema: &LemmaType, unit: Option<&str>) -> LemmaType {
810 match unit {
811 Some(unit)
812 if matches!(
813 &endpoint_schema.specifications,
814 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
815 ) && endpoint_schema.unit() != Some(unit) =>
816 {
817 endpoint_schema.clone().with_unit(unit)
818 }
819 _ => endpoint_schema.clone(),
820 }
821}
822
823fn element_type_from_range_rule(rule_type: &LemmaType) -> Option<LemmaType> {
824 range_element_type_specification(&rule_type.specifications).map(LemmaType::primitive)
825}
826
827fn format_unit_map(map: &BTreeMap<String, Decimal>) -> String {
828 map.iter()
829 .map(|(unit, value)| format!("{value} {unit}"))
830 .collect::<Vec<_>>()
831 .join(", ")
832}
833
834impl fmt::Display for RuleResultValue {
835 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
836 if let Some(range) = &self.range {
837 return write!(f, "{}...{}", range.from, range.to);
838 }
839 if let Some(measure) = &self.measure {
840 return write!(f, "{}", format_unit_map(measure));
841 }
842 if let Some(ratio) = &self.ratio {
843 return write!(f, "{}", format_unit_map(ratio));
844 }
845 if let Some(number) = &self.number {
846 return write!(f, "{number}");
847 }
848 if let Some(b) = self.boolean {
849 return write!(f, "{b}");
850 }
851 if let Some(text) = &self.text {
852 return write!(f, "{text}");
853 }
854 if let Some(date) = &self.date {
855 return write!(f, "{date}");
856 }
857 if let Some(time) = &self.time {
858 return write!(f, "{time}");
859 }
860 if let Some(calendar) = &self.calendar {
861 return write!(f, "{} {}", calendar.value, calendar.unit);
862 }
863 if let Some(display) = &self.result {
864 return write!(f, "{display}");
865 }
866 if let Some(reference) = &self.reference {
867 return write!(f, "{reference}");
868 }
869 write!(f, "")
870 }
871}