1#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
15pub struct Span {
16 pub start: usize,
17 pub end: usize,
18 pub line: usize,
19 pub col: usize,
20}
21
22pub struct DepthTracker {
24 depth: usize,
25 max_depth: usize,
26}
27
28impl DepthTracker {
29 pub fn with_max_depth(max_depth: usize) -> Self {
30 Self {
31 depth: 0,
32 max_depth,
33 }
34 }
35
36 pub fn push_depth(&mut self) -> Result<(), usize> {
38 self.depth += 1;
39 if self.depth > self.max_depth {
40 return Err(self.depth);
41 }
42 Ok(())
43 }
44
45 pub fn pop_depth(&mut self) {
46 if self.depth > 0 {
47 self.depth -= 1;
48 }
49 }
50
51 pub fn max_depth(&self) -> usize {
52 self.max_depth
53 }
54}
55
56impl Default for DepthTracker {
57 fn default() -> Self {
58 Self {
59 depth: 0,
60 max_depth: 5,
61 }
62 }
63}
64
65use crate::parsing::source::Source;
70use rust_decimal::Decimal;
71use serde::Serialize;
72use std::cmp::Ordering;
73use std::fmt;
74use std::hash::{Hash, Hasher};
75use std::sync::Arc;
76
77pub use crate::literals::{
78 BooleanValue, CalendarUnit, DateTimeValue, TimeValue, TimezoneValue, Value,
79};
80
81#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
82pub enum EffectiveDate {
83 Origin,
84 DateTimeValue(crate::DateTimeValue),
85}
86
87impl EffectiveDate {
88 pub fn as_ref(&self) -> Option<&crate::DateTimeValue> {
89 match self {
90 EffectiveDate::Origin => None,
91 EffectiveDate::DateTimeValue(dt) => Some(dt),
92 }
93 }
94
95 pub fn from_option(opt: Option<crate::DateTimeValue>) -> Self {
96 match opt {
97 None => EffectiveDate::Origin,
98 Some(dt) => EffectiveDate::DateTimeValue(dt),
99 }
100 }
101
102 pub fn to_option(&self) -> Option<crate::DateTimeValue> {
103 match self {
104 EffectiveDate::Origin => None,
105 EffectiveDate::DateTimeValue(dt) => Some(dt.clone()),
106 }
107 }
108
109 pub fn is_origin(&self) -> bool {
110 matches!(self, EffectiveDate::Origin)
111 }
112}
113
114impl PartialOrd for EffectiveDate {
115 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
116 Some(self.cmp(other))
117 }
118}
119
120impl Ord for EffectiveDate {
121 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
123 self.as_ref().cmp(&other.as_ref())
124 }
125}
126
127impl fmt::Display for EffectiveDate {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 match self {
130 EffectiveDate::Origin => Ok(()),
131 EffectiveDate::DateTimeValue(dt) => write!(f, "{}", dt),
132 }
133 }
134}
135
136#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
150pub struct LemmaRepository {
151 pub name: Option<String>,
153 pub dependency: Option<String>,
156 pub start_line: usize,
157 pub source_type: Option<crate::parsing::source::SourceType>,
158}
159
160impl LemmaRepository {
161 #[must_use]
162 pub fn new(name: Option<String>) -> Self {
163 Self {
164 name,
165 dependency: None,
166 start_line: 1,
167 source_type: None,
168 }
169 }
170
171 #[must_use]
172 pub fn with_start_line(mut self, start_line: usize) -> Self {
173 self.start_line = start_line;
174 self
175 }
176
177 #[must_use]
178 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
179 self.source_type = Some(source_type);
180 self
181 }
182
183 #[must_use]
184 pub fn with_dependency(mut self, dependency_id: impl Into<String>) -> Self {
185 self.dependency = Some(dependency_id.into());
186 self
187 }
188
189 #[must_use]
193 pub fn identity(&self) -> Option<&str> {
194 self.name.as_deref()
195 }
196}
197
198impl PartialEq for LemmaRepository {
199 fn eq(&self, other: &Self) -> bool {
200 self.name == other.name
201 }
202}
203
204impl Eq for LemmaRepository {}
205
206impl PartialOrd for LemmaRepository {
207 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
208 Some(self.cmp(other))
209 }
210}
211
212impl Ord for LemmaRepository {
213 fn cmp(&self, other: &Self) -> Ordering {
214 self.name.cmp(&other.name)
215 }
216}
217
218impl Hash for LemmaRepository {
219 fn hash<H: Hasher>(&self, state: &mut H) {
220 self.name.hash(state);
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
228pub struct RepositoryQualifier {
229 pub name: String,
230}
231
232impl RepositoryQualifier {
233 #[must_use]
234 pub fn new(name: impl Into<String>) -> Self {
235 Self { name: name.into() }
236 }
237
238 #[must_use]
240 pub fn is_registry(&self) -> bool {
241 self.name.starts_with('@')
242 }
243}
244
245impl fmt::Display for RepositoryQualifier {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "{}", self.name)
248 }
249}
250
251#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
263pub struct LemmaSpec {
264 pub name: String,
265 pub effective_from: EffectiveDate,
266 pub source_type: Option<crate::parsing::source::SourceType>,
267 pub start_line: usize,
268 pub commentary: Option<String>,
269 pub data: Vec<LemmaData>,
270 pub rules: Vec<LemmaRule>,
271 pub meta_fields: Vec<MetaField>,
272}
273
274#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
275pub struct MetaField {
276 pub key: String,
277 pub value: MetaValue,
278 pub source_location: Source,
279}
280
281#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
282#[serde(rename_all = "snake_case")]
283pub enum MetaValue {
284 Literal(Value),
285 Unquoted(String),
286}
287
288impl fmt::Display for MetaValue {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 match self {
291 MetaValue::Literal(v) => write!(f, "{}", v),
292 MetaValue::Unquoted(s) => write!(f, "{}", s),
293 }
294 }
295}
296
297impl fmt::Display for MetaField {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 write!(f, "meta {}: {}", self.key, self.value)
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
304pub struct LemmaData {
305 pub reference: Reference,
306 pub value: DataValue,
307 pub source_location: Source,
308}
309
310#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
316pub struct UnlessClause {
317 pub condition: Expression,
318 pub result: Expression,
319 pub source_location: Source,
320}
321
322#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
324pub struct LemmaRule {
325 pub name: String,
326 pub expression: Expression,
327 pub unless_clauses: Vec<UnlessClause>,
328 pub source_location: Source,
329}
330
331#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
337pub struct Expression {
338 pub kind: ExpressionKind,
339 pub source_location: Option<Source>,
340}
341
342impl Expression {
343 #[must_use]
345 pub fn new(kind: ExpressionKind, source_location: Source) -> Self {
346 Self {
347 kind,
348 source_location: Some(source_location),
349 }
350 }
351}
352
353impl PartialEq for Expression {
355 fn eq(&self, other: &Self) -> bool {
356 self.kind == other.kind
357 }
358}
359
360impl Eq for Expression {}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
364#[serde(rename_all = "snake_case")]
365pub enum DateRelativeKind {
366 InPast,
367 InFuture,
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
372#[serde(rename_all = "snake_case")]
373pub enum DateCalendarKind {
374 Current,
375 Past,
376 Future,
377 NotIn,
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
382#[serde(rename_all = "snake_case")]
383pub enum CalendarPeriodUnit {
384 Year,
385 Month,
386 Week,
387}
388
389impl CalendarPeriodUnit {
390 #[must_use]
391 pub fn from_keyword(s: &str) -> Option<Self> {
392 match s.trim().to_lowercase().as_str() {
393 "year" | "years" => Some(Self::Year),
394 "month" | "months" => Some(Self::Month),
395 "week" | "weeks" => Some(Self::Week),
396 _ => None,
397 }
398 }
399}
400
401impl fmt::Display for DateRelativeKind {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 match self {
404 DateRelativeKind::InPast => write!(f, "in past"),
405 DateRelativeKind::InFuture => write!(f, "in future"),
406 }
407 }
408}
409
410impl fmt::Display for DateCalendarKind {
411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
412 match self {
413 DateCalendarKind::Current => write!(f, "in calendar"),
414 DateCalendarKind::Past => write!(f, "in past calendar"),
415 DateCalendarKind::Future => write!(f, "in future calendar"),
416 DateCalendarKind::NotIn => write!(f, "not in calendar"),
417 }
418 }
419}
420
421impl fmt::Display for CalendarPeriodUnit {
422 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423 match self {
424 CalendarPeriodUnit::Year => write!(f, "year"),
425 CalendarPeriodUnit::Month => write!(f, "month"),
426 CalendarPeriodUnit::Week => write!(f, "week"),
427 }
428 }
429}
430
431#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
433#[serde(rename_all = "snake_case")]
434pub enum ExpressionKind {
435 Literal(Value),
437 Reference(Reference),
439 Now,
441 DateRelative(DateRelativeKind, Arc<Expression>),
444 DateCalendar(DateCalendarKind, CalendarPeriodUnit, Arc<Expression>),
447 RangeLiteral(Arc<Expression>, Arc<Expression>),
449 PastFutureRange(DateRelativeKind, Arc<Expression>),
451 RangeContainment(Arc<Expression>, Arc<Expression>),
453 LogicalAnd(Arc<Expression>, Arc<Expression>),
454 Arithmetic(Arc<Expression>, ArithmeticComputation, Arc<Expression>),
455 Comparison(Arc<Expression>, ComparisonComputation, Arc<Expression>),
456 UnitConversion(Arc<Expression>, ConversionTarget),
457 LogicalNegation(Arc<Expression>, NegationType),
458 MathematicalComputation(MathematicalComputation, Arc<Expression>),
459 Veto(VetoExpression),
460 ResultIsVeto(Arc<Expression>),
462}
463
464#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
474pub struct Reference {
475 pub segments: Vec<String>,
476 pub name: String,
477}
478
479impl Reference {
480 #[must_use]
481 pub fn local(name: String) -> Self {
482 Self {
483 segments: Vec::new(),
484 name,
485 }
486 }
487
488 #[must_use]
489 pub fn from_path(path: Vec<String>) -> Self {
490 if path.is_empty() {
491 Self {
492 segments: Vec::new(),
493 name: String::new(),
494 }
495 } else {
496 let name = path[path.len() - 1].clone();
498 let segments = path[..path.len() - 1].to_vec();
499 Self { segments, name }
500 }
501 }
502
503 #[must_use]
504 pub fn is_local(&self) -> bool {
505 self.segments.is_empty()
506 }
507
508 #[must_use]
509 pub fn full_path(&self) -> Vec<String> {
510 let mut path = self.segments.clone();
511 path.push(self.name.clone());
512 path
513 }
514}
515
516impl fmt::Display for Reference {
517 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
518 for segment in &self.segments {
519 write!(f, "{}.", segment)?;
520 }
521 write!(f, "{}", self.name)
522 }
523}
524
525#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
527#[serde(rename_all = "snake_case")]
528pub enum ArithmeticComputation {
529 Add,
530 Subtract,
531 Multiply,
532 Divide,
533 Modulo,
534 Power,
535}
536
537#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
539#[serde(rename_all = "snake_case")]
540pub enum ComparisonComputation {
541 GreaterThan,
542 LessThan,
543 GreaterThanOrEqual,
544 LessThanOrEqual,
545 Is,
546 IsNot,
547}
548
549impl ComparisonComputation {
550 #[must_use]
552 pub fn is_equal(&self) -> bool {
553 matches!(self, ComparisonComputation::Is)
554 }
555
556 #[must_use]
558 pub fn is_not_equal(&self) -> bool {
559 matches!(self, ComparisonComputation::IsNot)
560 }
561}
562
563#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
568#[serde(rename_all = "snake_case")]
569pub enum ConversionTarget {
570 Calendar(CalendarUnit),
571 Unit(String),
572 Type(PrimitiveKind),
573}
574
575#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
577#[serde(rename_all = "snake_case")]
578pub enum NegationType {
579 Not,
580}
581
582#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
590pub struct VetoExpression {
591 pub message: Option<String>,
592}
593
594#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
596#[serde(rename_all = "snake_case")]
597pub enum MathematicalComputation {
598 Sqrt,
599 Sin,
600 Cos,
601 Tan,
602 Asin,
603 Acos,
604 Atan,
605 Log,
606 Exp,
607 Abs,
608 Floor,
609 Ceil,
610 Round,
611}
612
613#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
620pub struct SpecRef {
621 pub repository: Option<RepositoryQualifier>,
624 pub name: String,
626 pub effective: Option<DateTimeValue>,
628 #[serde(default, skip_serializing_if = "Option::is_none")]
630 pub repository_span: Option<Span>,
631 #[serde(default, skip_serializing_if = "Option::is_none")]
633 pub target_span: Option<Span>,
634}
635
636impl std::fmt::Display for SpecRef {
637 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638 if let Some(qualifier) = &self.repository {
639 write!(f, "{} ", qualifier)?;
640 }
641 write!(f, "{}", self.name)?;
642 if let Some(d) = &self.effective {
643 write!(f, " {}", d)?;
644 }
645 Ok(())
646 }
647}
648
649impl SpecRef {
650 pub fn same_repository(name: impl Into<String>) -> Self {
652 Self {
653 name: name.into(),
654 repository: None,
655 effective: None,
656 repository_span: None,
657 target_span: None,
658 }
659 }
660
661 pub fn cross_repository(name: impl Into<String>, qualifier: RepositoryQualifier) -> Self {
663 Self {
664 name: name.into(),
665 repository: Some(qualifier),
666 effective: None,
667 repository_span: None,
668 target_span: None,
669 }
670 }
671
672 pub fn at(&self, effective: &EffectiveDate) -> EffectiveDate {
675 self.effective
676 .clone()
677 .map_or_else(|| effective.clone(), EffectiveDate::DateTimeValue)
678 }
679}
680
681#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
689pub struct UnitFactor {
690 pub quantity_ref: String,
691 pub exp: i32,
692}
693
694#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
703pub enum UnitArg {
704 Factor(Decimal),
705 Expr(Decimal, Vec<UnitFactor>),
706}
707
708impl fmt::Display for UnitArg {
709 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710 match self {
711 UnitArg::Factor(v) => write!(f, "{}", v),
712 UnitArg::Expr(prefix, factors) => {
713 if *prefix != Decimal::ONE {
714 write!(f, "{} ", prefix)?;
715 }
716 for (index, factor) in factors.iter().enumerate() {
717 if factor.exp == 0 {
718 unreachable!("BUG: unit factor exponent cannot be zero");
719 }
720 if factor.exp > 0 {
721 if index > 0 {
722 write!(f, " * ")?;
723 }
724 write!(f, "{}", factor.quantity_ref)?;
725 if factor.exp != 1 {
726 write!(f, "^{}", factor.exp)?;
727 }
728 } else {
729 let denominator_started =
730 factors[..index].iter().any(|prior| prior.exp < 0);
731 if denominator_started {
732 write!(f, " * ")?;
733 } else {
734 write!(f, "/")?;
735 }
736 write!(f, "{}", factor.quantity_ref)?;
737 let positive_exp = factor
738 .exp
739 .checked_neg()
740 .expect("BUG: negative unit factor exponent");
741 if positive_exp != 1 {
742 write!(f, "^{}", positive_exp)?;
743 }
744 }
745 }
746 Ok(())
747 }
748 }
749 }
750}
751
752#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
770#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
771pub enum CommandArg {
772 Literal(crate::literals::Value),
774 Label(String),
776 UnitExpr(UnitArg),
778}
779
780impl fmt::Display for CommandArg {
781 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
782 match self {
783 CommandArg::Literal(v) => write!(f, "{}", v),
784 CommandArg::Label(s) => write!(f, "{}", s),
785 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
786 }
787 }
788}
789
790#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
792#[serde(rename_all = "snake_case")]
793pub enum TypeConstraintCommand {
794 Help,
795 Default,
796 Unit,
797 Trait,
798 Minimum,
799 Maximum,
800 Decimals,
801 Option,
802 Options,
803 Length,
804}
805
806impl fmt::Display for TypeConstraintCommand {
807 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
808 let s = match self {
809 TypeConstraintCommand::Help => "help",
810 TypeConstraintCommand::Default => "default",
811 TypeConstraintCommand::Unit => "unit",
812 TypeConstraintCommand::Trait => "trait",
813 TypeConstraintCommand::Minimum => "minimum",
814 TypeConstraintCommand::Maximum => "maximum",
815 TypeConstraintCommand::Decimals => "decimals",
816 TypeConstraintCommand::Option => "option",
817 TypeConstraintCommand::Options => "options",
818 TypeConstraintCommand::Length => "length",
819 };
820 write!(f, "{}", s)
821 }
822}
823
824#[must_use]
826pub fn try_parse_type_constraint_command(s: &str) -> Option<TypeConstraintCommand> {
827 match s.trim().to_lowercase().as_str() {
828 "help" => Some(TypeConstraintCommand::Help),
829 "default" => Some(TypeConstraintCommand::Default),
830 "unit" => Some(TypeConstraintCommand::Unit),
831 "trait" => Some(TypeConstraintCommand::Trait),
832 "minimum" => Some(TypeConstraintCommand::Minimum),
833 "maximum" => Some(TypeConstraintCommand::Maximum),
834 "decimals" => Some(TypeConstraintCommand::Decimals),
835 "option" => Some(TypeConstraintCommand::Option),
836 "options" => Some(TypeConstraintCommand::Options),
837 "length" => Some(TypeConstraintCommand::Length),
838 _ => None,
839 }
840}
841
842pub type Constraint = (TypeConstraintCommand, Vec<CommandArg>);
844
845#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
847#[serde(rename_all = "snake_case")]
848pub enum FillRhs {
849 Literal(Value),
850 Reference { target: Reference },
851}
852
853#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
854#[serde(rename_all = "snake_case")]
855pub enum DataValue {
857 Definition {
865 #[serde(default, skip_serializing_if = "Option::is_none")]
866 base: Option<ParentType>,
867 constraints: Option<Vec<Constraint>>,
868 #[serde(default, skip_serializing_if = "Option::is_none")]
869 value: Option<Value>,
870 },
871 Import(SpecRef),
873 Fill(FillRhs),
879}
880
881impl DataValue {
882 #[must_use]
884 pub fn is_definition_literal_only(&self) -> bool {
885 matches!(
886 self,
887 DataValue::Definition {
888 base: None,
889 constraints: None,
890 value: Some(_),
891 }
892 )
893 }
894
895 #[must_use]
897 pub fn definition_needs_type_resolution(&self) -> bool {
898 match self {
899 DataValue::Definition { base: Some(_), .. }
900 | DataValue::Definition {
901 constraints: Some(_),
902 ..
903 } => true,
904 DataValue::Definition {
905 base: None,
906 constraints: None,
907 value: Some(v),
908 } => !matches!(v, Value::NumberWithUnit(_, _)),
909 DataValue::Import(_) | DataValue::Fill(_) | DataValue::Definition { .. } => false,
910 }
911 }
912}
913
914fn format_constraint_chain(constraints: &[Constraint]) -> String {
917 constraints
918 .iter()
919 .map(|(cmd, args)| {
920 let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
921 let joined = args_str.join(" ");
922 if joined.is_empty() {
923 format!("{}", cmd)
924 } else {
925 format!("{} {}", cmd, joined)
926 }
927 })
928 .collect::<Vec<_>>()
929 .join(" -> ")
930}
931
932impl fmt::Display for DataValue {
933 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
934 match self {
935 DataValue::Definition {
936 base,
937 constraints,
938 value,
939 } => {
940 if base.is_none() && constraints.is_none() {
941 return match value {
942 Some(v) => write!(f, "{}", v),
943 None => Ok(()),
944 };
945 }
946 let base_str = match base.as_ref() {
947 Some(b) => format!("{b}"),
948 None => match value {
949 Some(v) => {
950 if let Some(ref constraints_vec) = constraints {
951 let constraint_str = format_constraint_chain(constraints_vec);
952 return write!(f, "{v} -> {constraint_str}");
953 }
954 return write!(f, "{v}");
955 }
956 None => String::new(),
957 },
958 };
959 if let Some(ref constraints_vec) = constraints {
960 let constraint_str = format_constraint_chain(constraints_vec);
961 write!(f, "{base_str} -> {constraint_str}")
962 } else {
963 write!(f, "{base_str}")
964 }
965 }
966 DataValue::Import(spec_ref) => {
967 write!(f, "with {}", spec_ref)
968 }
969 DataValue::Fill(fill_rhs) => match fill_rhs {
970 FillRhs::Literal(v) => write!(f, "{v}"),
971 FillRhs::Reference { target } => write!(f, "{target}"),
972 },
973 }
974 }
975}
976
977impl LemmaData {
978 #[must_use]
979 pub fn new(reference: Reference, value: DataValue, source_location: Source) -> Self {
980 Self {
981 reference,
982 value,
983 source_location,
984 }
985 }
986}
987
988impl LemmaSpec {
989 #[must_use]
990 pub fn new(name: String) -> Self {
991 Self {
992 name,
993 effective_from: EffectiveDate::Origin,
994 source_type: None,
995 start_line: 1,
996 commentary: None,
997 data: Vec::new(),
998 rules: Vec::new(),
999 meta_fields: Vec::new(),
1000 }
1001 }
1002
1003 pub fn effective_from(&self) -> Option<&DateTimeValue> {
1005 self.effective_from.as_ref()
1006 }
1007
1008 #[must_use]
1009 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
1010 self.source_type = Some(source_type);
1011 self
1012 }
1013
1014 #[must_use]
1015 pub fn with_start_line(mut self, start_line: usize) -> Self {
1016 self.start_line = start_line;
1017 self
1018 }
1019
1020 #[must_use]
1021 pub fn set_commentary(mut self, commentary: String) -> Self {
1022 self.commentary = Some(commentary);
1023 self
1024 }
1025
1026 #[must_use]
1027 pub fn add_data(mut self, data: LemmaData) -> Self {
1028 self.data.push(data);
1029 self
1030 }
1031
1032 #[must_use]
1033 pub fn add_rule(mut self, rule: LemmaRule) -> Self {
1034 self.rules.push(rule);
1035 self
1036 }
1037
1038 #[must_use]
1039 pub fn add_meta_field(mut self, meta: MetaField) -> Self {
1040 self.meta_fields.push(meta);
1041 self
1042 }
1043}
1044
1045impl fmt::Display for LemmaSpec {
1046 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1047 write!(f, "spec {}", self.name)?;
1048 if let EffectiveDate::DateTimeValue(ref af) = self.effective_from {
1049 write!(f, " {}", af)?;
1050 }
1051 writeln!(f)?;
1052
1053 if let Some(ref commentary) = self.commentary {
1054 writeln!(f, "\"\"\"")?;
1055 writeln!(f, "{}", commentary)?;
1056 writeln!(f, "\"\"\"")?;
1057 }
1058
1059 if !self.data.is_empty() {
1060 writeln!(f)?;
1061 for data in &self.data {
1062 write!(f, "{}", data)?;
1063 }
1064 }
1065
1066 if !self.rules.is_empty() {
1067 writeln!(f)?;
1068 for (index, rule) in self.rules.iter().enumerate() {
1069 if index > 0 {
1070 writeln!(f)?;
1071 }
1072 write!(f, "{}", rule)?;
1073 }
1074 }
1075
1076 if !self.meta_fields.is_empty() {
1077 writeln!(f)?;
1078 for meta in &self.meta_fields {
1079 writeln!(f, "{}", meta)?;
1080 }
1081 }
1082
1083 Ok(())
1084 }
1085}
1086
1087impl fmt::Display for LemmaData {
1088 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1089 writeln!(f, "data {}: {}", self.reference, self.value)
1090 }
1091}
1092
1093impl fmt::Display for LemmaRule {
1094 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1095 write!(f, "rule {}: {}", self.name, self.expression)?;
1096 for unless_clause in &self.unless_clauses {
1097 write!(
1098 f,
1099 "\n unless {} then {}",
1100 unless_clause.condition, unless_clause.result
1101 )?;
1102 }
1103 writeln!(f)?;
1104 Ok(())
1105 }
1106}
1107
1108pub fn expression_precedence(kind: &ExpressionKind) -> u8 {
1116 match kind {
1117 ExpressionKind::LogicalAnd(..) => 2,
1118 ExpressionKind::LogicalNegation(..) => 3,
1119 ExpressionKind::Comparison(..) | ExpressionKind::ResultIsVeto(..) => 4,
1120 ExpressionKind::RangeContainment(..) => 4,
1121 ExpressionKind::DateRelative(..) | ExpressionKind::DateCalendar(..) => 4,
1122 ExpressionKind::Arithmetic(_, op, _) => match op {
1123 ArithmeticComputation::Add | ArithmeticComputation::Subtract => 5,
1124 ArithmeticComputation::Multiply
1125 | ArithmeticComputation::Divide
1126 | ArithmeticComputation::Modulo => 6,
1127 ArithmeticComputation::Power => 7,
1128 },
1129 ExpressionKind::UnitConversion(..) => 8,
1130 ExpressionKind::RangeLiteral(..) => 9,
1131 ExpressionKind::MathematicalComputation(..) => 10,
1132 ExpressionKind::PastFutureRange(..) => 10,
1133 ExpressionKind::Literal(..)
1134 | ExpressionKind::Reference(..)
1135 | ExpressionKind::Now
1136 | ExpressionKind::Veto(..) => 10,
1137 }
1138}
1139
1140fn write_expression_child(
1141 f: &mut fmt::Formatter<'_>,
1142 child: &Expression,
1143 parent_prec: u8,
1144) -> fmt::Result {
1145 let child_prec = expression_precedence(&child.kind);
1146 if child_prec < parent_prec {
1147 write!(f, "({})", child)
1148 } else {
1149 write!(f, "{}", child)
1150 }
1151}
1152
1153impl fmt::Display for Expression {
1154 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1155 match &self.kind {
1156 ExpressionKind::Literal(lit) => write!(f, "{}", AsLemmaSource(lit)),
1157 ExpressionKind::Reference(r) => write!(f, "{}", r),
1158 ExpressionKind::Arithmetic(left, op, right) => {
1159 let my_prec = expression_precedence(&self.kind);
1160 write_expression_child(f, left, my_prec)?;
1161 write!(f, " {} ", op)?;
1162 write_expression_child(f, right, my_prec)
1163 }
1164 ExpressionKind::Comparison(left, op, right) => {
1165 let my_prec = expression_precedence(&self.kind);
1166 write_expression_child(f, left, my_prec)?;
1167 write!(f, " {} ", op)?;
1168 write_expression_child(f, right, my_prec)
1169 }
1170 ExpressionKind::UnitConversion(value, target) => {
1171 let my_prec = expression_precedence(&self.kind);
1172 write_expression_child(f, value, my_prec)?;
1173 write!(f, " as {}", target)
1174 }
1175 ExpressionKind::LogicalNegation(expr, negation) => {
1176 if let (NegationType::Not, ExpressionKind::ResultIsVeto(operand)) =
1177 (negation, &expr.kind)
1178 {
1179 let my_prec = expression_precedence(&self.kind);
1180 write_expression_child(f, operand, my_prec)?;
1181 write!(f, " is not veto")
1182 } else {
1183 let my_prec = expression_precedence(&self.kind);
1184 write!(f, "not ")?;
1185 write_expression_child(f, expr, my_prec)
1186 }
1187 }
1188 ExpressionKind::ResultIsVeto(operand) => {
1189 let my_prec = expression_precedence(&self.kind);
1190 write_expression_child(f, operand, my_prec)?;
1191 write!(f, " is veto")
1192 }
1193 ExpressionKind::LogicalAnd(left, right) => {
1194 let my_prec = expression_precedence(&self.kind);
1195 write_expression_child(f, left, my_prec)?;
1196 write!(f, " and ")?;
1197 write_expression_child(f, right, my_prec)
1198 }
1199 ExpressionKind::MathematicalComputation(op, operand) => {
1200 let my_prec = expression_precedence(&self.kind);
1201 write!(f, "{} ", op)?;
1202 write_expression_child(f, operand, my_prec)
1203 }
1204 ExpressionKind::Veto(veto) => match &veto.message {
1205 Some(msg) => write!(f, "veto {}", quote_lemma_text(msg)),
1206 None => write!(f, "veto"),
1207 },
1208 ExpressionKind::Now => write!(f, "now"),
1209 ExpressionKind::DateRelative(kind, date_expr) => {
1210 write!(f, "{} {}", date_expr, kind)?;
1211 Ok(())
1212 }
1213 ExpressionKind::DateCalendar(kind, unit, date_expr) => {
1214 write!(f, "{} {} {}", date_expr, kind, unit)
1215 }
1216 ExpressionKind::RangeLiteral(left, right) => {
1217 let my_prec = expression_precedence(&self.kind);
1218 write_expression_child(f, left, my_prec)?;
1219 write!(f, "...")?;
1220 write_expression_child(f, right, my_prec)
1221 }
1222 ExpressionKind::PastFutureRange(kind, offset_expr) => {
1223 write!(f, "{} ", kind)?;
1224 let my_prec = expression_precedence(&self.kind);
1225 write_expression_child(f, offset_expr, my_prec)
1226 }
1227 ExpressionKind::RangeContainment(value, range) => {
1228 let my_prec = expression_precedence(&self.kind);
1229 write_expression_child(f, value, my_prec)?;
1230 write!(f, " in ")?;
1231 write_expression_child(f, range, my_prec)
1232 }
1233 }
1234 }
1235}
1236
1237impl fmt::Display for ConversionTarget {
1238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1239 match self {
1240 ConversionTarget::Calendar(unit) => write!(f, "{}", unit),
1241 ConversionTarget::Unit(unit) => write!(f, "{}", unit),
1242 ConversionTarget::Type(kind) => write!(f, "{:?}", kind),
1243 }
1244 }
1245}
1246
1247impl fmt::Display for ArithmeticComputation {
1248 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1249 match self {
1250 ArithmeticComputation::Add => write!(f, "+"),
1251 ArithmeticComputation::Subtract => write!(f, "-"),
1252 ArithmeticComputation::Multiply => write!(f, "*"),
1253 ArithmeticComputation::Divide => write!(f, "/"),
1254 ArithmeticComputation::Modulo => write!(f, "%"),
1255 ArithmeticComputation::Power => write!(f, "^"),
1256 }
1257 }
1258}
1259
1260impl fmt::Display for ComparisonComputation {
1261 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1262 match self {
1263 ComparisonComputation::GreaterThan => write!(f, ">"),
1264 ComparisonComputation::LessThan => write!(f, "<"),
1265 ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1266 ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1267 ComparisonComputation::Is => write!(f, "is"),
1268 ComparisonComputation::IsNot => write!(f, "is not"),
1269 }
1270 }
1271}
1272
1273impl fmt::Display for MathematicalComputation {
1274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1275 match self {
1276 MathematicalComputation::Sqrt => write!(f, "sqrt"),
1277 MathematicalComputation::Sin => write!(f, "sin"),
1278 MathematicalComputation::Cos => write!(f, "cos"),
1279 MathematicalComputation::Tan => write!(f, "tan"),
1280 MathematicalComputation::Asin => write!(f, "asin"),
1281 MathematicalComputation::Acos => write!(f, "acos"),
1282 MathematicalComputation::Atan => write!(f, "atan"),
1283 MathematicalComputation::Log => write!(f, "log"),
1284 MathematicalComputation::Exp => write!(f, "exp"),
1285 MathematicalComputation::Abs => write!(f, "abs"),
1286 MathematicalComputation::Floor => write!(f, "floor"),
1287 MathematicalComputation::Ceil => write!(f, "ceil"),
1288 MathematicalComputation::Round => write!(f, "round"),
1289 }
1290 }
1291}
1292
1293#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1299#[serde(rename_all = "snake_case")]
1300pub enum PrimitiveKind {
1301 Boolean,
1302 Quantity,
1303 QuantityRange,
1304 Number,
1305 NumberRange,
1306 Percent,
1307 Ratio,
1308 RatioRange,
1309 Text,
1310 Date,
1311 DateRange,
1312 Time,
1313 Calendar,
1314 CalendarRange,
1315}
1316
1317impl std::fmt::Display for PrimitiveKind {
1318 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1319 let s = match self {
1320 PrimitiveKind::Boolean => "boolean",
1321 PrimitiveKind::Quantity => "quantity",
1322 PrimitiveKind::QuantityRange => "quantity range",
1323 PrimitiveKind::Number => "number",
1324 PrimitiveKind::NumberRange => "number range",
1325 PrimitiveKind::Percent => "percent",
1326 PrimitiveKind::Ratio => "ratio",
1327 PrimitiveKind::RatioRange => "ratio range",
1328 PrimitiveKind::Text => "text",
1329 PrimitiveKind::Date => "date",
1330 PrimitiveKind::DateRange => "date range",
1331 PrimitiveKind::Time => "time",
1332 PrimitiveKind::Calendar => "calendar",
1333 PrimitiveKind::CalendarRange => "calendar range",
1334 };
1335 write!(f, "{}", s)
1336 }
1337}
1338
1339#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1344#[serde(tag = "kind", rename_all = "snake_case")]
1345pub enum ParentType {
1346 Primitive {
1347 primitive: PrimitiveKind,
1348 },
1349 Custom {
1350 name: String,
1351 },
1352 Qualified {
1355 spec_alias: String,
1356 inner: Box<ParentType>,
1357 },
1358}
1359
1360impl std::fmt::Display for ParentType {
1361 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1362 match self {
1363 ParentType::Primitive { primitive } => write!(f, "{}", primitive),
1364 ParentType::Custom { name } => write!(f, "{}", name),
1365 ParentType::Qualified { spec_alias, inner } => {
1366 write!(f, "{spec_alias}.{inner}")
1367 }
1368 }
1369 }
1370}
1371
1372pub struct AsLemmaSource<'a, T: ?Sized>(pub &'a T);
1378
1379pub fn quote_lemma_text(s: &str) -> String {
1382 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1383 format!("\"{}\"", escaped)
1384}
1385
1386fn format_decimal_source(n: &Decimal) -> String {
1391 let raw = if n.fract().is_zero() {
1392 n.trunc().to_string()
1393 } else {
1394 n.to_string()
1395 };
1396 group_digits(&raw)
1397}
1398
1399fn group_digits(s: &str) -> String {
1403 let (sign, rest) = if s.starts_with('-') || s.starts_with('+') {
1404 (&s[..1], &s[1..])
1405 } else {
1406 ("", s)
1407 };
1408
1409 let (int_part, frac_part) = match rest.find('.') {
1410 Some(pos) => (&rest[..pos], &rest[pos..]),
1411 None => (rest, ""),
1412 };
1413
1414 if int_part.len() < 4 {
1415 return s.to_string();
1416 }
1417
1418 let mut grouped = String::with_capacity(int_part.len() + int_part.len() / 3);
1419 for (i, ch) in int_part.chars().enumerate() {
1420 let digits_remaining = int_part.len() - i;
1421 if i > 0 && digits_remaining % 3 == 0 {
1422 grouped.push('_');
1423 }
1424 grouped.push(ch);
1425 }
1426
1427 format!("{}{}{}", sign, grouped, frac_part)
1428}
1429
1430impl<'a> fmt::Display for AsLemmaSource<'a, CommandArg> {
1431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1432 use crate::literals::Value;
1433 match self.0 {
1434 CommandArg::Literal(Value::Text(s)) => write!(f, "{}", quote_lemma_text(s)),
1435 CommandArg::Literal(Value::Number(d)) => {
1436 write!(f, "{}", group_digits(&d.to_string()))
1437 }
1438 CommandArg::Literal(Value::Boolean(bv)) => write!(f, "{}", bv),
1439 CommandArg::Literal(Value::NumberWithUnit(d, unit)) => {
1440 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1441 }
1442 CommandArg::Literal(Value::Calendar(d, unit)) => {
1443 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1444 }
1445 CommandArg::Literal(value @ Value::Range(_, _)) => {
1446 write!(f, "{}", AsLemmaSource(value))
1447 }
1448 CommandArg::Literal(Value::Date(dt)) => write!(f, "{}", dt),
1449 CommandArg::Literal(Value::Time(t)) => write!(f, "{}", t),
1450 CommandArg::Label(s) => write!(f, "{}", s),
1451 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
1452 }
1453 }
1454}
1455
1456pub(crate) fn format_constraint_as_source(
1458 cmd: &TypeConstraintCommand,
1459 args: &[CommandArg],
1460) -> String {
1461 if args.is_empty() {
1462 cmd.to_string()
1463 } else {
1464 let args_str: Vec<String> = args
1465 .iter()
1466 .map(|a| format!("{}", AsLemmaSource(a)))
1467 .collect();
1468 format!("{} {}", cmd, args_str.join(" "))
1469 }
1470}
1471
1472fn format_constraints_as_source(constraints: &[Constraint], separator: &str) -> String {
1475 constraints
1476 .iter()
1477 .map(|(cmd, args)| format_constraint_as_source(cmd, args))
1478 .collect::<Vec<_>>()
1479 .join(separator)
1480}
1481
1482impl<'a> fmt::Display for AsLemmaSource<'a, Value> {
1485 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1486 match self.0 {
1487 Value::Number(n) => write!(f, "{}", format_decimal_source(n)),
1488 Value::Text(s) => write!(f, "{}", quote_lemma_text(s)),
1489 Value::Date(dt) => {
1490 let is_date_only =
1491 dt.hour == 0 && dt.minute == 0 && dt.second == 0 && dt.timezone.is_none();
1492 if is_date_only {
1493 write!(f, "{:04}-{:02}-{:02}", dt.year, dt.month, dt.day)
1494 } else {
1495 write!(
1496 f,
1497 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1498 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
1499 )?;
1500 if let Some(tz) = &dt.timezone {
1501 write!(f, "{}", tz)?;
1502 }
1503 Ok(())
1504 }
1505 }
1506 Value::Time(t) => {
1507 write!(f, "{:02}:{:02}:{:02}", t.hour, t.minute, t.second)?;
1508 if let Some(tz) = &t.timezone {
1509 write!(f, "{}", tz)?;
1510 }
1511 Ok(())
1512 }
1513 Value::Boolean(b) => write!(f, "{}", b),
1514 Value::NumberWithUnit(n, u) => match u.as_str() {
1515 "percent" => write!(f, "{}%", format_decimal_source(n)),
1516 "permille" => write!(f, "{}%%", format_decimal_source(n)),
1517 unit => write!(f, "{} {}", format_decimal_source(n), unit),
1518 },
1519 Value::Calendar(n, u) => write!(f, "{} {}", format_decimal_source(n), u),
1520 Value::Range(left, right) => {
1521 write!(
1522 f,
1523 "{}...{}",
1524 AsLemmaSource(left.as_ref()),
1525 AsLemmaSource(right.as_ref())
1526 )
1527 }
1528 }
1529 }
1530}
1531
1532impl<'a> fmt::Display for AsLemmaSource<'a, MetaValue> {
1535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1536 match self.0 {
1537 MetaValue::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1538 MetaValue::Unquoted(s) => write!(f, "{}", s),
1539 }
1540 }
1541}
1542
1543impl<'a> fmt::Display for AsLemmaSource<'a, DataValue> {
1544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1545 match self.0 {
1546 DataValue::Definition {
1547 base,
1548 constraints,
1549 value,
1550 } => {
1551 if base.is_none() && constraints.is_none() {
1552 if let Some(v) = value {
1553 return write!(f, "{}", AsLemmaSource(v));
1554 }
1555 }
1556 let base_str = match base.as_ref() {
1557 Some(b) => format!("{}", b),
1558 None => match value {
1559 Some(v) => {
1560 if let Some(ref constraints_vec) = constraints {
1561 let constraint_str =
1562 format_constraints_as_source(constraints_vec, " -> ");
1563 return write!(f, "{} -> {}", AsLemmaSource(v), constraint_str);
1564 }
1565 return write!(f, "{}", AsLemmaSource(v));
1566 }
1567 None => String::new(),
1568 },
1569 };
1570 if let Some(ref constraints_vec) = constraints {
1571 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
1572 write!(f, "{} -> {}", base_str, constraint_str)
1573 } else {
1574 write!(f, "{}", base_str)
1575 }
1576 }
1577 DataValue::Import(spec_ref) => {
1578 write!(f, "with {}", spec_ref)
1579 }
1580 DataValue::Fill(fill_rhs) => match fill_rhs {
1581 FillRhs::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1582 FillRhs::Reference { target } => write!(f, "{target}"),
1583 },
1584 }
1585 }
1586}
1587
1588#[cfg(test)]
1589mod tests {
1590 use super::*;
1591
1592 #[test]
1593 fn test_conversion_target_display() {
1594 assert_eq!(
1595 format!("{}", ConversionTarget::Unit("hours".to_string())),
1596 "hours"
1597 );
1598 assert_eq!(
1599 format!("{}", ConversionTarget::Unit("usd".to_string())),
1600 "usd"
1601 );
1602 }
1603
1604 #[test]
1605 fn test_value_number_with_unit_ratio_display() {
1606 use rust_decimal::Decimal;
1607 use std::str::FromStr;
1608 let percent =
1609 Value::NumberWithUnit(Decimal::from_str("10").unwrap(), "percent".to_string());
1610 assert_eq!(format!("{}", percent), "10%");
1611 let permille =
1612 Value::NumberWithUnit(Decimal::from_str("5").unwrap(), "permille".to_string());
1613 assert_eq!(format!("{}", permille), "5%%");
1614 }
1615
1616 #[test]
1617 fn test_datetime_value_display() {
1618 let dt = DateTimeValue {
1619 year: 2024,
1620 month: 12,
1621 day: 25,
1622 hour: 14,
1623 minute: 30,
1624 second: 45,
1625 microsecond: 0,
1626 timezone: Some(TimezoneValue {
1627 offset_hours: 1,
1628 offset_minutes: 0,
1629 }),
1630 };
1631 assert_eq!(format!("{}", dt), "2024-12-25T14:30:45+01:00");
1632 }
1633
1634 #[test]
1635 fn test_datetime_value_display_date_only() {
1636 let dt = DateTimeValue {
1637 year: 2026,
1638 month: 3,
1639 day: 4,
1640 hour: 0,
1641 minute: 0,
1642 second: 0,
1643 microsecond: 0,
1644 timezone: None,
1645 };
1646 assert_eq!(format!("{}", dt), "2026-03-04");
1647 }
1648
1649 #[test]
1650 fn test_datetime_value_display_microseconds() {
1651 let dt = DateTimeValue {
1652 year: 2026,
1653 month: 2,
1654 day: 23,
1655 hour: 14,
1656 minute: 30,
1657 second: 45,
1658 microsecond: 123456,
1659 timezone: Some(TimezoneValue {
1660 offset_hours: 0,
1661 offset_minutes: 0,
1662 }),
1663 };
1664 assert_eq!(format!("{}", dt), "2026-02-23T14:30:45.123456Z");
1665 }
1666
1667 #[test]
1668 fn test_datetime_microsecond_in_ordering() {
1669 let a = DateTimeValue {
1670 year: 2026,
1671 month: 1,
1672 day: 1,
1673 hour: 0,
1674 minute: 0,
1675 second: 0,
1676 microsecond: 100,
1677 timezone: None,
1678 };
1679 let b = DateTimeValue {
1680 year: 2026,
1681 month: 1,
1682 day: 1,
1683 hour: 0,
1684 minute: 0,
1685 second: 0,
1686 microsecond: 200,
1687 timezone: None,
1688 };
1689 assert!(a < b);
1690 }
1691
1692 #[test]
1693 fn test_datetime_parse_iso_week() {
1694 let dt: DateTimeValue = "2026-W01".parse().unwrap();
1695 assert_eq!(dt.year, 2025);
1696 assert_eq!(dt.month, 12);
1697 assert_eq!(dt.day, 29);
1698 assert_eq!(dt.microsecond, 0);
1699 }
1700
1701 #[test]
1702 fn test_negation_types() {
1703 let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
1704 let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
1705 assert_eq!(decoded, NegationType::Not);
1706 }
1707
1708 #[test]
1709 fn parent_type_primitive_serde_internally_tagged() {
1710 let p = ParentType::Primitive {
1711 primitive: PrimitiveKind::Number,
1712 };
1713 let json = serde_json::to_string(&p).expect("ParentType::Primitive must serialize");
1714 assert!(json.contains("\"kind\"") && json.contains("\"primitive\""));
1715 let back: ParentType = serde_json::from_str(&json).expect("deserialize");
1716 assert_eq!(back, p);
1717 }
1718
1719 fn text_arg(s: &str) -> CommandArg {
1724 CommandArg::Literal(crate::literals::Value::Text(s.to_string()))
1725 }
1726
1727 fn number_arg(s: &str) -> CommandArg {
1728 let d: rust_decimal::Decimal = s.parse().expect("decimal");
1729 CommandArg::Literal(crate::literals::Value::Number(d))
1730 }
1731
1732 fn boolean_arg(b: BooleanValue) -> CommandArg {
1733 CommandArg::Literal(crate::literals::Value::Boolean(b))
1734 }
1735
1736 fn quantity_arg(value: &str, unit: &str) -> CommandArg {
1737 let d: rust_decimal::Decimal = value.parse().expect("decimal");
1738 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
1739 }
1740
1741 fn duration_arg(value: &str, unit: &str) -> CommandArg {
1742 let d: rust_decimal::Decimal = value.parse().expect("decimal");
1743 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
1744 }
1745
1746 #[test]
1747 fn as_lemma_source_text_default_is_quoted() {
1748 let fv = DataValue::Definition {
1749 base: Some(ParentType::Primitive {
1750 primitive: PrimitiveKind::Text,
1751 }),
1752 constraints: Some(vec![(
1753 TypeConstraintCommand::Default,
1754 vec![text_arg("single")],
1755 )]),
1756 value: None,
1757 };
1758 assert_eq!(
1759 format!("{}", AsLemmaSource(&fv)),
1760 "text -> default \"single\""
1761 );
1762 }
1763
1764 #[test]
1765 fn as_lemma_source_number_default_not_quoted() {
1766 let fv = DataValue::Definition {
1767 base: Some(ParentType::Primitive {
1768 primitive: PrimitiveKind::Number,
1769 }),
1770 constraints: Some(vec![(
1771 TypeConstraintCommand::Default,
1772 vec![number_arg("10")],
1773 )]),
1774 value: None,
1775 };
1776 assert_eq!(format!("{}", AsLemmaSource(&fv)), "number -> default 10");
1777 }
1778
1779 #[test]
1780 fn as_lemma_source_help_always_quoted() {
1781 let fv = DataValue::Definition {
1782 base: Some(ParentType::Primitive {
1783 primitive: PrimitiveKind::Number,
1784 }),
1785 constraints: Some(vec![(
1786 TypeConstraintCommand::Help,
1787 vec![text_arg("Enter a quantity")],
1788 )]),
1789 value: None,
1790 };
1791 assert_eq!(
1792 format!("{}", AsLemmaSource(&fv)),
1793 "number -> help \"Enter a quantity\""
1794 );
1795 }
1796
1797 #[test]
1798 fn as_lemma_source_text_option_quoted() {
1799 let fv = DataValue::Definition {
1800 base: Some(ParentType::Primitive {
1801 primitive: PrimitiveKind::Text,
1802 }),
1803 constraints: Some(vec![
1804 (TypeConstraintCommand::Option, vec![text_arg("active")]),
1805 (TypeConstraintCommand::Option, vec![text_arg("inactive")]),
1806 ]),
1807 value: None,
1808 };
1809 assert_eq!(
1810 format!("{}", AsLemmaSource(&fv)),
1811 "text -> option \"active\" -> option \"inactive\""
1812 );
1813 }
1814
1815 #[test]
1816 fn as_lemma_source_quantity_unit_not_quoted() {
1817 let fv = DataValue::Definition {
1818 base: Some(ParentType::Primitive {
1819 primitive: PrimitiveKind::Quantity,
1820 }),
1821 constraints: Some(vec![
1822 (
1823 TypeConstraintCommand::Unit,
1824 vec![CommandArg::Label("eur".to_string()), number_arg("1.00")],
1825 ),
1826 (
1827 TypeConstraintCommand::Unit,
1828 vec![CommandArg::Label("usd".to_string()), number_arg("0.91")],
1829 ),
1830 ]),
1831 value: None,
1832 };
1833 assert_eq!(
1834 format!("{}", AsLemmaSource(&fv)),
1835 "quantity -> unit eur 1.00 -> unit usd 0.91"
1836 );
1837 }
1838
1839 #[test]
1840 fn as_lemma_source_quantity_minimum_with_unit() {
1841 let fv = DataValue::Definition {
1842 base: Some(ParentType::Primitive {
1843 primitive: PrimitiveKind::Quantity,
1844 }),
1845 constraints: Some(vec![(
1846 TypeConstraintCommand::Minimum,
1847 vec![quantity_arg("0", "eur")],
1848 )]),
1849 value: None,
1850 };
1851 assert_eq!(
1852 format!("{}", AsLemmaSource(&fv)),
1853 "quantity -> minimum 0 eur"
1854 );
1855 }
1856
1857 #[test]
1858 fn as_lemma_source_boolean_default() {
1859 let fv = DataValue::Definition {
1860 base: Some(ParentType::Primitive {
1861 primitive: PrimitiveKind::Boolean,
1862 }),
1863 constraints: Some(vec![(
1864 TypeConstraintCommand::Default,
1865 vec![boolean_arg(BooleanValue::True)],
1866 )]),
1867 value: None,
1868 };
1869 assert_eq!(format!("{}", AsLemmaSource(&fv)), "boolean -> default true");
1870 }
1871
1872 #[test]
1873 fn as_lemma_source_duration_default() {
1874 let fv = DataValue::Definition {
1875 base: Some(ParentType::Custom {
1876 name: "duration".to_string(),
1877 }),
1878 constraints: Some(vec![(
1879 TypeConstraintCommand::Default,
1880 vec![duration_arg("40", "hours")],
1881 )]),
1882 value: None,
1883 };
1884 assert_eq!(
1885 format!("{}", AsLemmaSource(&fv)),
1886 "duration -> default 40 hours"
1887 );
1888 }
1889
1890 #[test]
1891 fn as_lemma_source_named_type_default_quoted() {
1892 let fv = DataValue::Definition {
1895 base: Some(ParentType::Custom {
1896 name: "filing_status_type".to_string(),
1897 }),
1898 constraints: Some(vec![(
1899 TypeConstraintCommand::Default,
1900 vec![text_arg("single")],
1901 )]),
1902 value: None,
1903 };
1904 assert_eq!(
1905 format!("{}", AsLemmaSource(&fv)),
1906 "filing_status_type -> default \"single\""
1907 );
1908 }
1909
1910 #[test]
1911 fn as_lemma_source_help_escapes_quotes() {
1912 let fv = DataValue::Definition {
1913 base: Some(ParentType::Primitive {
1914 primitive: PrimitiveKind::Text,
1915 }),
1916 constraints: Some(vec![(
1917 TypeConstraintCommand::Help,
1918 vec![text_arg("say \"hello\"")],
1919 )]),
1920 value: None,
1921 };
1922 assert_eq!(
1923 format!("{}", AsLemmaSource(&fv)),
1924 "text -> help \"say \\\"hello\\\"\""
1925 );
1926 }
1927
1928 fn unit_arg_expr(prefix: Decimal, factors: &[(&str, i32)]) -> UnitArg {
1929 UnitArg::Expr(
1930 prefix,
1931 factors
1932 .iter()
1933 .map(|(quantity_ref, exp)| UnitFactor {
1934 quantity_ref: (*quantity_ref).to_string(),
1935 exp: *exp,
1936 })
1937 .collect(),
1938 )
1939 }
1940
1941 #[test]
1942 fn unit_arg_display_metre_per_second() {
1943 let arg = unit_arg_expr(Decimal::ONE, &[("metre", 1), ("second", -1)]);
1944 assert_eq!(format!("{arg}"), "metre/second");
1945 assert!(
1946 !format!("{arg}").contains("second^-1"),
1947 "must not print denominator as negative exponent"
1948 );
1949 }
1950
1951 #[test]
1952 fn unit_arg_display_meter_per_second_squared() {
1953 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -2)]);
1954 assert_eq!(format!("{arg}"), "meter/second^2");
1955 }
1956
1957 #[test]
1958 fn unit_arg_display_kg_times_mps2() {
1959 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("mps2", 1)]);
1960 assert_eq!(format!("{arg}"), "kg * mps2");
1961 }
1962
1963 #[test]
1964 fn unit_arg_display_numeric_prefix_metre_per_second() {
1965 use std::str::FromStr;
1966 let prefix = Decimal::from_str("3.6").expect("decimal");
1967 let arg = unit_arg_expr(prefix, &[("metre", 1), ("second", -1)]);
1968 assert_eq!(format!("{arg}"), "3.6 metre/second");
1969 }
1970
1971 #[test]
1972 fn unit_arg_display_metre_per_second_times_kg() {
1973 let arg = unit_arg_expr(Decimal::ONE, &[("metre", 1), ("second", -1), ("kg", 1)]);
1974 assert_eq!(format!("{arg}"), "metre/second * kg");
1975 }
1976
1977 #[test]
1978 fn unit_arg_display_kg_meter_per_second_squared() {
1979 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("meter", 1), ("second", -2)]);
1980 assert_eq!(format!("{arg}"), "kg * meter/second^2");
1981 }
1982}