1pub use crate::parsing::ast::{
9 ArithmeticComputation, ComparisonComputation, MathematicalComputation, NegationType, Span,
10 VetoExpression,
11};
12pub use crate::parsing::source::Source;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
16#[serde(rename_all = "snake_case")]
17pub enum LogicalComputation {
18 And,
19 Or,
20 Not,
21}
22
23#[must_use]
25pub fn negated_comparison(op: ComparisonComputation) -> ComparisonComputation {
26 match op {
27 ComparisonComputation::LessThan => ComparisonComputation::GreaterThanOrEqual,
28 ComparisonComputation::LessThanOrEqual => ComparisonComputation::GreaterThan,
29 ComparisonComputation::GreaterThan => ComparisonComputation::LessThanOrEqual,
30 ComparisonComputation::GreaterThanOrEqual => ComparisonComputation::LessThan,
31 ComparisonComputation::Equal | ComparisonComputation::Is => ComparisonComputation::IsNot,
32 ComparisonComputation::NotEqual | ComparisonComputation::IsNot => ComparisonComputation::Is,
33 }
34}
35
36use crate::parsing::ast::{
38 BooleanValue, CalendarUnit, CommandArg, ConversionTarget, DateCalendarKind, DateRelativeKind,
39 DateTimeValue, DurationUnit, TimeValue,
40};
41use crate::parsing::literals::{parse_date_string, parse_duration_from_string, parse_time_string};
42use crate::Error;
43use rust_decimal::Decimal;
44use serde::{Deserialize, Serialize};
45use std::collections::HashMap;
46use std::fmt;
47use std::hash::{Hash, Hasher};
48use std::sync::{Arc, OnceLock};
49
50#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
55pub struct ScaleUnit {
56 pub name: String,
57 pub value: Decimal,
58}
59
60#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
61#[serde(transparent)]
62pub struct ScaleUnits(pub Vec<ScaleUnit>);
63
64impl ScaleUnits {
65 pub fn new() -> Self {
66 ScaleUnits(Vec::new())
67 }
68 pub fn get(&self, name: &str) -> Result<&ScaleUnit, String> {
69 self.0.iter().find(|u| u.name == name).ok_or_else(|| {
70 let valid: Vec<&str> = self.0.iter().map(|u| u.name.as_str()).collect();
71 format!(
72 "Unknown unit '{}' for this scale type. Valid units: {}",
73 name,
74 valid.join(", ")
75 )
76 })
77 }
78 pub fn iter(&self) -> std::slice::Iter<'_, ScaleUnit> {
79 self.0.iter()
80 }
81 pub fn push(&mut self, u: ScaleUnit) {
82 self.0.push(u);
83 }
84 pub fn is_empty(&self) -> bool {
85 self.0.is_empty()
86 }
87 pub fn len(&self) -> usize {
88 self.0.len()
89 }
90}
91
92impl Default for ScaleUnits {
93 fn default() -> Self {
94 ScaleUnits::new()
95 }
96}
97
98impl From<Vec<ScaleUnit>> for ScaleUnits {
99 fn from(v: Vec<ScaleUnit>) -> Self {
100 ScaleUnits(v)
101 }
102}
103
104impl<'a> IntoIterator for &'a ScaleUnits {
105 type Item = &'a ScaleUnit;
106 type IntoIter = std::slice::Iter<'a, ScaleUnit>;
107 fn into_iter(self) -> Self::IntoIter {
108 self.0.iter()
109 }
110}
111
112#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
113pub struct RatioUnit {
114 pub name: String,
115 pub value: Decimal,
116}
117
118#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
119#[serde(transparent)]
120pub struct RatioUnits(pub Vec<RatioUnit>);
121
122impl RatioUnits {
123 pub fn new() -> Self {
124 RatioUnits(Vec::new())
125 }
126 pub fn get(&self, name: &str) -> Result<&RatioUnit, String> {
127 self.0.iter().find(|u| u.name == name).ok_or_else(|| {
128 let valid: Vec<&str> = self.0.iter().map(|u| u.name.as_str()).collect();
129 format!(
130 "Unknown unit '{}' for this ratio type. Valid units: {}",
131 name,
132 valid.join(", ")
133 )
134 })
135 }
136 pub fn iter(&self) -> std::slice::Iter<'_, RatioUnit> {
137 self.0.iter()
138 }
139 pub fn push(&mut self, u: RatioUnit) {
140 self.0.push(u);
141 }
142 pub fn is_empty(&self) -> bool {
143 self.0.is_empty()
144 }
145 pub fn len(&self) -> usize {
146 self.0.len()
147 }
148}
149
150impl Default for RatioUnits {
151 fn default() -> Self {
152 RatioUnits::new()
153 }
154}
155
156impl From<Vec<RatioUnit>> for RatioUnits {
157 fn from(v: Vec<RatioUnit>) -> Self {
158 RatioUnits(v)
159 }
160}
161
162impl<'a> IntoIterator for &'a RatioUnits {
163 type Item = &'a RatioUnit;
164 type IntoIter = std::slice::Iter<'a, RatioUnit>;
165 fn into_iter(self) -> Self::IntoIter {
166 self.0.iter()
167 }
168}
169
170#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
171pub enum TypeSpecification {
172 Boolean {
173 help: String,
174 default: Option<bool>,
175 },
176 Scale {
177 minimum: Option<Decimal>,
178 maximum: Option<Decimal>,
179 decimals: Option<u8>,
180 precision: Option<Decimal>,
181 units: ScaleUnits,
182 help: String,
183 default: Option<(Decimal, String)>,
184 },
185 Number {
186 minimum: Option<Decimal>,
187 maximum: Option<Decimal>,
188 decimals: Option<u8>,
189 precision: Option<Decimal>,
190 help: String,
191 default: Option<Decimal>,
192 },
193 Ratio {
194 minimum: Option<Decimal>,
195 maximum: Option<Decimal>,
196 decimals: Option<u8>,
197 units: RatioUnits,
198 help: String,
199 default: Option<Decimal>,
200 },
201 Text {
202 minimum: Option<usize>,
203 maximum: Option<usize>,
204 length: Option<usize>,
205 options: Vec<String>,
206 help: String,
207 default: Option<String>,
208 },
209 Date {
210 minimum: Option<DateTimeValue>,
211 maximum: Option<DateTimeValue>,
212 help: String,
213 default: Option<DateTimeValue>,
214 },
215 Time {
216 minimum: Option<TimeValue>,
217 maximum: Option<TimeValue>,
218 help: String,
219 default: Option<TimeValue>,
220 },
221 Duration {
222 help: String,
223 default: Option<(Decimal, DurationUnit)>,
224 },
225 Veto {
226 message: Option<String>,
227 },
228 Undetermined,
232}
233
234impl TypeSpecification {
235 pub fn boolean() -> Self {
236 TypeSpecification::Boolean {
237 help: "Values: true, false".to_string(),
238 default: None,
239 }
240 }
241 pub fn scale() -> Self {
242 TypeSpecification::Scale {
243 minimum: None,
244 maximum: None,
245 decimals: None,
246 precision: None,
247 units: ScaleUnits::new(),
248 help: "Format: value+unit (e.g. 100+unit)".to_string(),
249 default: None,
250 }
251 }
252 pub fn number() -> Self {
253 TypeSpecification::Number {
254 minimum: None,
255 maximum: None,
256 decimals: None,
257 precision: None,
258 help: "Numeric value".to_string(),
259 default: None,
260 }
261 }
262 pub fn ratio() -> Self {
263 TypeSpecification::Ratio {
264 minimum: None,
265 maximum: None,
266 decimals: None,
267 units: RatioUnits(vec![
268 RatioUnit {
269 name: "percent".to_string(),
270 value: Decimal::from(100),
271 },
272 RatioUnit {
273 name: "permille".to_string(),
274 value: Decimal::from(1000),
275 },
276 ]),
277 help: "Format: value+unit (e.g. 21+percent)".to_string(),
278 default: None,
279 }
280 }
281 pub fn text() -> Self {
282 TypeSpecification::Text {
283 minimum: None,
284 maximum: None,
285 length: None,
286 options: vec![],
287 help: "Text value".to_string(),
288 default: None,
289 }
290 }
291 pub fn date() -> Self {
292 TypeSpecification::Date {
293 minimum: None,
294 maximum: None,
295 help: "Format: YYYY-MM-DD (e.g. 2024-01-15)".to_string(),
296 default: None,
297 }
298 }
299 pub fn time() -> Self {
300 TypeSpecification::Time {
301 minimum: None,
302 maximum: None,
303 help: "Format: HH:MM:SS (e.g. 14:30:00)".to_string(),
304 default: None,
305 }
306 }
307 pub fn duration() -> Self {
308 TypeSpecification::Duration {
309 help: "Format: value+unit (e.g. 40+hours). Units: years, months, weeks, days, hours, minutes, seconds".to_string(),
310 default: None,
311 }
312 }
313 pub fn veto() -> Self {
314 TypeSpecification::Veto { message: None }
315 }
316
317 pub fn apply_constraint(mut self, command: &str, args: &[CommandArg]) -> Result<Self, String> {
318 match &mut self {
319 TypeSpecification::Boolean { help, default } => match command {
320 "help" => {
321 *help = args
322 .first()
323 .map(|a| a.value().to_string())
324 .unwrap_or_default();
325 }
326 "default" => {
327 let arg = args
328 .first()
329 .ok_or_else(|| "default requires an argument".to_string())?;
330 match arg {
331 CommandArg::Boolean(s) | CommandArg::Label(s) => {
332 let d = s
333 .parse::<BooleanValue>()
334 .map_err(|_| format!("invalid default value: {:?}", s))?;
335 *default = Some(d.into());
336 }
337 other => {
338 return Err(format!(
339 "default for boolean type requires a boolean literal (true/false/yes/no/accept/reject), got {:?}",
340 other.value()
341 ));
342 }
343 }
344 }
345 _ => {
346 return Err(format!(
347 "Invalid command '{}' for boolean type. Valid commands: help, default",
348 command
349 ));
350 }
351 },
352 TypeSpecification::Scale {
353 decimals,
354 minimum,
355 maximum,
356 precision,
357 units,
358 help,
359 default,
360 } => match command {
361 "decimals" => {
362 let d = args
363 .first()
364 .ok_or_else(|| "decimals requires an argument".to_string())?
365 .value()
366 .parse::<u8>()
367 .map_err(|_| {
368 format!(
369 "invalid decimals value: {:?}",
370 args.first().map(|a| a.value())
371 )
372 })?;
373 *decimals = Some(d);
374 }
375 "unit" if args.len() >= 2 => {
376 let unit_name = args[0].value().to_string();
377 if units.iter().any(|u| u.name == unit_name) {
378 return Err(format!(
379 "Unit '{}' is already defined in this scale type.",
380 unit_name
381 ));
382 }
383 let value = args[1]
384 .value()
385 .parse::<Decimal>()
386 .map_err(|_| format!("invalid unit value: {}", args[1].value()))?;
387 units.0.push(ScaleUnit {
388 name: unit_name,
389 value,
390 });
391 }
392 "minimum" => {
393 let m = args
394 .first()
395 .ok_or_else(|| "minimum requires an argument".to_string())?
396 .value()
397 .parse::<Decimal>()
398 .map_err(|_| {
399 format!(
400 "invalid minimum value: {:?}",
401 args.first().map(|a| a.value())
402 )
403 })?;
404 *minimum = Some(m);
405 }
406 "maximum" => {
407 let m = args
408 .first()
409 .ok_or_else(|| "maximum requires an argument".to_string())?
410 .value()
411 .parse::<Decimal>()
412 .map_err(|_| {
413 format!(
414 "invalid maximum value: {:?}",
415 args.first().map(|a| a.value())
416 )
417 })?;
418 *maximum = Some(m);
419 }
420 "precision" => {
421 let p = args
422 .first()
423 .ok_or_else(|| "precision requires an argument".to_string())?
424 .value()
425 .parse::<Decimal>()
426 .map_err(|_| {
427 format!(
428 "invalid precision value: {:?}",
429 args.first().map(|a| a.value())
430 )
431 })?;
432 *precision = Some(p);
433 }
434 "help" => {
435 *help = args
436 .first()
437 .map(|a| a.value().to_string())
438 .unwrap_or_default();
439 }
440 "default" => {
441 if args.len() < 2 {
442 return Err(
443 "default requires a value and unit (e.g., 'default 1 kilogram')"
444 .to_string(),
445 );
446 }
447 match &args[0] {
448 CommandArg::Number(s) => {
449 let value = s
450 .parse::<Decimal>()
451 .map_err(|_| format!("invalid default value: {:?}", s))?;
452 let unit_name = args[1].value().to_string();
453 *default = Some((value, unit_name));
454 }
455 other => {
456 return Err(format!(
457 "default for scale type requires a number literal as value, got {:?}",
458 other.value()
459 ));
460 }
461 }
462 }
463 _ => {
464 return Err(format!(
465 "Invalid command '{}' for scale type. Valid commands: unit, minimum, maximum, decimals, precision, help, default",
466 command
467 ));
468 }
469 },
470 TypeSpecification::Number {
471 decimals,
472 minimum,
473 maximum,
474 precision,
475 help,
476 default,
477 } => match command {
478 "decimals" => {
479 let d = args
480 .first()
481 .ok_or_else(|| "decimals requires an argument".to_string())?
482 .value()
483 .parse::<u8>()
484 .map_err(|_| {
485 format!(
486 "invalid decimals value: {:?}",
487 args.first().map(|a| a.value())
488 )
489 })?;
490 *decimals = Some(d);
491 }
492 "unit" => {
493 return Err(
494 "Invalid command 'unit' for number type. Number types are dimensionless and cannot have units. Use 'scale' type instead.".to_string()
495 );
496 }
497 "minimum" => {
498 let m = args
499 .first()
500 .ok_or_else(|| "minimum requires an argument".to_string())?
501 .value()
502 .parse::<Decimal>()
503 .map_err(|_| {
504 format!(
505 "invalid minimum value: {:?}",
506 args.first().map(|a| a.value())
507 )
508 })?;
509 *minimum = Some(m);
510 }
511 "maximum" => {
512 let m = args
513 .first()
514 .ok_or_else(|| "maximum requires an argument".to_string())?
515 .value()
516 .parse::<Decimal>()
517 .map_err(|_| {
518 format!(
519 "invalid maximum value: {:?}",
520 args.first().map(|a| a.value())
521 )
522 })?;
523 *maximum = Some(m);
524 }
525 "precision" => {
526 let p = args
527 .first()
528 .ok_or_else(|| "precision requires an argument".to_string())?
529 .value()
530 .parse::<Decimal>()
531 .map_err(|_| {
532 format!(
533 "invalid precision value: {:?}",
534 args.first().map(|a| a.value())
535 )
536 })?;
537 *precision = Some(p);
538 }
539 "help" => {
540 *help = args
541 .first()
542 .map(|a| a.value().to_string())
543 .unwrap_or_default();
544 }
545 "default" => {
546 let arg = args
547 .first()
548 .ok_or_else(|| "default requires an argument".to_string())?;
549 match arg {
550 CommandArg::Number(s) => {
551 let d = s
552 .parse::<Decimal>()
553 .map_err(|_| format!("invalid default value: {:?}", s))?;
554 *default = Some(d);
555 }
556 other => {
557 return Err(format!(
558 "default for number type requires a number literal, got {:?}",
559 other.value()
560 ));
561 }
562 }
563 }
564 _ => {
565 return Err(format!(
566 "Invalid command '{}' for number type. Valid commands: minimum, maximum, decimals, precision, help, default",
567 command
568 ));
569 }
570 },
571 TypeSpecification::Ratio {
572 decimals,
573 minimum,
574 maximum,
575 units,
576 help,
577 default,
578 } => match command {
579 "decimals" => {
580 let d = args
581 .first()
582 .ok_or_else(|| "decimals requires an argument".to_string())?
583 .value()
584 .parse::<u8>()
585 .map_err(|_| {
586 format!(
587 "invalid decimals value: {:?}",
588 args.first().map(|a| a.value())
589 )
590 })?;
591 *decimals = Some(d);
592 }
593 "unit" if args.len() >= 2 => {
594 let unit_name = args[0].value().to_string();
595 if units.iter().any(|u| u.name == unit_name) {
596 return Err(format!(
597 "Unit '{}' is already defined in this ratio type.",
598 unit_name
599 ));
600 }
601 let value = args[1]
602 .value()
603 .parse::<Decimal>()
604 .map_err(|_| format!("invalid unit value: {}", args[1].value()))?;
605 units.0.push(RatioUnit {
606 name: unit_name,
607 value,
608 });
609 }
610 "minimum" => {
611 let m = args
612 .first()
613 .ok_or_else(|| "minimum requires an argument".to_string())?
614 .value()
615 .parse::<Decimal>()
616 .map_err(|_| {
617 format!(
618 "invalid minimum value: {:?}",
619 args.first().map(|a| a.value())
620 )
621 })?;
622 *minimum = Some(m);
623 }
624 "maximum" => {
625 let m = args
626 .first()
627 .ok_or_else(|| "maximum requires an argument".to_string())?
628 .value()
629 .parse::<Decimal>()
630 .map_err(|_| {
631 format!(
632 "invalid maximum value: {:?}",
633 args.first().map(|a| a.value())
634 )
635 })?;
636 *maximum = Some(m);
637 }
638 "help" => {
639 *help = args
640 .first()
641 .map(|a| a.value().to_string())
642 .unwrap_or_default();
643 }
644 "default" => {
645 let arg = args
646 .first()
647 .ok_or_else(|| "default requires an argument".to_string())?;
648 match arg {
649 CommandArg::Number(s) => {
650 let d = s
651 .parse::<Decimal>()
652 .map_err(|_| format!("invalid default value: {:?}", s))?;
653 *default = Some(d);
654 }
655 other => {
656 return Err(format!(
657 "default for ratio type requires a number literal, got {:?}",
658 other.value()
659 ));
660 }
661 }
662 }
663 _ => {
664 return Err(format!(
665 "Invalid command '{}' for ratio type. Valid commands: unit, minimum, maximum, decimals, help, default",
666 command
667 ));
668 }
669 },
670 TypeSpecification::Text {
671 minimum,
672 maximum,
673 length,
674 options,
675 help,
676 default,
677 } => match command {
678 "option" if args.len() == 1 => {
679 options.push(args[0].value().to_string());
680 }
681 "options" => {
682 *options = args.iter().map(|a| a.value().to_string()).collect();
683 }
684 "minimum" => {
685 let m = args
686 .first()
687 .ok_or_else(|| "minimum requires an argument".to_string())?
688 .value()
689 .parse::<usize>()
690 .map_err(|_| {
691 format!(
692 "invalid minimum value: {:?}",
693 args.first().map(|a| a.value())
694 )
695 })?;
696 *minimum = Some(m);
697 }
698 "maximum" => {
699 let m = args
700 .first()
701 .ok_or_else(|| "maximum requires an argument".to_string())?
702 .value()
703 .parse::<usize>()
704 .map_err(|_| {
705 format!(
706 "invalid maximum value: {:?}",
707 args.first().map(|a| a.value())
708 )
709 })?;
710 *maximum = Some(m);
711 }
712 "length" => {
713 let l = args
714 .first()
715 .ok_or_else(|| "length requires an argument".to_string())?
716 .value()
717 .parse::<usize>()
718 .map_err(|_| {
719 format!(
720 "invalid length value: {:?}",
721 args.first().map(|a| a.value())
722 )
723 })?;
724 *length = Some(l);
725 }
726 "help" => {
727 *help = args
728 .first()
729 .map(|a| a.value().to_string())
730 .unwrap_or_default();
731 }
732 "default" => {
733 let arg = args
734 .first()
735 .ok_or_else(|| "default requires an argument".to_string())?;
736 match arg {
737 CommandArg::Text(s) => {
738 *default = Some(s.clone());
739 }
740 other => {
741 return Err(format!(
742 "default for text type requires a text literal (quoted string), got {:?}",
743 other.value()
744 ));
745 }
746 }
747 }
748 _ => {
749 return Err(format!(
750 "Invalid command '{}' for text type. Valid commands: options, minimum, maximum, length, help, default",
751 command
752 ));
753 }
754 },
755 TypeSpecification::Date {
756 minimum,
757 maximum,
758 help,
759 default,
760 } => match command {
761 "minimum" => {
762 let arg = args
763 .first()
764 .ok_or_else(|| "minimum requires an argument".to_string())?;
765 *minimum = Some(parse_date_string(arg.value())?);
766 }
767 "maximum" => {
768 let arg = args
769 .first()
770 .ok_or_else(|| "maximum requires an argument".to_string())?;
771 *maximum = Some(parse_date_string(arg.value())?);
772 }
773 "help" => {
774 *help = args
775 .first()
776 .map(|a| a.value().to_string())
777 .unwrap_or_default();
778 }
779 "default" => {
780 let arg = args
781 .first()
782 .ok_or_else(|| "default requires an argument".to_string())?;
783 *default = Some(parse_date_string(arg.value())?);
784 }
785 _ => {
786 return Err(format!(
787 "Invalid command '{}' for date type. Valid commands: minimum, maximum, help, default",
788 command
789 ));
790 }
791 },
792 TypeSpecification::Time {
793 minimum,
794 maximum,
795 help,
796 default,
797 } => match command {
798 "minimum" => {
799 let arg = args
800 .first()
801 .ok_or_else(|| "minimum requires an argument".to_string())?;
802 *minimum = Some(parse_time_string(arg.value())?);
803 }
804 "maximum" => {
805 let arg = args
806 .first()
807 .ok_or_else(|| "maximum requires an argument".to_string())?;
808 *maximum = Some(parse_time_string(arg.value())?);
809 }
810 "help" => {
811 *help = args
812 .first()
813 .map(|a| a.value().to_string())
814 .unwrap_or_default();
815 }
816 "default" => {
817 let arg = args
818 .first()
819 .ok_or_else(|| "default requires an argument".to_string())?;
820 *default = Some(parse_time_string(arg.value())?);
821 }
822 _ => {
823 return Err(format!(
824 "Invalid command '{}' for time type. Valid commands: minimum, maximum, help, default",
825 command
826 ));
827 }
828 },
829 TypeSpecification::Duration { help, default } => match command {
830 "help" => {
831 *help = args
832 .first()
833 .map(|a| a.value().to_string())
834 .unwrap_or_default();
835 }
836 "default" if args.len() >= 2 => {
837 let value = args[0]
838 .value()
839 .parse::<Decimal>()
840 .map_err(|_| format!("invalid duration value: {}", args[0].value()))?;
841 let unit = args[1]
842 .value()
843 .parse::<DurationUnit>()
844 .map_err(|_| format!("invalid duration unit: {}", args[1].value()))?;
845 *default = Some((value, unit));
846 }
847 _ => {
848 return Err(format!(
849 "Invalid command '{}' for duration type. Valid commands: help, default",
850 command
851 ));
852 }
853 },
854 TypeSpecification::Veto { .. } => {
855 return Err(format!(
856 "Invalid command '{}' for veto type. Veto is not a user-declarable type and cannot have constraints",
857 command
858 ));
859 }
860 TypeSpecification::Undetermined => {
861 return Err(format!(
862 "Invalid command '{}' for undetermined sentinel type. Undetermined is an internal type used during type inference and cannot have constraints",
863 command
864 ));
865 }
866 }
867 Ok(self)
868 }
869}
870
871pub fn parse_number_unit(
874 value_str: &str,
875 type_spec: &TypeSpecification,
876) -> Result<crate::parsing::ast::Value, String> {
877 use crate::parsing::ast::Value;
878 use crate::parsing::literals::parse_number_unit_string;
879 use std::str::FromStr;
880
881 let trimmed = value_str.trim();
882 match type_spec {
883 TypeSpecification::Scale { units, .. } => {
884 if units.is_empty() {
885 unreachable!(
886 "BUG: Scale type has no units; should have been validated during planning"
887 );
888 }
889 match parse_number_unit_string(trimmed) {
890 Ok((n, unit_name)) => {
891 let unit = units.get(&unit_name).map_err(|e| e.to_string())?;
892 Ok(Value::Scale(n, unit.name.clone()))
893 }
894 Err(e) => {
895 if trimmed.split_whitespace().count() == 1 && !trimmed.is_empty() {
896 let valid: Vec<&str> = units.iter().map(|u| u.name.as_str()).collect();
897 let example_unit = units.iter().next().unwrap().name.as_str();
898 Err(format!(
899 "Scale value must include a unit, for example: '{} {}'. Valid units: {}.",
900 trimmed,
901 example_unit,
902 valid.join(", ")
903 ))
904 } else {
905 Err(e)
906 }
907 }
908 }
909 }
910 TypeSpecification::Ratio { units, .. } => {
911 if units.is_empty() {
912 unreachable!(
913 "BUG: Ratio type has no units; should have been validated during planning"
914 );
915 }
916 match parse_number_unit_string(trimmed) {
917 Ok((n, unit_name)) => {
918 let unit = units.get(&unit_name).map_err(|e| e.to_string())?;
919 Ok(Value::Ratio(n / unit.value, Some(unit.name.clone())))
920 }
921 Err(_) => {
922 if trimmed.split_whitespace().count() == 1 && !trimmed.is_empty() {
923 let clean = trimmed.replace(['_', ','], "");
924 let n = Decimal::from_str(&clean)
925 .map_err(|_| format!("Invalid ratio: '{}'", trimmed))?;
926 Ok(Value::Ratio(n, None))
927 } else {
928 Err("Ratio value must be a number, optionally followed by a unit (e.g. '0.5' or '50 percent').".to_string())
929 }
930 }
931 }
932 }
933 _ => Err("parse_number_unit only accepts Scale or Ratio type".to_string()),
934 }
935}
936
937pub fn parse_value_from_string(
940 value_str: &str,
941 type_spec: &TypeSpecification,
942 source: &Source,
943) -> Result<crate::parsing::ast::Value, Error> {
944 use crate::parsing::ast::{BooleanValue, Value};
945 use std::str::FromStr;
946
947 let to_err = |msg: String| Error::validation(msg, Some(source.clone()), None::<String>);
948
949 match type_spec {
950 TypeSpecification::Text { .. } => Ok(Value::Text(value_str.to_string())),
951 TypeSpecification::Number { .. } => {
952 let clean = value_str.replace(['_', ','], "");
953 let n = Decimal::from_str(&clean).map_err(|_| to_err(format!("Invalid number: '{}'", value_str)))?;
954 Ok(Value::Number(n))
955 }
956 TypeSpecification::Scale { .. } => {
957 parse_number_unit(value_str, type_spec).map_err(to_err)
958 }
959 TypeSpecification::Boolean { .. } => {
960 let bv = match value_str.to_lowercase().as_str() {
961 "true" => BooleanValue::True,
962 "false" => BooleanValue::False,
963 "yes" => BooleanValue::Yes,
964 "no" => BooleanValue::No,
965 "accept" => BooleanValue::Accept,
966 "reject" => BooleanValue::Reject,
967 _ => return Err(to_err(format!("Invalid boolean: '{}'", value_str))),
968 };
969 Ok(Value::Boolean(bv))
970 }
971 TypeSpecification::Date { .. } => {
972 let date = parse_date_string(value_str).map_err(to_err)?;
973 Ok(Value::Date(date))
974 }
975 TypeSpecification::Time { .. } => {
976 let time = parse_time_string(value_str).map_err(to_err)?;
977 Ok(Value::Time(time))
978 }
979 TypeSpecification::Duration { .. } => {
980 parse_duration_from_string(value_str, source)
981 }
982 TypeSpecification::Ratio { .. } => {
983 parse_number_unit(value_str, type_spec).map_err(to_err)
984 }
985 TypeSpecification::Veto { .. } => Err(to_err(
986 "Veto type cannot be parsed from string".to_string(),
987 )),
988 TypeSpecification::Undetermined => unreachable!(
989 "BUG: parse_value_from_string called with Undetermined sentinel type; this type exists only during type inference"
990 ),
991 }
992}
993
994#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1000#[serde(rename_all = "snake_case")]
1001pub enum SemanticDurationUnit {
1002 Year,
1003 Month,
1004 Week,
1005 Day,
1006 Hour,
1007 Minute,
1008 Second,
1009 Millisecond,
1010 Microsecond,
1011}
1012
1013impl fmt::Display for SemanticDurationUnit {
1014 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1015 let s = match self {
1016 SemanticDurationUnit::Year => "years",
1017 SemanticDurationUnit::Month => "months",
1018 SemanticDurationUnit::Week => "weeks",
1019 SemanticDurationUnit::Day => "days",
1020 SemanticDurationUnit::Hour => "hours",
1021 SemanticDurationUnit::Minute => "minutes",
1022 SemanticDurationUnit::Second => "seconds",
1023 SemanticDurationUnit::Millisecond => "milliseconds",
1024 SemanticDurationUnit::Microsecond => "microseconds",
1025 };
1026 write!(f, "{}", s)
1027 }
1028}
1029
1030#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1034#[serde(rename_all = "snake_case")]
1035pub enum SemanticConversionTarget {
1036 Duration(SemanticDurationUnit),
1037 ScaleUnit(String),
1038 RatioUnit(String),
1039}
1040
1041impl fmt::Display for SemanticConversionTarget {
1042 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1043 match self {
1044 SemanticConversionTarget::Duration(u) => write!(f, "{}", u),
1045 SemanticConversionTarget::ScaleUnit(s) => write!(f, "{}", s),
1046 SemanticConversionTarget::RatioUnit(s) => write!(f, "{}", s),
1047 }
1048 }
1049}
1050
1051#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1053pub struct SemanticTimezone {
1054 pub offset_hours: i8,
1055 pub offset_minutes: u8,
1056}
1057
1058impl fmt::Display for SemanticTimezone {
1059 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1060 if self.offset_hours == 0 && self.offset_minutes == 0 {
1061 write!(f, "Z")
1062 } else {
1063 let sign = if self.offset_hours >= 0 { "+" } else { "-" };
1064 let hours = self.offset_hours.abs();
1065 write!(f, "{}{:02}:{:02}", sign, hours, self.offset_minutes)
1066 }
1067 }
1068}
1069
1070#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1072pub struct SemanticTime {
1073 pub hour: u32,
1074 pub minute: u32,
1075 pub second: u32,
1076 pub timezone: Option<SemanticTimezone>,
1077}
1078
1079impl fmt::Display for SemanticTime {
1080 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1081 write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
1082 }
1083}
1084
1085#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1087pub struct SemanticDateTime {
1088 pub year: i32,
1089 pub month: u32,
1090 pub day: u32,
1091 pub hour: u32,
1092 pub minute: u32,
1093 pub second: u32,
1094 #[serde(default)]
1095 pub microsecond: u32,
1096 pub timezone: Option<SemanticTimezone>,
1097}
1098
1099impl fmt::Display for SemanticDateTime {
1100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1101 let has_time = self.hour != 0
1102 || self.minute != 0
1103 || self.second != 0
1104 || self.microsecond != 0
1105 || self.timezone.is_some();
1106 if !has_time {
1107 write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day)
1108 } else {
1109 write!(
1110 f,
1111 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1112 self.year, self.month, self.day, self.hour, self.minute, self.second
1113 )?;
1114 if self.microsecond != 0 {
1115 write!(f, ".{:06}", self.microsecond)?;
1116 }
1117 if let Some(tz) = &self.timezone {
1118 write!(f, "{}", tz)?;
1119 }
1120 Ok(())
1121 }
1122 }
1123}
1124
1125#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1128#[serde(rename_all = "snake_case")]
1129pub enum ValueKind {
1130 Number(Decimal),
1131 Scale(Decimal, String),
1133 Text(String),
1134 Date(SemanticDateTime),
1135 Time(SemanticTime),
1136 Boolean(bool),
1137 Duration(Decimal, SemanticDurationUnit),
1139 Ratio(Decimal, Option<String>),
1141}
1142
1143impl fmt::Display for ValueKind {
1144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1145 use crate::parsing::ast::Value;
1146 match self {
1147 ValueKind::Number(n) => {
1148 let norm = n.normalize();
1149 let s = if norm.fract().is_zero() {
1150 norm.trunc().to_string()
1151 } else {
1152 norm.to_string()
1153 };
1154 write!(f, "{}", s)
1155 }
1156 ValueKind::Scale(n, u) => write!(f, "{}", Value::Scale(*n, u.clone())),
1157 ValueKind::Text(s) => write!(f, "{}", Value::Text(s.clone())),
1158 ValueKind::Ratio(r, u) => write!(f, "{}", Value::Ratio(*r, u.clone())),
1159 ValueKind::Date(dt) => write!(f, "{}", dt),
1160 ValueKind::Time(t) => write!(
1161 f,
1162 "{}",
1163 Value::Time(crate::parsing::ast::TimeValue {
1164 hour: t.hour as u8,
1165 minute: t.minute as u8,
1166 second: t.second as u8,
1167 timezone: t
1168 .timezone
1169 .as_ref()
1170 .map(|tz| crate::parsing::ast::TimezoneValue {
1171 offset_hours: tz.offset_hours,
1172 offset_minutes: tz.offset_minutes,
1173 }),
1174 })
1175 ),
1176 ValueKind::Boolean(b) => write!(f, "{}", b),
1177 ValueKind::Duration(v, u) => write!(f, "{} {}", v, u),
1178 }
1179 }
1180}
1181
1182#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1191pub struct PathSegment {
1192 pub fact: String,
1194 pub spec: String,
1196}
1197
1198#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
1203pub struct FactPath {
1204 pub segments: Vec<PathSegment>,
1206 pub fact: String,
1208}
1209
1210impl FactPath {
1211 pub fn new(segments: Vec<PathSegment>, fact: String) -> Self {
1213 Self { segments, fact }
1214 }
1215
1216 pub fn local(fact: String) -> Self {
1218 Self {
1219 segments: vec![],
1220 fact,
1221 }
1222 }
1223
1224 pub fn input_key(&self) -> String {
1227 let mut s = String::new();
1228 for segment in &self.segments {
1229 s.push_str(&segment.fact);
1230 s.push('.');
1231 }
1232 s.push_str(&self.fact);
1233 s
1234 }
1235}
1236
1237#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1241pub struct RulePath {
1242 pub segments: Vec<PathSegment>,
1244 pub rule: String,
1246}
1247
1248impl RulePath {
1249 pub fn new(segments: Vec<PathSegment>, rule: String) -> Self {
1251 Self { segments, rule }
1252 }
1253}
1254
1255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1264pub struct Expression {
1265 pub kind: ExpressionKind,
1266 pub source_location: Option<Source>,
1267}
1268
1269impl Expression {
1270 pub fn new(kind: ExpressionKind, source_location: Source) -> Self {
1271 Self {
1272 kind,
1273 source_location: Some(source_location),
1274 }
1275 }
1276
1277 pub fn with_source(kind: ExpressionKind, source_location: Option<Source>) -> Self {
1279 Self {
1280 kind,
1281 source_location,
1282 }
1283 }
1284
1285 pub fn get_source_text(&self, sources: &HashMap<String, String>) -> Option<String> {
1287 let source = self.source_location.as_ref()?;
1288 let file_source = sources.get(&source.attribute)?;
1289 let span = &source.span;
1290 Some(file_source.get(span.start..span.end)?.to_string())
1291 }
1292
1293 pub fn collect_fact_paths(&self, facts: &mut std::collections::HashSet<FactPath>) {
1295 self.kind.collect_fact_paths(facts);
1296 }
1297
1298 pub fn semantic_hash<H: Hasher>(&self, state: &mut H) {
1300 self.kind.semantic_hash(state);
1301 }
1302}
1303
1304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1306#[serde(rename_all = "snake_case")]
1307pub enum ExpressionKind {
1308 Literal(Box<LiteralValue>),
1310 FactPath(FactPath),
1312 RulePath(RulePath),
1314 LogicalAnd(Arc<Expression>, Arc<Expression>),
1315 Arithmetic(Arc<Expression>, ArithmeticComputation, Arc<Expression>),
1316 Comparison(Arc<Expression>, ComparisonComputation, Arc<Expression>),
1317 UnitConversion(Arc<Expression>, SemanticConversionTarget),
1318 LogicalNegation(Arc<Expression>, NegationType),
1319 MathematicalComputation(MathematicalComputation, Arc<Expression>),
1320 Veto(VetoExpression),
1321 Now,
1323 DateRelative(DateRelativeKind, Arc<Expression>, Option<Arc<Expression>>),
1325 DateCalendar(DateCalendarKind, CalendarUnit, Arc<Expression>),
1327}
1328
1329impl ExpressionKind {
1330 fn collect_fact_paths(&self, facts: &mut std::collections::HashSet<FactPath>) {
1332 match self {
1333 ExpressionKind::FactPath(fp) => {
1334 facts.insert(fp.clone());
1335 }
1336 ExpressionKind::LogicalAnd(left, right)
1337 | ExpressionKind::Arithmetic(left, _, right)
1338 | ExpressionKind::Comparison(left, _, right) => {
1339 left.collect_fact_paths(facts);
1340 right.collect_fact_paths(facts);
1341 }
1342 ExpressionKind::UnitConversion(inner, _)
1343 | ExpressionKind::LogicalNegation(inner, _)
1344 | ExpressionKind::MathematicalComputation(_, inner) => {
1345 inner.collect_fact_paths(facts);
1346 }
1347 ExpressionKind::DateRelative(_, date_expr, tolerance) => {
1348 date_expr.collect_fact_paths(facts);
1349 if let Some(tol) = tolerance {
1350 tol.collect_fact_paths(facts);
1351 }
1352 }
1353 ExpressionKind::DateCalendar(_, _, date_expr) => {
1354 date_expr.collect_fact_paths(facts);
1355 }
1356 ExpressionKind::Literal(_)
1357 | ExpressionKind::RulePath(_)
1358 | ExpressionKind::Veto(_)
1359 | ExpressionKind::Now => {}
1360 }
1361 }
1362
1363 pub fn semantic_hash<H: Hasher>(&self, state: &mut H) {
1365 std::mem::discriminant(self).hash(state);
1367
1368 match self {
1369 ExpressionKind::Literal(lit) => lit.hash(state),
1370 ExpressionKind::FactPath(fp) => fp.hash(state),
1371 ExpressionKind::RulePath(rp) => rp.hash(state),
1372 ExpressionKind::LogicalAnd(left, right) => {
1373 left.semantic_hash(state);
1374 right.semantic_hash(state);
1375 }
1376 ExpressionKind::Arithmetic(left, op, right) => {
1377 left.semantic_hash(state);
1378 op.hash(state);
1379 right.semantic_hash(state);
1380 }
1381 ExpressionKind::Comparison(left, op, right) => {
1382 left.semantic_hash(state);
1383 op.hash(state);
1384 right.semantic_hash(state);
1385 }
1386 ExpressionKind::UnitConversion(expr, target) => {
1387 expr.semantic_hash(state);
1388 target.hash(state);
1389 }
1390 ExpressionKind::LogicalNegation(expr, neg_type) => {
1391 expr.semantic_hash(state);
1392 neg_type.hash(state);
1393 }
1394 ExpressionKind::MathematicalComputation(op, expr) => {
1395 op.hash(state);
1396 expr.semantic_hash(state);
1397 }
1398 ExpressionKind::Veto(v) => v.message.hash(state),
1399 ExpressionKind::Now => {}
1400 ExpressionKind::DateRelative(kind, date_expr, tolerance) => {
1401 kind.hash(state);
1402 date_expr.semantic_hash(state);
1403 if let Some(tol) = tolerance {
1404 tol.semantic_hash(state);
1405 }
1406 }
1407 ExpressionKind::DateCalendar(kind, unit, date_expr) => {
1408 kind.hash(state);
1409 unit.hash(state);
1410 date_expr.semantic_hash(state);
1411 }
1412 }
1413 }
1414}
1415
1416#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1422pub enum TypeExtends {
1423 Primitive,
1425 Custom { parent: String, family: String },
1427}
1428
1429impl TypeExtends {
1430 #[must_use]
1432 pub fn parent_name(&self) -> Option<&str> {
1433 match self {
1434 TypeExtends::Primitive => None,
1435 TypeExtends::Custom { parent, .. } => Some(parent.as_str()),
1436 }
1437 }
1438}
1439
1440#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1445pub struct LemmaType {
1446 pub name: Option<String>,
1448 pub specifications: TypeSpecification,
1450 pub extends: TypeExtends,
1452}
1453
1454impl LemmaType {
1455 pub fn new(name: String, specifications: TypeSpecification, extends: TypeExtends) -> Self {
1457 Self {
1458 name: Some(name),
1459 specifications,
1460 extends,
1461 }
1462 }
1463
1464 pub fn without_name(specifications: TypeSpecification, extends: TypeExtends) -> Self {
1466 Self {
1467 name: None,
1468 specifications,
1469 extends,
1470 }
1471 }
1472
1473 pub fn primitive(specifications: TypeSpecification) -> Self {
1475 Self {
1476 name: None,
1477 specifications,
1478 extends: TypeExtends::Primitive,
1479 }
1480 }
1481
1482 pub fn name(&self) -> String {
1484 self.name.clone().unwrap_or_else(|| {
1485 match &self.specifications {
1486 TypeSpecification::Boolean { .. } => "boolean",
1487 TypeSpecification::Scale { .. } => "scale",
1488 TypeSpecification::Number { .. } => "number",
1489 TypeSpecification::Text { .. } => "text",
1490 TypeSpecification::Date { .. } => "date",
1491 TypeSpecification::Time { .. } => "time",
1492 TypeSpecification::Duration { .. } => "duration",
1493 TypeSpecification::Ratio { .. } => "ratio",
1494 TypeSpecification::Veto { .. } => "veto",
1495 TypeSpecification::Undetermined => "undetermined",
1496 }
1497 .to_string()
1498 })
1499 }
1500
1501 pub fn is_boolean(&self) -> bool {
1503 matches!(&self.specifications, TypeSpecification::Boolean { .. })
1504 }
1505
1506 pub fn is_scale(&self) -> bool {
1508 matches!(&self.specifications, TypeSpecification::Scale { .. })
1509 }
1510
1511 pub fn is_number(&self) -> bool {
1513 matches!(&self.specifications, TypeSpecification::Number { .. })
1514 }
1515
1516 pub fn is_numeric(&self) -> bool {
1518 matches!(
1519 &self.specifications,
1520 TypeSpecification::Scale { .. } | TypeSpecification::Number { .. }
1521 )
1522 }
1523
1524 pub fn is_text(&self) -> bool {
1526 matches!(&self.specifications, TypeSpecification::Text { .. })
1527 }
1528
1529 pub fn is_date(&self) -> bool {
1531 matches!(&self.specifications, TypeSpecification::Date { .. })
1532 }
1533
1534 pub fn is_time(&self) -> bool {
1536 matches!(&self.specifications, TypeSpecification::Time { .. })
1537 }
1538
1539 pub fn is_duration(&self) -> bool {
1541 matches!(&self.specifications, TypeSpecification::Duration { .. })
1542 }
1543
1544 pub fn is_ratio(&self) -> bool {
1546 matches!(&self.specifications, TypeSpecification::Ratio { .. })
1547 }
1548
1549 pub fn vetoed(&self) -> bool {
1551 matches!(&self.specifications, TypeSpecification::Veto { .. })
1552 }
1553
1554 pub fn is_undetermined(&self) -> bool {
1556 matches!(&self.specifications, TypeSpecification::Undetermined)
1557 }
1558
1559 pub fn has_same_base_type(&self, other: &LemmaType) -> bool {
1561 use TypeSpecification::*;
1562 matches!(
1563 (&self.specifications, &other.specifications),
1564 (Boolean { .. }, Boolean { .. })
1565 | (Number { .. }, Number { .. })
1566 | (Scale { .. }, Scale { .. })
1567 | (Text { .. }, Text { .. })
1568 | (Date { .. }, Date { .. })
1569 | (Time { .. }, Time { .. })
1570 | (Duration { .. }, Duration { .. })
1571 | (Ratio { .. }, Ratio { .. })
1572 | (Veto { .. }, Veto { .. })
1573 | (Undetermined, Undetermined)
1574 )
1575 }
1576
1577 #[must_use]
1579 pub fn scale_family_name(&self) -> Option<&str> {
1580 if !self.is_scale() {
1581 return None;
1582 }
1583 match &self.extends {
1584 TypeExtends::Custom { family, .. } => Some(family.as_str()),
1585 TypeExtends::Primitive => self.name.as_deref(),
1586 }
1587 }
1588
1589 #[must_use]
1592 pub fn same_scale_family(&self, other: &LemmaType) -> bool {
1593 if !self.is_scale() || !other.is_scale() {
1594 return false;
1595 }
1596 match (self.scale_family_name(), other.scale_family_name()) {
1597 (Some(self_family), Some(other_family)) => self_family == other_family,
1598 (None, None) => true,
1600 _ => false,
1601 }
1602 }
1603
1604 pub fn create_default_value(&self) -> Option<LiteralValue> {
1606 let value = match &self.specifications {
1607 TypeSpecification::Text { default, .. } => default.clone().map(ValueKind::Text),
1608 TypeSpecification::Number { default, .. } => (*default).map(ValueKind::Number),
1609 TypeSpecification::Scale { default, .. } => {
1610 default.clone().map(|(d, u)| ValueKind::Scale(d, u))
1611 }
1612 TypeSpecification::Boolean { default, .. } => (*default).map(ValueKind::Boolean),
1613 TypeSpecification::Date { default, .. } => default
1614 .clone()
1615 .map(|dt| ValueKind::Date(date_time_to_semantic(&dt))),
1616 TypeSpecification::Time { default, .. } => default
1617 .clone()
1618 .map(|t| ValueKind::Time(time_to_semantic(&t))),
1619 TypeSpecification::Duration { default, .. } => default
1620 .clone()
1621 .map(|(v, u)| ValueKind::Duration(v, duration_unit_to_semantic(&u))),
1622 TypeSpecification::Ratio { .. } => None, TypeSpecification::Veto { .. } => None,
1624 TypeSpecification::Undetermined => None,
1625 };
1626
1627 value.map(|v| LiteralValue {
1628 value: v,
1629 lemma_type: self.clone(),
1630 })
1631 }
1632
1633 pub fn veto_type() -> Self {
1635 Self::primitive(TypeSpecification::veto())
1636 }
1637
1638 pub fn undetermined_type() -> Self {
1641 Self::primitive(TypeSpecification::Undetermined)
1642 }
1643
1644 pub fn decimal_places(&self) -> Option<u8> {
1647 match &self.specifications {
1648 TypeSpecification::Number { decimals, .. } => *decimals,
1649 TypeSpecification::Scale { decimals, .. } => *decimals,
1650 TypeSpecification::Ratio { decimals, .. } => *decimals,
1651 _ => None,
1652 }
1653 }
1654
1655 pub fn example_value(&self) -> &'static str {
1657 match &self.specifications {
1658 TypeSpecification::Text { .. } => "\"hello world\"",
1659 TypeSpecification::Scale { .. } => "12.50 eur",
1660 TypeSpecification::Number { .. } => "3.14",
1661 TypeSpecification::Boolean { .. } => "true",
1662 TypeSpecification::Date { .. } => "2023-12-25T14:30:00Z",
1663 TypeSpecification::Veto { .. } => "veto",
1664 TypeSpecification::Time { .. } => "14:30:00",
1665 TypeSpecification::Duration { .. } => "90 minutes",
1666 TypeSpecification::Ratio { .. } => "50%",
1667 TypeSpecification::Undetermined => unreachable!(
1668 "BUG: example_value called on Undetermined sentinel type; this type must never reach user-facing code"
1669 ),
1670 }
1671 }
1672
1673 #[must_use]
1677 pub fn scale_unit_factor(&self, unit_name: &str) -> Decimal {
1678 let units = match &self.specifications {
1679 TypeSpecification::Scale { units, .. } => units,
1680 _ => unreachable!(
1681 "BUG: scale_unit_factor called with non-scale type {}; only call during evaluation after planning validated scale conversion",
1682 self.name()
1683 ),
1684 };
1685 match units
1686 .iter()
1687 .find(|u| u.name.eq_ignore_ascii_case(unit_name))
1688 {
1689 Some(ScaleUnit { value, .. }) => *value,
1690 None => {
1691 let valid: Vec<&str> = units.iter().map(|u| u.name.as_str()).collect();
1692 unreachable!(
1693 "BUG: unknown unit '{}' for scale type {} (valid: {}); planning must reject invalid conversions with Error",
1694 unit_name,
1695 self.name(),
1696 valid.join(", ")
1697 );
1698 }
1699 }
1700 }
1701}
1702
1703#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize)]
1705pub struct LiteralValue {
1706 pub value: ValueKind,
1707 pub lemma_type: LemmaType,
1708}
1709
1710impl Serialize for LiteralValue {
1711 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1712 where
1713 S: serde::Serializer,
1714 {
1715 use serde::ser::SerializeStruct;
1716 let mut state = serializer.serialize_struct("LiteralValue", 3)?;
1717 state.serialize_field("value", &self.value)?;
1718 state.serialize_field("lemma_type", &self.lemma_type)?;
1719 state.serialize_field("display_value", &self.display_value())?;
1720 state.end()
1721 }
1722}
1723
1724impl LiteralValue {
1725 pub fn text(s: String) -> Self {
1726 Self {
1727 value: ValueKind::Text(s),
1728 lemma_type: primitive_text().clone(),
1729 }
1730 }
1731
1732 pub fn text_with_type(s: String, lemma_type: LemmaType) -> Self {
1733 Self {
1734 value: ValueKind::Text(s),
1735 lemma_type,
1736 }
1737 }
1738
1739 pub fn number(n: Decimal) -> Self {
1740 Self {
1741 value: ValueKind::Number(n),
1742 lemma_type: primitive_number().clone(),
1743 }
1744 }
1745
1746 pub fn number_with_type(n: Decimal, lemma_type: LemmaType) -> Self {
1747 Self {
1748 value: ValueKind::Number(n),
1749 lemma_type,
1750 }
1751 }
1752
1753 pub fn scale_with_type(n: Decimal, unit: String, lemma_type: LemmaType) -> Self {
1754 Self {
1755 value: ValueKind::Scale(n, unit),
1756 lemma_type,
1757 }
1758 }
1759
1760 pub fn number_interpreted_as_scale(value: Decimal, unit_name: String) -> Self {
1763 let lemma_type = LemmaType {
1764 name: None,
1765 specifications: TypeSpecification::Scale {
1766 minimum: None,
1767 maximum: None,
1768 decimals: None,
1769 precision: None,
1770 units: ScaleUnits::from(vec![ScaleUnit {
1771 name: unit_name.clone(),
1772 value: Decimal::from(1),
1773 }]),
1774 help: "Format: value+unit (e.g. 100+unit)".to_string(),
1775 default: None,
1776 },
1777 extends: TypeExtends::Primitive,
1778 };
1779 Self {
1780 value: ValueKind::Scale(value, unit_name),
1781 lemma_type,
1782 }
1783 }
1784
1785 pub fn from_bool(b: bool) -> Self {
1786 Self {
1787 value: ValueKind::Boolean(b),
1788 lemma_type: primitive_boolean().clone(),
1789 }
1790 }
1791
1792 pub fn date(dt: SemanticDateTime) -> Self {
1793 Self {
1794 value: ValueKind::Date(dt),
1795 lemma_type: primitive_date().clone(),
1796 }
1797 }
1798
1799 pub fn date_with_type(dt: SemanticDateTime, lemma_type: LemmaType) -> Self {
1800 Self {
1801 value: ValueKind::Date(dt),
1802 lemma_type,
1803 }
1804 }
1805
1806 pub fn time(t: SemanticTime) -> Self {
1807 Self {
1808 value: ValueKind::Time(t),
1809 lemma_type: primitive_time().clone(),
1810 }
1811 }
1812
1813 pub fn time_with_type(t: SemanticTime, lemma_type: LemmaType) -> Self {
1814 Self {
1815 value: ValueKind::Time(t),
1816 lemma_type,
1817 }
1818 }
1819
1820 pub fn duration(value: Decimal, unit: SemanticDurationUnit) -> Self {
1821 Self {
1822 value: ValueKind::Duration(value, unit),
1823 lemma_type: primitive_duration().clone(),
1824 }
1825 }
1826
1827 pub fn duration_with_type(
1828 value: Decimal,
1829 unit: SemanticDurationUnit,
1830 lemma_type: LemmaType,
1831 ) -> Self {
1832 Self {
1833 value: ValueKind::Duration(value, unit),
1834 lemma_type,
1835 }
1836 }
1837
1838 pub fn ratio(r: Decimal, unit: Option<String>) -> Self {
1839 Self {
1840 value: ValueKind::Ratio(r, unit),
1841 lemma_type: primitive_ratio().clone(),
1842 }
1843 }
1844
1845 pub fn ratio_with_type(r: Decimal, unit: Option<String>, lemma_type: LemmaType) -> Self {
1846 Self {
1847 value: ValueKind::Ratio(r, unit),
1848 lemma_type,
1849 }
1850 }
1851
1852 pub fn display_value(&self) -> String {
1854 format!("{}", self)
1855 }
1856
1857 pub fn byte_size(&self) -> usize {
1859 format!("{}", self).len()
1860 }
1861
1862 pub fn get_type(&self) -> &LemmaType {
1864 &self.lemma_type
1865 }
1866}
1867
1868#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1870pub enum FactValue {
1871 Literal(LiteralValue),
1872 TypeDeclaration { resolved_type: LemmaType },
1873 SpecReference(String),
1874}
1875
1876#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1878pub struct Fact {
1879 pub path: FactPath,
1880 pub value: FactValue,
1881 pub source: Option<Source>,
1882}
1883
1884#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
1886pub enum FactData {
1887 Value { value: LiteralValue, source: Source },
1889 TypeDeclaration {
1891 resolved_type: LemmaType,
1892 source: Source,
1893 },
1894 SpecRef {
1897 spec: Arc<crate::parsing::ast::LemmaSpec>,
1898 source: Source,
1899 expected_hash_pin: Option<String>,
1900 },
1901}
1902
1903impl FactData {
1904 pub fn schema_type(&self) -> Option<&LemmaType> {
1906 match self {
1907 FactData::Value { value, .. } => Some(&value.lemma_type),
1908 FactData::TypeDeclaration { resolved_type, .. } => Some(resolved_type),
1909 FactData::SpecRef { .. } => None,
1910 }
1911 }
1912
1913 pub fn value(&self) -> Option<&LiteralValue> {
1915 match self {
1916 FactData::Value { value, .. } => Some(value),
1917 FactData::TypeDeclaration { .. } | FactData::SpecRef { .. } => None,
1918 }
1919 }
1920
1921 pub fn source(&self) -> &Source {
1923 match self {
1924 FactData::Value { source, .. } => source,
1925 FactData::TypeDeclaration { source, .. } => source,
1926 FactData::SpecRef { source, .. } => source,
1927 }
1928 }
1929
1930 pub fn expected_hash_pin(&self) -> Option<&str> {
1932 match self {
1933 FactData::Value { .. } | FactData::TypeDeclaration { .. } => None,
1934 FactData::SpecRef {
1935 expected_hash_pin, ..
1936 } => expected_hash_pin.as_deref(),
1937 }
1938 }
1939
1940 pub fn spec_arc(&self) -> Option<&Arc<crate::parsing::ast::LemmaSpec>> {
1942 match self {
1943 FactData::Value { .. } | FactData::TypeDeclaration { .. } => None,
1944 FactData::SpecRef { spec: spec_arc, .. } => Some(spec_arc),
1945 }
1946 }
1947
1948 pub fn spec_ref(&self) -> Option<&str> {
1950 match self {
1951 FactData::Value { .. } | FactData::TypeDeclaration { .. } => None,
1952 FactData::SpecRef { spec, .. } => Some(&spec.name),
1953 }
1954 }
1955}
1956
1957pub fn value_to_semantic(value: &crate::parsing::ast::Value) -> Result<ValueKind, String> {
1959 use crate::parsing::ast::Value;
1960 Ok(match value {
1961 Value::Number(n) => ValueKind::Number(*n),
1962 Value::Text(s) => ValueKind::Text(s.clone()),
1963 Value::Boolean(b) => ValueKind::Boolean(bool::from(b.clone())),
1964 Value::Date(dt) => ValueKind::Date(date_time_to_semantic(dt)),
1965 Value::Time(t) => ValueKind::Time(time_to_semantic(t)),
1966 Value::Duration(n, u) => ValueKind::Duration(*n, duration_unit_to_semantic(u)),
1967 Value::Scale(n, unit) => ValueKind::Scale(*n, unit.clone()),
1968 Value::Ratio(n, unit) => ValueKind::Ratio(*n, unit.clone()),
1969 })
1970}
1971
1972pub(crate) fn date_time_to_semantic(dt: &crate::parsing::ast::DateTimeValue) -> SemanticDateTime {
1974 SemanticDateTime {
1975 year: dt.year,
1976 month: dt.month,
1977 day: dt.day,
1978 hour: dt.hour,
1979 minute: dt.minute,
1980 second: dt.second,
1981 microsecond: dt.microsecond,
1982 timezone: dt.timezone.as_ref().map(|tz| SemanticTimezone {
1983 offset_hours: tz.offset_hours,
1984 offset_minutes: tz.offset_minutes,
1985 }),
1986 }
1987}
1988
1989pub(crate) fn time_to_semantic(t: &crate::parsing::ast::TimeValue) -> SemanticTime {
1991 SemanticTime {
1992 hour: t.hour.into(),
1993 minute: t.minute.into(),
1994 second: t.second.into(),
1995 timezone: t.timezone.as_ref().map(|tz| SemanticTimezone {
1996 offset_hours: tz.offset_hours,
1997 offset_minutes: tz.offset_minutes,
1998 }),
1999 }
2000}
2001
2002pub(crate) fn duration_unit_to_semantic(
2004 u: &crate::parsing::ast::DurationUnit,
2005) -> SemanticDurationUnit {
2006 use crate::parsing::ast::DurationUnit as DU;
2007 match u {
2008 DU::Year => SemanticDurationUnit::Year,
2009 DU::Month => SemanticDurationUnit::Month,
2010 DU::Week => SemanticDurationUnit::Week,
2011 DU::Day => SemanticDurationUnit::Day,
2012 DU::Hour => SemanticDurationUnit::Hour,
2013 DU::Minute => SemanticDurationUnit::Minute,
2014 DU::Second => SemanticDurationUnit::Second,
2015 DU::Millisecond => SemanticDurationUnit::Millisecond,
2016 DU::Microsecond => SemanticDurationUnit::Microsecond,
2017 }
2018}
2019
2020pub fn conversion_target_to_semantic(
2027 ct: &ConversionTarget,
2028 unit_index: Option<&HashMap<String, LemmaType>>,
2029) -> Result<SemanticConversionTarget, String> {
2030 match ct {
2031 ConversionTarget::Duration(u) => Ok(SemanticConversionTarget::Duration(
2032 duration_unit_to_semantic(u),
2033 )),
2034 ConversionTarget::Unit(name) => {
2035 let index = unit_index.ok_or_else(|| {
2036 "Unit conversion requires type resolution; unit index not available.".to_string()
2037 })?;
2038 let lemma_type = index.get(name).ok_or_else(|| {
2039 format!(
2040 "Unknown unit '{}'. Unit must be defined by a scale or ratio type.",
2041 name
2042 )
2043 })?;
2044 if lemma_type.is_ratio() {
2045 Ok(SemanticConversionTarget::RatioUnit(name.clone()))
2046 } else if lemma_type.is_scale() {
2047 Ok(SemanticConversionTarget::ScaleUnit(name.clone()))
2048 } else {
2049 Err(format!(
2050 "Unit '{}' is not a ratio or scale type; cannot use it in conversion.",
2051 name
2052 ))
2053 }
2054 }
2055 }
2056}
2057
2058static PRIMITIVE_BOOLEAN: OnceLock<LemmaType> = OnceLock::new();
2064static PRIMITIVE_SCALE: OnceLock<LemmaType> = OnceLock::new();
2065static PRIMITIVE_NUMBER: OnceLock<LemmaType> = OnceLock::new();
2066static PRIMITIVE_TEXT: OnceLock<LemmaType> = OnceLock::new();
2067static PRIMITIVE_DATE: OnceLock<LemmaType> = OnceLock::new();
2068static PRIMITIVE_TIME: OnceLock<LemmaType> = OnceLock::new();
2069static PRIMITIVE_DURATION: OnceLock<LemmaType> = OnceLock::new();
2070static PRIMITIVE_RATIO: OnceLock<LemmaType> = OnceLock::new();
2071
2072#[must_use]
2074pub fn primitive_boolean() -> &'static LemmaType {
2075 PRIMITIVE_BOOLEAN.get_or_init(|| LemmaType::primitive(TypeSpecification::boolean()))
2076}
2077
2078#[must_use]
2079pub fn primitive_scale() -> &'static LemmaType {
2080 PRIMITIVE_SCALE.get_or_init(|| LemmaType::primitive(TypeSpecification::scale()))
2081}
2082
2083#[must_use]
2084pub fn primitive_number() -> &'static LemmaType {
2085 PRIMITIVE_NUMBER.get_or_init(|| LemmaType::primitive(TypeSpecification::number()))
2086}
2087
2088#[must_use]
2089pub fn primitive_text() -> &'static LemmaType {
2090 PRIMITIVE_TEXT.get_or_init(|| LemmaType::primitive(TypeSpecification::text()))
2091}
2092
2093#[must_use]
2094pub fn primitive_date() -> &'static LemmaType {
2095 PRIMITIVE_DATE.get_or_init(|| LemmaType::primitive(TypeSpecification::date()))
2096}
2097
2098#[must_use]
2099pub fn primitive_time() -> &'static LemmaType {
2100 PRIMITIVE_TIME.get_or_init(|| LemmaType::primitive(TypeSpecification::time()))
2101}
2102
2103#[must_use]
2104pub fn primitive_duration() -> &'static LemmaType {
2105 PRIMITIVE_DURATION.get_or_init(|| LemmaType::primitive(TypeSpecification::duration()))
2106}
2107
2108#[must_use]
2109pub fn primitive_ratio() -> &'static LemmaType {
2110 PRIMITIVE_RATIO.get_or_init(|| LemmaType::primitive(TypeSpecification::ratio()))
2111}
2112
2113impl fmt::Display for PathSegment {
2118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2119 write!(f, "{} → {}", self.fact, self.spec)
2120 }
2121}
2122
2123impl fmt::Display for FactPath {
2124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2125 for segment in &self.segments {
2126 write!(f, "{}.", segment)?;
2127 }
2128 write!(f, "{}", self.fact)
2129 }
2130}
2131
2132impl fmt::Display for RulePath {
2133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2134 for segment in &self.segments {
2135 write!(f, "{}.", segment)?;
2136 }
2137 write!(f, "{}", self.rule)
2138 }
2139}
2140
2141impl fmt::Display for LemmaType {
2142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2143 write!(f, "{}", self.name())
2144 }
2145}
2146
2147impl fmt::Display for LiteralValue {
2148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2149 match &self.value {
2150 ValueKind::Scale(n, u) => {
2151 if let TypeSpecification::Scale { decimals, .. } = &self.lemma_type.specifications {
2152 let s = match decimals {
2153 Some(d) => {
2154 let dp = u32::from(*d);
2155 let rounded = n.round_dp(dp);
2156 format!("{:.prec$}", rounded, prec = *d as usize)
2157 }
2158 None => n.normalize().to_string(),
2159 };
2160 return write!(f, "{} {}", s, u);
2161 }
2162 write!(f, "{}", self.value)
2163 }
2164 ValueKind::Ratio(r, Some(unit_name)) => {
2165 if let TypeSpecification::Ratio { units, .. } = &self.lemma_type.specifications {
2166 if let Ok(unit) = units.get(unit_name) {
2167 let display_value = (*r * unit.value).normalize();
2168 let s = if display_value.fract().is_zero() {
2169 display_value.trunc().to_string()
2170 } else {
2171 display_value.to_string()
2172 };
2173 return match unit_name.as_str() {
2175 "percent" => write!(f, "{}%", s),
2176 "permille" => write!(f, "{}%%", s),
2177 _ => write!(f, "{} {}", s, unit_name),
2178 };
2179 }
2180 }
2181 write!(f, "{}", self.value)
2182 }
2183 _ => write!(f, "{}", self.value),
2184 }
2185 }
2186}
2187
2188impl Eq for Expression {}
2193
2194impl Hash for Expression {
2195 fn hash<H: Hasher>(&self, state: &mut H) {
2196 self.semantic_hash(state);
2197 }
2198}
2199
2200impl Eq for ExpressionKind {}
2201
2202impl Hash for ExpressionKind {
2203 fn hash<H: Hasher>(&self, state: &mut H) {
2204 self.semantic_hash(state);
2205 }
2206}
2207
2208#[cfg(test)]
2213mod tests {
2214 use super::*;
2215 use crate::parsing::ast::{BooleanValue, DateTimeValue, DurationUnit, TimeValue};
2216 use rust_decimal::Decimal;
2217 use std::str::FromStr;
2218
2219 #[test]
2220 fn test_negated_comparison() {
2221 assert_eq!(
2222 negated_comparison(ComparisonComputation::LessThan),
2223 ComparisonComputation::GreaterThanOrEqual
2224 );
2225 assert_eq!(
2226 negated_comparison(ComparisonComputation::GreaterThanOrEqual),
2227 ComparisonComputation::LessThan
2228 );
2229 assert_eq!(
2230 negated_comparison(ComparisonComputation::Equal),
2231 ComparisonComputation::IsNot,
2232 "== negates to 'is not'"
2233 );
2234 assert_eq!(
2235 negated_comparison(ComparisonComputation::NotEqual),
2236 ComparisonComputation::Is,
2237 "!= negates to 'is'"
2238 );
2239 assert_eq!(
2240 negated_comparison(ComparisonComputation::Is),
2241 ComparisonComputation::IsNot
2242 );
2243 assert_eq!(
2244 negated_comparison(ComparisonComputation::IsNot),
2245 ComparisonComputation::Is
2246 );
2247 }
2248
2249 #[test]
2250 fn test_literal_value_to_primitive_type() {
2251 let one = Decimal::from_str("1").unwrap();
2252
2253 assert_eq!(LiteralValue::text("".to_string()).lemma_type.name(), "text");
2254 assert_eq!(LiteralValue::number(one).lemma_type.name(), "number");
2255 assert_eq!(
2256 LiteralValue::from_bool(bool::from(BooleanValue::True))
2257 .lemma_type
2258 .name(),
2259 "boolean"
2260 );
2261
2262 let dt = DateTimeValue {
2263 year: 2024,
2264 month: 1,
2265 day: 1,
2266 hour: 0,
2267 minute: 0,
2268 second: 0,
2269 microsecond: 0,
2270 timezone: None,
2271 };
2272 assert_eq!(
2273 LiteralValue::date(date_time_to_semantic(&dt))
2274 .lemma_type
2275 .name(),
2276 "date"
2277 );
2278 assert_eq!(
2279 LiteralValue::ratio(one / Decimal::from(100), Some("percent".to_string()))
2280 .lemma_type
2281 .name(),
2282 "ratio"
2283 );
2284 assert_eq!(
2285 LiteralValue::duration(one, duration_unit_to_semantic(&DurationUnit::Second))
2286 .lemma_type
2287 .name(),
2288 "duration"
2289 );
2290 }
2291
2292 #[test]
2293 fn test_spec_type_display() {
2294 assert_eq!(format!("{}", primitive_text()), "text");
2295 assert_eq!(format!("{}", primitive_number()), "number");
2296 assert_eq!(format!("{}", primitive_date()), "date");
2297 assert_eq!(format!("{}", primitive_boolean()), "boolean");
2298 assert_eq!(format!("{}", primitive_duration()), "duration");
2299 }
2300
2301 #[test]
2302 fn test_type_constructor() {
2303 let specs = TypeSpecification::number();
2304 let lemma_type = LemmaType::new("dice".to_string(), specs, TypeExtends::Primitive);
2305 assert_eq!(lemma_type.name(), "dice");
2306 }
2307
2308 #[test]
2309 fn test_type_display() {
2310 let specs = TypeSpecification::text();
2311 let lemma_type = LemmaType::new("name".to_string(), specs, TypeExtends::Primitive);
2312 assert_eq!(format!("{}", lemma_type), "name");
2313 }
2314
2315 #[test]
2316 fn test_type_equality() {
2317 let specs1 = TypeSpecification::number();
2318 let specs2 = TypeSpecification::number();
2319 let lemma_type1 = LemmaType::new("dice".to_string(), specs1, TypeExtends::Primitive);
2320 let lemma_type2 = LemmaType::new("dice".to_string(), specs2, TypeExtends::Primitive);
2321 assert_eq!(lemma_type1, lemma_type2);
2322 }
2323
2324 #[test]
2325 fn test_type_serialization() {
2326 let specs = TypeSpecification::number();
2327 let lemma_type = LemmaType::new("dice".to_string(), specs, TypeExtends::Primitive);
2328 let serialized = serde_json::to_string(&lemma_type).unwrap();
2329 let deserialized: LemmaType = serde_json::from_str(&serialized).unwrap();
2330 assert_eq!(lemma_type, deserialized);
2331 }
2332
2333 #[test]
2334 fn test_literal_value_display_value() {
2335 let ten = Decimal::from_str("10").unwrap();
2336
2337 assert_eq!(
2338 LiteralValue::text("hello".to_string()).display_value(),
2339 "hello"
2340 );
2341 assert_eq!(LiteralValue::number(ten).display_value(), "10");
2342 assert_eq!(LiteralValue::from_bool(true).display_value(), "true");
2343 assert_eq!(LiteralValue::from_bool(false).display_value(), "false");
2344
2345 let ten_percent_ratio = LiteralValue::ratio(
2347 Decimal::from_str("0.10").unwrap(),
2348 Some("percent".to_string()),
2349 );
2350 assert_eq!(ten_percent_ratio.display_value(), "10%");
2351
2352 let time = TimeValue {
2353 hour: 14,
2354 minute: 30,
2355 second: 0,
2356 timezone: None,
2357 };
2358 let time_display = LiteralValue::time(time_to_semantic(&time)).display_value();
2359 assert!(time_display.contains("14"));
2360 assert!(time_display.contains("30"));
2361 }
2362
2363 #[test]
2364 fn test_scale_display_respects_type_decimals() {
2365 let money_type = LemmaType {
2366 name: Some("money".to_string()),
2367 specifications: TypeSpecification::Scale {
2368 minimum: None,
2369 maximum: None,
2370 decimals: Some(2),
2371 precision: None,
2372 units: ScaleUnits::from(vec![ScaleUnit {
2373 name: "eur".to_string(),
2374 value: Decimal::from(1),
2375 }]),
2376 help: String::new(),
2377 default: None,
2378 },
2379 extends: TypeExtends::Primitive,
2380 };
2381 let val = LiteralValue::scale_with_type(
2382 Decimal::from_str("1.8").unwrap(),
2383 "eur".to_string(),
2384 money_type.clone(),
2385 );
2386 assert_eq!(val.display_value(), "1.80 eur");
2387 let more_precision = LiteralValue::scale_with_type(
2388 Decimal::from_str("1.80000").unwrap(),
2389 "eur".to_string(),
2390 money_type,
2391 );
2392 assert_eq!(more_precision.display_value(), "1.80 eur");
2393 let scale_no_decimals = LemmaType {
2394 name: Some("count".to_string()),
2395 specifications: TypeSpecification::Scale {
2396 minimum: None,
2397 maximum: None,
2398 decimals: None,
2399 precision: None,
2400 units: ScaleUnits::from(vec![ScaleUnit {
2401 name: "items".to_string(),
2402 value: Decimal::from(1),
2403 }]),
2404 help: String::new(),
2405 default: None,
2406 },
2407 extends: TypeExtends::Primitive,
2408 };
2409 let val_any = LiteralValue::scale_with_type(
2410 Decimal::from_str("42.50").unwrap(),
2411 "items".to_string(),
2412 scale_no_decimals,
2413 );
2414 assert_eq!(val_any.display_value(), "42.5 items");
2415 }
2416
2417 #[test]
2418 fn test_literal_value_time_type() {
2419 let time = TimeValue {
2420 hour: 14,
2421 minute: 30,
2422 second: 0,
2423 timezone: None,
2424 };
2425 let lit = LiteralValue::time(time_to_semantic(&time));
2426 assert_eq!(lit.lemma_type.name(), "time");
2427 }
2428
2429 #[test]
2430 fn test_scale_family_name_primitive_root() {
2431 let scale_spec = TypeSpecification::scale();
2432 let money_primitive = LemmaType::new(
2433 "money".to_string(),
2434 scale_spec.clone(),
2435 TypeExtends::Primitive,
2436 );
2437 assert_eq!(money_primitive.scale_family_name(), Some("money"));
2438 }
2439
2440 #[test]
2441 fn test_scale_family_name_custom() {
2442 let scale_spec = TypeSpecification::scale();
2443 let money_custom = LemmaType::new(
2444 "money".to_string(),
2445 scale_spec,
2446 TypeExtends::Custom {
2447 parent: "money".to_string(),
2448 family: "money".to_string(),
2449 },
2450 );
2451 assert_eq!(money_custom.scale_family_name(), Some("money"));
2452 }
2453
2454 #[test]
2455 fn test_same_scale_family_same_name_different_extends() {
2456 let scale_spec = TypeSpecification::scale();
2457 let money_primitive = LemmaType::new(
2458 "money".to_string(),
2459 scale_spec.clone(),
2460 TypeExtends::Primitive,
2461 );
2462 let money_custom = LemmaType::new(
2463 "money".to_string(),
2464 scale_spec,
2465 TypeExtends::Custom {
2466 parent: "money".to_string(),
2467 family: "money".to_string(),
2468 },
2469 );
2470 assert!(money_primitive.same_scale_family(&money_custom));
2471 assert!(money_custom.same_scale_family(&money_primitive));
2472 }
2473
2474 #[test]
2475 fn test_same_scale_family_parent_and_child() {
2476 let scale_spec = TypeSpecification::scale();
2477 let type_x = LemmaType::new("x".to_string(), scale_spec.clone(), TypeExtends::Primitive);
2478 let type_x2 = LemmaType::new(
2479 "x2".to_string(),
2480 scale_spec,
2481 TypeExtends::Custom {
2482 parent: "x".to_string(),
2483 family: "x".to_string(),
2484 },
2485 );
2486 assert_eq!(type_x.scale_family_name(), Some("x"));
2487 assert_eq!(type_x2.scale_family_name(), Some("x"));
2488 assert!(type_x.same_scale_family(&type_x2));
2489 assert!(type_x2.same_scale_family(&type_x));
2490 }
2491
2492 #[test]
2493 fn test_same_scale_family_siblings() {
2494 let scale_spec = TypeSpecification::scale();
2495 let type_x2_a = LemmaType::new(
2496 "x2a".to_string(),
2497 scale_spec.clone(),
2498 TypeExtends::Custom {
2499 parent: "x".to_string(),
2500 family: "x".to_string(),
2501 },
2502 );
2503 let type_x2_b = LemmaType::new(
2504 "x2b".to_string(),
2505 scale_spec,
2506 TypeExtends::Custom {
2507 parent: "x".to_string(),
2508 family: "x".to_string(),
2509 },
2510 );
2511 assert!(type_x2_a.same_scale_family(&type_x2_b));
2512 }
2513
2514 #[test]
2515 fn test_same_scale_family_different_families() {
2516 let scale_spec = TypeSpecification::scale();
2517 let money = LemmaType::new(
2518 "money".to_string(),
2519 scale_spec.clone(),
2520 TypeExtends::Primitive,
2521 );
2522 let temperature = LemmaType::new(
2523 "temperature".to_string(),
2524 scale_spec,
2525 TypeExtends::Primitive,
2526 );
2527 assert!(!money.same_scale_family(&temperature));
2528 assert!(!temperature.same_scale_family(&money));
2529 }
2530
2531 #[test]
2532 fn test_same_scale_family_scale_vs_non_scale() {
2533 let scale_spec = TypeSpecification::scale();
2534 let number_spec = TypeSpecification::number();
2535 let scale_type = LemmaType::new("money".to_string(), scale_spec, TypeExtends::Primitive);
2536 let number_type = LemmaType::new("amount".to_string(), number_spec, TypeExtends::Primitive);
2537 assert!(!scale_type.same_scale_family(&number_type));
2538 assert!(!number_type.same_scale_family(&scale_type));
2539 }
2540
2541 #[test]
2542 fn test_scale_family_name_non_scale_returns_none() {
2543 let number_spec = TypeSpecification::number();
2544 let number_type = LemmaType::new("amount".to_string(), number_spec, TypeExtends::Primitive);
2545 assert_eq!(number_type.scale_family_name(), None);
2546 }
2547}