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