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