1use crate::error::LemmaError;
2use crate::parsing::ast::Span;
3use crate::parsing::source::Source;
4use chrono::{Datelike, Timelike};
5use rust_decimal::Decimal;
6use serde::Serialize;
7use std::fmt;
8use std::hash::{Hash, Hasher};
9use std::str::FromStr;
10use std::sync::{Arc, OnceLock};
11
12#[derive(Debug, Clone, PartialEq)]
14pub struct LemmaDoc {
15 pub name: String,
16 pub attribute: Option<String>,
17 pub start_line: usize,
18 pub commentary: Option<String>,
19 pub types: Vec<TypeDef>,
20 pub facts: Vec<LemmaFact>,
21 pub rules: Vec<LemmaRule>,
22}
23
24#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
25pub struct LemmaFact {
26 pub reference: FactReference,
27 pub value: FactValue,
28 pub source_location: Option<Source>,
29}
30
31#[derive(Debug, Clone, PartialEq, serde::Serialize)]
37pub struct UnlessClause {
38 pub condition: Expression,
39 pub result: Expression,
40 pub source_location: Option<Source>,
41}
42
43#[derive(Debug, Clone, PartialEq, serde::Serialize)]
45pub struct LemmaRule {
46 pub name: String,
47 pub expression: Expression,
48 pub unless_clauses: Vec<UnlessClause>,
49 pub source_location: Option<Source>,
50}
51
52#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub struct Expression {
58 pub kind: ExpressionKind,
59 pub source_location: Option<Source>,
60}
61
62impl Expression {
63 #[must_use]
65 pub fn new(kind: ExpressionKind, source_location: Option<Source>) -> Self {
66 Self {
67 kind,
68 source_location,
69 }
70 }
71
72 pub fn get_source_text(
76 &self,
77 sources: &std::collections::HashMap<String, String>,
78 ) -> Option<String> {
79 self.source_location.as_ref().and_then(|loc| {
80 sources
81 .get(&loc.attribute)
82 .and_then(|source| loc.extract_text(source))
83 })
84 }
85
86 pub fn collect_fact_paths(&self, facts: &mut std::collections::HashSet<FactPath>) {
88 match &self.kind {
89 ExpressionKind::FactPath(fp) => {
90 facts.insert(fp.clone());
91 }
92 ExpressionKind::LogicalAnd(left, right)
93 | ExpressionKind::LogicalOr(left, right)
94 | ExpressionKind::Arithmetic(left, _, right)
95 | ExpressionKind::Comparison(left, _, right) => {
96 left.collect_fact_paths(facts);
97 right.collect_fact_paths(facts);
98 }
99 ExpressionKind::UnitConversion(inner, _)
100 | ExpressionKind::LogicalNegation(inner, _)
101 | ExpressionKind::MathematicalComputation(_, inner) => {
102 inner.collect_fact_paths(facts);
103 }
104 ExpressionKind::Literal(_)
105 | ExpressionKind::Reference(_)
106 | ExpressionKind::UnresolvedUnitLiteral(_, _)
107 | ExpressionKind::FactReference(_)
108 | ExpressionKind::RuleReference(_)
109 | ExpressionKind::Veto(_)
110 | ExpressionKind::RulePath(_) => {}
111 }
112 }
113
114 fn semantic_hash<H: Hasher>(&self, state: &mut H) {
116 self.kind.semantic_hash(state);
117 }
118}
119
120impl PartialEq for Expression {
122 fn eq(&self, other: &Self) -> bool {
123 self.kind == other.kind
124 }
125}
126
127impl Eq for Expression {}
128
129impl Hash for Expression {
131 fn hash<H: Hasher>(&self, state: &mut H) {
132 self.semantic_hash(state);
133 }
134}
135
136#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum ExpressionKind {
140 Literal(LiteralValue),
141 Reference(Reference),
143 UnresolvedUnitLiteral(Decimal, String),
146 FactReference(FactReference),
148 RuleReference(RuleReference),
149 LogicalAnd(Arc<Expression>, Arc<Expression>),
150 LogicalOr(Arc<Expression>, Arc<Expression>),
151 Arithmetic(Arc<Expression>, ArithmeticComputation, Arc<Expression>),
152 Comparison(Arc<Expression>, ComparisonComputation, Arc<Expression>),
153 UnitConversion(Arc<Expression>, ConversionTarget),
154 LogicalNegation(Arc<Expression>, NegationType),
155 MathematicalComputation(MathematicalComputation, Arc<Expression>),
156 Veto(VetoExpression),
157 FactPath(FactPath),
159 RulePath(RulePath),
161}
162
163impl ExpressionKind {
164 fn semantic_hash<H: Hasher>(&self, state: &mut H) {
166 std::mem::discriminant(self).hash(state);
168
169 match self {
170 ExpressionKind::Literal(lit) => lit.semantic_hash(state),
171 ExpressionKind::Reference(r) => r.hash(state),
172 ExpressionKind::UnresolvedUnitLiteral(_, _) => {
173 unreachable!("UnresolvedUnitLiteral found during hashing - this indicates a bug: unresolved units should be resolved during planning");
174 }
175 ExpressionKind::FactReference(fr) => fr.hash(state),
176 ExpressionKind::RuleReference(rr) => rr.hash(state),
177 ExpressionKind::LogicalAnd(left, right) | ExpressionKind::LogicalOr(left, right) => {
178 left.semantic_hash(state);
179 right.semantic_hash(state);
180 }
181 ExpressionKind::Arithmetic(left, op, right) => {
182 left.semantic_hash(state);
183 op.hash(state);
184 right.semantic_hash(state);
185 }
186 ExpressionKind::Comparison(left, op, right) => {
187 left.semantic_hash(state);
188 op.hash(state);
189 right.semantic_hash(state);
190 }
191 ExpressionKind::UnitConversion(expr, target) => {
192 expr.semantic_hash(state);
193 target.hash(state);
194 }
195 ExpressionKind::LogicalNegation(expr, neg_type) => {
196 expr.semantic_hash(state);
197 neg_type.hash(state);
198 }
199 ExpressionKind::MathematicalComputation(op, expr) => {
200 op.hash(state);
201 expr.semantic_hash(state);
202 }
203 ExpressionKind::Veto(veto) => veto.semantic_hash(state),
204 ExpressionKind::FactPath(fp) => fp.hash(state),
205 ExpressionKind::RulePath(rp) => rp.hash(state),
206 }
207 }
208}
209
210#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
218pub struct Reference {
219 pub segments: Vec<String>,
220 pub name: String,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
230pub struct FactReference {
231 pub segments: Vec<String>,
232 pub fact: String,
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
242pub struct RuleReference {
243 pub segments: Vec<String>,
244 pub rule: String,
245}
246
247#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
252pub struct PathSegment {
253 pub fact: String,
255
256 pub doc: String,
258}
259
260#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
265pub struct FactPath {
266 pub segments: Vec<PathSegment>,
268
269 pub fact: String,
271}
272
273impl FactPath {
274 #[must_use]
276 pub fn is_local(&self) -> bool {
277 self.segments.is_empty()
278 }
279
280 #[must_use]
282 pub fn new(segments: Vec<PathSegment>, fact: String) -> Self {
283 Self { segments, fact }
284 }
285
286 #[must_use]
288 pub fn local(fact: String) -> Self {
289 Self {
290 segments: Vec::new(),
291 fact,
292 }
293 }
294
295 #[must_use]
301 pub fn from_path(mut path: Vec<String>) -> Self {
302 if path.is_empty() {
303 return Self {
304 segments: Vec::new(),
305 fact: String::new(),
306 };
307 }
308 let fact = path.pop().unwrap_or_default();
309 let segments = path
310 .into_iter()
311 .map(|fact_name| PathSegment {
312 fact: fact_name,
313 doc: String::new(),
314 })
315 .collect();
316 Self { segments, fact }
317 }
318
319 #[must_use]
321 pub fn full_path(&self) -> Vec<String> {
322 let mut path: Vec<String> = self.segments.iter().map(|s| s.fact.clone()).collect();
323 path.push(self.fact.clone());
324 path
325 }
326}
327
328#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
333pub struct RulePath {
334 pub segments: Vec<PathSegment>,
336
337 pub rule: String,
339}
340
341impl RulePath {
342 #[must_use]
344 pub fn is_local(&self) -> bool {
345 self.segments.is_empty()
346 }
347
348 #[must_use]
350 pub fn local(rule: String) -> Self {
351 Self {
352 segments: Vec::new(),
353 rule,
354 }
355 }
356}
357
358impl RuleReference {
359 pub fn from_path(mut full_path: Vec<String>) -> Self {
361 let rule = full_path.pop().unwrap_or_default();
362 Self {
363 segments: full_path,
364 rule,
365 }
366 }
367
368 #[must_use]
370 pub fn is_local(&self) -> bool {
371 self.segments.is_empty()
372 }
373
374 #[must_use]
376 pub fn full_path(&self) -> Vec<String> {
377 let mut path = self.segments.clone();
378 path.push(self.rule.clone());
379 path
380 }
381}
382
383#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
385#[serde(rename_all = "snake_case")]
386pub enum ArithmeticComputation {
387 Add,
388 Subtract,
389 Multiply,
390 Divide,
391 Modulo,
392 Power,
393}
394
395impl ArithmeticComputation {
396 #[must_use]
398 pub fn name(&self) -> &'static str {
399 match self {
400 ArithmeticComputation::Add => "addition",
401 ArithmeticComputation::Subtract => "subtraction",
402 ArithmeticComputation::Multiply => "multiplication",
403 ArithmeticComputation::Divide => "division",
404 ArithmeticComputation::Modulo => "modulo",
405 ArithmeticComputation::Power => "exponentiation",
406 }
407 }
408
409 #[must_use]
411 pub fn symbol(&self) -> &'static str {
412 match self {
413 ArithmeticComputation::Add => "+",
414 ArithmeticComputation::Subtract => "-",
415 ArithmeticComputation::Multiply => "*",
416 ArithmeticComputation::Divide => "/",
417 ArithmeticComputation::Modulo => "%",
418 ArithmeticComputation::Power => "^",
419 }
420 }
421}
422
423#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
425#[serde(rename_all = "snake_case")]
426pub enum ComparisonComputation {
427 GreaterThan,
428 LessThan,
429 GreaterThanOrEqual,
430 LessThanOrEqual,
431 Equal,
432 NotEqual,
433 Is,
434 IsNot,
435}
436
437impl ComparisonComputation {
438 #[must_use]
440 pub fn name(&self) -> &'static str {
441 match self {
442 ComparisonComputation::GreaterThan => "greater than",
443 ComparisonComputation::LessThan => "less than",
444 ComparisonComputation::GreaterThanOrEqual => "greater than or equal",
445 ComparisonComputation::LessThanOrEqual => "less than or equal",
446 ComparisonComputation::Equal => "equal",
447 ComparisonComputation::NotEqual => "not equal",
448 ComparisonComputation::Is => "is",
449 ComparisonComputation::IsNot => "is not",
450 }
451 }
452
453 #[must_use]
455 pub fn symbol(&self) -> &'static str {
456 match self {
457 ComparisonComputation::GreaterThan => ">",
458 ComparisonComputation::LessThan => "<",
459 ComparisonComputation::GreaterThanOrEqual => ">=",
460 ComparisonComputation::LessThanOrEqual => "<=",
461 ComparisonComputation::Equal => "==",
462 ComparisonComputation::NotEqual => "!=",
463 ComparisonComputation::Is => "is",
464 ComparisonComputation::IsNot => "is not",
465 }
466 }
467
468 #[must_use]
470 pub fn is_equal(&self) -> bool {
471 matches!(
472 self,
473 ComparisonComputation::Equal | ComparisonComputation::Is
474 )
475 }
476
477 #[must_use]
479 pub fn is_not_equal(&self) -> bool {
480 matches!(
481 self,
482 ComparisonComputation::NotEqual | ComparisonComputation::IsNot
483 )
484 }
485}
486
487#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
489#[serde(rename_all = "snake_case")]
490pub enum ConversionTarget {
491 Duration(DurationUnit),
492 Percentage,
493 ScaleUnit(String),
494}
495
496#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
498pub enum NegationType {
499 Not,
500}
501
502#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
504#[serde(rename_all = "snake_case")]
505pub enum LogicalComputation {
506 And,
507 Or,
508 Not,
509}
510
511#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
519pub struct VetoExpression {
520 pub message: Option<String>,
521}
522
523impl VetoExpression {
524 fn semantic_hash<H: Hasher>(&self, state: &mut H) {
525 self.message.hash(state);
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
531#[serde(rename_all = "snake_case")]
532pub enum MathematicalComputation {
533 Sqrt,
534 Sin,
535 Cos,
536 Tan,
537 Asin,
538 Acos,
539 Atan,
540 Log,
541 Exp,
542 Abs,
543 Floor,
544 Ceil,
545 Round,
546}
547
548#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
549#[serde(rename_all = "snake_case")]
550pub enum FactValue {
551 Literal(LiteralValue),
552 DocumentReference(String),
553 TypeDeclaration {
554 base: String,
555 overrides: Option<Vec<(String, Vec<String>)>>,
556 from: Option<String>,
557 },
558}
559
560#[derive(
563 Debug,
564 Clone,
565 PartialEq,
566 Serialize,
567 serde::Deserialize,
568 strum_macros::EnumString,
569 strum_macros::Display,
570)]
571#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
572pub enum BooleanValue {
573 True,
574 False,
575 Yes,
576 No,
577 Accept,
578 Reject,
579}
580
581impl From<BooleanValue> for bool {
582 fn from(value: BooleanValue) -> bool {
583 match value {
584 BooleanValue::True | BooleanValue::Yes | BooleanValue::Accept => true,
585 BooleanValue::False | BooleanValue::No | BooleanValue::Reject => false,
586 }
587 }
588}
589
590impl From<&BooleanValue> for bool {
591 fn from(value: &BooleanValue) -> bool {
592 match value {
593 BooleanValue::True | BooleanValue::Yes | BooleanValue::Accept => true,
594 BooleanValue::False | BooleanValue::No | BooleanValue::Reject => false,
595 }
596 }
597}
598
599impl From<bool> for BooleanValue {
600 fn from(value: bool) -> BooleanValue {
601 if value {
602 BooleanValue::True
603 } else {
604 BooleanValue::False
605 }
606 }
607}
608
609impl std::ops::Not for BooleanValue {
610 type Output = BooleanValue;
611
612 fn not(self) -> Self::Output {
613 if self.into() {
614 BooleanValue::False
615 } else {
616 BooleanValue::True
617 }
618 }
619}
620
621impl std::ops::Not for &BooleanValue {
622 type Output = BooleanValue;
623
624 fn not(self) -> Self::Output {
625 if self.into() {
626 BooleanValue::False
627 } else {
628 BooleanValue::True
629 }
630 }
631}
632
633#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
635#[serde(rename_all = "snake_case")]
636pub enum Value {
637 Number(Decimal),
638 Scale(Decimal, Option<String>), Text(String),
640 Date(DateTimeValue),
641 Time(TimeValue),
642 Boolean(BooleanValue),
643 Duration(Decimal, DurationUnit),
644 Ratio(Decimal, Option<String>), }
646
647#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
651pub struct LiteralValue {
652 pub value: Value,
653 pub lemma_type: LemmaType,
654}
655
656impl LiteralValue {
657 pub fn number<T: Into<Decimal>>(value: T) -> Self {
660 LiteralValue {
661 value: Value::Number(value.into()),
662 lemma_type: standard_number().clone(),
663 }
664 }
665
666 pub fn number_with_type<T: Into<Decimal>>(value: T, lemma_type: LemmaType) -> Self {
668 LiteralValue {
669 value: Value::Number(value.into()),
670 lemma_type,
671 }
672 }
673
674 pub fn scale<T: Into<Decimal>>(value: T, unit: Option<String>) -> Self {
677 LiteralValue {
678 value: Value::Scale(value.into(), unit),
679 lemma_type: crate::semantic::standard_scale().clone(),
680 }
681 }
682
683 pub fn scale_with_type<T: Into<Decimal>>(
685 value: T,
686 unit: Option<String>,
687 lemma_type: LemmaType,
688 ) -> Self {
689 LiteralValue {
690 value: Value::Scale(value.into(), unit),
691 lemma_type,
692 }
693 }
694
695 pub fn text(value: String) -> Self {
698 LiteralValue {
699 value: Value::Text(value),
700 lemma_type: standard_text().clone(),
701 }
702 }
703
704 pub fn text_with_type(value: String, lemma_type: LemmaType) -> Self {
706 LiteralValue {
707 value: Value::Text(value),
708 lemma_type,
709 }
710 }
711
712 pub fn boolean(value: BooleanValue) -> Self {
715 let canonical: BooleanValue = bool::from(&value).into();
716 LiteralValue {
717 value: Value::Boolean(canonical),
718 lemma_type: standard_boolean().clone(),
719 }
720 }
721
722 pub fn boolean_with_type(value: BooleanValue, lemma_type: LemmaType) -> Self {
724 let canonical: BooleanValue = bool::from(&value).into();
725 LiteralValue {
726 value: Value::Boolean(canonical),
727 lemma_type,
728 }
729 }
730
731 pub fn date(value: DateTimeValue) -> Self {
734 LiteralValue {
735 value: Value::Date(value),
736 lemma_type: standard_date().clone(),
737 }
738 }
739
740 pub fn date_with_type(value: DateTimeValue, lemma_type: LemmaType) -> Self {
742 LiteralValue {
743 value: Value::Date(value),
744 lemma_type,
745 }
746 }
747
748 pub fn time(value: TimeValue) -> Self {
751 LiteralValue {
752 value: Value::Time(value),
753 lemma_type: standard_time().clone(),
754 }
755 }
756
757 pub fn time_with_type(value: TimeValue, lemma_type: LemmaType) -> Self {
759 LiteralValue {
760 value: Value::Time(value),
761 lemma_type,
762 }
763 }
764
765 pub fn duration(value: Decimal, unit: DurationUnit) -> Self {
768 LiteralValue {
769 value: Value::Duration(value, unit),
770 lemma_type: standard_duration().clone(),
771 }
772 }
773
774 pub fn duration_with_type(value: Decimal, unit: DurationUnit, lemma_type: LemmaType) -> Self {
776 LiteralValue {
777 value: Value::Duration(value, unit),
778 lemma_type,
779 }
780 }
781
782 pub fn ratio<T: Into<Decimal>>(value: T, unit: Option<String>) -> Self {
785 LiteralValue {
786 value: Value::Ratio(value.into(), unit),
787 lemma_type: standard_ratio().clone(),
788 }
789 }
790
791 pub fn ratio_with_type<T: Into<Decimal>>(
793 value: T,
794 unit: Option<String>,
795 lemma_type: LemmaType,
796 ) -> Self {
797 LiteralValue {
798 value: Value::Ratio(value.into(), unit),
799 lemma_type,
800 }
801 }
802
803 pub fn get_type(&self) -> &LemmaType {
805 &self.lemma_type
806 }
807
808 #[must_use]
810 pub fn display_value(&self) -> String {
811 self.to_string()
812 }
813
814 pub fn byte_size(&self) -> usize {
816 match &self.value {
817 Value::Text(s) => s.len(),
818 Value::Number(d) => std::mem::size_of_val(d),
819 Value::Scale(d, _) => std::mem::size_of_val(d),
820 Value::Boolean(_) => std::mem::size_of::<bool>(),
821 Value::Date(_) => std::mem::size_of::<DateTimeValue>(),
822 Value::Time(_) => std::mem::size_of::<TimeValue>(),
823 Value::Duration(value, _) => std::mem::size_of_val(value),
824 Value::Ratio(value, _) => std::mem::size_of_val(value),
825 }
826 }
827
828 fn semantic_hash<H: Hasher>(&self, state: &mut H) {
831 std::mem::discriminant(&self.value).hash(state);
832 match &self.value {
833 Value::Number(d) | Value::Scale(d, _) | Value::Ratio(d, _) => {
834 d.to_string().hash(state);
835 }
836 Value::Text(s) => s.hash(state),
837 Value::Boolean(b) => std::mem::discriminant(b).hash(state),
838 Value::Date(dt) => {
839 dt.year.hash(state);
840 dt.month.hash(state);
841 dt.day.hash(state);
842 dt.hour.hash(state);
843 dt.minute.hash(state);
844 dt.second.hash(state);
845 if let Some(tz) = &dt.timezone {
846 tz.offset_hours.hash(state);
847 tz.offset_minutes.hash(state);
848 }
849 }
850 Value::Time(t) => {
851 t.hour.hash(state);
852 t.minute.hash(state);
853 t.second.hash(state);
854 if let Some(tz) = &t.timezone {
855 tz.offset_hours.hash(state);
856 tz.offset_minutes.hash(state);
857 }
858 }
859 Value::Duration(value, unit) => {
860 value.to_string().hash(state);
861 std::mem::discriminant(unit).hash(state);
862 }
863 }
864 }
865}
866
867#[derive(
869 Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, serde::Deserialize,
870)]
871pub struct TimeValue {
872 pub hour: u8,
873 pub minute: u8,
874 pub second: u8,
875 pub timezone: Option<TimezoneValue>,
876}
877
878#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, serde::Deserialize)]
880pub struct TimezoneValue {
881 pub offset_hours: i8,
882 pub offset_minutes: u8,
883}
884
885#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, serde::Deserialize)]
887pub struct DateTimeValue {
888 pub year: i32,
889 pub month: u32,
890 pub day: u32,
891 pub hour: u32,
892 pub minute: u32,
893 pub second: u32,
894 pub timezone: Option<TimezoneValue>,
895}
896
897macro_rules! impl_unit_serialize {
899 ($($unit_type:ty),+) => {
900 $(
901 impl Serialize for $unit_type {
902 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
903 where
904 S: serde::Serializer,
905 {
906 serializer.serialize_str(&self.to_string())
907 }
908 }
909 )+
910 };
911}
912
913impl_unit_serialize!(DurationUnit);
914
915#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Deserialize, strum_macros::EnumString)]
916#[strum(serialize_all = "lowercase")]
917pub enum DurationUnit {
918 Year,
919 Month,
920 Week,
921 Day,
922 Hour,
923 Minute,
924 Second,
925 Millisecond,
926 Microsecond,
927}
928
929impl fmt::Display for DurationUnit {
930 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
931 let s = match self {
932 DurationUnit::Year => "years",
933 DurationUnit::Month => "months",
934 DurationUnit::Week => "weeks",
935 DurationUnit::Day => "days",
936 DurationUnit::Hour => "hours",
937 DurationUnit::Minute => "minutes",
938 DurationUnit::Second => "seconds",
939 DurationUnit::Millisecond => "milliseconds",
940 DurationUnit::Microsecond => "microseconds",
941 };
942 write!(f, "{}", s)
943 }
944}
945
946impl Reference {
947 #[must_use]
948 pub fn new(segments: Vec<String>, name: String) -> Self {
949 Self { segments, name }
950 }
951
952 #[must_use]
953 pub fn local(name: String) -> Self {
954 Self {
955 segments: Vec::new(),
956 name,
957 }
958 }
959
960 #[must_use]
961 pub fn from_path(path: Vec<String>) -> Self {
962 if path.is_empty() {
963 Self {
964 segments: Vec::new(),
965 name: String::new(),
966 }
967 } else {
968 let name = path[path.len() - 1].clone();
970 let segments = path[..path.len() - 1].to_vec();
971 Self { segments, name }
972 }
973 }
974
975 #[must_use]
976 pub fn is_local(&self) -> bool {
977 self.segments.is_empty()
978 }
979
980 #[must_use]
981 pub fn full_path(&self) -> Vec<String> {
982 let mut path = self.segments.clone();
983 path.push(self.name.clone());
984 path
985 }
986
987 #[must_use]
989 pub fn to_fact_reference(&self) -> FactReference {
990 FactReference {
991 segments: self.segments.clone(),
992 fact: self.name.clone(),
993 }
994 }
995}
996
997impl FactReference {
998 #[must_use]
1000 pub fn new(segments: Vec<String>, fact: String) -> Self {
1001 Self { segments, fact }
1002 }
1003
1004 #[must_use]
1006 pub fn local(fact: String) -> Self {
1007 Self {
1008 segments: Vec::new(),
1009 fact,
1010 }
1011 }
1012
1013 #[must_use]
1015 pub fn from_path(path: Vec<String>) -> Self {
1016 if path.is_empty() {
1017 Self {
1018 segments: Vec::new(),
1019 fact: String::new(),
1020 }
1021 } else {
1022 let fact = path[path.len() - 1].clone();
1024 let segments = path[..path.len() - 1].to_vec();
1025 Self { segments, fact }
1026 }
1027 }
1028
1029 #[must_use]
1031 pub fn is_local(&self) -> bool {
1032 self.segments.is_empty()
1033 }
1034
1035 #[must_use]
1037 pub fn full_path(&self) -> Vec<String> {
1038 let mut path = self.segments.clone();
1039 path.push(self.fact.clone());
1040 path
1041 }
1042}
1043
1044impl LemmaFact {
1045 #[must_use]
1046 pub fn new(reference: FactReference, value: FactValue) -> Self {
1047 Self {
1048 reference,
1049 value,
1050 source_location: None,
1051 }
1052 }
1053
1054 #[must_use]
1055 pub fn with_source_location(mut self, source_location: Source) -> Self {
1056 self.source_location = Some(source_location);
1057 self
1058 }
1059
1060 #[must_use]
1062 pub fn is_local(&self) -> bool {
1063 self.reference.is_local()
1064 }
1065}
1066
1067impl LemmaDoc {
1068 #[must_use]
1069 pub fn new(name: String) -> Self {
1070 Self {
1071 name,
1072 attribute: None,
1073 start_line: 1,
1074 commentary: None,
1075 types: Vec::new(),
1076 facts: Vec::new(),
1077 rules: Vec::new(),
1078 }
1079 }
1080
1081 #[must_use]
1082 pub fn with_attribute(mut self, attribute: String) -> Self {
1083 self.attribute = Some(attribute);
1084 self
1085 }
1086
1087 #[must_use]
1088 pub fn with_start_line(mut self, start_line: usize) -> Self {
1089 self.start_line = start_line;
1090 self
1091 }
1092
1093 #[must_use]
1094 pub fn set_commentary(mut self, commentary: String) -> Self {
1095 self.commentary = Some(commentary);
1096 self
1097 }
1098
1099 #[must_use]
1100 pub fn add_fact(mut self, fact: LemmaFact) -> Self {
1101 self.facts.push(fact);
1102 self
1103 }
1104
1105 #[must_use]
1106 pub fn add_rule(mut self, rule: LemmaRule) -> Self {
1107 self.rules.push(rule);
1108 self
1109 }
1110
1111 #[must_use]
1112 pub fn add_type(mut self, type_def: TypeDef) -> Self {
1113 self.types.push(type_def);
1114 self
1115 }
1116
1117 pub fn get_fact_type(&self, fact_ref: &[String]) -> Option<LemmaType> {
1120 let fact_path: Vec<String> = fact_ref.to_vec();
1121 let fact_name = fact_path.last()?.clone();
1122 let segments: Vec<String> = fact_path[..fact_path.len().saturating_sub(1)].to_vec();
1123 let target_ref = FactReference {
1124 segments,
1125 fact: fact_name,
1126 };
1127 self.facts
1128 .iter()
1129 .find(|fact| fact.reference == target_ref)
1130 .and_then(|fact| match &fact.value {
1131 FactValue::Literal(lit) => Some(lit.get_type().clone()),
1132 FactValue::TypeDeclaration { .. } => {
1133 None
1135 }
1136 FactValue::DocumentReference(_) => None,
1137 })
1138 }
1139}
1140
1141impl fmt::Display for LemmaDoc {
1142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143 write!(f, "doc {}", self.name)?;
1144 writeln!(f)?;
1145
1146 if let Some(ref commentary) = self.commentary {
1147 writeln!(f, "\"\"\"{}", commentary)?;
1148 writeln!(f, "\"\"\"")?;
1149 }
1150
1151 for fact in &self.facts {
1152 write!(f, "{}", fact)?;
1153 }
1154
1155 for rule in &self.rules {
1156 write!(f, "{}", rule)?;
1157 }
1158
1159 Ok(())
1160 }
1161}
1162
1163impl fmt::Display for FactReference {
1164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1165 for segment in &self.segments {
1166 write!(f, "{}.", segment)?;
1167 }
1168 write!(f, "{}", self.fact)
1169 }
1170}
1171
1172impl fmt::Display for LemmaFact {
1173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1174 writeln!(f, "fact {} = {}", self.reference, self.value)
1175 }
1176}
1177
1178impl fmt::Display for LemmaRule {
1179 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1180 write!(f, "rule {} = {}", self.name, self.expression)?;
1181
1182 for unless_clause in &self.unless_clauses {
1183 write!(
1184 f,
1185 " unless {} then {}",
1186 unless_clause.condition, unless_clause.result
1187 )?;
1188 }
1189
1190 writeln!(f)?;
1191 Ok(())
1192 }
1193}
1194
1195impl fmt::Display for Expression {
1196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1197 match &self.kind {
1198 ExpressionKind::Literal(lit) => write!(f, "{}", lit),
1199 ExpressionKind::Reference(r) => {
1200 if r.segments.is_empty() {
1201 write!(f, "{}", r.name)
1202 } else {
1203 write!(f, "{}.{}", r.segments.join("."), r.name)
1204 }
1205 }
1206 ExpressionKind::FactReference(fact_ref) => write!(f, "{}", fact_ref),
1207 ExpressionKind::FactPath(fact_path) => write!(f, "{}", fact_path),
1208 ExpressionKind::RuleReference(rule_ref) => write!(f, "{}", rule_ref),
1209 ExpressionKind::RulePath(rule_path) => write!(f, "{}", rule_path),
1210 ExpressionKind::Arithmetic(left, op, right) => {
1211 write!(f, "{} {} {}", left, op, right)
1212 }
1213 ExpressionKind::Comparison(left, op, right) => {
1214 write!(f, "{} {} {}", left, op, right)
1215 }
1216 ExpressionKind::UnitConversion(value, target) => {
1217 write!(f, "{} in {}", value, target)
1218 }
1219 ExpressionKind::LogicalNegation(expr, _) => {
1220 write!(f, "not {}", expr)
1221 }
1222 ExpressionKind::LogicalAnd(left, right) => {
1223 write!(f, "{} and {}", left, right)
1224 }
1225 ExpressionKind::LogicalOr(left, right) => {
1226 write!(f, "{} or {}", left, right)
1227 }
1228 ExpressionKind::MathematicalComputation(op, operand) => {
1229 let op_name = match op {
1230 MathematicalComputation::Sqrt => "sqrt",
1231 MathematicalComputation::Sin => "sin",
1232 MathematicalComputation::Cos => "cos",
1233 MathematicalComputation::Tan => "tan",
1234 MathematicalComputation::Asin => "asin",
1235 MathematicalComputation::Acos => "acos",
1236 MathematicalComputation::Atan => "atan",
1237 MathematicalComputation::Log => "log",
1238 MathematicalComputation::Exp => "exp",
1239 MathematicalComputation::Abs => "abs",
1240 MathematicalComputation::Floor => "floor",
1241 MathematicalComputation::Ceil => "ceil",
1242 MathematicalComputation::Round => "round",
1243 };
1244 write!(f, "{} {}", op_name, operand)
1245 }
1246 ExpressionKind::Veto(veto) => match &veto.message {
1247 Some(msg) => write!(f, "veto \"{}\"", msg),
1248 None => write!(f, "veto"),
1249 },
1250 ExpressionKind::UnresolvedUnitLiteral(number, unit_name) => {
1251 write!(f, "{} {}", number, unit_name)
1252 }
1253 }
1254 }
1255}
1256
1257impl fmt::Display for LiteralValue {
1258 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1259 match &self.value {
1260 Value::Number(n) => {
1261 let decimals_opt = match &self.lemma_type.specifications {
1263 TypeSpecification::Number { decimals, .. } => *decimals,
1264 _ => None,
1265 };
1266
1267 if let Some(decimals) = decimals_opt {
1268 let rounded = n.round_dp(decimals as u32);
1270 let mut s = rounded.to_string();
1271 if let Some(dot_pos) = s.find('.') {
1273 let current_decimals = s.len() - dot_pos - 1;
1274 if current_decimals < decimals as usize {
1275 s.push_str(&"0".repeat(decimals as usize - current_decimals));
1277 } else if current_decimals > decimals as usize {
1278 let truncate_pos = dot_pos + 1 + decimals as usize;
1280 s = s[..truncate_pos].to_string();
1281 }
1282 } else {
1283 s.push('.');
1285 s.push_str(&"0".repeat(decimals as usize));
1286 }
1287 write!(f, "{}", s)
1288 } else {
1289 let normalized = n.normalize();
1291 if normalized.fract().is_zero() {
1292 let int_part = normalized.trunc().to_string();
1293 let formatted = int_part
1294 .chars()
1295 .rev()
1296 .enumerate()
1297 .flat_map(|(i, c)| {
1298 if i > 0 && i % 3 == 0 && c != '-' {
1299 vec![',', c]
1300 } else {
1301 vec![c]
1302 }
1303 })
1304 .collect::<String>()
1305 .chars()
1306 .rev()
1307 .collect::<String>();
1308 write!(f, "{}", formatted)
1309 } else {
1310 write!(f, "{}", normalized)
1311 }
1312 }
1313 }
1314 Value::Text(s) => {
1315 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1316 write!(f, "\"{}\"", escaped)
1317 }
1318 Value::Date(dt) => write!(f, "{}", dt),
1319 Value::Boolean(b) => write!(f, "{}", b),
1320 Value::Time(time) => {
1321 write!(f, "time({}, {}, {})", time.hour, time.minute, time.second)
1322 }
1323 Value::Scale(n, unit_opt) => {
1324 let decimals_opt = match &self.lemma_type.specifications {
1326 TypeSpecification::Scale { decimals, .. } => *decimals,
1327 _ => None,
1328 };
1329
1330 let number_str = if let Some(decimals) = decimals_opt {
1331 let rounded = n.round_dp(decimals as u32);
1333 let mut s = rounded.to_string();
1334 if let Some(dot_pos) = s.find('.') {
1335 let current_decimals = s.len() - dot_pos - 1;
1336 if current_decimals < decimals as usize {
1337 s.push_str(&"0".repeat(decimals as usize - current_decimals));
1338 }
1339 } else {
1340 s.push('.');
1341 s.push_str(&"0".repeat(decimals as usize));
1342 }
1343 s
1344 } else {
1345 let normalized = n.normalize();
1347 if normalized.fract().is_zero() {
1348 normalized.trunc().to_string()
1349 } else {
1350 normalized.to_string()
1351 }
1352 };
1353
1354 if let Some(unit) = unit_opt {
1356 write!(f, "{} {}", number_str, unit)
1357 } else {
1358 write!(f, "{}", number_str)
1359 }
1360 }
1361 Value::Duration(value, unit) => write!(f, "{} {}", value, unit),
1362 Value::Ratio(r, unit_opt) => {
1363 if let Some(unit) = unit_opt {
1365 if unit == "percent" {
1366 let percentage_value = *r * rust_decimal::Decimal::from(100);
1368 let rounded = percentage_value.round_dp(2);
1369 if rounded.fract().is_zero() {
1370 write!(f, "{}%", rounded.trunc())
1371 } else {
1372 write!(f, "{}%", rounded)
1373 }
1374 } else {
1375 let normalized = r.normalize();
1377 if normalized.fract().is_zero() {
1378 write!(f, "{} {}", normalized.trunc(), unit)
1379 } else {
1380 write!(f, "{} {}", normalized, unit)
1381 }
1382 }
1383 } else {
1384 let normalized = r.normalize();
1386 if normalized.fract().is_zero() {
1387 write!(f, "{}", normalized.trunc())
1388 } else {
1389 write!(f, "{}", normalized)
1390 }
1391 }
1392 }
1393 }
1394 }
1395}
1396
1397impl fmt::Display for ConversionTarget {
1398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1399 match self {
1400 ConversionTarget::Duration(unit) => write!(f, "{}", unit),
1401 ConversionTarget::Percentage => write!(f, "percent"),
1402 ConversionTarget::ScaleUnit(unit) => write!(f, "{}", unit),
1403 }
1404 }
1405}
1406
1407impl fmt::Display for FactValue {
1408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1409 match self {
1410 FactValue::Literal(lit) => write!(f, "{}", lit),
1411 FactValue::TypeDeclaration {
1412 base,
1413 overrides,
1414 from,
1415 } => {
1416 let base_str = if let Some(from_doc) = from {
1417 format!("{} from {}", base, from_doc)
1418 } else {
1419 base.clone()
1420 };
1421
1422 if let Some(ref overrides_vec) = overrides {
1423 let override_str = overrides_vec
1424 .iter()
1425 .map(|(cmd, args)| {
1426 let args_str = args.join(" ");
1427 if args_str.is_empty() {
1428 cmd.clone()
1429 } else {
1430 format!("{} {}", cmd, args_str)
1431 }
1432 })
1433 .collect::<Vec<_>>()
1434 .join(" -> ");
1435 write!(f, "[{} -> {}]", base_str, override_str)
1436 } else {
1437 write!(f, "[{}]", base_str)
1438 }
1439 }
1440 FactValue::DocumentReference(doc_name) => write!(f, "doc {}", doc_name),
1441 }
1442 }
1443}
1444
1445impl fmt::Display for ArithmeticComputation {
1446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1447 match self {
1448 ArithmeticComputation::Add => write!(f, "+"),
1449 ArithmeticComputation::Subtract => write!(f, "-"),
1450 ArithmeticComputation::Multiply => write!(f, "*"),
1451 ArithmeticComputation::Divide => write!(f, "/"),
1452 ArithmeticComputation::Modulo => write!(f, "%"),
1453 ArithmeticComputation::Power => write!(f, "^"),
1454 }
1455 }
1456}
1457
1458impl fmt::Display for ComparisonComputation {
1459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1460 match self {
1461 ComparisonComputation::GreaterThan => write!(f, ">"),
1462 ComparisonComputation::LessThan => write!(f, "<"),
1463 ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1464 ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1465 ComparisonComputation::Equal => write!(f, "=="),
1466 ComparisonComputation::NotEqual => write!(f, "!="),
1467 ComparisonComputation::Is => write!(f, "is"),
1468 ComparisonComputation::IsNot => write!(f, "is not"),
1469 }
1470 }
1471}
1472
1473impl fmt::Display for MathematicalComputation {
1474 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1475 match self {
1476 MathematicalComputation::Sqrt => write!(f, "sqrt"),
1477 MathematicalComputation::Sin => write!(f, "sin"),
1478 MathematicalComputation::Cos => write!(f, "cos"),
1479 MathematicalComputation::Tan => write!(f, "tan"),
1480 MathematicalComputation::Asin => write!(f, "asin"),
1481 MathematicalComputation::Acos => write!(f, "acos"),
1482 MathematicalComputation::Atan => write!(f, "atan"),
1483 MathematicalComputation::Log => write!(f, "log"),
1484 MathematicalComputation::Exp => write!(f, "exp"),
1485 MathematicalComputation::Abs => write!(f, "abs"),
1486 MathematicalComputation::Floor => write!(f, "floor"),
1487 MathematicalComputation::Ceil => write!(f, "ceil"),
1488 MathematicalComputation::Round => write!(f, "round"),
1489 }
1490 }
1491}
1492
1493impl fmt::Display for TimeValue {
1494 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1495 write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
1496 }
1497}
1498
1499impl fmt::Display for TimezoneValue {
1500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1501 if self.offset_hours == 0 && self.offset_minutes == 0 {
1502 write!(f, "Z")
1503 } else {
1504 let sign = if self.offset_hours >= 0 { "+" } else { "-" };
1505 let hours = self.offset_hours.abs();
1506 write!(f, "{}{:02}:{:02}", sign, hours, self.offset_minutes)
1507 }
1508 }
1509}
1510
1511impl fmt::Display for DateTimeValue {
1512 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1513 write!(
1514 f,
1515 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1516 self.year, self.month, self.day, self.hour, self.minute, self.second
1517 )?;
1518 if let Some(tz) = &self.timezone {
1519 write!(f, "{}", tz)?;
1520 }
1521 Ok(())
1522 }
1523}
1524
1525impl fmt::Display for RuleReference {
1526 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1527 if self.segments.is_empty() {
1528 write!(f, "{}?", self.rule)
1529 } else {
1530 write!(f, "{}.{}?", self.segments.join("."), self.rule)
1531 }
1532 }
1533}
1534
1535impl fmt::Display for FactPath {
1536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1537 for segment in &self.segments {
1538 write!(f, "{}.", segment.fact)?;
1539 }
1540 write!(f, "{}", self.fact)
1541 }
1542}
1543
1544impl fmt::Display for RulePath {
1545 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1546 for segment in &self.segments {
1547 write!(f, "{}.", segment.fact)?;
1548 }
1549 write!(f, "{}?", self.rule)
1550 }
1551}
1552
1553#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
1555pub struct Unit {
1556 pub name: String,
1557 pub value: Decimal,
1558}
1559
1560#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
1563pub enum TypeSpecification {
1564 Boolean {
1565 help: Option<String>,
1566 default: Option<bool>,
1567 },
1568 Scale {
1569 minimum: Option<Decimal>,
1570 maximum: Option<Decimal>,
1571 decimals: Option<u8>,
1572 precision: Option<Decimal>,
1573 units: Vec<Unit>,
1574 help: Option<String>,
1575 default: Option<(Decimal, String)>,
1576 },
1577 Number {
1578 minimum: Option<Decimal>,
1579 maximum: Option<Decimal>,
1580 decimals: Option<u8>,
1581 precision: Option<Decimal>,
1582 help: Option<String>,
1583 default: Option<Decimal>,
1584 },
1585 Ratio {
1586 minimum: Option<Decimal>,
1587 maximum: Option<Decimal>,
1588 units: Vec<Unit>,
1589 help: Option<String>,
1590 default: Option<Decimal>,
1591 },
1592 Text {
1593 minimum: Option<usize>,
1594 maximum: Option<usize>,
1595 length: Option<usize>,
1596 options: Vec<String>,
1597 help: Option<String>,
1598 default: Option<String>,
1599 },
1600 Date {
1601 minimum: Option<DateTimeValue>,
1602 maximum: Option<DateTimeValue>,
1603 help: Option<String>,
1604 default: Option<DateTimeValue>,
1605 },
1606 Time {
1607 minimum: Option<TimeValue>,
1608 maximum: Option<TimeValue>,
1609 help: Option<String>,
1610 default: Option<TimeValue>,
1611 },
1612 Duration {
1613 help: Option<String>,
1614 default: Option<(Decimal, DurationUnit)>,
1615 },
1616 Veto {
1617 message: Option<String>,
1618 },
1619}
1620
1621impl TypeSpecification {
1622 pub fn boolean() -> Self {
1624 TypeSpecification::Boolean {
1625 help: None,
1626 default: None,
1627 }
1628 }
1629
1630 pub fn scale() -> Self {
1632 TypeSpecification::Scale {
1633 minimum: None,
1634 maximum: None,
1635 decimals: None,
1636 precision: None,
1637 units: vec![],
1638 help: None,
1639 default: None,
1640 }
1641 }
1642
1643 pub fn number() -> Self {
1645 TypeSpecification::Number {
1646 minimum: None,
1647 maximum: None,
1648 decimals: None,
1649 precision: None,
1650 help: None,
1651 default: None,
1652 }
1653 }
1654
1655 pub fn ratio() -> Self {
1658 TypeSpecification::Ratio {
1659 minimum: None,
1660 maximum: None,
1661 units: vec![
1662 Unit {
1663 name: "percent".to_string(),
1664 value: Decimal::from(100),
1665 },
1666 Unit {
1667 name: "permille".to_string(),
1668 value: Decimal::from(1000),
1669 },
1670 ],
1671 help: None,
1672 default: None,
1673 }
1674 }
1675
1676 pub fn text() -> Self {
1678 TypeSpecification::Text {
1679 minimum: None,
1680 maximum: None,
1681 length: None,
1682 options: vec![],
1683 help: None,
1684 default: None,
1685 }
1686 }
1687
1688 pub fn date() -> Self {
1690 TypeSpecification::Date {
1691 minimum: None,
1692 maximum: None,
1693 help: None,
1694 default: None,
1695 }
1696 }
1697
1698 pub fn time() -> Self {
1700 TypeSpecification::Time {
1701 minimum: None,
1702 maximum: None,
1703 help: None,
1704 default: None,
1705 }
1706 }
1707
1708 pub fn duration() -> Self {
1710 TypeSpecification::Duration {
1711 help: None,
1712 default: None,
1713 }
1714 }
1715
1716 pub fn veto() -> Self {
1718 TypeSpecification::Veto { message: None }
1719 }
1720
1721 pub fn apply_override(mut self, command: &str, args: &[String]) -> Result<Self, String> {
1723 match &mut self {
1724 TypeSpecification::Boolean { help, default } => match command {
1725 "help" => *help = args.first().cloned(),
1726 "default" => {
1727 let d = args
1728 .first()
1729 .ok_or_else(|| "default requires an argument".to_string())?
1730 .parse::<BooleanValue>()
1731 .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1732 *default = Some(d.into());
1733 }
1734 _ => {
1735 return Err(format!(
1736 "Invalid command '{}' for boolean type. Valid commands: help, default",
1737 command
1738 ));
1739 }
1740 },
1741 TypeSpecification::Scale {
1742 decimals,
1743 minimum,
1744 maximum,
1745 precision,
1746 units,
1747 help,
1748 default,
1749 } => match command {
1750 "decimals" => {
1751 let d = args
1752 .first()
1753 .ok_or_else(|| "decimals requires an argument".to_string())?
1754 .parse::<u8>()
1755 .map_err(|_| format!("invalid decimals value: {:?}", args.first()))?;
1756 *decimals = Some(d);
1757 }
1758 "unit" if args.len() >= 2 => {
1759 let unit_name = args[0].clone();
1760 if units.iter().any(|u| u.name == unit_name) {
1762 return Err(format!(
1763 "Duplicate unit name '{}' in type definition. Unit names must be unique within a type.",
1764 unit_name
1765 ));
1766 }
1767 let value = args[1]
1768 .parse::<Decimal>()
1769 .map_err(|_| format!("invalid unit value: {}", args[1]))?;
1770 units.push(Unit {
1771 name: unit_name,
1772 value,
1773 });
1774 }
1775 "minimum" => {
1776 let m = args
1777 .first()
1778 .ok_or_else(|| "minimum requires an argument".to_string())?
1779 .parse::<Decimal>()
1780 .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1781 *minimum = Some(m);
1782 }
1783 "maximum" => {
1784 let m = args
1785 .first()
1786 .ok_or_else(|| "maximum requires an argument".to_string())?
1787 .parse::<Decimal>()
1788 .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1789 *maximum = Some(m);
1790 }
1791 "precision" => {
1792 let p = args
1793 .first()
1794 .ok_or_else(|| "precision requires an argument".to_string())?
1795 .parse::<Decimal>()
1796 .map_err(|_| format!("invalid precision value: {:?}", args.first()))?;
1797 *precision = Some(p);
1798 }
1799 "help" => *help = args.first().cloned(),
1800 "default" => {
1801 if args.len() < 2 {
1802 return Err(
1803 "default requires a value and unit (e.g., 'default 1 kilogram')"
1804 .to_string(),
1805 );
1806 }
1807 let value = args[0]
1808 .parse::<Decimal>()
1809 .map_err(|_| format!("invalid default value: {:?}", args[0]))?;
1810 let unit_name = args[1].clone();
1811 if !units.iter().any(|u| u.name == unit_name) {
1813 return Err(format!(
1814 "Invalid unit '{}' for default. Valid units: {}",
1815 unit_name,
1816 units
1817 .iter()
1818 .map(|u| u.name.clone())
1819 .collect::<Vec<_>>()
1820 .join(", ")
1821 ));
1822 }
1823 *default = Some((value, unit_name));
1824 }
1825 _ => {
1826 return Err(format!(
1827 "Invalid command '{}' for scale type. Valid commands: unit, minimum, maximum, decimals, precision, help, default",
1828 command
1829 ));
1830 }
1831 },
1832 TypeSpecification::Number {
1833 decimals,
1834 minimum,
1835 maximum,
1836 precision,
1837 help,
1838 default,
1839 } => match command {
1840 "decimals" => {
1841 let d = args
1842 .first()
1843 .ok_or_else(|| "decimals requires an argument".to_string())?
1844 .parse::<u8>()
1845 .map_err(|_| format!("invalid decimals value: {:?}", args.first()))?;
1846 *decimals = Some(d);
1847 }
1848 "unit" => {
1849 return Err(
1850 "Invalid command 'unit' for number type. Number types are dimensionless and cannot have units. Use 'scale' type instead.".to_string()
1851 );
1852 }
1853 "minimum" => {
1854 let m = args
1855 .first()
1856 .ok_or_else(|| "minimum requires an argument".to_string())?
1857 .parse::<Decimal>()
1858 .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1859 *minimum = Some(m);
1860 }
1861 "maximum" => {
1862 let m = args
1863 .first()
1864 .ok_or_else(|| "maximum requires an argument".to_string())?
1865 .parse::<Decimal>()
1866 .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1867 *maximum = Some(m);
1868 }
1869 "precision" => {
1870 let p = args
1871 .first()
1872 .ok_or_else(|| "precision requires an argument".to_string())?
1873 .parse::<Decimal>()
1874 .map_err(|_| format!("invalid precision value: {:?}", args.first()))?;
1875 *precision = Some(p);
1876 }
1877 "help" => *help = args.first().cloned(),
1878 "default" => {
1879 let d = args
1880 .first()
1881 .ok_or_else(|| "default requires an argument".to_string())?
1882 .parse::<Decimal>()
1883 .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1884 *default = Some(d);
1885 }
1886 _ => {
1887 return Err(format!(
1888 "Invalid command '{}' for number type. Valid commands: minimum, maximum, decimals, precision, help, default",
1889 command
1890 ));
1891 }
1892 },
1893 TypeSpecification::Ratio {
1894 minimum,
1895 maximum,
1896 units,
1897 help,
1898 default,
1899 } => match command {
1900 "unit" if args.len() >= 2 => {
1901 let value = args[1]
1902 .parse::<Decimal>()
1903 .map_err(|_| format!("invalid unit value: {}", args[1]))?;
1904 units.push(Unit {
1905 name: args[0].clone(),
1906 value,
1907 });
1908 }
1909 "minimum" => {
1910 let m = args
1911 .first()
1912 .ok_or_else(|| "minimum requires an argument".to_string())?
1913 .parse::<Decimal>()
1914 .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1915 *minimum = Some(m);
1916 }
1917 "maximum" => {
1918 let m = args
1919 .first()
1920 .ok_or_else(|| "maximum requires an argument".to_string())?
1921 .parse::<Decimal>()
1922 .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1923 *maximum = Some(m);
1924 }
1925 "help" => *help = args.first().cloned(),
1926 "default" => {
1927 let d = args
1928 .first()
1929 .ok_or_else(|| "default requires an argument".to_string())?
1930 .parse::<Decimal>()
1931 .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1932 *default = Some(d);
1933 }
1934 _ => {
1935 return Err(format!(
1936 "Invalid command '{}' for ratio type. Valid commands: unit, minimum, maximum, help, default",
1937 command
1938 ));
1939 }
1940 },
1941 TypeSpecification::Text {
1942 minimum,
1943 maximum,
1944 length,
1945 options,
1946 help,
1947 default,
1948 } => match command {
1949 "option" if args.len() == 1 => {
1950 options.push(strip_surrounding_quotes(&args[0]));
1951 }
1952 "options" => {
1953 *options = args.iter().map(|s| strip_surrounding_quotes(s)).collect();
1954 }
1955 "minimum" => {
1956 let m = args
1957 .first()
1958 .ok_or_else(|| "minimum requires an argument".to_string())?
1959 .parse::<usize>()
1960 .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1961 *minimum = Some(m);
1962 }
1963 "maximum" => {
1964 let m = args
1965 .first()
1966 .ok_or_else(|| "maximum requires an argument".to_string())?
1967 .parse::<usize>()
1968 .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1969 *maximum = Some(m);
1970 }
1971 "length" => {
1972 let l = args
1973 .first()
1974 .ok_or_else(|| "length requires an argument".to_string())?
1975 .parse::<usize>()
1976 .map_err(|_| format!("invalid length value: {:?}", args.first()))?;
1977 *length = Some(l);
1978 }
1979 "help" => *help = args.first().cloned(),
1980 "default" => {
1981 let arg = args
1982 .first()
1983 .ok_or_else(|| "default requires an argument".to_string())?;
1984 *default = Some(strip_surrounding_quotes(arg));
1985 }
1986 _ => {
1987 return Err(format!(
1988 "Invalid command '{}' for text type. Valid commands: options, minimum, maximum, length, help, default",
1989 command
1990 ));
1991 }
1992 },
1993 TypeSpecification::Date {
1994 minimum,
1995 maximum,
1996 help,
1997 default,
1998 } => match command {
1999 "minimum" => {
2000 let arg = args
2001 .first()
2002 .ok_or_else(|| "default requires an argument".to_string())?;
2003 let span = Span {
2004 start: 0,
2005 end: 0,
2006 line: 1,
2007 col: 0,
2008 };
2009 let Value::Date(date) = &standard_date()
2010 .parse_value(arg, span.clone(), "<internal>", "<internal>")
2011 .map_err(|_| format!("invalid default date value: {}", arg))?
2012 .value
2013 else {
2014 return Err(format!("invalid default date value: {}", arg));
2015 };
2016 *minimum = Some(date.clone());
2017 }
2018 "maximum" => {
2019 let arg = args
2020 .first()
2021 .ok_or_else(|| "default requires an argument".to_string())?;
2022 let span = Span {
2023 start: 0,
2024 end: 0,
2025 line: 1,
2026 col: 0,
2027 };
2028 let Value::Date(date) = standard_date()
2029 .parse_value(arg, span, "<internal>", "<internal>")
2030 .map_err(|_| format!("invalid default date value: {}", arg))?
2031 .value
2032 else {
2033 return Err(format!("invalid default date value: {}", arg));
2034 };
2035 *maximum = Some(date);
2036 }
2037 "help" => *help = args.first().cloned(),
2038 "default" => {
2039 let arg = args
2040 .first()
2041 .ok_or_else(|| "default requires an argument".to_string())?;
2042 let span = Span {
2043 start: 0,
2044 end: 0,
2045 line: 1,
2046 col: 0,
2047 };
2048 let Value::Date(date) = standard_date()
2049 .parse_value(arg, span, "<internal>", "<internal>")
2050 .map_err(|_| format!("invalid default date value: {}", arg))?
2051 .value
2052 else {
2053 return Err(format!("invalid default date value: {}", arg));
2054 };
2055 *default = Some(date);
2056 }
2057 _ => {
2058 return Err(format!(
2059 "Invalid command '{}' for date type. Valid commands: minimum, maximum, help, default",
2060 command
2061 ));
2062 }
2063 },
2064 TypeSpecification::Time {
2065 minimum,
2066 maximum,
2067 help,
2068 default,
2069 } => match command {
2070 "minimum" => {
2071 let arg = args
2072 .first()
2073 .ok_or_else(|| "minimum requires an argument".to_string())?;
2074 let span = Span {
2075 start: 0,
2076 end: 0,
2077 line: 1,
2078 col: 0,
2079 };
2080 let Value::Time(time) = &standard_time()
2081 .parse_value(arg, span.clone(), "<internal>", "<internal>")
2082 .map_err(|_| format!("invalid minimum time value: {}", arg))?
2083 .value
2084 else {
2085 return Err(format!("invalid minimum time value: {}", arg));
2086 };
2087 *minimum = Some(time.clone());
2088 }
2089 "maximum" => {
2090 let arg = args
2091 .first()
2092 .ok_or_else(|| "maximum requires an argument".to_string())?;
2093 let span = Span {
2094 start: 0,
2095 end: 0,
2096 line: 1,
2097 col: 0,
2098 };
2099 let Value::Time(time) = &standard_time()
2100 .parse_value(arg, span.clone(), "<internal>", "<internal>")
2101 .map_err(|_| format!("invalid maximum time value: {}", arg))?
2102 .value
2103 else {
2104 return Err(format!("invalid maximum time value: {}", arg));
2105 };
2106 *maximum = Some(time.clone());
2107 }
2108 "help" => *help = args.first().cloned(),
2109 "default" => {
2110 let arg = args
2111 .first()
2112 .ok_or_else(|| "default requires an argument".to_string())?;
2113 let span = Span {
2114 start: 0,
2115 end: 0,
2116 line: 1,
2117 col: 0,
2118 };
2119 let Value::Time(time) = &standard_time()
2120 .parse_value(arg, span, "<internal>", "<internal>")
2121 .map_err(|_| format!("invalid default time value: {}", arg))?
2122 .value
2123 else {
2124 return Err(format!("invalid default time value: {}", arg));
2125 };
2126 *default = Some(time.clone());
2127 }
2128 _ => {
2129 return Err(format!(
2130 "Invalid command '{}' for time type. Valid commands: minimum, maximum, help, default",
2131 command
2132 ));
2133 }
2134 },
2135 TypeSpecification::Duration { help, default } => match command {
2136 "help" => *help = args.first().cloned(),
2137 "default" if args.len() >= 2 => {
2138 let value = args[0]
2139 .parse::<Decimal>()
2140 .map_err(|_| format!("invalid duration value: {}", args[0]))?;
2141 let unit = args[1]
2142 .parse::<DurationUnit>()
2143 .map_err(|_| format!("invalid duration unit: {}", args[1]))?;
2144 *default = Some((value, unit));
2145 }
2146 _ => {
2147 return Err(format!(
2148 "Invalid command '{}' for duration type. Valid commands: help, default",
2149 command
2150 ));
2151 }
2152 },
2153 TypeSpecification::Veto { .. } => {
2154 return Err(format!(
2155 "Invalid command '{}' for veto type. Veto is not a user-declarable type and cannot have overrides",
2156 command
2157 ));
2158 }
2159 }
2160 Ok(self)
2161 }
2162}
2163
2164#[derive(Clone, Debug, PartialEq)]
2173pub enum TypeDef {
2174 Regular {
2177 source_location: Source,
2178 name: String,
2179 parent: String,
2180 overrides: Option<Vec<(String, Vec<String>)>>,
2181 },
2182 Import {
2185 source_location: Source,
2186 name: String,
2187 source_type: String,
2188 from: String,
2189 overrides: Option<Vec<(String, Vec<String>)>>,
2190 },
2191 Inline {
2196 source_location: Source,
2197 parent: String,
2198 overrides: Option<Vec<(String, Vec<String>)>>,
2199 fact_ref: FactReference,
2200 from: Option<String>,
2201 },
2202}
2203
2204impl TypeDef {
2205 pub fn source_location(&self) -> &Source {
2206 match self {
2207 TypeDef::Regular {
2208 source_location, ..
2209 }
2210 | TypeDef::Import {
2211 source_location, ..
2212 }
2213 | TypeDef::Inline {
2214 source_location, ..
2215 } => source_location,
2216 }
2217 }
2218}
2219
2220#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
2224pub struct LemmaType {
2225 pub name: Option<String>,
2226 pub specifications: TypeSpecification,
2227}
2228
2229impl LemmaType {
2230 pub fn new(name: String, specifications: TypeSpecification) -> Self {
2232 Self {
2233 name: Some(name),
2234 specifications,
2235 }
2236 }
2237
2238 pub fn without_name(specifications: TypeSpecification) -> Self {
2240 Self {
2241 name: None,
2242 specifications,
2243 }
2244 }
2245
2246 pub fn name(&self) -> &str {
2248 match &self.name {
2249 Some(n) => n.as_str(),
2250 None => match &self.specifications {
2251 TypeSpecification::Boolean { .. } => "boolean",
2252 TypeSpecification::Scale { .. } => "scale",
2253 TypeSpecification::Number { .. } => "number",
2254 TypeSpecification::Text { .. } => "text",
2255 TypeSpecification::Date { .. } => "date",
2256 TypeSpecification::Time { .. } => "time",
2257 TypeSpecification::Duration { .. } => "duration",
2258 TypeSpecification::Ratio { .. } => "ratio",
2259 TypeSpecification::Veto { .. } => "veto",
2260 },
2261 }
2262 }
2263
2264 pub fn is_boolean(&self) -> bool {
2266 matches!(&self.specifications, TypeSpecification::Boolean { .. })
2267 }
2268
2269 pub fn is_scale(&self) -> bool {
2271 matches!(&self.specifications, TypeSpecification::Scale { .. })
2272 }
2273
2274 pub fn is_number(&self) -> bool {
2276 matches!(&self.specifications, TypeSpecification::Number { .. })
2277 }
2278
2279 pub fn is_numeric(&self) -> bool {
2281 matches!(
2282 &self.specifications,
2283 TypeSpecification::Scale { .. } | TypeSpecification::Number { .. }
2284 )
2285 }
2286
2287 pub fn is_text(&self) -> bool {
2289 matches!(&self.specifications, TypeSpecification::Text { .. })
2290 }
2291
2292 pub fn is_date(&self) -> bool {
2294 matches!(&self.specifications, TypeSpecification::Date { .. })
2295 }
2296
2297 pub fn is_time(&self) -> bool {
2299 matches!(&self.specifications, TypeSpecification::Time { .. })
2300 }
2301
2302 pub fn is_duration(&self) -> bool {
2304 matches!(&self.specifications, TypeSpecification::Duration { .. })
2305 }
2306
2307 pub fn is_ratio(&self) -> bool {
2309 matches!(&self.specifications, TypeSpecification::Ratio { .. })
2310 }
2311
2312 pub fn is_veto(&self) -> bool {
2314 matches!(&self.specifications, TypeSpecification::Veto { .. })
2315 }
2316
2317 pub fn has_same_base_type(&self, other: &LemmaType) -> bool {
2321 use TypeSpecification::*;
2322 matches!(
2323 (&self.specifications, &other.specifications),
2324 (Boolean { .. }, Boolean { .. })
2325 | (Number { .. }, Number { .. })
2326 | (Scale { .. }, Scale { .. })
2327 | (Text { .. }, Text { .. })
2328 | (Date { .. }, Date { .. })
2329 | (Time { .. }, Time { .. })
2330 | (Duration { .. }, Duration { .. })
2331 | (Ratio { .. }, Ratio { .. })
2332 | (Veto { .. }, Veto { .. })
2333 )
2334 }
2335
2336 pub fn veto_type() -> Self {
2338 Self::without_name(TypeSpecification::veto())
2339 }
2340
2341 pub fn create_default_value(&self) -> Option<LiteralValue> {
2343 use TypeSpecification::*;
2344 match &self.specifications {
2345 Boolean { default, .. } => default
2346 .map(|b| LiteralValue::boolean_with_type(BooleanValue::from(b), self.clone())),
2347 Text { default, .. } => default
2348 .as_ref()
2349 .map(|s| LiteralValue::text_with_type(s.clone(), self.clone())),
2350 Number { default, .. } => {
2351 default.map(|d| LiteralValue::number_with_type(d, self.clone()))
2352 }
2353 Scale { default, .. } => default.as_ref().map(|(value, unit_name)| {
2354 LiteralValue::scale_with_type(*value, Some(unit_name.clone()), self.clone())
2355 }),
2356 Ratio { default, .. } => {
2357 default.map(|d| LiteralValue::ratio_with_type(d, None, self.clone()))
2358 }
2359 Date { default, .. } => default
2360 .as_ref()
2361 .map(|d| LiteralValue::date_with_type(d.clone(), self.clone())),
2362 Time { default, .. } => default
2363 .as_ref()
2364 .map(|t| LiteralValue::time_with_type(t.clone(), self.clone())),
2365 Duration { default, .. } => default
2366 .as_ref()
2367 .map(|(v, u)| LiteralValue::duration_with_type(*v, u.clone(), self.clone())),
2368 Veto { .. } => None,
2369 }
2370 }
2371
2372 pub fn parse_value(
2376 &self,
2377 raw: &str,
2378 span: Span,
2379 attribute: &str,
2380 doc_name: &str,
2381 ) -> Result<LiteralValue, LemmaError> {
2382 let value = match &self.specifications {
2383 TypeSpecification::Boolean { .. } => {
2384 Self::parse_boolean_value(raw, span.clone(), attribute, doc_name)?
2385 }
2386 TypeSpecification::Scale { .. } => {
2387 Self::parse_scale_value(raw, self, span.clone(), attribute, doc_name)?
2388 }
2389 TypeSpecification::Number { .. } => {
2390 Self::parse_number_value(raw, span.clone(), attribute, doc_name)?
2391 }
2392 TypeSpecification::Text { .. } => Self::parse_text_value(raw)?,
2393 TypeSpecification::Date { .. } => {
2394 Self::parse_date_value(raw, span.clone(), attribute, doc_name)?
2395 }
2396 TypeSpecification::Time { .. } => {
2397 Self::parse_time_value(raw, span.clone(), attribute, doc_name)?
2398 }
2399 TypeSpecification::Duration { .. } => {
2400 Self::parse_duration_value(raw, span.clone(), attribute, doc_name)?
2401 }
2402 TypeSpecification::Ratio { .. } => {
2403 Self::parse_ratio_value(raw, span.clone(), attribute, doc_name)?
2404 }
2405 TypeSpecification::Veto { .. } => {
2406 return Err(LemmaError::engine(
2407 "Cannot parse value for veto type - veto is not a user-declarable type",
2408 span,
2409 attribute,
2410 Arc::from(raw),
2411 doc_name,
2412 1,
2413 None::<String>,
2414 ));
2415 }
2416 };
2417 Ok(match &value {
2419 Value::Number(n) => LiteralValue::number_with_type(*n, self.clone()),
2420 Value::Scale(n, u) => LiteralValue::scale_with_type(*n, u.clone(), self.clone()),
2421 Value::Text(s) => LiteralValue::text_with_type(s.clone(), self.clone()),
2422 Value::Boolean(b) => LiteralValue::boolean_with_type(b.clone(), self.clone()),
2423 Value::Date(d) => LiteralValue::date_with_type(d.clone(), self.clone()),
2424 Value::Time(t) => LiteralValue::time_with_type(t.clone(), self.clone()),
2425 Value::Duration(v, u) => LiteralValue::duration_with_type(*v, u.clone(), self.clone()),
2426 Value::Ratio(r, u) => LiteralValue::ratio_with_type(*r, u.clone(), self.clone()),
2427 })
2428 }
2429
2430 fn parse_text_value(raw: &str) -> Result<Value, LemmaError> {
2431 Ok(Value::Text(raw.to_string()))
2432 }
2433
2434 fn parse_scale_value(
2435 raw: &str,
2436 lemma_type: &LemmaType,
2437 span: Span,
2438 attribute: &str,
2439 doc_name: &str,
2440 ) -> Result<Value, LemmaError> {
2441 let trimmed = raw.trim();
2442
2443 let mut number_end = 0;
2449 let chars: Vec<char> = trimmed.chars().collect();
2450 let mut has_decimal = false;
2451
2452 let start = if chars.first().is_some_and(|c| *c == '+' || *c == '-') {
2454 1
2455 } else {
2456 0
2457 };
2458
2459 for (i, &ch) in chars.iter().enumerate().skip(start) {
2460 match ch {
2461 '0'..='9' => number_end = i + 1,
2462 '.' if !has_decimal => {
2463 has_decimal = true;
2464 number_end = i + 1;
2465 }
2466 '_' | ',' => {
2467 number_end = i + 1;
2469 }
2470 _ => {
2471 break;
2473 }
2474 }
2475 }
2476
2477 let number_part = trimmed[..number_end].trim();
2479 let unit_part = trimmed[number_end..].trim();
2480
2481 let clean_number = number_part.replace(['_', ','], "");
2483 let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2484 LemmaError::engine(
2485 format!("Invalid scale string: '{}' is not a valid number", raw),
2486 span.clone(),
2487 attribute,
2488 Arc::from(raw),
2489 doc_name,
2490 1,
2491 None::<String>,
2492 )
2493 })?;
2494
2495 let allowed_units = match &lemma_type.specifications {
2497 TypeSpecification::Scale { units, .. } => units,
2498 _ => unreachable!(
2499 "BUG: parse_scale_value called with non-scale type {}",
2500 lemma_type.name()
2501 ),
2502 };
2503
2504 if unit_part.is_empty() && !allowed_units.is_empty() {
2505 let valid: Vec<String> = allowed_units.iter().map(|u| u.name.clone()).collect();
2506 return Err(LemmaError::engine(
2507 format!(
2508 "Missing unit for scale type. Valid units: {}",
2509 valid.join(", ")
2510 ),
2511 span.clone(),
2512 attribute,
2513 Arc::from(raw),
2514 doc_name,
2515 1,
2516 None::<String>,
2517 ));
2518 }
2519
2520 let unit = if unit_part.is_empty() {
2521 None
2522 } else {
2523 let unit_matched = allowed_units
2524 .iter()
2525 .find(|u| u.name.eq_ignore_ascii_case(unit_part));
2526
2527 if let Some(unit_def) = unit_matched {
2528 Some(unit_def.name.clone())
2529 } else {
2530 let valid: Vec<String> = allowed_units.iter().map(|u| u.name.clone()).collect();
2531 return Err(LemmaError::engine(
2532 format!(
2533 "Invalid unit '{}' for scale type. Valid units: {}",
2534 unit_part,
2535 valid.join(", ")
2536 ),
2537 span,
2538 attribute,
2539 Arc::from(raw),
2540 doc_name,
2541 1,
2542 None::<String>,
2543 ));
2544 }
2545 };
2546
2547 Ok(Value::Scale(decimal, unit))
2548 }
2549
2550 fn parse_number_value(
2551 raw: &str,
2552 span: Span,
2553 attribute: &str,
2554 doc_name: &str,
2555 ) -> Result<Value, LemmaError> {
2556 let clean_number = raw.replace(['_', ','], "");
2557 let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2558 LemmaError::engine(
2559 format!("Invalid number: '{}'. Expected a valid decimal number (e.g., 42, 3.14, 1_000_000)", raw),
2560 span,
2561 attribute,
2562 Arc::from(raw),
2563 doc_name,
2564 1,
2565 None::<String>,
2566 )
2567 })?;
2568 Ok(Value::Number(decimal))
2569 }
2570
2571 fn parse_boolean_value(
2572 raw: &str,
2573 span: Span,
2574 attribute: &str,
2575 doc_name: &str,
2576 ) -> Result<Value, LemmaError> {
2577 let boolean_value: BooleanValue = raw.parse().map_err(|_| {
2578 LemmaError::engine(
2579 format!(
2580 "Invalid boolean: '{}'. Expected one of: true, false, yes, no, accept, reject",
2581 raw
2582 ),
2583 span,
2584 attribute,
2585 Arc::from(raw),
2586 doc_name,
2587 1,
2588 None::<String>,
2589 )
2590 })?;
2591 Ok(Value::Boolean(boolean_value))
2592 }
2593
2594 fn parse_date_value(
2595 raw: &str,
2596 span: Span,
2597 attribute: &str,
2598 doc_name: &str,
2599 ) -> Result<Value, LemmaError> {
2600 let datetime_str = raw.trim();
2601
2602 if let Ok(dt) = datetime_str.parse::<chrono::DateTime<chrono::FixedOffset>>() {
2603 let offset = dt.offset().local_minus_utc();
2604 return Ok(Value::Date(DateTimeValue {
2605 year: dt.year(),
2606 month: dt.month(),
2607 day: dt.day(),
2608 hour: dt.hour(),
2609 minute: dt.minute(),
2610 second: dt.second(),
2611 timezone: Some(TimezoneValue {
2612 offset_hours: (offset / 3600) as i8,
2613 offset_minutes: ((offset % 3600) / 60) as u8,
2614 }),
2615 }));
2616 }
2617
2618 if let Ok(dt) = datetime_str.parse::<chrono::NaiveDateTime>() {
2619 return Ok(Value::Date(DateTimeValue {
2620 year: dt.year(),
2621 month: dt.month(),
2622 day: dt.day(),
2623 hour: dt.hour(),
2624 minute: dt.minute(),
2625 second: dt.second(),
2626 timezone: None,
2627 }));
2628 }
2629
2630 if let Ok(d) = datetime_str.parse::<chrono::NaiveDate>() {
2631 return Ok(Value::Date(DateTimeValue {
2632 year: d.year(),
2633 month: d.month(),
2634 day: d.day(),
2635 hour: 0,
2636 minute: 0,
2637 second: 0,
2638 timezone: None,
2639 }));
2640 }
2641
2642 Err(LemmaError::engine(
2643 format!("Invalid date/time format: '{}'. Expected one of: YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, or YYYY-MM-DDTHH:MM:SSZ", raw),
2644 span,
2645 attribute,
2646 Arc::from(raw),
2647 doc_name,
2648 1,
2649 None::<String>,
2650 ))
2651 }
2652
2653 fn parse_time_value(
2654 raw: &str,
2655 span: Span,
2656 attribute: &str,
2657 doc_name: &str,
2658 ) -> Result<Value, LemmaError> {
2659 let time_str = raw.trim();
2660
2661 if let Ok(dt) = time_str.parse::<chrono::DateTime<chrono::FixedOffset>>() {
2663 let offset = dt.offset().local_minus_utc();
2664 return Ok(Value::Time(TimeValue {
2665 hour: dt.hour() as u8,
2666 minute: dt.minute() as u8,
2667 second: dt.second() as u8,
2668 timezone: Some(TimezoneValue {
2669 offset_hours: (offset / 3600) as i8,
2670 offset_minutes: ((offset % 3600) / 60) as u8,
2671 }),
2672 }));
2673 }
2674
2675 if let Ok(nt) = time_str.parse::<chrono::NaiveTime>() {
2677 return Ok(Value::Time(TimeValue {
2678 hour: nt.hour() as u8,
2679 minute: nt.minute() as u8,
2680 second: nt.second() as u8,
2681 timezone: None,
2682 }));
2683 }
2684
2685 let parts: Vec<&str> = time_str.split(':').collect();
2687 if parts.len() == 2 || parts.len() == 3 {
2688 if let (Ok(hour_u32), Ok(minute_u32)) =
2689 (parts[0].parse::<u32>(), parts[1].parse::<u32>())
2690 {
2691 if hour_u32 < 24 && minute_u32 < 60 {
2692 let second_u32 = if parts.len() == 3 {
2693 let Ok(s) = parts[2].parse::<u32>() else {
2694 return Err(LemmaError::engine(
2695 format!(
2696 "Invalid time format: '{}'. Expected: HH:MM or HH:MM:SS (e.g., 14:30 or 14:30:00)",
2697 raw
2698 ),
2699 span.clone(),
2700 attribute,
2701 Arc::from(raw),
2702 doc_name,
2703 1,
2704 None::<String>,
2705 ));
2706 };
2707 s
2708 } else {
2709 0
2710 };
2711 if second_u32 < 60 {
2712 return Ok(Value::Time(TimeValue {
2713 hour: hour_u32 as u8,
2714 minute: minute_u32 as u8,
2715 second: second_u32 as u8,
2716 timezone: None,
2717 }));
2718 }
2719 }
2720 }
2721 }
2722
2723 Err(LemmaError::engine(
2724 format!(
2725 "Invalid time format: '{}'. Expected: HH:MM or HH:MM:SS (e.g., 14:30 or 14:30:00)",
2726 raw
2727 ),
2728 span,
2729 attribute,
2730 Arc::from(raw),
2731 doc_name,
2732 1,
2733 None::<String>,
2734 ))
2735 }
2736
2737 fn parse_duration_value(
2738 raw: &str,
2739 span: Span,
2740 attribute: &str,
2741 doc_name: &str,
2742 ) -> Result<Value, LemmaError> {
2743 let parts: Vec<&str> = raw.split_whitespace().collect();
2745 if parts.len() != 2 {
2746 return Err(LemmaError::engine(
2747 format!(
2748 "Invalid duration: '{}'. Expected format: NUMBER UNIT (e.g., '90 minutes')",
2749 raw
2750 ),
2751 span.clone(),
2752 attribute,
2753 Arc::from(raw),
2754 doc_name,
2755 1,
2756 None::<String>,
2757 ));
2758 }
2759
2760 let number_str = parts[0].replace(['_', ','], "");
2761 let value = Decimal::from_str(&number_str).map_err(|_| {
2762 LemmaError::engine(
2763 format!("Invalid number in duration: '{}'", parts[0]),
2764 span.clone(),
2765 attribute,
2766 Arc::from(raw),
2767 doc_name,
2768 1,
2769 None::<String>,
2770 )
2771 })?;
2772 let unit = parts[1];
2773
2774 let unit_lower = unit.to_lowercase();
2776 let duration_unit = match unit_lower.as_str() {
2777 "year" | "years" => DurationUnit::Year,
2778 "month" | "months" => DurationUnit::Month,
2779 "week" | "weeks" => DurationUnit::Week,
2780 "day" | "days" => DurationUnit::Day,
2781 "hour" | "hours" => DurationUnit::Hour,
2782 "minute" | "minutes" => DurationUnit::Minute,
2783 "second" | "seconds" => DurationUnit::Second,
2784 "millisecond" | "milliseconds" => DurationUnit::Millisecond,
2785 "microsecond" | "microseconds" => DurationUnit::Microsecond,
2786 _ => {
2787 return Err(LemmaError::engine(
2788 format!("Unknown duration unit: '{}'. Expected one of: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds", unit),
2789 span,
2790 attribute,
2791 Arc::from(raw),
2792 doc_name,
2793 1,
2794 None::<String>,
2795 ));
2796 }
2797 };
2798 Ok(Value::Duration(value, duration_unit))
2799 }
2800
2801 fn parse_ratio_value(
2802 raw: &str,
2803 span: Span,
2804 attribute: &str,
2805 doc_name: &str,
2806 ) -> Result<Value, LemmaError> {
2807 let clean_number = raw.replace(['_', ','], "");
2809 let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2810 LemmaError::engine(
2811 format!("Invalid ratio: '{}'. Expected a valid decimal number", raw),
2812 span,
2813 attribute,
2814 Arc::from(raw),
2815 doc_name,
2816 1,
2817 None::<String>,
2818 )
2819 })?;
2820 Ok(Value::Ratio(decimal, None))
2821 }
2822}
2823
2824static STANDARD_BOOLEAN: OnceLock<LemmaType> = OnceLock::new();
2826static STANDARD_SCALE: OnceLock<LemmaType> = OnceLock::new();
2827static STANDARD_NUMBER: OnceLock<LemmaType> = OnceLock::new();
2828static STANDARD_TEXT: OnceLock<LemmaType> = OnceLock::new();
2829static STANDARD_DATE: OnceLock<LemmaType> = OnceLock::new();
2830static STANDARD_TIME: OnceLock<LemmaType> = OnceLock::new();
2831static STANDARD_DURATION: OnceLock<LemmaType> = OnceLock::new();
2832static STANDARD_RATIO: OnceLock<LemmaType> = OnceLock::new();
2833
2834pub fn standard_boolean() -> &'static LemmaType {
2836 STANDARD_BOOLEAN.get_or_init(|| LemmaType {
2837 name: None,
2838 specifications: TypeSpecification::Boolean {
2839 help: None,
2840 default: None,
2841 },
2842 })
2843}
2844
2845pub fn standard_scale() -> &'static LemmaType {
2847 STANDARD_SCALE.get_or_init(|| LemmaType {
2848 name: None,
2849 specifications: TypeSpecification::Scale {
2850 minimum: None,
2851 maximum: None,
2852 decimals: None,
2853 precision: None,
2854 units: Vec::new(),
2855 help: None,
2856 default: None,
2857 },
2858 })
2859}
2860
2861pub fn standard_number() -> &'static LemmaType {
2863 STANDARD_NUMBER.get_or_init(|| LemmaType {
2864 name: None,
2865 specifications: TypeSpecification::Number {
2866 minimum: None,
2867 maximum: None,
2868 decimals: None,
2869 precision: None,
2870 help: None,
2871 default: None,
2872 },
2873 })
2874}
2875
2876pub fn standard_text() -> &'static LemmaType {
2878 STANDARD_TEXT.get_or_init(|| LemmaType {
2879 name: None,
2880 specifications: TypeSpecification::Text {
2881 minimum: None,
2882 maximum: None,
2883 length: None,
2884 options: Vec::new(),
2885 help: None,
2886 default: None,
2887 },
2888 })
2889}
2890
2891pub fn standard_date() -> &'static LemmaType {
2893 STANDARD_DATE.get_or_init(|| LemmaType {
2894 name: None,
2895 specifications: TypeSpecification::Date {
2896 minimum: None,
2897 maximum: None,
2898 help: None,
2899 default: None,
2900 },
2901 })
2902}
2903
2904pub fn standard_time() -> &'static LemmaType {
2906 STANDARD_TIME.get_or_init(|| LemmaType {
2907 name: None,
2908 specifications: TypeSpecification::Time {
2909 minimum: None,
2910 maximum: None,
2911 help: None,
2912 default: None,
2913 },
2914 })
2915}
2916
2917pub fn standard_duration() -> &'static LemmaType {
2919 STANDARD_DURATION.get_or_init(|| LemmaType {
2920 name: None,
2921 specifications: TypeSpecification::Duration {
2922 help: None,
2923 default: None,
2924 },
2925 })
2926}
2927
2928pub fn standard_ratio() -> &'static LemmaType {
2930 STANDARD_RATIO.get_or_init(|| LemmaType {
2931 name: None,
2932 specifications: TypeSpecification::Ratio {
2933 minimum: None,
2934 maximum: None,
2935 units: vec![
2936 Unit {
2937 name: "percent".to_string(),
2938 value: Decimal::from(100),
2939 },
2940 Unit {
2941 name: "permille".to_string(),
2942 value: Decimal::from(1000),
2943 },
2944 ],
2945 help: None,
2946 default: None,
2947 },
2948 })
2949}
2950
2951impl LemmaType {
2954 pub fn example_value(&self) -> &'static str {
2956 match &self.specifications {
2957 TypeSpecification::Text { .. } => "\"hello world\"",
2958 TypeSpecification::Scale { .. } => "3.14",
2959 TypeSpecification::Number { .. } => "3.14",
2960 TypeSpecification::Boolean { .. } => "true",
2961 TypeSpecification::Date { .. } => "2023-12-25T14:30:00Z",
2962 TypeSpecification::Veto { .. } => "veto",
2963 TypeSpecification::Time { .. } => "14:30:00",
2964 TypeSpecification::Duration { .. } => "90 minutes",
2965 TypeSpecification::Ratio { .. } => "50%",
2966 }
2967 }
2968}
2969
2970fn strip_surrounding_quotes(s: &str) -> String {
2971 let bytes = s.as_bytes();
2972 if bytes.len() >= 2 {
2973 let first = bytes[0];
2974 let last = bytes[bytes.len() - 1];
2975 if (first == b'"' && last == b'"') || (first == b'\'' && last == b'\'') {
2976 return s[1..bytes.len() - 1].to_string();
2977 }
2978 }
2979 s.to_string()
2980}
2981
2982impl fmt::Display for LemmaType {
2983 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2984 write!(f, "{}", self.name())
2985 }
2986}
2987
2988#[cfg(test)]
2989mod tests {
2990 use super::*;
2991 use rust_decimal::Decimal;
2992 use std::str::FromStr;
2993
2994 #[test]
2995 fn test_arithmetic_operation_name() {
2996 assert_eq!(ArithmeticComputation::Add.name(), "addition");
2997 assert_eq!(ArithmeticComputation::Subtract.name(), "subtraction");
2998 assert_eq!(ArithmeticComputation::Multiply.name(), "multiplication");
2999 assert_eq!(ArithmeticComputation::Divide.name(), "division");
3000 assert_eq!(ArithmeticComputation::Modulo.name(), "modulo");
3001 assert_eq!(ArithmeticComputation::Power.name(), "exponentiation");
3002 }
3003
3004 #[test]
3005 fn test_comparison_operator_name() {
3006 assert_eq!(ComparisonComputation::GreaterThan.name(), "greater than");
3007 assert_eq!(ComparisonComputation::LessThan.name(), "less than");
3008 assert_eq!(
3009 ComparisonComputation::GreaterThanOrEqual.name(),
3010 "greater than or equal"
3011 );
3012 assert_eq!(
3013 ComparisonComputation::LessThanOrEqual.name(),
3014 "less than or equal"
3015 );
3016 assert_eq!(ComparisonComputation::Equal.name(), "equal");
3017 assert_eq!(ComparisonComputation::NotEqual.name(), "not equal");
3018 assert_eq!(ComparisonComputation::Is.name(), "is");
3019 assert_eq!(ComparisonComputation::IsNot.name(), "is not");
3020 }
3021
3022 #[test]
3023 fn test_literal_value_to_standard_type() {
3024 let one = Decimal::from_str("1").unwrap();
3025
3026 assert_eq!(LiteralValue::text("".to_string()).lemma_type.name(), "text");
3027 assert_eq!(LiteralValue::number(one).lemma_type.name(), "number");
3028 assert_eq!(
3029 LiteralValue::boolean(BooleanValue::True).lemma_type.name(),
3030 "boolean"
3031 );
3032
3033 let dt = DateTimeValue {
3034 year: 2024,
3035 month: 1,
3036 day: 1,
3037 hour: 0,
3038 minute: 0,
3039 second: 0,
3040 timezone: None,
3041 };
3042 assert_eq!(LiteralValue::date(dt).lemma_type.name(), "date");
3043 assert_eq!(
3044 LiteralValue::ratio(one / Decimal::from(100), Some("percent".to_string()))
3045 .lemma_type
3046 .name(),
3047 "ratio"
3048 );
3049 assert_eq!(
3050 LiteralValue::duration(one, DurationUnit::Second)
3051 .lemma_type
3052 .name(),
3053 "duration"
3054 );
3055 }
3056
3057 #[test]
3058 fn test_arithmetic_operation_display() {
3059 assert_eq!(format!("{}", ArithmeticComputation::Add), "+");
3060 assert_eq!(format!("{}", ArithmeticComputation::Subtract), "-");
3061 assert_eq!(format!("{}", ArithmeticComputation::Multiply), "*");
3062 assert_eq!(format!("{}", ArithmeticComputation::Divide), "/");
3063 assert_eq!(format!("{}", ArithmeticComputation::Modulo), "%");
3064 assert_eq!(format!("{}", ArithmeticComputation::Power), "^");
3065 }
3066
3067 #[test]
3068 fn test_comparison_operator_display() {
3069 assert_eq!(format!("{}", ComparisonComputation::GreaterThan), ">");
3070 assert_eq!(format!("{}", ComparisonComputation::LessThan), "<");
3071 assert_eq!(
3072 format!("{}", ComparisonComputation::GreaterThanOrEqual),
3073 ">="
3074 );
3075 assert_eq!(format!("{}", ComparisonComputation::LessThanOrEqual), "<=");
3076 assert_eq!(format!("{}", ComparisonComputation::Equal), "==");
3077 assert_eq!(format!("{}", ComparisonComputation::NotEqual), "!=");
3078 assert_eq!(format!("{}", ComparisonComputation::Is), "is");
3079 assert_eq!(format!("{}", ComparisonComputation::IsNot), "is not");
3080 }
3081
3082 #[test]
3083 fn test_duration_unit_display() {
3084 assert_eq!(format!("{}", DurationUnit::Second), "seconds");
3085 assert_eq!(format!("{}", DurationUnit::Minute), "minutes");
3086 assert_eq!(format!("{}", DurationUnit::Hour), "hours");
3087 assert_eq!(format!("{}", DurationUnit::Day), "days");
3088 assert_eq!(format!("{}", DurationUnit::Week), "weeks");
3089 assert_eq!(format!("{}", DurationUnit::Millisecond), "milliseconds");
3090 assert_eq!(format!("{}", DurationUnit::Microsecond), "microseconds");
3091 }
3092
3093 #[test]
3094 fn test_conversion_target_display() {
3095 assert_eq!(format!("{}", ConversionTarget::Percentage), "percent");
3096 assert_eq!(
3097 format!("{}", ConversionTarget::Duration(DurationUnit::Hour)),
3098 "hours"
3099 );
3100 assert_eq!(
3101 format!("{}", ConversionTarget::ScaleUnit("usd".to_string())),
3102 "usd"
3103 );
3104 }
3105
3106 #[test]
3107 fn test_doc_type_display() {
3108 assert_eq!(format!("{}", standard_text()), "text");
3109 assert_eq!(format!("{}", standard_number()), "number");
3110 assert_eq!(format!("{}", standard_date()), "date");
3111 assert_eq!(format!("{}", standard_boolean()), "boolean");
3112 assert_eq!(format!("{}", standard_duration()), "duration");
3113 }
3114
3115 #[test]
3116 fn test_type_constructor() {
3117 let specs = TypeSpecification::number();
3118 let lemma_type = LemmaType::new("dice".to_string(), specs);
3119 assert_eq!(lemma_type.name(), "dice");
3120 }
3121
3122 #[test]
3123 fn test_type_display() {
3124 let specs = TypeSpecification::text();
3125 let lemma_type = LemmaType::new("name".to_string(), specs);
3126 assert_eq!(format!("{}", lemma_type), "name");
3127 }
3128
3129 #[test]
3130 fn test_type_equality() {
3131 let specs1 = TypeSpecification::number();
3132 let specs2 = TypeSpecification::number();
3133 let lemma_type1 = LemmaType::new("dice".to_string(), specs1);
3134 let lemma_type2 = LemmaType::new("dice".to_string(), specs2);
3135 assert_eq!(lemma_type1, lemma_type2);
3136 }
3137
3138 #[test]
3139 fn test_type_serialization() {
3140 let specs = TypeSpecification::number();
3141 let lemma_type = LemmaType::new("dice".to_string(), specs);
3142 let serialized = serde_json::to_string(&lemma_type).unwrap();
3143 let deserialized: LemmaType = serde_json::from_str(&serialized).unwrap();
3144 assert_eq!(lemma_type, deserialized);
3145 }
3146
3147 #[test]
3148 fn test_literal_value_display_value() {
3149 let ten = Decimal::from_str("10").unwrap();
3150
3151 assert_eq!(
3152 LiteralValue::text("hello".to_string()).display_value(),
3153 "\"hello\""
3154 );
3155 assert_eq!(LiteralValue::number(ten).display_value(), "10");
3156 assert_eq!(
3157 LiteralValue::boolean(BooleanValue::True).display_value(),
3158 "true"
3159 );
3160 assert_eq!(
3161 LiteralValue::boolean(BooleanValue::False).display_value(),
3162 "false"
3163 );
3164 let ten_percent_ratio = LiteralValue::ratio(
3166 Decimal::from_str("0.10").unwrap(),
3167 Some("percent".to_string()),
3168 );
3169 assert_eq!(ten_percent_ratio.display_value(), "10%");
3171
3172 let time = TimeValue {
3173 hour: 14,
3174 minute: 30,
3175 second: 0,
3176 timezone: None,
3177 };
3178 let time_display = LiteralValue::time(time).display_value();
3179 assert!(time_display.contains("14"));
3180 assert!(time_display.contains("30"));
3181 }
3182
3183 #[test]
3184 fn test_literal_value_time_type() {
3185 let time = TimeValue {
3186 hour: 14,
3187 minute: 30,
3188 second: 0,
3189 timezone: None,
3190 };
3191 assert_eq!(LiteralValue::time(time).lemma_type.name(), "time");
3192 }
3193
3194 #[test]
3195 fn test_datetime_value_display() {
3196 let dt = DateTimeValue {
3197 year: 2024,
3198 month: 12,
3199 day: 25,
3200 hour: 14,
3201 minute: 30,
3202 second: 45,
3203 timezone: Some(TimezoneValue {
3204 offset_hours: 1,
3205 offset_minutes: 0,
3206 }),
3207 };
3208 let display = format!("{}", dt);
3209 assert!(display.contains("2024"));
3210 assert!(display.contains("12"));
3211 assert!(display.contains("25"));
3212 }
3213
3214 #[test]
3215 fn test_time_value_display() {
3216 let time = TimeValue {
3217 hour: 14,
3218 minute: 30,
3219 second: 45,
3220 timezone: Some(TimezoneValue {
3221 offset_hours: -5,
3222 offset_minutes: 30,
3223 }),
3224 };
3225 let display = format!("{}", time);
3226 assert!(display.contains("14"));
3227 assert!(display.contains("30"));
3228 assert!(display.contains("45"));
3229 }
3230
3231 #[test]
3232 fn test_timezone_value() {
3233 let tz_positive = TimezoneValue {
3234 offset_hours: 5,
3235 offset_minutes: 30,
3236 };
3237 assert_eq!(tz_positive.offset_hours, 5);
3238 assert_eq!(tz_positive.offset_minutes, 30);
3239
3240 let tz_negative = TimezoneValue {
3241 offset_hours: -8,
3242 offset_minutes: 0,
3243 };
3244 assert_eq!(tz_negative.offset_hours, -8);
3245 }
3246
3247 #[test]
3248 fn test_negation_types() {
3249 let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
3250 let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
3251 assert_eq!(decoded, NegationType::Not);
3252 }
3253
3254 #[test]
3255 fn test_veto_expression() {
3256 let veto_with_message = VetoExpression {
3257 message: Some("Must be over 18".to_string()),
3258 };
3259 assert_eq!(
3260 veto_with_message.message,
3261 Some("Must be over 18".to_string())
3262 );
3263
3264 let veto_without_message = VetoExpression { message: None };
3265 assert!(veto_without_message.message.is_none());
3266 }
3267
3268 #[test]
3269 fn test_expression_get_source_text_with_location() {
3270 use crate::{Expression, ExpressionKind, LiteralValue, Source, Span};
3271 use std::collections::HashMap;
3272
3273 let source = "fact value = 42";
3274 let mut sources = HashMap::new();
3275 sources.insert("test.lemma".to_string(), source.to_string());
3276
3277 let span = Span {
3278 start: 13,
3279 end: 15,
3280 line: 1,
3281 col: 13,
3282 };
3283 let source_location = Some(Source::new("test.lemma", span, "test"));
3284 let expr = Expression::new(
3285 ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3286 source_location,
3287 );
3288
3289 assert_eq!(expr.get_source_text(&sources), Some("42".to_string()));
3290 }
3291
3292 #[test]
3293 fn test_expression_get_source_text_no_location() {
3294 use crate::{Expression, ExpressionKind, LiteralValue};
3295 use std::collections::HashMap;
3296
3297 let mut sources = HashMap::new();
3298 sources.insert("test.lemma".to_string(), "fact value = 42".to_string());
3299
3300 let expr = Expression::new(
3301 ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3302 None,
3303 );
3304
3305 assert_eq!(expr.get_source_text(&sources), None);
3306 }
3307
3308 #[test]
3309 fn test_expression_get_source_text_source_not_found() {
3310 use crate::{Expression, ExpressionKind, LiteralValue, Source, Span};
3311 use std::collections::HashMap;
3312
3313 let sources = HashMap::new();
3314 let span = Span {
3315 start: 0,
3316 end: 5,
3317 line: 1,
3318 col: 0,
3319 };
3320 let source_location = Some(Source::new("missing.lemma", span, "test"));
3321 let expr = Expression::new(
3322 ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3323 source_location,
3324 );
3325
3326 assert_eq!(expr.get_source_text(&sources), None);
3327 }
3328}