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