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