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, DateGranularity, 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)]
578#[serde(rename_all = "snake_case")]
579pub enum ConversionTarget {
580 Type(PrimitiveKind),
581 Unit { unit_name: String },
582}
583
584#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
586#[serde(rename_all = "snake_case")]
587pub enum NegationType {
588 Not,
589}
590
591#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
599pub struct VetoExpression {
600 pub message: Option<String>,
601}
602
603#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
605#[serde(rename_all = "snake_case")]
606pub enum MathematicalComputation {
607 Sqrt,
608 Sin,
609 Cos,
610 Tan,
611 Asin,
612 Acos,
613 Atan,
614 Log,
615 Exp,
616 Abs,
617 Floor,
618 Ceil,
619 Round,
620}
621
622#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
624#[serde(rename_all = "snake_case")]
625pub enum LogicalComputation {
626 And,
627 Or,
628 Not,
629}
630
631#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
638pub struct SpecRef {
639 pub repository: Option<RepositoryQualifier>,
642 pub name: String,
644 pub effective: Option<DateTimeValue>,
646 #[serde(default, skip_serializing_if = "Option::is_none")]
648 pub repository_span: Option<Span>,
649 #[serde(default, skip_serializing_if = "Option::is_none")]
651 pub target_span: Option<Span>,
652}
653
654impl std::fmt::Display for SpecRef {
655 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
656 if let Some(qualifier) = &self.repository {
657 write!(f, "{} ", qualifier)?;
658 }
659 write!(f, "{}", self.name)?;
660 if let Some(d) = &self.effective {
661 write!(f, " {}", d)?;
662 }
663 Ok(())
664 }
665}
666
667impl SpecRef {
668 pub fn same_repository(name: impl Into<String>) -> Self {
670 Self {
671 name: ascii_lowercase_logical_name(name.into()),
672 repository: None,
673 effective: None,
674 repository_span: None,
675 target_span: None,
676 }
677 }
678
679 pub fn cross_repository(name: impl Into<String>, qualifier: RepositoryQualifier) -> Self {
681 Self {
682 name: ascii_lowercase_logical_name(name.into()),
683 repository: Some(qualifier),
684 effective: None,
685 repository_span: None,
686 target_span: None,
687 }
688 }
689
690 pub fn at(&self, effective: &EffectiveDate) -> EffectiveDate {
693 self.effective
694 .clone()
695 .map_or_else(|| effective.clone(), EffectiveDate::DateTimeValue)
696 }
697}
698
699#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
707pub struct UnitFactor {
708 pub quantity_ref: String,
709 pub exp: i32,
710}
711
712#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
721pub enum UnitArg {
722 Factor(Decimal),
723 Expr(Decimal, Vec<UnitFactor>),
724}
725
726impl fmt::Display for UnitArg {
727 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
728 match self {
729 UnitArg::Factor(v) => write!(f, "{}", v),
730 UnitArg::Expr(prefix, factors) => {
731 if *prefix != Decimal::ONE {
732 write!(f, "{} ", prefix)?;
733 }
734 for (index, factor) in factors.iter().enumerate() {
735 if factor.exp == 0 {
736 unreachable!("BUG: unit factor exponent cannot be zero");
737 }
738 if factor.exp > 0 {
739 if index > 0 {
740 write!(f, " * ")?;
741 }
742 write!(f, "{}", factor.quantity_ref)?;
743 if factor.exp != 1 {
744 write!(f, "^{}", factor.exp)?;
745 }
746 } else {
747 let denominator_started =
748 factors[..index].iter().any(|prior| prior.exp < 0);
749 if denominator_started {
750 write!(f, " * ")?;
751 } else {
752 write!(f, "/")?;
753 }
754 write!(f, "{}", factor.quantity_ref)?;
755 let positive_exp = factor
756 .exp
757 .checked_neg()
758 .expect("BUG: negative unit factor exponent");
759 if positive_exp != 1 {
760 write!(f, "^{}", positive_exp)?;
761 }
762 }
763 }
764 Ok(())
765 }
766 }
767 }
768}
769
770#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
788#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
789pub enum CommandArg {
790 Literal(crate::literals::Value),
792 Label(String),
794 UnitExpr(UnitArg),
796}
797
798impl fmt::Display for CommandArg {
799 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
800 match self {
801 CommandArg::Literal(v) => write!(f, "{}", v),
802 CommandArg::Label(s) => write!(f, "{}", s),
803 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
804 }
805 }
806}
807
808#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
810#[serde(rename_all = "snake_case")]
811pub enum TypeConstraintCommand {
812 Help,
813 Default,
814 Unit,
815 Trait,
816 Minimum,
817 Maximum,
818 Decimals,
819 Option,
820 Options,
821 Length,
822}
823
824impl fmt::Display for TypeConstraintCommand {
825 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
826 let s = match self {
827 TypeConstraintCommand::Help => "help",
828 TypeConstraintCommand::Default => "default",
829 TypeConstraintCommand::Unit => "unit",
830 TypeConstraintCommand::Trait => "trait",
831 TypeConstraintCommand::Minimum => "minimum",
832 TypeConstraintCommand::Maximum => "maximum",
833 TypeConstraintCommand::Decimals => "decimals",
834 TypeConstraintCommand::Option => "option",
835 TypeConstraintCommand::Options => "options",
836 TypeConstraintCommand::Length => "length",
837 };
838 write!(f, "{}", s)
839 }
840}
841
842#[must_use]
844pub fn try_parse_type_constraint_command(s: &str) -> Option<TypeConstraintCommand> {
845 match s.trim().to_lowercase().as_str() {
846 "help" => Some(TypeConstraintCommand::Help),
847 "default" => Some(TypeConstraintCommand::Default),
848 "unit" => Some(TypeConstraintCommand::Unit),
849 "trait" => Some(TypeConstraintCommand::Trait),
850 "minimum" => Some(TypeConstraintCommand::Minimum),
851 "maximum" => Some(TypeConstraintCommand::Maximum),
852 "decimals" => Some(TypeConstraintCommand::Decimals),
853 "option" => Some(TypeConstraintCommand::Option),
854 "options" => Some(TypeConstraintCommand::Options),
855 "length" => Some(TypeConstraintCommand::Length),
856 _ => None,
857 }
858}
859
860pub type Constraint = (TypeConstraintCommand, Vec<CommandArg>);
862
863#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
865#[serde(rename_all = "snake_case")]
866pub enum WithRhs {
867 Literal(Value),
868 Reference { target: Reference },
869}
870
871#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
872#[serde(rename_all = "snake_case")]
873pub enum DataValue {
875 Definition {
883 #[serde(default, skip_serializing_if = "Option::is_none")]
884 base: Option<ParentType>,
885 constraints: Option<Vec<Constraint>>,
886 #[serde(default, skip_serializing_if = "Option::is_none")]
887 value: Option<Value>,
888 },
889 Import(SpecRef),
891 With(WithRhs),
897}
898
899impl DataValue {
900 #[must_use]
902 pub fn is_definition_literal_only(&self) -> bool {
903 matches!(
904 self,
905 DataValue::Definition {
906 base: None,
907 constraints: None,
908 value: Some(_),
909 }
910 )
911 }
912
913 #[must_use]
915 pub fn definition_needs_type_resolution(&self) -> bool {
916 match self {
917 DataValue::Definition { base: Some(_), .. }
918 | DataValue::Definition {
919 constraints: Some(_),
920 ..
921 } => true,
922 DataValue::Definition {
923 base: None,
924 constraints: None,
925 value: Some(v),
926 } => !matches!(v, Value::NumberWithUnit(_, _)),
927 DataValue::Import(_) | DataValue::With(_) | DataValue::Definition { .. } => false,
928 }
929 }
930}
931
932fn format_constraint_chain(constraints: &[Constraint]) -> String {
935 constraints
936 .iter()
937 .map(|(cmd, args)| {
938 let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
939 let joined = args_str.join(" ");
940 if joined.is_empty() {
941 format!("{}", cmd)
942 } else {
943 format!("{} {}", cmd, joined)
944 }
945 })
946 .collect::<Vec<_>>()
947 .join(" -> ")
948}
949
950impl fmt::Display for DataValue {
951 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
952 match self {
953 DataValue::Definition {
954 base,
955 constraints,
956 value,
957 } => {
958 if base.is_none() && constraints.is_none() {
959 return match value {
960 Some(v) => write!(f, "{}", v),
961 None => Ok(()),
962 };
963 }
964 let base_str = match base.as_ref() {
965 Some(b) => format!("{b}"),
966 None => match value {
967 Some(v) => {
968 if let Some(ref constraints_vec) = constraints {
969 let constraint_str = format_constraint_chain(constraints_vec);
970 return write!(f, "{v} -> {constraint_str}");
971 }
972 return write!(f, "{v}");
973 }
974 None => String::new(),
975 },
976 };
977 if let Some(ref constraints_vec) = constraints {
978 let constraint_str = format_constraint_chain(constraints_vec);
979 write!(f, "{base_str} -> {constraint_str}")
980 } else {
981 write!(f, "{base_str}")
982 }
983 }
984 DataValue::Import(spec_ref) => {
985 write!(f, "with {}", spec_ref)
986 }
987 DataValue::With(with_rhs) => match with_rhs {
988 WithRhs::Literal(v) => write!(f, "{v}"),
989 WithRhs::Reference { target } => write!(f, "{target}"),
990 },
991 }
992 }
993}
994
995impl LemmaData {
996 #[must_use]
997 pub fn new(reference: Reference, value: DataValue, source_location: Source) -> Self {
998 Self {
999 reference,
1000 value,
1001 source_location,
1002 }
1003 }
1004}
1005
1006impl LemmaSpec {
1007 #[must_use]
1008 pub fn new(name: String) -> Self {
1009 Self {
1010 name: ascii_lowercase_logical_name(name),
1011 effective_from: EffectiveDate::Origin,
1012 source_type: None,
1013 start_line: 1,
1014 commentary: None,
1015 data: Vec::new(),
1016 rules: Vec::new(),
1017 meta_fields: Vec::new(),
1018 }
1019 }
1020
1021 pub fn effective_from(&self) -> Option<&DateTimeValue> {
1023 self.effective_from.as_ref()
1024 }
1025
1026 #[must_use]
1027 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
1028 self.source_type = Some(source_type);
1029 self
1030 }
1031
1032 #[must_use]
1033 pub fn with_start_line(mut self, start_line: usize) -> Self {
1034 self.start_line = start_line;
1035 self
1036 }
1037
1038 #[must_use]
1039 pub fn set_commentary(mut self, commentary: String) -> Self {
1040 self.commentary = Some(commentary);
1041 self
1042 }
1043
1044 #[must_use]
1045 pub fn add_data(mut self, data: LemmaData) -> Self {
1046 self.data.push(data);
1047 self
1048 }
1049
1050 #[must_use]
1051 pub fn add_rule(mut self, rule: LemmaRule) -> Self {
1052 self.rules.push(rule);
1053 self
1054 }
1055
1056 #[must_use]
1057 pub fn add_meta_field(mut self, meta: MetaField) -> Self {
1058 self.meta_fields.push(meta);
1059 self
1060 }
1061}
1062
1063impl fmt::Display for LemmaSpec {
1064 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1065 write!(f, "spec {}", self.name)?;
1066 if let EffectiveDate::DateTimeValue(ref af) = self.effective_from {
1067 write!(f, " {}", af)?;
1068 }
1069 writeln!(f)?;
1070
1071 if let Some(ref commentary) = self.commentary {
1072 writeln!(f, "\"\"\"")?;
1073 writeln!(f, "{}", commentary)?;
1074 writeln!(f, "\"\"\"")?;
1075 }
1076
1077 if !self.data.is_empty() {
1078 writeln!(f)?;
1079 for data in &self.data {
1080 write!(f, "{}", data)?;
1081 }
1082 }
1083
1084 if !self.rules.is_empty() {
1085 writeln!(f)?;
1086 for (index, rule) in self.rules.iter().enumerate() {
1087 if index > 0 {
1088 writeln!(f)?;
1089 }
1090 write!(f, "{}", rule)?;
1091 }
1092 }
1093
1094 if !self.meta_fields.is_empty() {
1095 writeln!(f)?;
1096 for meta in &self.meta_fields {
1097 writeln!(f, "{}", meta)?;
1098 }
1099 }
1100
1101 Ok(())
1102 }
1103}
1104
1105impl fmt::Display for LemmaData {
1106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1107 writeln!(f, "data {}: {}", self.reference, self.value)
1108 }
1109}
1110
1111impl fmt::Display for LemmaRule {
1112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1113 write!(f, "rule {}: {}", self.name, self.expression)?;
1114 for unless_clause in &self.unless_clauses {
1115 write!(
1116 f,
1117 "\n unless {} then {}",
1118 unless_clause.condition, unless_clause.result
1119 )?;
1120 }
1121 writeln!(f)?;
1122 Ok(())
1123 }
1124}
1125
1126pub fn expression_precedence(kind: &ExpressionKind) -> u8 {
1134 match kind {
1135 ExpressionKind::LogicalAnd(..) => 2,
1136 ExpressionKind::LogicalNegation(..) => 3,
1137 ExpressionKind::Comparison(..) | ExpressionKind::ResultIsVeto(..) => 4,
1138 ExpressionKind::RangeContainment(..) => 4,
1139 ExpressionKind::DateRelative(..) | ExpressionKind::DateCalendar(..) => 4,
1140 ExpressionKind::Arithmetic(_, op, _) => match op {
1141 ArithmeticComputation::Add | ArithmeticComputation::Subtract => 5,
1142 ArithmeticComputation::Multiply
1143 | ArithmeticComputation::Divide
1144 | ArithmeticComputation::Modulo => 6,
1145 ArithmeticComputation::Power => 7,
1146 },
1147 ExpressionKind::UnitConversion(..) => 8,
1148 ExpressionKind::RangeLiteral(..) => 9,
1149 ExpressionKind::MathematicalComputation(..) => 10,
1150 ExpressionKind::PastFutureRange(..) => 10,
1151 ExpressionKind::Literal(..)
1152 | ExpressionKind::Reference(..)
1153 | ExpressionKind::Now
1154 | ExpressionKind::Veto(..) => 10,
1155 }
1156}
1157
1158fn write_expression_child(
1159 f: &mut fmt::Formatter<'_>,
1160 child: &Expression,
1161 parent_prec: u8,
1162) -> fmt::Result {
1163 let child_prec = expression_precedence(&child.kind);
1164 if child_prec < parent_prec {
1165 write!(f, "({})", child)
1166 } else {
1167 write!(f, "{}", child)
1168 }
1169}
1170
1171impl fmt::Display for Expression {
1172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1173 match &self.kind {
1174 ExpressionKind::Literal(lit) => write!(f, "{}", AsLemmaSource(lit)),
1175 ExpressionKind::Reference(r) => write!(f, "{}", r),
1176 ExpressionKind::Arithmetic(left, op, right) => {
1177 let my_prec = expression_precedence(&self.kind);
1178 write_expression_child(f, left, my_prec)?;
1179 write!(f, " {} ", op)?;
1180 write_expression_child(f, right, my_prec)
1181 }
1182 ExpressionKind::Comparison(left, op, right) => {
1183 let my_prec = expression_precedence(&self.kind);
1184 write_expression_child(f, left, my_prec)?;
1185 write!(f, " {} ", op)?;
1186 write_expression_child(f, right, my_prec)
1187 }
1188 ExpressionKind::UnitConversion(value, target) => {
1189 let my_prec = expression_precedence(&self.kind);
1190 write_expression_child(f, value, my_prec)?;
1191 write!(f, " as {}", target)
1192 }
1193 ExpressionKind::LogicalNegation(expr, negation) => {
1194 if let (NegationType::Not, ExpressionKind::ResultIsVeto(operand)) =
1195 (negation, &expr.kind)
1196 {
1197 let my_prec = expression_precedence(&self.kind);
1198 write_expression_child(f, operand, my_prec)?;
1199 write!(f, " is not veto")
1200 } else {
1201 let my_prec = expression_precedence(&self.kind);
1202 write!(f, "not ")?;
1203 write_expression_child(f, expr, my_prec)
1204 }
1205 }
1206 ExpressionKind::ResultIsVeto(operand) => {
1207 let my_prec = expression_precedence(&self.kind);
1208 write_expression_child(f, operand, my_prec)?;
1209 write!(f, " is veto")
1210 }
1211 ExpressionKind::LogicalAnd(left, right) => {
1212 let my_prec = expression_precedence(&self.kind);
1213 write_expression_child(f, left, my_prec)?;
1214 write!(f, " and ")?;
1215 write_expression_child(f, right, my_prec)
1216 }
1217 ExpressionKind::MathematicalComputation(op, operand) => {
1218 let my_prec = expression_precedence(&self.kind);
1219 write!(f, "{} ", op)?;
1220 write_expression_child(f, operand, my_prec)
1221 }
1222 ExpressionKind::Veto(veto) => match &veto.message {
1223 Some(msg) => write!(f, "veto {}", quote_lemma_text(msg)),
1224 None => write!(f, "veto"),
1225 },
1226 ExpressionKind::Now => write!(f, "now"),
1227 ExpressionKind::DateRelative(kind, date_expr) => {
1228 write!(f, "{} {}", date_expr, kind)?;
1229 Ok(())
1230 }
1231 ExpressionKind::DateCalendar(kind, unit, date_expr) => {
1232 write!(f, "{} {} {}", date_expr, kind, unit)
1233 }
1234 ExpressionKind::RangeLiteral(left, right) => {
1235 let my_prec = expression_precedence(&self.kind);
1236 write_expression_child(f, left, my_prec)?;
1237 write!(f, "...")?;
1238 write_expression_child(f, right, my_prec)
1239 }
1240 ExpressionKind::PastFutureRange(kind, offset_expr) => {
1241 write!(f, "{} ", kind)?;
1242 let my_prec = expression_precedence(&self.kind);
1243 write_expression_child(f, offset_expr, my_prec)
1244 }
1245 ExpressionKind::RangeContainment(value, range) => {
1246 let my_prec = expression_precedence(&self.kind);
1247 write_expression_child(f, value, my_prec)?;
1248 write!(f, " in ")?;
1249 write_expression_child(f, range, my_prec)
1250 }
1251 }
1252 }
1253}
1254
1255impl fmt::Display for ConversionTarget {
1256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1257 match self {
1258 ConversionTarget::Type(kind) => write!(f, "{:?}", kind),
1259 ConversionTarget::Unit { unit_name } => write!(f, "{unit_name}"),
1260 }
1261 }
1262}
1263
1264impl fmt::Display for ArithmeticComputation {
1265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1266 match self {
1267 ArithmeticComputation::Add => write!(f, "+"),
1268 ArithmeticComputation::Subtract => write!(f, "-"),
1269 ArithmeticComputation::Multiply => write!(f, "*"),
1270 ArithmeticComputation::Divide => write!(f, "/"),
1271 ArithmeticComputation::Modulo => write!(f, "%"),
1272 ArithmeticComputation::Power => write!(f, "^"),
1273 }
1274 }
1275}
1276
1277impl fmt::Display for ComparisonComputation {
1278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1279 match self {
1280 ComparisonComputation::GreaterThan => write!(f, ">"),
1281 ComparisonComputation::LessThan => write!(f, "<"),
1282 ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1283 ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1284 ComparisonComputation::Is => write!(f, "is"),
1285 ComparisonComputation::IsNot => write!(f, "is not"),
1286 }
1287 }
1288}
1289
1290impl fmt::Display for MathematicalComputation {
1291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1292 match self {
1293 MathematicalComputation::Sqrt => write!(f, "sqrt"),
1294 MathematicalComputation::Sin => write!(f, "sin"),
1295 MathematicalComputation::Cos => write!(f, "cos"),
1296 MathematicalComputation::Tan => write!(f, "tan"),
1297 MathematicalComputation::Asin => write!(f, "asin"),
1298 MathematicalComputation::Acos => write!(f, "acos"),
1299 MathematicalComputation::Atan => write!(f, "atan"),
1300 MathematicalComputation::Log => write!(f, "log"),
1301 MathematicalComputation::Exp => write!(f, "exp"),
1302 MathematicalComputation::Abs => write!(f, "abs"),
1303 MathematicalComputation::Floor => write!(f, "floor"),
1304 MathematicalComputation::Ceil => write!(f, "ceil"),
1305 MathematicalComputation::Round => write!(f, "round"),
1306 }
1307 }
1308}
1309
1310impl fmt::Display for LogicalComputation {
1311 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1312 match self {
1313 LogicalComputation::And => write!(f, "and"),
1314 LogicalComputation::Or => write!(f, "or"),
1315 LogicalComputation::Not => write!(f, "not"),
1316 }
1317 }
1318}
1319
1320#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1326#[serde(rename_all = "snake_case")]
1327pub enum PrimitiveKind {
1328 Boolean,
1329 Quantity,
1330 QuantityRange,
1331 Number,
1332 NumberRange,
1333 Percent,
1334 Ratio,
1335 RatioRange,
1336 Text,
1337 Date,
1338 DateRange,
1339 Time,
1340 TimeRange,
1341}
1342
1343impl std::fmt::Display for PrimitiveKind {
1344 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1345 let s = match self {
1346 PrimitiveKind::Boolean => "boolean",
1347 PrimitiveKind::Quantity => "quantity",
1348 PrimitiveKind::QuantityRange => "quantity range",
1349 PrimitiveKind::Number => "number",
1350 PrimitiveKind::NumberRange => "number range",
1351 PrimitiveKind::Percent => "percent",
1352 PrimitiveKind::Ratio => "ratio",
1353 PrimitiveKind::RatioRange => "ratio range",
1354 PrimitiveKind::Text => "text",
1355 PrimitiveKind::Date => "date",
1356 PrimitiveKind::DateRange => "date range",
1357 PrimitiveKind::Time => "time",
1358 PrimitiveKind::TimeRange => "time range",
1359 };
1360 write!(f, "{}", s)
1361 }
1362}
1363
1364#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1369#[serde(tag = "kind", rename_all = "snake_case")]
1370pub enum ParentType {
1371 Primitive {
1372 primitive: PrimitiveKind,
1373 },
1374 Custom {
1375 name: String,
1376 },
1377 Qualified {
1380 spec_alias: String,
1381 inner: Box<ParentType>,
1382 },
1383 Ranged {
1385 inner: Box<ParentType>,
1386 },
1387}
1388
1389impl std::fmt::Display for ParentType {
1390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1391 match self {
1392 ParentType::Primitive { primitive } => write!(f, "{}", primitive),
1393 ParentType::Custom { name } => write!(f, "{}", name),
1394 ParentType::Qualified { spec_alias, inner } => {
1395 write!(f, "{spec_alias}.{inner}")
1396 }
1397 ParentType::Ranged { inner } => write!(f, "{inner} range"),
1398 }
1399 }
1400}
1401
1402pub struct AsLemmaSource<'a, T: ?Sized>(pub &'a T);
1408
1409pub fn quote_lemma_text(s: &str) -> String {
1412 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1413 format!("\"{}\"", escaped)
1414}
1415
1416fn format_decimal_source(n: &Decimal) -> String {
1421 let raw = if n.fract().is_zero() {
1422 n.trunc().to_string()
1423 } else {
1424 n.to_string()
1425 };
1426 group_digits(&raw)
1427}
1428
1429fn group_digits(s: &str) -> String {
1433 let (sign, rest) = if s.starts_with('-') || s.starts_with('+') {
1434 (&s[..1], &s[1..])
1435 } else {
1436 ("", s)
1437 };
1438
1439 let (int_part, frac_part) = match rest.find('.') {
1440 Some(pos) => (&rest[..pos], &rest[pos..]),
1441 None => (rest, ""),
1442 };
1443
1444 if int_part.len() < 4 {
1445 return s.to_string();
1446 }
1447
1448 let mut grouped = String::with_capacity(int_part.len() + int_part.len() / 3);
1449 for (i, ch) in int_part.chars().enumerate() {
1450 let digits_remaining = int_part.len() - i;
1451 if i > 0 && digits_remaining % 3 == 0 {
1452 grouped.push('_');
1453 }
1454 grouped.push(ch);
1455 }
1456
1457 format!("{}{}{}", sign, grouped, frac_part)
1458}
1459
1460impl<'a> fmt::Display for AsLemmaSource<'a, CommandArg> {
1461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1462 use crate::literals::Value;
1463 match self.0 {
1464 CommandArg::Literal(Value::Text(s)) => write!(f, "{}", quote_lemma_text(s)),
1465 CommandArg::Literal(Value::Number(d)) => {
1466 write!(f, "{}", group_digits(&d.to_string()))
1467 }
1468 CommandArg::Literal(Value::Boolean(bv)) => write!(f, "{}", bv),
1469 CommandArg::Literal(Value::NumberWithUnit(d, unit)) => {
1470 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1471 }
1472 CommandArg::Literal(value @ Value::Range(_, _)) => {
1473 write!(f, "{}", AsLemmaSource(value))
1474 }
1475 CommandArg::Literal(Value::Date(dt)) => write!(f, "{}", dt),
1476 CommandArg::Literal(Value::Time(t)) => write!(f, "{}", t),
1477 CommandArg::Label(s) => write!(f, "{}", s),
1478 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
1479 }
1480 }
1481}
1482
1483pub(crate) fn format_constraint_as_source(
1485 cmd: &TypeConstraintCommand,
1486 args: &[CommandArg],
1487) -> String {
1488 if args.is_empty() {
1489 cmd.to_string()
1490 } else {
1491 let args_str: Vec<String> = args
1492 .iter()
1493 .map(|a| format!("{}", AsLemmaSource(a)))
1494 .collect();
1495 format!("{} {}", cmd, args_str.join(" "))
1496 }
1497}
1498
1499fn format_constraints_as_source(constraints: &[Constraint], separator: &str) -> String {
1502 constraints
1503 .iter()
1504 .map(|(cmd, args)| format_constraint_as_source(cmd, args))
1505 .collect::<Vec<_>>()
1506 .join(separator)
1507}
1508
1509impl<'a> fmt::Display for AsLemmaSource<'a, Value> {
1512 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1513 match self.0 {
1514 Value::Number(n) => write!(f, "{}", format_decimal_source(n)),
1515 Value::Text(s) => write!(f, "{}", quote_lemma_text(s)),
1516 Value::Date(dt) => match dt.granularity {
1517 crate::literals::DateGranularity::Year => write!(f, "{:04}", dt.year),
1518 crate::literals::DateGranularity::YearMonth => {
1519 write!(f, "{:04}-{:02}", dt.year, dt.month)
1520 }
1521 crate::literals::DateGranularity::IsoWeek { iso_year, week } => {
1522 write!(f, "{:04}-W{:02}", iso_year, week)
1523 }
1524 crate::literals::DateGranularity::Full => {
1525 write!(f, "{:04}-{:02}-{:02}", dt.year, dt.month, dt.day)
1526 }
1527 crate::literals::DateGranularity::DateTime => {
1528 write!(
1529 f,
1530 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1531 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
1532 )?;
1533 if let Some(tz) = &dt.timezone {
1534 write!(f, "{}", tz)?;
1535 }
1536 Ok(())
1537 }
1538 },
1539 Value::Time(t) => {
1540 write!(f, "{:02}:{:02}:{:02}", t.hour, t.minute, t.second)?;
1541 if let Some(tz) = &t.timezone {
1542 write!(f, "{}", tz)?;
1543 }
1544 Ok(())
1545 }
1546 Value::Boolean(b) => write!(f, "{}", b),
1547 Value::NumberWithUnit(n, u) => match u.as_str() {
1548 "percent" => write!(f, "{}%", format_decimal_source(n)),
1549 "permille" => write!(f, "{}%%", format_decimal_source(n)),
1550 unit => write!(f, "{} {}", format_decimal_source(n), unit),
1551 },
1552 Value::Range(left, right) => {
1553 write!(
1554 f,
1555 "{}...{}",
1556 AsLemmaSource(left.as_ref()),
1557 AsLemmaSource(right.as_ref())
1558 )
1559 }
1560 }
1561 }
1562}
1563
1564impl<'a> fmt::Display for AsLemmaSource<'a, MetaValue> {
1567 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1568 match self.0 {
1569 MetaValue::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1570 MetaValue::Unquoted(s) => write!(f, "{}", s),
1571 }
1572 }
1573}
1574
1575impl<'a> fmt::Display for AsLemmaSource<'a, DataValue> {
1576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1577 match self.0 {
1578 DataValue::Definition {
1579 base,
1580 constraints,
1581 value,
1582 } => {
1583 if base.is_none() && constraints.is_none() {
1584 if let Some(v) = value {
1585 return write!(f, "{}", AsLemmaSource(v));
1586 }
1587 }
1588 let base_str = match base.as_ref() {
1589 Some(b) => format!("{}", b),
1590 None => match value {
1591 Some(v) => {
1592 if let Some(ref constraints_vec) = constraints {
1593 let constraint_str =
1594 format_constraints_as_source(constraints_vec, " -> ");
1595 return write!(f, "{} -> {}", AsLemmaSource(v), constraint_str);
1596 }
1597 return write!(f, "{}", AsLemmaSource(v));
1598 }
1599 None => String::new(),
1600 },
1601 };
1602 if let Some(ref constraints_vec) = constraints {
1603 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
1604 write!(f, "{} -> {}", base_str, constraint_str)
1605 } else {
1606 write!(f, "{}", base_str)
1607 }
1608 }
1609 DataValue::Import(spec_ref) => {
1610 write!(f, "with {}", spec_ref)
1611 }
1612 DataValue::With(with_rhs) => match with_rhs {
1613 WithRhs::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1614 WithRhs::Reference { target } => write!(f, "{target}"),
1615 },
1616 }
1617 }
1618}
1619
1620pub(crate) fn canonicalize_value(value: &mut Value) {
1621 if let Value::NumberWithUnit(_, unit) = value {
1622 *unit = ascii_lowercase_logical_name(std::mem::take(unit));
1623 }
1624}
1625
1626pub(crate) fn canonicalize_reference(reference: &mut Reference) {
1627 for segment in &mut reference.segments {
1628 *segment = ascii_lowercase_logical_name(std::mem::take(segment));
1629 }
1630 reference.name = ascii_lowercase_logical_name(std::mem::take(&mut reference.name));
1631}
1632
1633pub(crate) fn canonicalize_spec_ref(spec_ref: &mut SpecRef) {
1634 spec_ref.name = ascii_lowercase_logical_name(std::mem::take(&mut spec_ref.name));
1635 if let Some(qualifier) = spec_ref.repository.as_mut() {
1636 qualifier.name = ascii_lowercase_logical_name(std::mem::take(&mut qualifier.name));
1637 }
1638}
1639
1640pub(crate) fn canonicalize_parent_type(parent: &mut ParentType) {
1641 match parent {
1642 ParentType::Custom { name } => {
1643 *name = ascii_lowercase_logical_name(std::mem::take(name));
1644 }
1645 ParentType::Qualified { spec_alias, inner } => {
1646 *spec_alias = ascii_lowercase_logical_name(std::mem::take(spec_alias));
1647 canonicalize_parent_type(inner);
1648 }
1649 ParentType::Ranged { inner } => {
1650 canonicalize_parent_type(inner);
1651 }
1652 ParentType::Primitive { .. } => {}
1653 }
1654}
1655
1656pub(crate) fn canonicalize_unit_factor(factor: &mut UnitFactor) {
1657 factor.quantity_ref = ascii_lowercase_logical_name(std::mem::take(&mut factor.quantity_ref));
1658}
1659
1660pub(crate) fn canonicalize_unit_arg(unit_arg: &mut UnitArg) {
1661 if let UnitArg::Expr(_, factors) = unit_arg {
1662 for factor in factors {
1663 canonicalize_unit_factor(factor);
1664 }
1665 }
1666}
1667
1668pub(crate) fn canonicalize_command_arg(command_arg: &mut CommandArg) {
1669 match command_arg {
1670 CommandArg::Literal(value) => canonicalize_value(value),
1671 CommandArg::Label(label) => {
1672 *label = ascii_lowercase_logical_name(std::mem::take(label));
1673 }
1674 CommandArg::UnitExpr(unit_arg) => canonicalize_unit_arg(unit_arg),
1675 }
1676}
1677
1678pub(crate) fn canonicalize_constraints(constraints: &mut [Constraint]) {
1679 for (_, args) in constraints {
1680 for arg in args {
1681 canonicalize_command_arg(arg);
1682 }
1683 }
1684}
1685
1686pub(crate) fn canonicalize_expression(expression: &mut Expression) {
1687 match &mut expression.kind {
1688 ExpressionKind::Literal(value) => canonicalize_value(value),
1689 ExpressionKind::Reference(reference) => canonicalize_reference(reference),
1690 ExpressionKind::Now => {}
1691 ExpressionKind::DateRelative(_, expression) => {
1692 canonicalize_expression(Arc::make_mut(expression));
1693 }
1694 ExpressionKind::DateCalendar(_, _, expression) => {
1695 canonicalize_expression(Arc::make_mut(expression));
1696 }
1697 ExpressionKind::RangeLiteral(left, right) => {
1698 canonicalize_expression(Arc::make_mut(left));
1699 canonicalize_expression(Arc::make_mut(right));
1700 }
1701 ExpressionKind::PastFutureRange(_, expression) => {
1702 canonicalize_expression(Arc::make_mut(expression));
1703 }
1704 ExpressionKind::RangeContainment(value, range) => {
1705 canonicalize_expression(Arc::make_mut(value));
1706 canonicalize_expression(Arc::make_mut(range));
1707 }
1708 ExpressionKind::LogicalAnd(left, right) => {
1709 canonicalize_expression(Arc::make_mut(left));
1710 canonicalize_expression(Arc::make_mut(right));
1711 }
1712 ExpressionKind::Arithmetic(left, _, right) => {
1713 canonicalize_expression(Arc::make_mut(left));
1714 canonicalize_expression(Arc::make_mut(right));
1715 }
1716 ExpressionKind::Comparison(left, _, right) => {
1717 canonicalize_expression(Arc::make_mut(left));
1718 canonicalize_expression(Arc::make_mut(right));
1719 }
1720 ExpressionKind::UnitConversion(expression, _) => {
1721 canonicalize_expression(Arc::make_mut(expression));
1722 }
1723 ExpressionKind::LogicalNegation(expression, _) => {
1724 canonicalize_expression(Arc::make_mut(expression));
1725 }
1726 ExpressionKind::MathematicalComputation(_, expression) => {
1727 canonicalize_expression(Arc::make_mut(expression));
1728 }
1729 ExpressionKind::Veto(_) => {}
1730 ExpressionKind::ResultIsVeto(expression) => {
1731 canonicalize_expression(Arc::make_mut(expression));
1732 }
1733 }
1734}
1735
1736pub(crate) fn canonicalize_unless_clause(unless_clause: &mut UnlessClause) {
1737 canonicalize_expression(&mut unless_clause.condition);
1738 canonicalize_expression(&mut unless_clause.result);
1739}
1740
1741pub(crate) fn canonicalize_data_value(data_value: &mut DataValue) {
1742 match data_value {
1743 DataValue::Definition {
1744 base,
1745 constraints,
1746 value,
1747 } => {
1748 if let Some(base) = base {
1749 canonicalize_parent_type(base);
1750 }
1751 if let Some(constraints) = constraints {
1752 canonicalize_constraints(constraints);
1753 }
1754 if let Some(value) = value {
1755 canonicalize_value(value);
1756 }
1757 }
1758 DataValue::Import(spec_ref) => canonicalize_spec_ref(spec_ref),
1759 DataValue::With(with_rhs) => match with_rhs {
1760 WithRhs::Literal(value) => canonicalize_value(value),
1761 WithRhs::Reference { target } => canonicalize_reference(target),
1762 },
1763 }
1764}
1765
1766pub(crate) fn canonicalize_lemma_data(data: &mut LemmaData) {
1767 canonicalize_reference(&mut data.reference);
1768 canonicalize_data_value(&mut data.value);
1769}
1770
1771pub(crate) fn canonicalize_lemma_rule(rule: &mut LemmaRule) {
1772 rule.name = ascii_lowercase_logical_name(std::mem::take(&mut rule.name));
1773 canonicalize_expression(&mut rule.expression);
1774 for unless_clause in &mut rule.unless_clauses {
1775 canonicalize_unless_clause(unless_clause);
1776 }
1777}
1778
1779pub(crate) fn canonicalize_lemma_spec(spec: &mut LemmaSpec) {
1780 spec.name = ascii_lowercase_logical_name(std::mem::take(&mut spec.name));
1781 for meta in &mut spec.meta_fields {
1782 meta.key = ascii_lowercase_logical_name(std::mem::take(&mut meta.key));
1783 }
1784 for data in &mut spec.data {
1785 canonicalize_lemma_data(data);
1786 }
1787 for rule in &mut spec.rules {
1788 canonicalize_lemma_rule(rule);
1789 }
1790}
1791
1792pub(crate) fn canonicalize_repository(repository: &mut LemmaRepository) {
1793 if let Some(name) = repository.name.take() {
1794 repository.name = Some(ascii_lowercase_logical_name(name));
1795 }
1796}
1797
1798#[cfg(test)]
1799mod tests {
1800 use super::*;
1801
1802 #[test]
1803 fn test_conversion_target_display() {
1804 assert_eq!(
1805 format!("{}", ConversionTarget::Type(PrimitiveKind::Number)),
1806 "Number"
1807 );
1808 }
1809
1810 #[test]
1811 fn test_value_number_with_unit_ratio_display() {
1812 use rust_decimal::Decimal;
1813 use std::str::FromStr;
1814 let percent =
1815 Value::NumberWithUnit(Decimal::from_str("10").unwrap(), "percent".to_string());
1816 assert_eq!(format!("{}", percent), "10%");
1817 let permille =
1818 Value::NumberWithUnit(Decimal::from_str("5").unwrap(), "permille".to_string());
1819 assert_eq!(format!("{}", permille), "5%%");
1820 }
1821
1822 #[test]
1823 fn test_datetime_value_display() {
1824 let dt = DateTimeValue {
1825 year: 2024,
1826 month: 12,
1827 day: 25,
1828 hour: 14,
1829 minute: 30,
1830 second: 45,
1831 microsecond: 0,
1832 timezone: Some(TimezoneValue {
1833 offset_hours: 1,
1834 offset_minutes: 0,
1835 }),
1836
1837 granularity: DateGranularity::DateTime,
1838 };
1839 assert_eq!(format!("{}", dt), "2024-12-25T14:30:45+01:00");
1840 }
1841
1842 #[test]
1843 fn test_datetime_value_display_date_only() {
1844 let dt = DateTimeValue {
1845 year: 2026,
1846 month: 3,
1847 day: 4,
1848 hour: 0,
1849 minute: 0,
1850 second: 0,
1851 microsecond: 0,
1852 timezone: None,
1853
1854 granularity: DateGranularity::Full,
1855 };
1856 assert_eq!(format!("{}", dt), "2026-03-04");
1857 }
1858
1859 #[test]
1860 fn test_datetime_value_display_microseconds() {
1861 let dt = DateTimeValue {
1862 year: 2026,
1863 month: 2,
1864 day: 23,
1865 hour: 14,
1866 minute: 30,
1867 second: 45,
1868 microsecond: 123456,
1869 timezone: Some(TimezoneValue {
1870 offset_hours: 0,
1871 offset_minutes: 0,
1872 }),
1873
1874 granularity: DateGranularity::DateTime,
1875 };
1876 assert_eq!(format!("{}", dt), "2026-02-23T14:30:45.123456Z");
1877 }
1878
1879 #[test]
1880 fn test_datetime_microsecond_in_ordering() {
1881 let a = DateTimeValue {
1882 year: 2026,
1883 month: 1,
1884 day: 1,
1885 hour: 0,
1886 minute: 0,
1887 second: 0,
1888 microsecond: 100,
1889 timezone: None,
1890
1891 granularity: DateGranularity::DateTime,
1892 };
1893 let b = DateTimeValue {
1894 year: 2026,
1895 month: 1,
1896 day: 1,
1897 hour: 0,
1898 minute: 0,
1899 second: 0,
1900 microsecond: 200,
1901 timezone: None,
1902
1903 granularity: DateGranularity::DateTime,
1904 };
1905 assert!(a < b);
1906 }
1907
1908 #[test]
1909 fn test_datetime_parse_iso_week() {
1910 let dt: DateTimeValue = "2026-W01".parse().unwrap();
1911 assert_eq!(dt.year, 2025);
1912 assert_eq!(dt.month, 12);
1913 assert_eq!(dt.day, 29);
1914 assert_eq!(dt.microsecond, 0);
1915 assert_eq!(dt.to_string(), "2026-W01");
1916 assert!(matches!(
1917 dt.granularity,
1918 DateGranularity::IsoWeek {
1919 iso_year: 2026,
1920 week: 1
1921 }
1922 ));
1923 }
1924
1925 #[test]
1926 fn test_negation_types() {
1927 let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
1928 let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
1929 assert_eq!(decoded, NegationType::Not);
1930 }
1931
1932 #[test]
1933 fn parent_type_primitive_serde_internally_tagged() {
1934 let p = ParentType::Primitive {
1935 primitive: PrimitiveKind::Number,
1936 };
1937 let json = serde_json::to_string(&p).expect("ParentType::Primitive must serialize");
1938 assert!(json.contains("\"kind\"") && json.contains("\"primitive\""));
1939 let back: ParentType = serde_json::from_str(&json).expect("deserialize");
1940 assert_eq!(back, p);
1941 }
1942
1943 fn text_arg(s: &str) -> CommandArg {
1948 CommandArg::Literal(crate::literals::Value::Text(s.to_string()))
1949 }
1950
1951 fn number_arg(s: &str) -> CommandArg {
1952 let d: rust_decimal::Decimal = s.parse().expect("decimal");
1953 CommandArg::Literal(crate::literals::Value::Number(d))
1954 }
1955
1956 fn boolean_arg(b: BooleanValue) -> CommandArg {
1957 CommandArg::Literal(crate::literals::Value::Boolean(b))
1958 }
1959
1960 fn quantity_arg(value: &str, unit: &str) -> CommandArg {
1961 let d: rust_decimal::Decimal = value.parse().expect("decimal");
1962 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
1963 }
1964
1965 fn duration_arg(value: &str, unit: &str) -> CommandArg {
1966 let d: rust_decimal::Decimal = value.parse().expect("decimal");
1967 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
1968 }
1969
1970 #[test]
1971 fn as_lemma_source_text_default_is_quoted() {
1972 let fv = DataValue::Definition {
1973 base: Some(ParentType::Primitive {
1974 primitive: PrimitiveKind::Text,
1975 }),
1976 constraints: Some(vec![(
1977 TypeConstraintCommand::Default,
1978 vec![text_arg("single")],
1979 )]),
1980 value: None,
1981 };
1982 assert_eq!(
1983 format!("{}", AsLemmaSource(&fv)),
1984 "text -> default \"single\""
1985 );
1986 }
1987
1988 #[test]
1989 fn as_lemma_source_number_default_not_quoted() {
1990 let fv = DataValue::Definition {
1991 base: Some(ParentType::Primitive {
1992 primitive: PrimitiveKind::Number,
1993 }),
1994 constraints: Some(vec![(
1995 TypeConstraintCommand::Default,
1996 vec![number_arg("10")],
1997 )]),
1998 value: None,
1999 };
2000 assert_eq!(format!("{}", AsLemmaSource(&fv)), "number -> default 10");
2001 }
2002
2003 #[test]
2004 fn as_lemma_source_help_always_quoted() {
2005 let fv = DataValue::Definition {
2006 base: Some(ParentType::Primitive {
2007 primitive: PrimitiveKind::Number,
2008 }),
2009 constraints: Some(vec![(
2010 TypeConstraintCommand::Help,
2011 vec![text_arg("Enter a quantity")],
2012 )]),
2013 value: None,
2014 };
2015 assert_eq!(
2016 format!("{}", AsLemmaSource(&fv)),
2017 "number -> help \"Enter a quantity\""
2018 );
2019 }
2020
2021 #[test]
2022 fn as_lemma_source_text_option_quoted() {
2023 let fv = DataValue::Definition {
2024 base: Some(ParentType::Primitive {
2025 primitive: PrimitiveKind::Text,
2026 }),
2027 constraints: Some(vec![
2028 (TypeConstraintCommand::Option, vec![text_arg("active")]),
2029 (TypeConstraintCommand::Option, vec![text_arg("inactive")]),
2030 ]),
2031 value: None,
2032 };
2033 assert_eq!(
2034 format!("{}", AsLemmaSource(&fv)),
2035 "text -> option \"active\" -> option \"inactive\""
2036 );
2037 }
2038
2039 #[test]
2040 fn as_lemma_source_quantity_unit_not_quoted() {
2041 let fv = DataValue::Definition {
2042 base: Some(ParentType::Primitive {
2043 primitive: PrimitiveKind::Quantity,
2044 }),
2045 constraints: Some(vec![
2046 (
2047 TypeConstraintCommand::Unit,
2048 vec![CommandArg::Label("eur".to_string()), number_arg("1.00")],
2049 ),
2050 (
2051 TypeConstraintCommand::Unit,
2052 vec![CommandArg::Label("usd".to_string()), number_arg("0.91")],
2053 ),
2054 ]),
2055 value: None,
2056 };
2057 assert_eq!(
2058 format!("{}", AsLemmaSource(&fv)),
2059 "quantity -> unit eur 1.00 -> unit usd 0.91"
2060 );
2061 }
2062
2063 #[test]
2064 fn as_lemma_source_quantity_minimum_with_unit() {
2065 let fv = DataValue::Definition {
2066 base: Some(ParentType::Primitive {
2067 primitive: PrimitiveKind::Quantity,
2068 }),
2069 constraints: Some(vec![(
2070 TypeConstraintCommand::Minimum,
2071 vec![quantity_arg("0", "eur")],
2072 )]),
2073 value: None,
2074 };
2075 assert_eq!(
2076 format!("{}", AsLemmaSource(&fv)),
2077 "quantity -> minimum 0 eur"
2078 );
2079 }
2080
2081 #[test]
2082 fn as_lemma_source_boolean_default() {
2083 let fv = DataValue::Definition {
2084 base: Some(ParentType::Primitive {
2085 primitive: PrimitiveKind::Boolean,
2086 }),
2087 constraints: Some(vec![(
2088 TypeConstraintCommand::Default,
2089 vec![boolean_arg(BooleanValue::True)],
2090 )]),
2091 value: None,
2092 };
2093 assert_eq!(format!("{}", AsLemmaSource(&fv)), "boolean -> default true");
2094 }
2095
2096 #[test]
2097 fn as_lemma_source_duration_default() {
2098 let fv = DataValue::Definition {
2099 base: Some(ParentType::Custom {
2100 name: "duration".to_string(),
2101 }),
2102 constraints: Some(vec![(
2103 TypeConstraintCommand::Default,
2104 vec![duration_arg("40", "hours")],
2105 )]),
2106 value: None,
2107 };
2108 assert_eq!(
2109 format!("{}", AsLemmaSource(&fv)),
2110 "duration -> default 40 hours"
2111 );
2112 }
2113
2114 #[test]
2115 fn as_lemma_source_named_type_default_quoted() {
2116 let fv = DataValue::Definition {
2119 base: Some(ParentType::Custom {
2120 name: "filing_status_type".to_string(),
2121 }),
2122 constraints: Some(vec![(
2123 TypeConstraintCommand::Default,
2124 vec![text_arg("single")],
2125 )]),
2126 value: None,
2127 };
2128 assert_eq!(
2129 format!("{}", AsLemmaSource(&fv)),
2130 "filing_status_type -> default \"single\""
2131 );
2132 }
2133
2134 #[test]
2135 fn as_lemma_source_help_escapes_quotes() {
2136 let fv = DataValue::Definition {
2137 base: Some(ParentType::Primitive {
2138 primitive: PrimitiveKind::Text,
2139 }),
2140 constraints: Some(vec![(
2141 TypeConstraintCommand::Help,
2142 vec![text_arg("say \"hello\"")],
2143 )]),
2144 value: None,
2145 };
2146 assert_eq!(
2147 format!("{}", AsLemmaSource(&fv)),
2148 "text -> help \"say \\\"hello\\\"\""
2149 );
2150 }
2151
2152 fn unit_arg_expr(prefix: Decimal, factors: &[(&str, i32)]) -> UnitArg {
2153 UnitArg::Expr(
2154 prefix,
2155 factors
2156 .iter()
2157 .map(|(quantity_ref, exp)| UnitFactor {
2158 quantity_ref: (*quantity_ref).to_string(),
2159 exp: *exp,
2160 })
2161 .collect(),
2162 )
2163 }
2164
2165 #[test]
2166 fn unit_arg_display_metre_per_second() {
2167 let arg = unit_arg_expr(Decimal::ONE, &[("metre", 1), ("second", -1)]);
2168 assert_eq!(format!("{arg}"), "metre/second");
2169 assert!(
2170 !format!("{arg}").contains("second^-1"),
2171 "must not print denominator as negative exponent"
2172 );
2173 }
2174
2175 #[test]
2176 fn unit_arg_display_meter_per_second_squared() {
2177 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -2)]);
2178 assert_eq!(format!("{arg}"), "meter/second^2");
2179 }
2180
2181 #[test]
2182 fn unit_arg_display_kg_times_mps2() {
2183 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("mps2", 1)]);
2184 assert_eq!(format!("{arg}"), "kg * mps2");
2185 }
2186
2187 #[test]
2188 fn unit_arg_display_numeric_prefix_metre_per_second() {
2189 use std::str::FromStr;
2190 let prefix = Decimal::from_str("3.6").expect("decimal");
2191 let arg = unit_arg_expr(prefix, &[("metre", 1), ("second", -1)]);
2192 assert_eq!(format!("{arg}"), "3.6 metre/second");
2193 }
2194
2195 #[test]
2196 fn unit_arg_display_metre_per_second_times_kg() {
2197 let arg = unit_arg_expr(Decimal::ONE, &[("metre", 1), ("second", -1), ("kg", 1)]);
2198 assert_eq!(format!("{arg}"), "metre/second * kg");
2199 }
2200
2201 #[test]
2202 fn unit_arg_display_kg_meter_per_second_squared() {
2203 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("meter", 1), ("second", -2)]);
2204 assert_eq!(format!("{arg}"), "kg * meter/second^2");
2205 }
2206}