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
196impl PartialEq for LemmaRepository {
197 fn eq(&self, other: &Self) -> bool {
198 self.name == other.name
199 }
200}
201
202impl Eq for LemmaRepository {}
203
204impl PartialOrd for LemmaRepository {
205 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
206 Some(self.cmp(other))
207 }
208}
209
210impl Ord for LemmaRepository {
211 fn cmp(&self, other: &Self) -> Ordering {
212 self.name.cmp(&other.name)
213 }
214}
215
216impl Hash for LemmaRepository {
217 fn hash<H: Hasher>(&self, state: &mut H) {
218 self.name.hash(state);
219 }
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
226pub struct RepositoryQualifier {
227 pub name: String,
228}
229
230impl RepositoryQualifier {
231 #[must_use]
232 pub fn new(name: impl Into<String>) -> Self {
233 Self {
234 name: ascii_lowercase_logical_name(name.into()),
235 }
236 }
237
238 #[must_use]
240 pub fn is_registry(&self) -> bool {
241 self.name.starts_with('@')
242 }
243}
244
245impl fmt::Display for RepositoryQualifier {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "{}", self.name)
248 }
249}
250
251#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
264pub struct LemmaSpec {
265 pub name: String,
266 pub effective_from: EffectiveDate,
267 pub source_type: Option<crate::parsing::source::SourceType>,
268 pub start_line: usize,
269 pub commentary: Option<String>,
270 pub data: Vec<LemmaData>,
271 pub rules: Vec<LemmaRule>,
272 pub meta_fields: Vec<MetaField>,
273}
274
275#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
276pub struct MetaField {
277 pub key: String,
278 pub value: MetaValue,
279 pub source_location: Source,
280}
281
282#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
283#[serde(rename_all = "snake_case")]
284pub enum MetaValue {
285 Literal(Value),
286 Unquoted(String),
287}
288
289impl fmt::Display for MetaValue {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 match self {
292 MetaValue::Literal(v) => write!(f, "{}", v),
293 MetaValue::Unquoted(s) => write!(f, "{}", s),
294 }
295 }
296}
297
298impl fmt::Display for MetaField {
299 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
300 write!(f, "meta {}: {}", self.key, self.value)
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
305pub struct LemmaData {
306 pub reference: Reference,
307 pub value: DataValue,
308 pub source_location: Source,
309}
310
311#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
317pub struct UnlessClause {
318 pub condition: Expression,
319 pub result: Expression,
320 pub source_location: Source,
321}
322
323#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
325pub struct LemmaRule {
326 pub name: String,
327 pub expression: Expression,
328 pub unless_clauses: Vec<UnlessClause>,
329 pub source_location: Source,
330}
331
332#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
338pub struct Expression {
339 pub kind: ExpressionKind,
340 pub source_location: Option<Source>,
341}
342
343impl Expression {
344 #[must_use]
346 pub fn new(kind: ExpressionKind, source_location: Source) -> Self {
347 Self {
348 kind,
349 source_location: Some(source_location),
350 }
351 }
352}
353
354impl PartialEq for Expression {
356 fn eq(&self, other: &Self) -> bool {
357 self.kind == other.kind
358 }
359}
360
361impl Eq for Expression {}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
365#[serde(rename_all = "snake_case")]
366pub enum DateRelativeKind {
367 InPast,
368 InFuture,
369}
370
371#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
373#[serde(rename_all = "snake_case")]
374pub enum DateCalendarKind {
375 Current,
376 Past,
377 Future,
378 NotIn,
379}
380
381#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
383#[serde(rename_all = "snake_case")]
384pub enum CalendarPeriodUnit {
385 Year,
386 Month,
387 Week,
388}
389
390impl CalendarPeriodUnit {
391 #[must_use]
392 pub fn from_keyword(s: &str) -> Option<Self> {
393 match s.trim().to_lowercase().as_str() {
394 "year" => Some(Self::Year),
395 "month" => Some(Self::Month),
396 "week" => Some(Self::Week),
397 _ => None,
398 }
399 }
400}
401
402impl fmt::Display for DateRelativeKind {
403 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404 match self {
405 DateRelativeKind::InPast => write!(f, "in past"),
406 DateRelativeKind::InFuture => write!(f, "in future"),
407 }
408 }
409}
410
411impl fmt::Display for DateCalendarKind {
412 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413 match self {
414 DateCalendarKind::Current => write!(f, "in calendar"),
415 DateCalendarKind::Past => write!(f, "in past calendar"),
416 DateCalendarKind::Future => write!(f, "in future calendar"),
417 DateCalendarKind::NotIn => write!(f, "not in calendar"),
418 }
419 }
420}
421
422impl fmt::Display for CalendarPeriodUnit {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 match self {
425 CalendarPeriodUnit::Year => write!(f, "year"),
426 CalendarPeriodUnit::Month => write!(f, "month"),
427 CalendarPeriodUnit::Week => write!(f, "week"),
428 }
429 }
430}
431
432#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
434#[serde(rename_all = "snake_case")]
435pub enum ExpressionKind {
436 Literal(Value),
438 Reference(Reference),
440 Now,
442 DateRelative(DateRelativeKind, Arc<Expression>),
445 DateCalendar(DateCalendarKind, CalendarPeriodUnit, Arc<Expression>),
448 RangeLiteral(Arc<Expression>, Arc<Expression>),
450 PastFutureRange(DateRelativeKind, Arc<Expression>),
452 RangeContainment(Arc<Expression>, Arc<Expression>),
454 LogicalAnd(Arc<Expression>, Arc<Expression>),
455 Arithmetic(Arc<Expression>, ArithmeticComputation, Arc<Expression>),
456 Comparison(Arc<Expression>, ComparisonComputation, Arc<Expression>),
457 UnitConversion(Arc<Expression>, ConversionTarget),
458 LogicalNegation(Arc<Expression>, NegationType),
459 MathematicalComputation(MathematicalComputation, Arc<Expression>),
460 Veto(VetoExpression),
461 ResultIsVeto(Arc<Expression>),
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
475pub struct Reference {
476 pub segments: Vec<String>,
477 pub name: String,
478}
479
480impl Reference {
481 #[must_use]
482 pub fn local(name: String) -> Self {
483 Self {
484 segments: Vec::new(),
485 name: ascii_lowercase_logical_name(name),
486 }
487 }
488
489 #[must_use]
490 pub fn from_path(path: Vec<String>) -> Self {
491 if path.is_empty() {
492 Self {
493 segments: Vec::new(),
494 name: String::new(),
495 }
496 } else {
497 let name = ascii_lowercase_logical_name(path[path.len() - 1].clone());
499 let segments = path[..path.len() - 1]
500 .iter()
501 .map(|segment| ascii_lowercase_logical_name(segment.clone()))
502 .collect();
503 Self { segments, name }
504 }
505 }
506
507 #[must_use]
508 pub fn is_local(&self) -> bool {
509 self.segments.is_empty()
510 }
511
512 #[must_use]
513 pub fn full_path(&self) -> Vec<String> {
514 let mut path = self.segments.clone();
515 path.push(self.name.clone());
516 path
517 }
518}
519
520impl fmt::Display for Reference {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 for segment in &self.segments {
523 write!(f, "{}.", segment)?;
524 }
525 write!(f, "{}", self.name)
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
531#[serde(rename_all = "snake_case")]
532pub enum ArithmeticComputation {
533 Add,
534 Subtract,
535 Multiply,
536 Divide,
537 Modulo,
538 Power,
539}
540
541#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
543#[serde(rename_all = "snake_case")]
544pub enum ComparisonComputation {
545 GreaterThan,
546 LessThan,
547 GreaterThanOrEqual,
548 LessThanOrEqual,
549 Is,
550 IsNot,
551}
552
553impl ComparisonComputation {
554 #[must_use]
556 pub fn is_equal(&self) -> bool {
557 matches!(self, ComparisonComputation::Is)
558 }
559
560 #[must_use]
562 pub fn is_not_equal(&self) -> bool {
563 matches!(self, ComparisonComputation::IsNot)
564 }
565}
566
567#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
569#[serde(rename_all = "snake_case")]
570pub enum ConversionTarget {
571 Type(PrimitiveKind),
572 Unit { unit_name: String },
573}
574
575#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
577#[serde(rename_all = "snake_case")]
578pub enum NegationType {
579 Not,
580}
581
582#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
589pub struct VetoExpression {
590 pub message: Option<String>,
591}
592
593#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
595#[serde(rename_all = "snake_case")]
596pub enum MathematicalComputation {
597 Sqrt,
598 Sin,
599 Cos,
600 Tan,
601 Asin,
602 Acos,
603 Atan,
604 Log,
605 Exp,
606 Abs,
607 Floor,
608 Ceil,
609 Round,
610}
611
612#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
619pub struct SpecRef {
620 pub repository: Option<RepositoryQualifier>,
623 pub name: String,
625 pub effective: Option<DateTimeValue>,
627 #[serde(default, skip_serializing_if = "Option::is_none")]
629 pub repository_span: Option<Span>,
630 #[serde(default, skip_serializing_if = "Option::is_none")]
632 pub target_span: Option<Span>,
633}
634
635impl std::fmt::Display for SpecRef {
636 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
637 if let Some(qualifier) = &self.repository {
638 write!(f, "{} ", qualifier)?;
639 }
640 write!(f, "{}", self.name)?;
641 if let Some(d) = &self.effective {
642 write!(f, " {}", d)?;
643 }
644 Ok(())
645 }
646}
647
648impl SpecRef {
649 pub fn same_repository(name: impl Into<String>) -> Self {
651 Self {
652 name: ascii_lowercase_logical_name(name.into()),
653 repository: None,
654 effective: None,
655 repository_span: None,
656 target_span: None,
657 }
658 }
659
660 pub fn cross_repository(name: impl Into<String>, qualifier: RepositoryQualifier) -> Self {
662 Self {
663 name: ascii_lowercase_logical_name(name.into()),
664 repository: Some(qualifier),
665 effective: None,
666 repository_span: None,
667 target_span: None,
668 }
669 }
670
671 pub fn at(&self, effective: &EffectiveDate) -> EffectiveDate {
674 self.effective
675 .clone()
676 .map_or_else(|| effective.clone(), EffectiveDate::DateTimeValue)
677 }
678
679 pub fn resolved_instant(
682 &self,
683 consumer_effective_from: Option<&DateTimeValue>,
684 ) -> Option<DateTimeValue> {
685 self.effective
686 .clone()
687 .or_else(|| consumer_effective_from.cloned())
688 }
689}
690
691#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
699pub struct UnitFactor {
700 pub measure_ref: String,
701 pub exp: i32,
702}
703
704#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
713pub enum UnitArg {
714 Factor(Decimal),
715 Expr(Decimal, Vec<UnitFactor>),
716}
717
718impl fmt::Display for UnitArg {
719 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
720 match self {
721 UnitArg::Factor(v) => write!(f, "{}", v),
722 UnitArg::Expr(prefix, factors) => {
723 if *prefix != Decimal::ONE {
724 write!(f, "{} ", prefix)?;
725 }
726 for (index, factor) in factors.iter().enumerate() {
727 if factor.exp == 0 {
728 unreachable!("BUG: unit factor exponent cannot be zero");
729 }
730 if factor.exp > 0 {
731 if index > 0 {
732 write!(f, " * ")?;
733 }
734 write!(f, "{}", factor.measure_ref)?;
735 if factor.exp != 1 {
736 write!(f, "^{}", factor.exp)?;
737 }
738 } else {
739 let denominator_started =
740 factors[..index].iter().any(|prior| prior.exp < 0);
741 if denominator_started {
742 write!(f, " * ")?;
743 } else {
744 write!(f, "/")?;
745 }
746 write!(f, "{}", factor.measure_ref)?;
747 let positive_exp = factor
748 .exp
749 .checked_neg()
750 .expect("BUG: negative unit factor exponent");
751 if positive_exp != 1 {
752 write!(f, "^{}", positive_exp)?;
753 }
754 }
755 }
756 Ok(())
757 }
758 }
759 }
760}
761
762#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
780#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
781pub enum CommandArg {
782 Literal(crate::literals::Value),
784 Label(String),
786 UnitExpr(UnitArg),
788}
789
790impl fmt::Display for CommandArg {
791 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
792 match self {
793 CommandArg::Literal(v) => write!(f, "{}", v),
794 CommandArg::Label(s) => write!(f, "{}", s),
795 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
796 }
797 }
798}
799
800#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
802#[serde(rename_all = "snake_case")]
803pub enum TypeConstraintCommand {
804 Help,
805 Suggest,
806 Unit,
807 Trait,
808 Minimum,
809 Maximum,
810 Lower,
811 Upper,
812 Decimals,
813 Option,
814 Options,
815 Length,
816}
817
818impl fmt::Display for TypeConstraintCommand {
819 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
820 let s = match self {
821 TypeConstraintCommand::Help => "help",
822 TypeConstraintCommand::Suggest => "suggest",
823 TypeConstraintCommand::Unit => "unit",
824 TypeConstraintCommand::Trait => "trait",
825 TypeConstraintCommand::Minimum => "minimum",
826 TypeConstraintCommand::Maximum => "maximum",
827 TypeConstraintCommand::Lower => "lower",
828 TypeConstraintCommand::Upper => "upper",
829 TypeConstraintCommand::Decimals => "decimals",
830 TypeConstraintCommand::Option => "option",
831 TypeConstraintCommand::Options => "options",
832 TypeConstraintCommand::Length => "length",
833 };
834 write!(f, "{}", s)
835 }
836}
837
838#[must_use]
840pub fn try_parse_type_constraint_command(s: &str) -> Option<TypeConstraintCommand> {
841 match s.trim().to_lowercase().as_str() {
842 "help" => Some(TypeConstraintCommand::Help),
843 "suggest" => Some(TypeConstraintCommand::Suggest),
844 "unit" => Some(TypeConstraintCommand::Unit),
845 "trait" => Some(TypeConstraintCommand::Trait),
846 "minimum" => Some(TypeConstraintCommand::Minimum),
847 "maximum" => Some(TypeConstraintCommand::Maximum),
848 "lower" => Some(TypeConstraintCommand::Lower),
849 "upper" => Some(TypeConstraintCommand::Upper),
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, _) => arithmetic_precedence(op),
1139 ExpressionKind::UnitConversion(..) => 8,
1140 ExpressionKind::RangeLiteral(..) => 9,
1141 ExpressionKind::MathematicalComputation(..) => 10,
1142 ExpressionKind::PastFutureRange(..) => 10,
1143 ExpressionKind::Literal(..)
1144 | ExpressionKind::Reference(..)
1145 | ExpressionKind::Now
1146 | ExpressionKind::Veto(..) => 10,
1147 }
1148}
1149
1150pub fn arithmetic_precedence(op: &ArithmeticComputation) -> u8 {
1152 match op {
1153 ArithmeticComputation::Add | ArithmeticComputation::Subtract => 5,
1154 ArithmeticComputation::Multiply
1155 | ArithmeticComputation::Divide
1156 | ArithmeticComputation::Modulo => 6,
1157 ArithmeticComputation::Power => 7,
1158 }
1159}
1160
1161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1163pub enum OperandSide {
1164 Left,
1165 Right,
1166}
1167
1168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1170pub enum Associativity {
1171 Left,
1172 Right,
1173}
1174
1175pub fn operand_needs_parentheses(
1181 child_prec: u8,
1182 parent_prec: u8,
1183 side: OperandSide,
1184 parent_assoc: Option<Associativity>,
1185) -> bool {
1186 if child_prec < parent_prec {
1187 return true;
1188 }
1189 if child_prec > parent_prec {
1190 return false;
1191 }
1192 match parent_assoc {
1193 None => false,
1194 Some(Associativity::Left) => matches!(side, OperandSide::Right),
1195 Some(Associativity::Right) => matches!(side, OperandSide::Left),
1196 }
1197}
1198
1199pub fn arithmetic_associativity(op: &ArithmeticComputation) -> Associativity {
1200 match op {
1201 ArithmeticComputation::Power => Associativity::Right,
1202 ArithmeticComputation::Add
1203 | ArithmeticComputation::Subtract
1204 | ArithmeticComputation::Multiply
1205 | ArithmeticComputation::Divide
1206 | ArithmeticComputation::Modulo => Associativity::Left,
1207 }
1208}
1209
1210fn write_expression_child(
1211 f: &mut fmt::Formatter<'_>,
1212 child: &Expression,
1213 parent_prec: u8,
1214 side: OperandSide,
1215 parent_assoc: Option<Associativity>,
1216) -> fmt::Result {
1217 let child_prec = expression_precedence(&child.kind);
1218 if operand_needs_parentheses(child_prec, parent_prec, side, parent_assoc) {
1219 write!(f, "({})", child)
1220 } else {
1221 write!(f, "{}", child)
1222 }
1223}
1224
1225impl fmt::Display for Expression {
1226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1227 match &self.kind {
1228 ExpressionKind::Literal(lit) => write!(f, "{}", AsLemmaSource(lit)),
1229 ExpressionKind::Reference(r) => write!(f, "{}", r),
1230 ExpressionKind::Arithmetic(left, op, right) => {
1231 let my_prec = expression_precedence(&self.kind);
1232 let assoc = Some(arithmetic_associativity(op));
1233 write_expression_child(f, left, my_prec, OperandSide::Left, assoc)?;
1234 write!(f, " {} ", op)?;
1235 write_expression_child(f, right, my_prec, OperandSide::Right, assoc)
1236 }
1237 ExpressionKind::Comparison(left, op, right) => {
1238 let my_prec = expression_precedence(&self.kind);
1239 write_expression_child(f, left, my_prec, OperandSide::Left, None)?;
1240 write!(f, " {} ", op)?;
1241 write_expression_child(f, right, my_prec, OperandSide::Right, None)
1242 }
1243 ExpressionKind::UnitConversion(value, target) => {
1244 let my_prec = expression_precedence(&self.kind);
1245 write_expression_child(f, value, my_prec, OperandSide::Left, None)?;
1246 write!(f, " as {}", target)
1247 }
1248 ExpressionKind::LogicalNegation(expr, negation) => {
1249 if let (NegationType::Not, ExpressionKind::ResultIsVeto(operand)) =
1250 (negation, &expr.kind)
1251 {
1252 let my_prec = expression_precedence(&self.kind);
1253 write_expression_child(f, operand, my_prec, OperandSide::Left, None)?;
1254 write!(f, " is not veto")
1255 } else {
1256 let my_prec = expression_precedence(&self.kind);
1257 write!(f, "not ")?;
1258 write_expression_child(f, expr, my_prec, OperandSide::Right, None)
1259 }
1260 }
1261 ExpressionKind::ResultIsVeto(operand) => {
1262 let my_prec = expression_precedence(&self.kind);
1263 write_expression_child(f, operand, my_prec, OperandSide::Left, None)?;
1264 write!(f, " is veto")
1265 }
1266 ExpressionKind::LogicalAnd(left, right) => {
1267 let my_prec = expression_precedence(&self.kind);
1268 let assoc = Some(Associativity::Left);
1269 write_expression_child(f, left, my_prec, OperandSide::Left, assoc)?;
1270 write!(f, " and ")?;
1271 write_expression_child(f, right, my_prec, OperandSide::Right, assoc)
1272 }
1273 ExpressionKind::MathematicalComputation(op, operand) => {
1274 let my_prec = expression_precedence(&self.kind);
1275 write!(f, "{} ", op)?;
1276 write_expression_child(f, operand, my_prec, OperandSide::Right, None)
1277 }
1278 ExpressionKind::Veto(veto) => match &veto.message {
1279 Some(msg) => write!(f, "veto {}", quote_lemma_text(msg)),
1280 None => write!(f, "veto"),
1281 },
1282 ExpressionKind::Now => write!(f, "now"),
1283 ExpressionKind::DateRelative(kind, date_expr) => {
1284 write!(f, "{} {}", date_expr, kind)?;
1285 Ok(())
1286 }
1287 ExpressionKind::DateCalendar(kind, unit, date_expr) => {
1288 write!(f, "{} {} {}", date_expr, kind, unit)
1289 }
1290 ExpressionKind::RangeLiteral(left, right) => {
1291 let my_prec = expression_precedence(&self.kind);
1292 write_expression_child(f, left, my_prec, OperandSide::Left, None)?;
1293 write!(f, "...")?;
1294 write_expression_child(f, right, my_prec, OperandSide::Right, None)
1295 }
1296 ExpressionKind::PastFutureRange(kind, offset_expr) => {
1297 match kind {
1298 DateRelativeKind::InPast => write!(f, "past ")?,
1299 DateRelativeKind::InFuture => write!(f, "future ")?,
1300 }
1301 let my_prec = expression_precedence(&self.kind);
1302 write_expression_child(f, offset_expr, my_prec, OperandSide::Right, None)
1303 }
1304 ExpressionKind::RangeContainment(value, range) => {
1305 let my_prec = expression_precedence(&self.kind);
1306 write_expression_child(f, value, my_prec, OperandSide::Left, None)?;
1307 write!(f, " in ")?;
1308 write_expression_child(f, range, my_prec, OperandSide::Right, None)
1309 }
1310 }
1311 }
1312}
1313
1314impl fmt::Display for ConversionTarget {
1315 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1316 match self {
1317 ConversionTarget::Type(kind) => write!(f, "{kind}"),
1318 ConversionTarget::Unit { unit_name } => write!(f, "{unit_name}"),
1319 }
1320 }
1321}
1322
1323impl fmt::Display for ArithmeticComputation {
1324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1325 match self {
1326 ArithmeticComputation::Add => write!(f, "+"),
1327 ArithmeticComputation::Subtract => write!(f, "-"),
1328 ArithmeticComputation::Multiply => write!(f, "*"),
1329 ArithmeticComputation::Divide => write!(f, "/"),
1330 ArithmeticComputation::Modulo => write!(f, "%"),
1331 ArithmeticComputation::Power => write!(f, "^"),
1332 }
1333 }
1334}
1335
1336impl fmt::Display for ComparisonComputation {
1337 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1338 match self {
1339 ComparisonComputation::GreaterThan => write!(f, ">"),
1340 ComparisonComputation::LessThan => write!(f, "<"),
1341 ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1342 ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1343 ComparisonComputation::Is => write!(f, "is"),
1344 ComparisonComputation::IsNot => write!(f, "is not"),
1345 }
1346 }
1347}
1348
1349impl fmt::Display for MathematicalComputation {
1350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1351 match self {
1352 MathematicalComputation::Sqrt => write!(f, "sqrt"),
1353 MathematicalComputation::Sin => write!(f, "sin"),
1354 MathematicalComputation::Cos => write!(f, "cos"),
1355 MathematicalComputation::Tan => write!(f, "tan"),
1356 MathematicalComputation::Asin => write!(f, "asin"),
1357 MathematicalComputation::Acos => write!(f, "acos"),
1358 MathematicalComputation::Atan => write!(f, "atan"),
1359 MathematicalComputation::Log => write!(f, "log"),
1360 MathematicalComputation::Exp => write!(f, "exp"),
1361 MathematicalComputation::Abs => write!(f, "abs"),
1362 MathematicalComputation::Floor => write!(f, "floor"),
1363 MathematicalComputation::Ceil => write!(f, "ceil"),
1364 MathematicalComputation::Round => write!(f, "round"),
1365 }
1366 }
1367}
1368
1369#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1375#[serde(rename_all = "snake_case")]
1376pub enum PrimitiveKind {
1377 Boolean,
1378 Measure,
1379 MeasureRange,
1380 Number,
1381 NumberRange,
1382 Ratio,
1383 RatioRange,
1384 Text,
1385 Date,
1386 DateRange,
1387 Time,
1388 TimeRange,
1389}
1390
1391impl std::fmt::Display for PrimitiveKind {
1392 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1393 let s = match self {
1394 PrimitiveKind::Boolean => "boolean",
1395 PrimitiveKind::Measure => "measure",
1396 PrimitiveKind::MeasureRange => "measure range",
1397 PrimitiveKind::Number => "number",
1398 PrimitiveKind::NumberRange => "number range",
1399 PrimitiveKind::Ratio => "ratio",
1400 PrimitiveKind::RatioRange => "ratio range",
1401 PrimitiveKind::Text => "text",
1402 PrimitiveKind::Date => "date",
1403 PrimitiveKind::DateRange => "date range",
1404 PrimitiveKind::Time => "time",
1405 PrimitiveKind::TimeRange => "time range",
1406 };
1407 write!(f, "{}", s)
1408 }
1409}
1410
1411#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1416#[serde(tag = "kind", rename_all = "snake_case")]
1417pub enum ParentType {
1418 Primitive {
1419 primitive: PrimitiveKind,
1420 },
1421 Custom {
1422 name: String,
1423 },
1424 Qualified {
1427 spec_alias: String,
1428 inner: Box<ParentType>,
1429 },
1430 Ranged {
1432 inner: Box<ParentType>,
1433 },
1434}
1435
1436impl std::fmt::Display for ParentType {
1437 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1438 match self {
1439 ParentType::Primitive { primitive } => write!(f, "{}", primitive),
1440 ParentType::Custom { name } => write!(f, "{}", name),
1441 ParentType::Qualified { spec_alias, inner } => {
1442 write!(f, "{spec_alias}.{inner}")
1443 }
1444 ParentType::Ranged { inner } => write!(f, "{inner} range"),
1445 }
1446 }
1447}
1448
1449pub struct AsLemmaSource<'a, T: ?Sized>(pub &'a T);
1455
1456pub fn quote_lemma_text(s: &str) -> String {
1459 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1460 format!("\"{}\"", escaped)
1461}
1462
1463fn format_decimal_source(n: &Decimal) -> String {
1468 let raw = if n.fract().is_zero() {
1469 n.trunc().to_string()
1470 } else {
1471 n.to_string()
1472 };
1473 group_digits(&raw)
1474}
1475
1476fn group_digits(s: &str) -> String {
1480 let (sign, rest) = if s.starts_with('-') || s.starts_with('+') {
1481 (&s[..1], &s[1..])
1482 } else {
1483 ("", s)
1484 };
1485
1486 let (int_part, frac_part) = match rest.find('.') {
1487 Some(pos) => (&rest[..pos], &rest[pos..]),
1488 None => (rest, ""),
1489 };
1490
1491 if int_part.len() < 4 {
1492 return s.to_string();
1493 }
1494
1495 let mut grouped = String::with_capacity(int_part.len() + int_part.len() / 3);
1496 for (i, ch) in int_part.chars().enumerate() {
1497 let digits_remaining = int_part.len() - i;
1498 if i > 0 && digits_remaining % 3 == 0 {
1499 grouped.push('_');
1500 }
1501 grouped.push(ch);
1502 }
1503
1504 format!("{}{}{}", sign, grouped, frac_part)
1505}
1506
1507impl<'a> fmt::Display for AsLemmaSource<'a, CommandArg> {
1508 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1509 use crate::literals::Value;
1510 match self.0 {
1511 CommandArg::Literal(Value::Text(s)) => write!(f, "{}", quote_lemma_text(s)),
1512 CommandArg::Literal(Value::Number(d)) => {
1513 write!(f, "{}", group_digits(&d.to_string()))
1514 }
1515 CommandArg::Literal(Value::Boolean(bv)) => write!(f, "{}", bv),
1516 CommandArg::Literal(Value::NumberWithUnit(d, unit)) => {
1517 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1518 }
1519 CommandArg::Literal(value @ Value::Range(_, _)) => {
1520 write!(f, "{}", AsLemmaSource(value))
1521 }
1522 CommandArg::Literal(Value::Date(dt)) => write!(f, "{}", dt),
1523 CommandArg::Literal(Value::Time(t)) => write!(f, "{}", t),
1524 CommandArg::Label(s) => write!(f, "{}", s),
1525 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
1526 }
1527 }
1528}
1529
1530pub(crate) fn format_constraint_as_source(
1532 cmd: &TypeConstraintCommand,
1533 args: &[CommandArg],
1534) -> String {
1535 if args.is_empty() {
1536 cmd.to_string()
1537 } else {
1538 let args_str: Vec<String> = args
1539 .iter()
1540 .map(|a| format!("{}", AsLemmaSource(a)))
1541 .collect();
1542 format!("{} {}", cmd, args_str.join(" "))
1543 }
1544}
1545
1546fn format_constraints_as_source(constraints: &[Constraint], separator: &str) -> String {
1549 constraints
1550 .iter()
1551 .map(|(cmd, args)| format_constraint_as_source(cmd, args))
1552 .collect::<Vec<_>>()
1553 .join(separator)
1554}
1555
1556impl<'a> fmt::Display for AsLemmaSource<'a, Value> {
1559 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1560 match self.0 {
1561 Value::Number(n) => write!(f, "{}", format_decimal_source(n)),
1562 Value::Text(s) => write!(f, "{}", quote_lemma_text(s)),
1563 Value::Date(dt) => match dt.granularity {
1564 crate::literals::DateGranularity::Year => write!(f, "{:04}", dt.year),
1565 crate::literals::DateGranularity::YearMonth => {
1566 write!(f, "{:04}-{:02}", dt.year, dt.month)
1567 }
1568 crate::literals::DateGranularity::IsoWeek { iso_year, week } => {
1569 write!(f, "{:04}-W{:02}", iso_year, week)
1570 }
1571 crate::literals::DateGranularity::Full => {
1572 write!(f, "{:04}-{:02}-{:02}", dt.year, dt.month, dt.day)
1573 }
1574 crate::literals::DateGranularity::DateTime => {
1575 write!(
1576 f,
1577 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1578 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
1579 )?;
1580 if let Some(tz) = &dt.timezone {
1581 write!(f, "{}", tz)?;
1582 }
1583 Ok(())
1584 }
1585 },
1586 Value::Time(t) => {
1587 write!(f, "{:02}:{:02}:{:02}", t.hour, t.minute, t.second)?;
1588 if let Some(tz) = &t.timezone {
1589 write!(f, "{}", tz)?;
1590 }
1591 Ok(())
1592 }
1593 Value::Boolean(b) => write!(f, "{}", b),
1594 Value::NumberWithUnit(n, u) => match u.as_str() {
1595 "percent" => write!(f, "{}%", format_decimal_source(n)),
1596 "permille" => write!(f, "{}%%", format_decimal_source(n)),
1597 unit => write!(f, "{} {}", format_decimal_source(n), unit),
1598 },
1599 Value::Range(left, right) => {
1600 write!(
1601 f,
1602 "{}...{}",
1603 AsLemmaSource(left.as_ref()),
1604 AsLemmaSource(right.as_ref())
1605 )
1606 }
1607 }
1608 }
1609}
1610
1611impl<'a> fmt::Display for AsLemmaSource<'a, MetaValue> {
1614 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1615 match self.0 {
1616 MetaValue::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1617 MetaValue::Unquoted(s) => write!(f, "{}", s),
1618 }
1619 }
1620}
1621
1622impl<'a> fmt::Display for AsLemmaSource<'a, DataValue> {
1623 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1624 match self.0 {
1625 DataValue::Definition {
1626 base,
1627 constraints,
1628 value,
1629 } => {
1630 if base.is_none() && constraints.is_none() {
1631 if let Some(v) = value {
1632 return write!(f, "{}", AsLemmaSource(v));
1633 }
1634 }
1635 let base_str = match base.as_ref() {
1636 Some(b) => format!("{}", b),
1637 None => match value {
1638 Some(v) => {
1639 if let Some(ref constraints_vec) = constraints {
1640 let constraint_str =
1641 format_constraints_as_source(constraints_vec, " -> ");
1642 return write!(f, "{} -> {}", AsLemmaSource(v), constraint_str);
1643 }
1644 return write!(f, "{}", AsLemmaSource(v));
1645 }
1646 None => String::new(),
1647 },
1648 };
1649 if let Some(ref constraints_vec) = constraints {
1650 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
1651 write!(f, "{} -> {}", base_str, constraint_str)
1652 } else {
1653 write!(f, "{}", base_str)
1654 }
1655 }
1656 DataValue::Import(spec_ref) => {
1657 write!(f, "with {}", spec_ref)
1658 }
1659 DataValue::With(with_rhs) => match with_rhs {
1660 WithRhs::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1661 WithRhs::Reference { target } => write!(f, "{target}"),
1662 },
1663 }
1664 }
1665}
1666
1667pub(crate) fn canonicalize_value(value: &mut Value) {
1668 if let Value::NumberWithUnit(_, unit) = value {
1669 *unit = ascii_lowercase_logical_name(std::mem::take(unit));
1670 }
1671}
1672
1673pub(crate) fn canonicalize_reference(reference: &mut Reference) {
1674 for segment in &mut reference.segments {
1675 *segment = ascii_lowercase_logical_name(std::mem::take(segment));
1676 }
1677 reference.name = ascii_lowercase_logical_name(std::mem::take(&mut reference.name));
1678}
1679
1680pub(crate) fn canonicalize_spec_ref(spec_ref: &mut SpecRef) {
1681 spec_ref.name = ascii_lowercase_logical_name(std::mem::take(&mut spec_ref.name));
1682 if let Some(qualifier) = spec_ref.repository.as_mut() {
1683 qualifier.name = ascii_lowercase_logical_name(std::mem::take(&mut qualifier.name));
1684 }
1685}
1686
1687pub(crate) fn canonicalize_parent_type(parent: &mut ParentType) {
1688 match parent {
1689 ParentType::Custom { name } => {
1690 *name = ascii_lowercase_logical_name(std::mem::take(name));
1691 }
1692 ParentType::Qualified { spec_alias, inner } => {
1693 *spec_alias = ascii_lowercase_logical_name(std::mem::take(spec_alias));
1694 canonicalize_parent_type(inner);
1695 }
1696 ParentType::Ranged { inner } => {
1697 canonicalize_parent_type(inner);
1698 }
1699 ParentType::Primitive { .. } => {}
1700 }
1701}
1702
1703pub(crate) fn canonicalize_unit_factor(factor: &mut UnitFactor) {
1704 factor.measure_ref = ascii_lowercase_logical_name(std::mem::take(&mut factor.measure_ref));
1705}
1706
1707pub(crate) fn canonicalize_unit_arg(unit_arg: &mut UnitArg) {
1708 if let UnitArg::Expr(_, factors) = unit_arg {
1709 for factor in factors {
1710 canonicalize_unit_factor(factor);
1711 }
1712 }
1713}
1714
1715pub(crate) fn canonicalize_command_arg(command_arg: &mut CommandArg) {
1716 match command_arg {
1717 CommandArg::Literal(value) => canonicalize_value(value),
1718 CommandArg::Label(label) => {
1719 *label = ascii_lowercase_logical_name(std::mem::take(label));
1720 }
1721 CommandArg::UnitExpr(unit_arg) => canonicalize_unit_arg(unit_arg),
1722 }
1723}
1724
1725pub(crate) fn canonicalize_constraints(constraints: &mut [Constraint]) {
1726 for (_, args) in constraints {
1727 for arg in args {
1728 canonicalize_command_arg(arg);
1729 }
1730 }
1731}
1732
1733pub(crate) fn canonicalize_expression(expression: &mut Expression) {
1734 match &mut expression.kind {
1735 ExpressionKind::Literal(value) => canonicalize_value(value),
1736 ExpressionKind::Reference(reference) => canonicalize_reference(reference),
1737 ExpressionKind::Now => {}
1738 ExpressionKind::DateRelative(_, expression) => {
1739 canonicalize_expression(Arc::make_mut(expression));
1740 }
1741 ExpressionKind::DateCalendar(_, _, expression) => {
1742 canonicalize_expression(Arc::make_mut(expression));
1743 }
1744 ExpressionKind::RangeLiteral(left, right) => {
1745 canonicalize_expression(Arc::make_mut(left));
1746 canonicalize_expression(Arc::make_mut(right));
1747 }
1748 ExpressionKind::PastFutureRange(_, expression) => {
1749 canonicalize_expression(Arc::make_mut(expression));
1750 }
1751 ExpressionKind::RangeContainment(value, range) => {
1752 canonicalize_expression(Arc::make_mut(value));
1753 canonicalize_expression(Arc::make_mut(range));
1754 }
1755 ExpressionKind::LogicalAnd(left, right) => {
1756 canonicalize_expression(Arc::make_mut(left));
1757 canonicalize_expression(Arc::make_mut(right));
1758 }
1759 ExpressionKind::Arithmetic(left, _, right) => {
1760 canonicalize_expression(Arc::make_mut(left));
1761 canonicalize_expression(Arc::make_mut(right));
1762 }
1763 ExpressionKind::Comparison(left, _, right) => {
1764 canonicalize_expression(Arc::make_mut(left));
1765 canonicalize_expression(Arc::make_mut(right));
1766 }
1767 ExpressionKind::UnitConversion(expression, _) => {
1768 canonicalize_expression(Arc::make_mut(expression));
1769 }
1770 ExpressionKind::LogicalNegation(expression, _) => {
1771 canonicalize_expression(Arc::make_mut(expression));
1772 }
1773 ExpressionKind::MathematicalComputation(_, expression) => {
1774 canonicalize_expression(Arc::make_mut(expression));
1775 }
1776 ExpressionKind::Veto(_) => {}
1777 ExpressionKind::ResultIsVeto(expression) => {
1778 canonicalize_expression(Arc::make_mut(expression));
1779 }
1780 }
1781}
1782
1783pub(crate) fn canonicalize_unless_clause(unless_clause: &mut UnlessClause) {
1784 canonicalize_expression(&mut unless_clause.condition);
1785 canonicalize_expression(&mut unless_clause.result);
1786}
1787
1788pub(crate) fn canonicalize_data_value(data_value: &mut DataValue) {
1789 match data_value {
1790 DataValue::Definition {
1791 base,
1792 constraints,
1793 value,
1794 } => {
1795 if let Some(base) = base {
1796 canonicalize_parent_type(base);
1797 }
1798 if let Some(constraints) = constraints {
1799 canonicalize_constraints(constraints);
1800 }
1801 if let Some(value) = value {
1802 canonicalize_value(value);
1803 }
1804 }
1805 DataValue::Import(spec_ref) => canonicalize_spec_ref(spec_ref),
1806 DataValue::With(with_rhs) => match with_rhs {
1807 WithRhs::Literal(value) => canonicalize_value(value),
1808 WithRhs::Reference { target } => canonicalize_reference(target),
1809 },
1810 }
1811}
1812
1813pub(crate) fn canonicalize_lemma_data(data: &mut LemmaData) {
1814 canonicalize_reference(&mut data.reference);
1815 canonicalize_data_value(&mut data.value);
1816}
1817
1818pub(crate) fn canonicalize_lemma_rule(rule: &mut LemmaRule) {
1819 rule.name = ascii_lowercase_logical_name(std::mem::take(&mut rule.name));
1820 canonicalize_expression(&mut rule.expression);
1821 for unless_clause in &mut rule.unless_clauses {
1822 canonicalize_unless_clause(unless_clause);
1823 }
1824}
1825
1826pub(crate) fn canonicalize_lemma_spec(spec: &mut LemmaSpec) {
1827 spec.name = ascii_lowercase_logical_name(std::mem::take(&mut spec.name));
1828 for meta in &mut spec.meta_fields {
1829 meta.key = ascii_lowercase_logical_name(std::mem::take(&mut meta.key));
1830 }
1831 for data in &mut spec.data {
1832 canonicalize_lemma_data(data);
1833 }
1834 for rule in &mut spec.rules {
1835 canonicalize_lemma_rule(rule);
1836 }
1837}
1838
1839pub(crate) fn canonicalize_repository(repository: &mut LemmaRepository) {
1840 if let Some(name) = repository.name.take() {
1841 repository.name = Some(ascii_lowercase_logical_name(name));
1842 }
1843}
1844
1845#[cfg(test)]
1846mod tests {
1847 use super::*;
1848 use crate::literals::DateGranularity;
1849
1850 #[test]
1851 fn test_conversion_target_display() {
1852 assert_eq!(
1853 format!("{}", ConversionTarget::Type(PrimitiveKind::Number)),
1854 "number"
1855 );
1856 }
1857
1858 #[test]
1859 fn test_value_number_with_unit_ratio_display() {
1860 use rust_decimal::Decimal;
1861 use std::str::FromStr;
1862 let percent =
1863 Value::NumberWithUnit(Decimal::from_str("10").unwrap(), "percent".to_string());
1864 assert_eq!(format!("{}", percent), "10%");
1865 let permille =
1866 Value::NumberWithUnit(Decimal::from_str("5").unwrap(), "permille".to_string());
1867 assert_eq!(format!("{}", permille), "5%%");
1868 }
1869
1870 #[test]
1871 fn test_datetime_value_display() {
1872 let dt = DateTimeValue {
1873 year: 2024,
1874 month: 12,
1875 day: 25,
1876 hour: 14,
1877 minute: 30,
1878 second: 45,
1879 microsecond: 0,
1880 timezone: Some(TimezoneValue {
1881 offset_hours: 1,
1882 offset_minutes: 0,
1883 }),
1884
1885 granularity: DateGranularity::DateTime,
1886 };
1887 assert_eq!(format!("{}", dt), "2024-12-25T14:30:45+01:00");
1888 }
1889
1890 #[test]
1891 fn test_datetime_value_display_date_only() {
1892 let dt = DateTimeValue {
1893 year: 2026,
1894 month: 3,
1895 day: 4,
1896 hour: 0,
1897 minute: 0,
1898 second: 0,
1899 microsecond: 0,
1900 timezone: None,
1901
1902 granularity: DateGranularity::Full,
1903 };
1904 assert_eq!(format!("{}", dt), "2026-03-04");
1905 }
1906
1907 #[test]
1908 fn test_datetime_value_display_microseconds() {
1909 let dt = DateTimeValue {
1910 year: 2026,
1911 month: 2,
1912 day: 23,
1913 hour: 14,
1914 minute: 30,
1915 second: 45,
1916 microsecond: 123456,
1917 timezone: Some(TimezoneValue {
1918 offset_hours: 0,
1919 offset_minutes: 0,
1920 }),
1921
1922 granularity: DateGranularity::DateTime,
1923 };
1924 assert_eq!(format!("{}", dt), "2026-02-23T14:30:45.123456Z");
1925 }
1926
1927 #[test]
1928 fn test_datetime_microsecond_in_ordering() {
1929 let a = DateTimeValue {
1930 year: 2026,
1931 month: 1,
1932 day: 1,
1933 hour: 0,
1934 minute: 0,
1935 second: 0,
1936 microsecond: 100,
1937 timezone: None,
1938
1939 granularity: DateGranularity::DateTime,
1940 };
1941 let b = DateTimeValue {
1942 year: 2026,
1943 month: 1,
1944 day: 1,
1945 hour: 0,
1946 minute: 0,
1947 second: 0,
1948 microsecond: 200,
1949 timezone: None,
1950
1951 granularity: DateGranularity::DateTime,
1952 };
1953 assert!(a < b);
1954 }
1955
1956 #[test]
1957 fn test_datetime_parse_iso_week() {
1958 let dt: DateTimeValue = "2026-W01".parse().unwrap();
1959 assert_eq!(dt.year, 2025);
1960 assert_eq!(dt.month, 12);
1961 assert_eq!(dt.day, 29);
1962 assert_eq!(dt.microsecond, 0);
1963 assert_eq!(dt.to_string(), "2026-W01");
1964 assert!(matches!(
1965 dt.granularity,
1966 DateGranularity::IsoWeek {
1967 iso_year: 2026,
1968 week: 1
1969 }
1970 ));
1971 }
1972
1973 #[test]
1974 fn test_negation_types() {
1975 let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
1976 let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
1977 assert_eq!(decoded, NegationType::Not);
1978 }
1979
1980 #[test]
1981 fn parent_type_primitive_serde_internally_tagged() {
1982 let p = ParentType::Primitive {
1983 primitive: PrimitiveKind::Number,
1984 };
1985 let json = serde_json::to_string(&p).expect("ParentType::Primitive must serialize");
1986 assert!(json.contains("\"kind\"") && json.contains("\"primitive\""));
1987 let back: ParentType = serde_json::from_str(&json).expect("deserialize");
1988 assert_eq!(back, p);
1989 }
1990
1991 fn text_arg(s: &str) -> CommandArg {
1996 CommandArg::Literal(crate::literals::Value::Text(s.to_string()))
1997 }
1998
1999 fn number_arg(s: &str) -> CommandArg {
2000 let d: rust_decimal::Decimal = s.parse().expect("decimal");
2001 CommandArg::Literal(crate::literals::Value::Number(d))
2002 }
2003
2004 fn boolean_arg(b: BooleanValue) -> CommandArg {
2005 CommandArg::Literal(crate::literals::Value::Boolean(b))
2006 }
2007
2008 fn measure_arg(value: &str, unit: &str) -> CommandArg {
2009 let d: rust_decimal::Decimal = value.parse().expect("decimal");
2010 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
2011 }
2012
2013 fn duration_arg(value: &str, unit: &str) -> CommandArg {
2014 let d: rust_decimal::Decimal = value.parse().expect("decimal");
2015 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
2016 }
2017
2018 #[test]
2019 fn as_lemma_source_text_default_is_quoted() {
2020 let fv = DataValue::Definition {
2021 base: Some(ParentType::Primitive {
2022 primitive: PrimitiveKind::Text,
2023 }),
2024 constraints: Some(vec![(
2025 TypeConstraintCommand::Suggest,
2026 vec![text_arg("single")],
2027 )]),
2028 value: None,
2029 };
2030 assert_eq!(
2031 format!("{}", AsLemmaSource(&fv)),
2032 "text -> suggest \"single\""
2033 );
2034 }
2035
2036 #[test]
2037 fn as_lemma_source_number_default_not_quoted() {
2038 let fv = DataValue::Definition {
2039 base: Some(ParentType::Primitive {
2040 primitive: PrimitiveKind::Number,
2041 }),
2042 constraints: Some(vec![(
2043 TypeConstraintCommand::Suggest,
2044 vec![number_arg("10")],
2045 )]),
2046 value: None,
2047 };
2048 assert_eq!(format!("{}", AsLemmaSource(&fv)), "number -> suggest 10");
2049 }
2050
2051 #[test]
2052 fn as_lemma_source_help_always_quoted() {
2053 let fv = DataValue::Definition {
2054 base: Some(ParentType::Primitive {
2055 primitive: PrimitiveKind::Number,
2056 }),
2057 constraints: Some(vec![(
2058 TypeConstraintCommand::Help,
2059 vec![text_arg("Enter a measure")],
2060 )]),
2061 value: None,
2062 };
2063 assert_eq!(
2064 format!("{}", AsLemmaSource(&fv)),
2065 "number -> help \"Enter a measure\""
2066 );
2067 }
2068
2069 #[test]
2070 fn as_lemma_source_text_option_quoted() {
2071 let fv = DataValue::Definition {
2072 base: Some(ParentType::Primitive {
2073 primitive: PrimitiveKind::Text,
2074 }),
2075 constraints: Some(vec![
2076 (TypeConstraintCommand::Option, vec![text_arg("active")]),
2077 (TypeConstraintCommand::Option, vec![text_arg("inactive")]),
2078 ]),
2079 value: None,
2080 };
2081 assert_eq!(
2082 format!("{}", AsLemmaSource(&fv)),
2083 "text -> option \"active\" -> option \"inactive\""
2084 );
2085 }
2086
2087 #[test]
2088 fn as_lemma_source_measure_unit_not_quoted() {
2089 let fv = DataValue::Definition {
2090 base: Some(ParentType::Primitive {
2091 primitive: PrimitiveKind::Measure,
2092 }),
2093 constraints: Some(vec![
2094 (
2095 TypeConstraintCommand::Unit,
2096 vec![CommandArg::Label("eur".to_string()), number_arg("1.00")],
2097 ),
2098 (
2099 TypeConstraintCommand::Unit,
2100 vec![CommandArg::Label("usd".to_string()), number_arg("0.91")],
2101 ),
2102 ]),
2103 value: None,
2104 };
2105 assert_eq!(
2106 format!("{}", AsLemmaSource(&fv)),
2107 "measure -> unit eur 1.00 -> unit usd 0.91"
2108 );
2109 }
2110
2111 #[test]
2112 fn as_lemma_source_measure_minimum_with_unit() {
2113 let fv = DataValue::Definition {
2114 base: Some(ParentType::Primitive {
2115 primitive: PrimitiveKind::Measure,
2116 }),
2117 constraints: Some(vec![(
2118 TypeConstraintCommand::Minimum,
2119 vec![measure_arg("0", "eur")],
2120 )]),
2121 value: None,
2122 };
2123 assert_eq!(
2124 format!("{}", AsLemmaSource(&fv)),
2125 "measure -> minimum 0 eur"
2126 );
2127 }
2128
2129 #[test]
2130 fn as_lemma_source_boolean_default() {
2131 let fv = DataValue::Definition {
2132 base: Some(ParentType::Primitive {
2133 primitive: PrimitiveKind::Boolean,
2134 }),
2135 constraints: Some(vec![(
2136 TypeConstraintCommand::Suggest,
2137 vec![boolean_arg(BooleanValue::True)],
2138 )]),
2139 value: None,
2140 };
2141 assert_eq!(format!("{}", AsLemmaSource(&fv)), "boolean -> suggest true");
2142 }
2143
2144 #[test]
2145 fn as_lemma_source_duration_default() {
2146 let fv = DataValue::Definition {
2147 base: Some(ParentType::Custom {
2148 name: "duration".to_string(),
2149 }),
2150 constraints: Some(vec![(
2151 TypeConstraintCommand::Suggest,
2152 vec![duration_arg("40", "hour")],
2153 )]),
2154 value: None,
2155 };
2156 assert_eq!(
2157 format!("{}", AsLemmaSource(&fv)),
2158 "duration -> suggest 40 hour"
2159 );
2160 }
2161
2162 #[test]
2163 fn as_lemma_source_named_type_default_quoted() {
2164 let fv = DataValue::Definition {
2167 base: Some(ParentType::Custom {
2168 name: "filing_status_type".to_string(),
2169 }),
2170 constraints: Some(vec![(
2171 TypeConstraintCommand::Suggest,
2172 vec![text_arg("single")],
2173 )]),
2174 value: None,
2175 };
2176 assert_eq!(
2177 format!("{}", AsLemmaSource(&fv)),
2178 "filing_status_type -> suggest \"single\""
2179 );
2180 }
2181
2182 #[test]
2183 fn as_lemma_source_help_escapes_quotes() {
2184 let fv = DataValue::Definition {
2185 base: Some(ParentType::Primitive {
2186 primitive: PrimitiveKind::Text,
2187 }),
2188 constraints: Some(vec![(
2189 TypeConstraintCommand::Help,
2190 vec![text_arg("say \"hello\"")],
2191 )]),
2192 value: None,
2193 };
2194 assert_eq!(
2195 format!("{}", AsLemmaSource(&fv)),
2196 "text -> help \"say \\\"hello\\\"\""
2197 );
2198 }
2199
2200 fn unit_arg_expr(prefix: Decimal, factors: &[(&str, i32)]) -> UnitArg {
2201 UnitArg::Expr(
2202 prefix,
2203 factors
2204 .iter()
2205 .map(|(measure_ref, exp)| UnitFactor {
2206 measure_ref: (*measure_ref).to_string(),
2207 exp: *exp,
2208 })
2209 .collect(),
2210 )
2211 }
2212
2213 #[test]
2214 fn unit_arg_display_metre_per_second() {
2215 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -1)]);
2216 assert_eq!(format!("{arg}"), "meter/second");
2217 assert!(
2218 !format!("{arg}").contains("second^-1"),
2219 "must not print denominator as negative exponent"
2220 );
2221 }
2222
2223 #[test]
2224 fn unit_arg_display_meter_per_second_squared() {
2225 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -2)]);
2226 assert_eq!(format!("{arg}"), "meter/second^2");
2227 }
2228
2229 #[test]
2230 fn unit_arg_display_kg_times_mps2() {
2231 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("mps2", 1)]);
2232 assert_eq!(format!("{arg}"), "kg * mps2");
2233 }
2234
2235 #[test]
2236 fn unit_arg_display_numeric_prefix_metre_per_second() {
2237 use std::str::FromStr;
2238 let prefix = Decimal::from_str("3.6").expect("decimal");
2239 let arg = unit_arg_expr(prefix, &[("meter", 1), ("second", -1)]);
2240 assert_eq!(format!("{arg}"), "3.6 meter/second");
2241 }
2242
2243 #[test]
2244 fn unit_arg_display_metre_per_second_times_kg() {
2245 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -1), ("kg", 1)]);
2246 assert_eq!(format!("{arg}"), "meter/second * kg");
2247 }
2248
2249 #[test]
2250 fn unit_arg_display_kg_meter_per_second_squared() {
2251 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("meter", 1), ("second", -2)]);
2252 assert_eq!(format!("{arg}"), "kg * meter/second^2");
2253 }
2254}