1pub(crate) fn ascii_lowercase_logical_name(name: String) -> String {
18 name.to_ascii_lowercase()
19}
20
21pub(crate) fn canonical_logical_name(name: &str) -> std::borrow::Cow<'_, str> {
24 if name.bytes().any(|byte| byte.is_ascii_uppercase()) {
25 std::borrow::Cow::Owned(name.to_ascii_lowercase())
26 } else {
27 std::borrow::Cow::Borrowed(name)
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
33pub struct Span {
34 pub start: usize,
35 pub end: usize,
36 pub line: usize,
37 pub col: usize,
38}
39
40pub struct DepthTracker {
42 depth: usize,
43 max_depth: usize,
44}
45
46impl DepthTracker {
47 pub fn with_max_depth(max_depth: usize) -> Self {
48 Self {
49 depth: 0,
50 max_depth,
51 }
52 }
53
54 pub fn push_depth(&mut self) -> Result<(), usize> {
56 self.depth += 1;
57 if self.depth > self.max_depth {
58 return Err(self.depth);
59 }
60 Ok(())
61 }
62
63 pub fn pop_depth(&mut self) {
64 if self.depth > 0 {
65 self.depth -= 1;
66 }
67 }
68
69 pub fn max_depth(&self) -> usize {
70 self.max_depth
71 }
72}
73
74impl Default for DepthTracker {
75 fn default() -> Self {
76 Self {
77 depth: 0,
78 max_depth: 5,
79 }
80 }
81}
82
83use crate::formatting::expression::{
88 CommandSyntax, CommandView, PredicateView, Syntax, SyntaxNode, SyntaxView,
89};
90use crate::parsing::arena::{ExprKind, ExprRef};
91use crate::parsing::source::Source;
92use rust_decimal::Decimal;
93use serde::Serialize;
94use std::cmp::Ordering;
95use std::fmt;
96use std::hash::{Hash, Hasher};
97
98pub use crate::literals::{BooleanValue, DateTimeValue, TimeValue, TimezoneValue, Value};
99
100#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
101#[serde(rename_all = "snake_case")]
102pub enum EffectiveDate {
103 Origin,
104 DateTimeValue(crate::DateTimeValue),
105}
106
107impl EffectiveDate {
108 pub fn as_ref(&self) -> Option<&crate::DateTimeValue> {
109 match self {
110 EffectiveDate::Origin => None,
111 EffectiveDate::DateTimeValue(dt) => Some(dt),
112 }
113 }
114
115 pub fn from_option(opt: Option<crate::DateTimeValue>) -> Self {
116 match opt {
117 None => EffectiveDate::Origin,
118 Some(dt) => EffectiveDate::DateTimeValue(dt),
119 }
120 }
121
122 pub fn to_option(&self) -> Option<crate::DateTimeValue> {
123 match self {
124 EffectiveDate::Origin => None,
125 EffectiveDate::DateTimeValue(dt) => Some(dt.clone()),
126 }
127 }
128
129 pub fn is_origin(&self) -> bool {
130 matches!(self, EffectiveDate::Origin)
131 }
132}
133
134impl PartialOrd for EffectiveDate {
135 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
136 Some(self.cmp(other))
137 }
138}
139
140impl Ord for EffectiveDate {
141 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
143 self.as_ref().cmp(&other.as_ref())
144 }
145}
146
147impl fmt::Display for EffectiveDate {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match self {
150 EffectiveDate::Origin => Ok(()),
151 EffectiveDate::DateTimeValue(dt) => write!(f, "{}", dt),
152 }
153 }
154}
155
156#[derive(Debug, Clone)]
170pub struct LemmaRepository {
171 pub name: Option<String>,
173 pub dependency: Option<String>,
176 pub start_line: usize,
177 pub source_type: Option<crate::parsing::source::SourceType>,
178}
179
180impl LemmaRepository {
181 #[must_use]
182 pub fn new(name: Option<String>) -> Self {
183 Self {
184 name: name.map(ascii_lowercase_logical_name),
185 dependency: None,
186 start_line: 1,
187 source_type: None,
188 }
189 }
190
191 #[must_use]
192 pub fn with_start_line(mut self, start_line: usize) -> Self {
193 self.start_line = start_line;
194 self
195 }
196
197 #[must_use]
198 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
199 self.source_type = Some(source_type);
200 self
201 }
202
203 #[must_use]
204 pub fn with_dependency(mut self, dependency_id: impl Into<String>) -> Self {
205 self.dependency = Some(dependency_id.into());
206 self
207 }
208}
209
210impl PartialEq for LemmaRepository {
211 fn eq(&self, other: &Self) -> bool {
212 self.name == other.name
213 }
214}
215
216impl Eq for LemmaRepository {}
217
218impl PartialOrd for LemmaRepository {
219 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
220 Some(self.cmp(other))
221 }
222}
223
224impl Ord for LemmaRepository {
225 fn cmp(&self, other: &Self) -> Ordering {
226 self.name.cmp(&other.name)
227 }
228}
229
230impl Hash for LemmaRepository {
231 fn hash<H: Hasher>(&self, state: &mut H) {
232 self.name.hash(state);
233 }
234}
235
236#[derive(Debug, Clone, PartialEq, Eq, Hash)]
240pub struct RepositoryQualifier {
241 pub name: String,
242}
243
244impl RepositoryQualifier {
245 #[must_use]
246 pub fn new(name: impl Into<String>) -> Self {
247 Self {
248 name: ascii_lowercase_logical_name(name.into()),
249 }
250 }
251
252 #[must_use]
254 pub fn is_registry(&self) -> bool {
255 self.name.starts_with('@')
256 }
257}
258
259impl fmt::Display for RepositoryQualifier {
260 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
261 write!(f, "{}", self.name)
262 }
263}
264
265#[derive(Debug, Clone, PartialEq)]
272pub struct LemmaSpec {
273 pub name: String,
274 pub effective_from: EffectiveDate,
275 pub source_type: Option<crate::parsing::source::SourceType>,
276 pub start_line: usize,
277 pub commentary: Option<String>,
278 pub data: Vec<LemmaData>,
279 pub rules: Vec<LemmaRule>,
280 pub meta_fields: Vec<MetaField>,
281}
282
283#[derive(Debug, Clone, PartialEq)]
284pub struct MetaField {
285 pub key: String,
286 pub value: Value,
287 pub source_location: Source,
288}
289
290impl fmt::Display for MetaField {
291 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292 write!(f, "meta {}: {}", self.key, AsLemmaSource(&self.value))
293 }
294}
295
296#[derive(Debug, Clone, PartialEq)]
297pub struct LemmaData {
298 pub reference: Reference,
299 pub value: DataValue,
300 pub source_location: Source,
301}
302
303#[derive(Debug, Clone, PartialEq)]
309pub struct UnlessClause {
310 pub condition: Expression,
311 pub result: Expression,
312 pub source_location: Source,
313}
314
315#[derive(Debug, Clone, PartialEq)]
317pub struct LemmaRule {
318 pub name: String,
319 pub expression: Expression,
320 pub unless_clauses: Vec<UnlessClause>,
321 pub source_location: Source,
322}
323
324pub type Expression = ExprTree<ExpressionKind>;
329
330pub use crate::parsing::arena::{ExprId, ExprTable, ExprTree};
331
332#[derive(Debug, Clone)]
335pub struct ChainStep<Op> {
336 pub op: Op,
337 pub operand: ExprId,
338 pub source_location: Source,
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
342pub enum LogicalConnective {
343 And,
344 Or,
345}
346
347impl LogicalConnective {
348 #[must_use]
349 pub fn absorbing_operand(self) -> bool {
350 match self {
351 Self::And => false,
352 Self::Or => true,
353 }
354 }
355
356 #[must_use]
357 pub fn keyword(self) -> &'static str {
358 match self {
359 Self::And => "and",
360 Self::Or => "or",
361 }
362 }
363}
364
365impl<Op: PartialEq> PartialEq for ChainStep<Op> {
367 fn eq(&self, other: &Self) -> bool {
368 self.op == other.op && self.operand == other.operand
369 }
370}
371
372impl<Op: Eq> Eq for ChainStep<Op> {}
373
374#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
376#[serde(rename_all = "snake_case")]
377pub enum DateRelativeKind {
378 InPast,
379 InFuture,
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
384#[serde(rename_all = "snake_case")]
385pub enum DateCalendarKind {
386 Current,
387 Past,
388 Future,
389 NotIn,
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
394#[serde(rename_all = "snake_case")]
395pub enum CalendarPeriodUnit {
396 Year,
397 Month,
398 Week,
399}
400
401impl CalendarPeriodUnit {
402 #[must_use]
403 pub fn from_keyword(s: &str) -> Option<Self> {
404 match s.trim().to_lowercase().as_str() {
405 "year" => Some(Self::Year),
406 "month" => Some(Self::Month),
407 "week" => Some(Self::Week),
408 _ => None,
409 }
410 }
411}
412
413impl fmt::Display for DateRelativeKind {
414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
415 match self {
416 DateRelativeKind::InPast => write!(f, "in past"),
417 DateRelativeKind::InFuture => write!(f, "in future"),
418 }
419 }
420}
421
422impl fmt::Display for DateCalendarKind {
423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424 match self {
425 DateCalendarKind::Current => write!(f, "in calendar"),
426 DateCalendarKind::Past => write!(f, "in past calendar"),
427 DateCalendarKind::Future => write!(f, "in future calendar"),
428 DateCalendarKind::NotIn => write!(f, "not in calendar"),
429 }
430 }
431}
432
433impl fmt::Display for CalendarPeriodUnit {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 match self {
436 CalendarPeriodUnit::Year => write!(f, "year"),
437 CalendarPeriodUnit::Month => write!(f, "month"),
438 CalendarPeriodUnit::Week => write!(f, "week"),
439 }
440 }
441}
442
443#[derive(Debug, Clone, PartialEq)]
445pub enum ExpressionKind {
446 Literal(Value),
448 ImplicitRowValue,
450 Reference(Reference),
452 Now,
454 DateRelative(DateRelativeKind, ExprId),
457 DateCalendar(DateCalendarKind, CalendarPeriodUnit, ExprId),
460 RangeLiteral(ExprId, ExprId),
462 PastFutureRange(DateRelativeKind, ExprId),
464 RangeContainment(ExprId, ExprId),
466 RangeBound(RangeBound, ExprId),
468 Logical {
470 connective: LogicalConnective,
471 first: ExprId,
472 steps: Vec<ChainStep<()>>,
473 },
474 Arithmetic {
476 first: ExprId,
477 steps: Vec<ChainStep<ArithmeticComputation>>,
478 },
479 Comparison(ExprId, ComparisonComputation, ExprId),
480 UnitConversion(ExprId, ConversionTarget),
481 LogicalNegation(ExprId, NegationType),
482 MathematicalComputation(MathematicalComputation, ExprId),
483 Veto(VetoExpression),
484 ResultIsVeto(ExprId),
486 ListType {
488 element: ParentType,
489 },
490 ListCommand {
492 list: ExprId,
493 command: ListCommand,
494 },
495 StepRounding {
497 operand: ExprId,
498 mode: RoundingMode,
499 step: Option<ExprId>,
500 },
501 Member {
503 base: ExprId,
504 path: Reference,
505 },
506}
507
508impl ExprKind for ExpressionKind {
509 fn for_each_child(&self, mut f: impl FnMut(ExprId)) {
510 match self {
511 ExpressionKind::Literal(_)
512 | ExpressionKind::ImplicitRowValue
513 | ExpressionKind::Reference(_)
514 | ExpressionKind::Now
515 | ExpressionKind::Veto(_)
516 | ExpressionKind::ListType { .. } => {}
517 ExpressionKind::DateRelative(_, inner)
518 | ExpressionKind::DateCalendar(_, _, inner)
519 | ExpressionKind::PastFutureRange(_, inner)
520 | ExpressionKind::RangeBound(_, inner)
521 | ExpressionKind::UnitConversion(inner, _)
522 | ExpressionKind::LogicalNegation(inner, _)
523 | ExpressionKind::MathematicalComputation(_, inner)
524 | ExpressionKind::ResultIsVeto(inner)
525 | ExpressionKind::Member { base: inner, .. } => f(*inner),
526 ExpressionKind::RangeLiteral(left, right)
527 | ExpressionKind::RangeContainment(left, right)
528 | ExpressionKind::Comparison(left, _, right) => {
529 f(*left);
530 f(*right);
531 }
532 ExpressionKind::StepRounding { operand, step, .. } => {
533 f(*operand);
534 if let Some(step) = step {
535 f(*step);
536 }
537 }
538 ExpressionKind::Logical { first, steps, .. } => {
539 f(*first);
540 steps.iter().for_each(|step| f(step.operand));
541 }
542 ExpressionKind::Arithmetic { first, steps } => {
543 f(*first);
544 steps.iter().for_each(|step| f(step.operand));
545 }
546 ExpressionKind::ListCommand { list, command } => {
547 f(*list);
548 command.for_each_child(f);
549 }
550 }
551 }
552
553 fn children_mut(&mut self) -> Vec<&mut ExprId> {
554 match self {
555 ExpressionKind::Literal(_)
556 | ExpressionKind::ImplicitRowValue
557 | ExpressionKind::Reference(_)
558 | ExpressionKind::Now
559 | ExpressionKind::Veto(_)
560 | ExpressionKind::ListType { .. } => Vec::new(),
561 ExpressionKind::DateRelative(_, inner)
562 | ExpressionKind::DateCalendar(_, _, inner)
563 | ExpressionKind::PastFutureRange(_, inner)
564 | ExpressionKind::RangeBound(_, inner)
565 | ExpressionKind::UnitConversion(inner, _)
566 | ExpressionKind::LogicalNegation(inner, _)
567 | ExpressionKind::MathematicalComputation(_, inner)
568 | ExpressionKind::ResultIsVeto(inner)
569 | ExpressionKind::Member { base: inner, .. } => vec![inner],
570 ExpressionKind::RangeLiteral(left, right)
571 | ExpressionKind::RangeContainment(left, right)
572 | ExpressionKind::Comparison(left, _, right) => vec![left, right],
573 ExpressionKind::StepRounding { operand, step, .. } => {
574 let mut children = vec![operand];
575 if let Some(step) = step {
576 children.push(step);
577 }
578 children
579 }
580 ExpressionKind::Logical { first, steps, .. } => std::iter::once(first)
581 .chain(steps.iter_mut().map(|step| &mut step.operand))
582 .collect(),
583 ExpressionKind::Arithmetic { first, steps } => std::iter::once(first)
584 .chain(steps.iter_mut().map(|step| &mut step.operand))
585 .collect(),
586 ExpressionKind::ListCommand { list, command } => std::iter::once(list)
587 .chain(command.children_mut())
588 .collect(),
589 }
590 }
591}
592
593impl ListCommand {
594 fn for_each_child(&self, mut f: impl FnMut(ExprId)) {
595 match self {
596 ListCommand::First
597 | ListCommand::Last
598 | ListCommand::Count
599 | ListCommand::Key { .. } => {}
600 ListCommand::Find { keys } => keys.iter().copied().for_each(f),
601 ListCommand::Sum { argument }
602 | ListCommand::Avg { argument }
603 | ListCommand::Product { argument }
604 | ListCommand::Min { argument }
605 | ListCommand::Max { argument }
606 | ListCommand::Sort { argument, .. } => argument.iter().copied().for_each(f),
607 ListCommand::Any { form } | ListCommand::All { form } | ListCommand::Where { form } => {
608 match form {
609 ListPredicate::Value { expr } => f(*expr),
610 ListPredicate::Instance { expr } => f(*expr),
611 }
612 }
613 ListCommand::Top { n } => f(*n),
614 ListCommand::With { rows } => match rows {
615 ListWithRows::Values(values) => values.iter().copied().for_each(f),
616 ListWithRows::FieldPairs(pairs) => pairs.iter().for_each(|(_, value)| f(*value)),
617 },
618 }
619 }
620
621 fn children_mut(&mut self) -> Vec<&mut ExprId> {
622 match self {
623 ListCommand::First
624 | ListCommand::Last
625 | ListCommand::Count
626 | ListCommand::Key { .. } => Vec::new(),
627 ListCommand::Find { keys } => keys.iter_mut().collect(),
628 ListCommand::Sum { argument }
629 | ListCommand::Avg { argument }
630 | ListCommand::Product { argument }
631 | ListCommand::Min { argument }
632 | ListCommand::Max { argument }
633 | ListCommand::Sort { argument, .. } => argument.iter_mut().collect(),
634 ListCommand::Any { form } | ListCommand::All { form } | ListCommand::Where { form } => {
635 match form {
636 ListPredicate::Value { expr } => vec![expr],
637 ListPredicate::Instance { expr } => vec![expr],
638 }
639 }
640 ListCommand::Top { n } => vec![n],
641 ListCommand::With { rows } => match rows {
642 ListWithRows::Values(values) => values.iter_mut().collect(),
643 ListWithRows::FieldPairs(pairs) => {
644 pairs.iter_mut().map(|(_, value)| value).collect()
645 }
646 },
647 }
648 }
649}
650
651#[derive(Debug, Clone, PartialEq)]
653pub enum ListCommand {
654 Find {
655 keys: Vec<ExprId>,
656 },
657 First,
658 Last,
659 Sum {
660 argument: Option<ExprId>,
661 },
662 Avg {
663 argument: Option<ExprId>,
664 },
665 Product {
666 argument: Option<ExprId>,
667 },
668 Count,
669 Min {
670 argument: Option<ExprId>,
671 },
672 Max {
673 argument: Option<ExprId>,
674 },
675 Any {
676 form: ListPredicate,
677 },
678 All {
679 form: ListPredicate,
680 },
681 Where {
682 form: ListPredicate,
683 },
684 Sort {
685 descending: bool,
686 argument: Option<ExprId>,
687 },
688 Top {
689 n: ExprId,
690 },
691 With {
692 rows: ListWithRows,
693 },
694 Key {
696 fields: Vec<Reference>,
697 },
698}
699
700#[derive(Debug, Clone, PartialEq, Eq, Hash)]
702pub enum RowCondition {
703 Compare(ComparisonComputation),
704 In,
705}
706
707impl fmt::Display for RowCondition {
708 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
709 match self {
710 RowCondition::Compare(op) => write!(f, "{op}"),
711 RowCondition::In => write!(f, "in"),
712 }
713 }
714}
715
716#[derive(Debug, Clone, PartialEq)]
718pub enum ListPredicate {
719 Value { expr: ExprId },
720 Instance { expr: ExprId },
721}
722
723#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
724pub enum ListPredicateForm {
725 Value,
726 Instance,
727}
728
729#[derive(Debug, Clone, PartialEq)]
731pub enum ListWithRows {
732 Values(Vec<ExprId>),
733 FieldPairs(Vec<(Reference, ExprId)>),
734}
735
736#[derive(Debug, Clone, PartialEq, Eq, Hash)]
746pub struct Reference {
747 pub segments: Vec<String>,
748 pub name: String,
749}
750
751impl Reference {
752 #[must_use]
753 pub fn local(name: String) -> Self {
754 Self {
755 segments: Vec::new(),
756 name: ascii_lowercase_logical_name(name),
757 }
758 }
759
760 #[must_use]
761 pub fn from_path(path: Vec<String>) -> Self {
762 if path.is_empty() {
763 Self {
764 segments: Vec::new(),
765 name: String::new(),
766 }
767 } else {
768 let name = ascii_lowercase_logical_name(path[path.len() - 1].clone());
770 let segments = path[..path.len() - 1]
771 .iter()
772 .map(|segment| ascii_lowercase_logical_name(segment.clone()))
773 .collect();
774 Self { segments, name }
775 }
776 }
777
778 #[must_use]
779 pub fn is_local(&self) -> bool {
780 self.segments.is_empty()
781 }
782
783 #[must_use]
784 pub fn full_path(&self) -> Vec<String> {
785 let mut path = self.segments.clone();
786 path.push(self.name.clone());
787 path
788 }
789}
790
791impl fmt::Display for Reference {
792 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
793 for segment in &self.segments {
794 write!(f, "{}.", segment)?;
795 }
796 write!(f, "{}", self.name)
797 }
798}
799
800#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
802#[serde(rename_all = "snake_case")]
803pub enum ArithmeticComputation {
804 Add,
805 Subtract,
806 Multiply,
807 Divide,
808 Modulo,
809 Power,
810}
811
812#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
814#[serde(rename_all = "snake_case")]
815pub enum ComparisonComputation {
816 GreaterThan,
817 LessThan,
818 GreaterThanOrEqual,
819 LessThanOrEqual,
820 Is,
821 IsNot,
822}
823
824impl ComparisonComputation {
825 #[must_use]
827 pub fn is_equal(&self) -> bool {
828 matches!(self, ComparisonComputation::Is)
829 }
830
831 #[must_use]
833 pub fn is_not_equal(&self) -> bool {
834 matches!(self, ComparisonComputation::IsNot)
835 }
836}
837
838#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
844#[serde(rename_all = "snake_case")]
845pub enum ConversionTypeKind {
846 Boolean,
847 Measure,
848 Number,
849 Ratio,
850 Date,
851 Time,
852}
853
854impl fmt::Display for ConversionTypeKind {
855 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
856 let s = match self {
857 ConversionTypeKind::Boolean => "boolean",
858 ConversionTypeKind::Measure => "measure",
859 ConversionTypeKind::Number => "number",
860 ConversionTypeKind::Ratio => "ratio",
861 ConversionTypeKind::Date => "date",
862 ConversionTypeKind::Time => "time",
863 };
864 write!(f, "{}", s)
865 }
866}
867
868impl ConversionTypeKind {
869 #[must_use]
871 pub fn to_primitive_kind(self) -> PrimitiveKind {
872 match self {
873 ConversionTypeKind::Boolean => PrimitiveKind::Boolean,
874 ConversionTypeKind::Measure => PrimitiveKind::Measure,
875 ConversionTypeKind::Number => PrimitiveKind::Number,
876 ConversionTypeKind::Ratio => PrimitiveKind::Ratio,
877 ConversionTypeKind::Date => PrimitiveKind::Date,
878 ConversionTypeKind::Time => PrimitiveKind::Time,
879 }
880 }
881}
882
883#[derive(Debug, Clone, PartialEq, Eq, Hash)]
885pub enum ConversionTarget {
886 Type(ConversionTypeKind),
887 Unit { unit_name: String },
888}
889
890#[derive(Debug, Clone, PartialEq, Eq, Hash)]
892pub enum NegationType {
893 Not,
894}
895
896#[derive(Debug, Clone, PartialEq, Eq, Hash)]
903pub struct VetoExpression {
904 pub message: Option<String>,
905}
906
907#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
909pub enum MathematicalComputation {
910 Sqrt,
911 Sin,
912 Cos,
913 Tan,
914 Asin,
915 Acos,
916 Atan,
917 Log,
918 Exp,
919 Abs,
920}
921
922#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
924#[serde(rename_all = "snake_case")]
925pub enum RoundingMode {
926 Round,
928 Floor,
930 Ceil,
932}
933
934impl RoundingMode {
935 pub fn keyword(self) -> &'static str {
936 match self {
937 RoundingMode::Round => "round",
938 RoundingMode::Floor => "floor",
939 RoundingMode::Ceil => "ceil",
940 }
941 }
942}
943
944impl fmt::Display for RoundingMode {
945 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
946 f.write_str(self.keyword())
947 }
948}
949
950#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
952pub enum RangeBound {
953 Lower,
954 Upper,
955}
956
957impl fmt::Display for RangeBound {
958 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
959 match self {
960 RangeBound::Lower => write!(f, "lower"),
961 RangeBound::Upper => write!(f, "upper"),
962 }
963 }
964}
965
966#[derive(Debug, Clone, PartialEq)]
972pub struct SpecRef {
973 pub repository: Option<RepositoryQualifier>,
976 pub name: String,
978 pub effective: Option<DateTimeValue>,
980 pub repository_span: Option<Span>,
982 pub target_span: Option<Span>,
984}
985
986impl std::fmt::Display for SpecRef {
987 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
988 if let Some(qualifier) = &self.repository {
989 write!(f, "{} ", qualifier)?;
990 }
991 write!(f, "{}", self.name)?;
992 if let Some(d) = &self.effective {
993 write!(f, " {}", d)?;
994 }
995 Ok(())
996 }
997}
998
999impl SpecRef {
1000 pub fn same_repository(name: impl Into<String>) -> Self {
1002 Self {
1003 name: ascii_lowercase_logical_name(name.into()),
1004 repository: None,
1005 effective: None,
1006 repository_span: None,
1007 target_span: None,
1008 }
1009 }
1010
1011 pub fn cross_repository(name: impl Into<String>, qualifier: RepositoryQualifier) -> Self {
1013 Self {
1014 name: ascii_lowercase_logical_name(name.into()),
1015 repository: Some(qualifier),
1016 effective: None,
1017 repository_span: None,
1018 target_span: None,
1019 }
1020 }
1021
1022 pub fn at(&self, effective: &EffectiveDate) -> EffectiveDate {
1025 self.effective
1026 .clone()
1027 .map_or_else(|| effective.clone(), EffectiveDate::DateTimeValue)
1028 }
1029
1030 pub fn resolved_instant(
1033 &self,
1034 consumer_effective_from: Option<&DateTimeValue>,
1035 ) -> Option<DateTimeValue> {
1036 self.effective
1037 .clone()
1038 .or_else(|| consumer_effective_from.cloned())
1039 }
1040}
1041
1042#[derive(Debug, Clone, PartialEq, Eq)]
1050pub struct UnitFactor {
1051 pub measure_ref: String,
1052 pub exp: i32,
1053}
1054
1055#[derive(Debug, Clone, PartialEq, Eq)]
1069pub enum UnitArg {
1070 Factor(Decimal),
1071 Expr(Decimal, Vec<UnitFactor>),
1072 Reference(Reference),
1073}
1074
1075impl fmt::Display for UnitArg {
1076 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1077 match self {
1078 UnitArg::Factor(v) => write!(f, "{}", v),
1079 UnitArg::Expr(prefix, factors) => {
1080 if *prefix != Decimal::ONE {
1081 write!(f, "{} ", prefix)?;
1082 }
1083 for (index, factor) in factors.iter().enumerate() {
1084 if factor.exp == 0 {
1085 unreachable!("BUG: unit factor exponent cannot be zero");
1086 }
1087 if factor.exp > 0 {
1088 if index > 0 {
1089 write!(f, " * ")?;
1090 }
1091 write!(f, "{}", factor.measure_ref)?;
1092 if factor.exp != 1 {
1093 write!(f, "^{}", factor.exp)?;
1094 }
1095 } else {
1096 let denominator_started =
1097 factors[..index].iter().any(|prior| prior.exp < 0);
1098 if denominator_started {
1099 write!(f, " * ")?;
1100 } else {
1101 write!(f, "/")?;
1102 }
1103 write!(f, "{}", factor.measure_ref)?;
1104 let positive_exp = factor
1105 .exp
1106 .checked_neg()
1107 .expect("BUG: negative unit factor exponent");
1108 if positive_exp != 1 {
1109 write!(f, "^{}", positive_exp)?;
1110 }
1111 }
1112 }
1113 Ok(())
1114 }
1115 UnitArg::Reference(reference) => write!(f, "{reference}"),
1116 }
1117 }
1118}
1119
1120#[derive(Debug, Clone, PartialEq, Eq)]
1138pub enum CommandArg {
1139 Literal(crate::literals::Value),
1141 Label(String),
1143 UnitExpr(UnitArg),
1145}
1146
1147impl fmt::Display for CommandArg {
1148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1149 match self {
1150 CommandArg::Literal(v) => write!(f, "{}", v),
1151 CommandArg::Label(s) => write!(f, "{}", s),
1152 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
1153 }
1154 }
1155}
1156
1157#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1159pub enum TypeConstraintCommand {
1160 Help,
1161 Suggest,
1162 With,
1163 Unit,
1164 Trait,
1165 Minimum,
1166 Maximum,
1167 Lower,
1168 Upper,
1169 Decimals,
1170 Option,
1171 Options,
1172 Length,
1173}
1174
1175impl fmt::Display for TypeConstraintCommand {
1176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1177 let s = match self {
1178 TypeConstraintCommand::Help => "help",
1179 TypeConstraintCommand::Suggest => "suggest",
1180 TypeConstraintCommand::With => "with",
1181 TypeConstraintCommand::Unit => "unit",
1182 TypeConstraintCommand::Trait => "trait",
1183 TypeConstraintCommand::Minimum => "minimum",
1184 TypeConstraintCommand::Maximum => "maximum",
1185 TypeConstraintCommand::Lower => "lower",
1186 TypeConstraintCommand::Upper => "upper",
1187 TypeConstraintCommand::Decimals => "decimals",
1188 TypeConstraintCommand::Option => "option",
1189 TypeConstraintCommand::Options => "options",
1190 TypeConstraintCommand::Length => "length",
1191 };
1192 write!(f, "{}", s)
1193 }
1194}
1195
1196#[must_use]
1198pub fn try_parse_type_constraint_command(s: &str) -> Option<TypeConstraintCommand> {
1199 match s.trim() {
1201 s if s.eq_ignore_ascii_case("help") => Some(TypeConstraintCommand::Help),
1202 s if s.eq_ignore_ascii_case("suggest") => Some(TypeConstraintCommand::Suggest),
1203 s if s.eq_ignore_ascii_case("with") => Some(TypeConstraintCommand::With),
1204 s if s.eq_ignore_ascii_case("unit") => Some(TypeConstraintCommand::Unit),
1205 s if s.eq_ignore_ascii_case("trait") => Some(TypeConstraintCommand::Trait),
1206 s if s.eq_ignore_ascii_case("minimum") => Some(TypeConstraintCommand::Minimum),
1207 s if s.eq_ignore_ascii_case("maximum") => Some(TypeConstraintCommand::Maximum),
1208 s if s.eq_ignore_ascii_case("lower") => Some(TypeConstraintCommand::Lower),
1209 s if s.eq_ignore_ascii_case("upper") => Some(TypeConstraintCommand::Upper),
1210 s if s.eq_ignore_ascii_case("decimals") => Some(TypeConstraintCommand::Decimals),
1211 s if s.eq_ignore_ascii_case("option") => Some(TypeConstraintCommand::Option),
1212 s if s.eq_ignore_ascii_case("options") => Some(TypeConstraintCommand::Options),
1213 s if s.eq_ignore_ascii_case("length") => Some(TypeConstraintCommand::Length),
1214 _ => None,
1215 }
1216}
1217
1218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1220pub enum ContinuationShape {
1221 Assignment,
1222 Single,
1223 CommaSeparated,
1224}
1225
1226impl TypeConstraintCommand {
1227 #[must_use]
1228 pub fn continuation_shape(self) -> ContinuationShape {
1229 match self {
1230 TypeConstraintCommand::Unit => ContinuationShape::Assignment,
1231 TypeConstraintCommand::With | TypeConstraintCommand::Options => {
1232 ContinuationShape::CommaSeparated
1233 }
1234 TypeConstraintCommand::Help
1235 | TypeConstraintCommand::Suggest
1236 | TypeConstraintCommand::Trait
1237 | TypeConstraintCommand::Minimum
1238 | TypeConstraintCommand::Maximum
1239 | TypeConstraintCommand::Lower
1240 | TypeConstraintCommand::Upper
1241 | TypeConstraintCommand::Decimals
1242 | TypeConstraintCommand::Option
1243 | TypeConstraintCommand::Length => ContinuationShape::Single,
1244 }
1245 }
1246
1247 pub const ALL: &[Self] = &[
1248 Self::Help,
1249 Self::Suggest,
1250 Self::With,
1251 Self::Unit,
1252 Self::Trait,
1253 Self::Minimum,
1254 Self::Maximum,
1255 Self::Lower,
1256 Self::Upper,
1257 Self::Decimals,
1258 Self::Option,
1259 Self::Options,
1260 Self::Length,
1261 ];
1262
1263 #[must_use]
1265 pub fn is_data_constraint(self) -> bool {
1266 true
1267 }
1268}
1269
1270#[derive(Debug, Clone, PartialEq)]
1272pub struct Constraint {
1273 pub command: TypeConstraintCommand,
1274 pub args: Vec<CommandArg>,
1275 pub source_location: crate::parsing::source::Source,
1276 pub deprecated_without_colon: bool,
1278}
1279
1280impl Constraint {
1281 #[must_use]
1282 pub fn new(
1283 command: TypeConstraintCommand,
1284 args: Vec<CommandArg>,
1285 source_location: crate::parsing::source::Source,
1286 ) -> Self {
1287 Self {
1288 command,
1289 args,
1290 source_location,
1291 deprecated_without_colon: false,
1292 }
1293 }
1294}
1295
1296#[cfg(test)]
1297pub(crate) fn test_constraint(command: TypeConstraintCommand, args: Vec<CommandArg>) -> Constraint {
1298 Constraint::new(
1299 command,
1300 args,
1301 crate::parsing::source::Source::new(
1302 crate::parsing::source::SourceType::Volatile,
1303 Span {
1304 start: 0,
1305 end: 0,
1306 line: 1,
1307 col: 0,
1308 },
1309 ),
1310 )
1311}
1312
1313#[derive(Debug, Clone, PartialEq)]
1315pub enum WithRhs {
1316 Literal(Value),
1317 Reference { target: Reference },
1318}
1319
1320#[derive(Debug, Clone, PartialEq)]
1322pub struct UsesBinding {
1323 pub path: Reference,
1325 pub rhs: UsesBindingRhs,
1326 pub source_location: Source,
1327}
1328
1329#[derive(Debug, Clone, PartialEq)]
1331pub enum UsesBindingRhs {
1332 Literal(Value),
1333 References { targets: Vec<Reference> },
1334}
1335
1336#[derive(Debug, Clone, PartialEq)]
1338pub struct RowBinding {
1339 pub path: Reference,
1340 pub rhs: WithRhs,
1341 pub source_location: Source,
1342}
1343
1344#[must_use]
1346pub fn prefix_reference(alias: &str, relative: &Reference) -> Reference {
1347 let mut segments = vec![ascii_lowercase_logical_name(alias.to_string())];
1348 segments.extend(relative.segments.iter().cloned());
1349 Reference {
1350 segments,
1351 name: relative.name.clone(),
1352 }
1353}
1354
1355#[derive(Debug, Clone, PartialEq)]
1356pub enum DataValue {
1358 Definition {
1370 base: Option<ParentType>,
1371 constraints: Option<Vec<Constraint>>,
1372 value: Option<Value>,
1373 list: Option<InstanceListBody>,
1374 },
1375 Import {
1379 spec_ref: SpecRef,
1380 bindings: Vec<UsesBinding>,
1381 effective_ref: Option<Reference>,
1382 },
1383 ImportList {
1386 spec_ref: SpecRef,
1387 list: InstanceListBody,
1388 },
1389}
1390
1391#[derive(Debug, Clone, PartialEq)]
1393pub struct InstanceListBody {
1394 pub key: Option<InstanceListKey>,
1395 pub rows: Vec<InstanceListRow>,
1396 pub option_rows: Vec<InstanceListRow>,
1398}
1399
1400#[derive(Debug, Clone, PartialEq)]
1402pub struct InstanceListKey {
1403 pub fields: Vec<Reference>,
1404 pub source_location: Source,
1405}
1406
1407#[derive(Debug, Clone, PartialEq)]
1409pub enum InstanceListRow {
1410 Fields {
1412 bindings: Vec<RowBinding>,
1413 source_location: Source,
1414 },
1415 Values {
1417 values: Vec<WithRhs>,
1418 source_location: Source,
1419 },
1420}
1421
1422impl DataValue {
1423 #[must_use]
1424 pub fn import(spec_ref: SpecRef) -> Self {
1425 Self::Import {
1426 spec_ref,
1427 bindings: Vec::new(),
1428 effective_ref: None,
1429 }
1430 }
1431
1432 #[must_use]
1434 pub fn is_definition_literal_only(&self) -> bool {
1435 matches!(
1436 self,
1437 DataValue::Definition {
1438 base: None,
1439 constraints: None,
1440 value: Some(_),
1441 list: None,
1442 }
1443 )
1444 }
1445
1446 #[must_use]
1451 pub fn definition_needs_type_resolution(&self) -> bool {
1452 match self {
1453 DataValue::Definition { list: Some(_), .. } => false,
1454 DataValue::Definition { base: Some(_), .. }
1455 | DataValue::Definition {
1456 constraints: Some(_),
1457 ..
1458 } => true,
1459 DataValue::Definition {
1460 base: None,
1461 constraints: None,
1462 value: Some(v),
1463 list: None,
1464 } => !matches!(v, Value::NumberWithUnit(_, _)),
1465 DataValue::Import { .. }
1466 | DataValue::ImportList { .. }
1467 | DataValue::Definition { .. } => false,
1468 }
1469 }
1470
1471 #[must_use]
1474 pub fn uses_spec_ref(&self) -> Option<&SpecRef> {
1475 match self {
1476 DataValue::Import { spec_ref, .. } | DataValue::ImportList { spec_ref, .. } => {
1477 Some(spec_ref)
1478 }
1479 DataValue::Definition { .. } => None,
1480 }
1481 }
1482}
1483
1484impl LemmaData {
1485 #[must_use]
1486 pub fn new(reference: Reference, value: DataValue, source_location: Source) -> Self {
1487 Self {
1488 reference,
1489 value,
1490 source_location,
1491 }
1492 }
1493}
1494
1495impl LemmaSpec {
1496 #[must_use]
1497 pub fn new(name: String) -> Self {
1498 Self {
1499 name: ascii_lowercase_logical_name(name),
1500 effective_from: EffectiveDate::Origin,
1501 source_type: None,
1502 start_line: 1,
1503 commentary: None,
1504 data: Vec::new(),
1505 rules: Vec::new(),
1506 meta_fields: Vec::new(),
1507 }
1508 }
1509
1510 pub fn effective_from(&self) -> Option<&DateTimeValue> {
1512 self.effective_from.as_ref()
1513 }
1514
1515 #[must_use]
1516 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
1517 self.source_type = Some(source_type);
1518 self
1519 }
1520
1521 #[must_use]
1522 pub fn with_start_line(mut self, start_line: usize) -> Self {
1523 self.start_line = start_line;
1524 self
1525 }
1526
1527 #[must_use]
1528 pub fn set_commentary(mut self, commentary: String) -> Self {
1529 self.commentary = Some(commentary);
1530 self
1531 }
1532
1533 #[must_use]
1534 pub fn add_data(mut self, data: LemmaData) -> Self {
1535 self.data.push(data);
1536 self
1537 }
1538
1539 #[must_use]
1540 pub fn add_rule(mut self, rule: LemmaRule) -> Self {
1541 self.rules.push(rule);
1542 self
1543 }
1544
1545 #[must_use]
1546 pub fn add_meta_field(mut self, meta: MetaField) -> Self {
1547 self.meta_fields.push(meta);
1548 self
1549 }
1550}
1551
1552fn value_starts_with_sign(value: &crate::literals::Value) -> bool {
1553 let mut current = value;
1554 loop {
1555 match current {
1556 crate::literals::Value::Number(n) | crate::literals::Value::NumberWithUnit(n, _) => {
1557 return n.is_sign_negative()
1558 }
1559 crate::literals::Value::Range(left, _) => current = left.as_ref(),
1560 _ => return false,
1561 }
1562 }
1563}
1564
1565#[derive(Clone, Copy)]
1567pub(crate) enum AstAtom<'a> {
1568 Literal(&'a crate::literals::Value),
1569 Reference(&'a Reference),
1570 Now,
1571 Veto(&'a VetoExpression),
1572 ListType(&'a ParentType),
1573}
1574
1575impl fmt::Display for AstAtom<'_> {
1576 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1577 match self {
1578 AstAtom::Literal(value) => write!(f, "{}", AsLemmaSource(*value)),
1579 AstAtom::Reference(reference) => write!(f, "{reference}"),
1580 AstAtom::Now => write!(f, "now"),
1581 AstAtom::Veto(veto) => write!(f, "{veto}"),
1582 AstAtom::ListType(element) => write!(f, "{element}"),
1583 }
1584 }
1585}
1586
1587impl fmt::Display for VetoExpression {
1588 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1589 match &self.message {
1590 Some(msg) => write!(f, "veto {}", quote_lemma_text(msg)),
1591 None => write!(f, "veto"),
1592 }
1593 }
1594}
1595
1596impl<'a> SyntaxNode<'a> for ExprRef<'a, ExpressionKind> {
1597 type Atom = AstAtom<'a>;
1598
1599 fn view(self) -> SyntaxView<'a, Self> {
1600 let atom = |atom| SyntaxView::Atom {
1601 atom,
1602 starts_with_sign: false,
1603 };
1604 match self.kind() {
1605 ExpressionKind::Literal(value) => SyntaxView::Atom {
1606 atom: AstAtom::Literal(value),
1607 starts_with_sign: value_starts_with_sign(value),
1608 },
1609 ExpressionKind::ImplicitRowValue => SyntaxView::ImplicitRowValue,
1610 ExpressionKind::Reference(reference) => atom(AstAtom::Reference(reference)),
1611 ExpressionKind::Now => atom(AstAtom::Now),
1612 ExpressionKind::Veto(veto) => atom(AstAtom::Veto(veto)),
1613 ExpressionKind::ListType { element } => atom(AstAtom::ListType(element)),
1614 ExpressionKind::DateRelative(kind, date) => {
1615 SyntaxView::DateRelative(kind, self.child(*date))
1616 }
1617 ExpressionKind::DateCalendar(kind, unit, date) => {
1618 SyntaxView::DateCalendar(kind, unit, self.child(*date))
1619 }
1620 ExpressionKind::RangeLiteral(left, right) => {
1621 SyntaxView::RangeLiteral(self.child(*left), self.child(*right))
1622 }
1623 ExpressionKind::PastFutureRange(kind, offset) => {
1624 SyntaxView::PastFutureRange(kind, self.child(*offset))
1625 }
1626 ExpressionKind::RangeContainment(value, range) => {
1627 SyntaxView::RangeContainment(self.child(*value), self.child(*range))
1628 }
1629 ExpressionKind::RangeBound(bound, operand) => {
1630 SyntaxView::Prefix(bound, self.child(*operand))
1631 }
1632 ExpressionKind::Logical {
1633 connective,
1634 first,
1635 steps,
1636 } => SyntaxView::Logical {
1637 connective: *connective,
1638 operands: std::iter::once(*first)
1639 .chain(steps.iter().map(|step| step.operand))
1640 .map(|id| self.child(id))
1641 .collect(),
1642 },
1643 ExpressionKind::Arithmetic { first, steps } => {
1644 let level = match steps.first() {
1645 Some(step) => &step.op,
1646 None => panic!("BUG: arithmetic chain without steps"),
1647 };
1648 SyntaxView::Chain {
1649 level,
1650 operands: std::iter::once(*first)
1651 .chain(steps.iter().map(|step| step.operand))
1652 .map(|id| self.child(id))
1653 .collect(),
1654 ops: steps.iter().map(|step| &step.op).collect(),
1655 }
1656 }
1657 ExpressionKind::Comparison(left, op, right) => {
1658 SyntaxView::Comparison(self.child(*left), op, self.child(*right))
1659 }
1660 ExpressionKind::UnitConversion(value, target) => {
1661 SyntaxView::UnitConversion(self.child(*value), target)
1662 }
1663 ExpressionKind::LogicalNegation(operand, NegationType::Not) => {
1664 match self.child(*operand).kind() {
1665 ExpressionKind::ResultIsVeto(inner) => {
1666 SyntaxView::IsNotVeto(self.child(*inner))
1667 }
1668 _ => SyntaxView::Not(self.child(*operand)),
1669 }
1670 }
1671 ExpressionKind::MathematicalComputation(op, operand) => {
1672 SyntaxView::Prefix(op, self.child(*operand))
1673 }
1674 ExpressionKind::ResultIsVeto(operand) => SyntaxView::IsVeto(self.child(*operand)),
1675 ExpressionKind::ListCommand { list, command } => SyntaxView::ListCommand {
1676 list: self.child(*list),
1677 command: command_view(self.table, command),
1678 },
1679 ExpressionKind::StepRounding {
1680 operand,
1681 mode,
1682 step,
1683 } => SyntaxView::ListCommand {
1684 list: self.child(*operand),
1685 command: CommandView::Step(mode.keyword(), step.map(|id| self.child(id))),
1686 },
1687 ExpressionKind::Member { base, path } => SyntaxView::Member {
1688 base: self.child(*base),
1689 path: AstAtom::Reference(path),
1690 parenthesize_base: true,
1691 },
1692 }
1693 }
1694}
1695
1696fn predicate_view<'a>(
1697 table: &'a ExprTable<ExpressionKind>,
1698 form: &'a ListPredicate,
1699) -> PredicateView<ExprRef<'a, ExpressionKind>> {
1700 let child = |id: ExprId| ExprRef { table, id };
1701 match form {
1702 ListPredicate::Value { expr } => PredicateView::Value(child(*expr)),
1703 ListPredicate::Instance { expr } => PredicateView::Instance(child(*expr)),
1704 }
1705}
1706
1707pub(crate) fn display_list_command(
1709 table: &ExprTable<ExpressionKind>,
1710 command: &ListCommand,
1711) -> String {
1712 CommandSyntax(&command_view(table, command)).to_string()
1713}
1714
1715pub(crate) fn display_pipe_command(table: &ExprTable<ExpressionKind>, id: ExprId) -> String {
1717 let command = match table.kind(id) {
1718 ExpressionKind::ListCommand { command, .. } => command_view(table, command),
1719 ExpressionKind::StepRounding { mode, step, .. } => {
1720 CommandView::Step(mode.keyword(), step.map(|id| ExprRef { table, id }))
1721 }
1722 other => panic!("BUG: piped expression must be a `->` command, got {other:?}"),
1723 };
1724 format!("{:#}", CommandSyntax(&command))
1725}
1726
1727pub(crate) fn display_expression(table: &ExprTable<ExpressionKind>, id: ExprId) -> String {
1729 Syntax(ExprRef { table, id }).to_string()
1730}
1731
1732fn command_view<'a>(
1733 table: &'a ExprTable<ExpressionKind>,
1734 command: &'a ListCommand,
1735) -> CommandView<'a, ExprRef<'a, ExpressionKind>> {
1736 let child = |id: &ExprId| ExprRef { table, id: *id };
1737 match command {
1738 ListCommand::Find { keys } => CommandView::Find(keys.iter().map(child).collect()),
1739 ListCommand::First => CommandView::First,
1740 ListCommand::Last => CommandView::Last,
1741 ListCommand::Count => CommandView::Count,
1742 ListCommand::Sum { argument } => CommandView::Reduce("sum", argument.as_ref().map(child)),
1743 ListCommand::Avg { argument } => CommandView::Reduce("avg", argument.as_ref().map(child)),
1744 ListCommand::Product { argument } => {
1745 CommandView::Reduce("product", argument.as_ref().map(child))
1746 }
1747 ListCommand::Min { argument } => CommandView::Reduce("min", argument.as_ref().map(child)),
1748 ListCommand::Max { argument } => CommandView::Reduce("max", argument.as_ref().map(child)),
1749 ListCommand::Any { form } => CommandView::Predicate("any", predicate_view(table, form)),
1750 ListCommand::All { form } => CommandView::Predicate("all", predicate_view(table, form)),
1751 ListCommand::Where { form } => CommandView::Predicate("where", predicate_view(table, form)),
1752 ListCommand::Sort {
1753 descending,
1754 argument,
1755 } => CommandView::Sort {
1756 descending: *descending,
1757 argument: argument.as_ref().map(child),
1758 },
1759 ListCommand::Top { n } => CommandView::Top(child(n)),
1760 ListCommand::With { rows } => CommandView::With(match rows {
1761 ListWithRows::Values(values) => {
1762 values.iter().map(|value| (None, child(value))).collect()
1763 }
1764 ListWithRows::FieldPairs(pairs) => pairs
1765 .iter()
1766 .map(|(name, value)| (Some(AstAtom::Reference(name)), child(value)))
1767 .collect(),
1768 }),
1769 ListCommand::Key { fields } => {
1770 CommandView::Key(fields.iter().map(AstAtom::Reference).collect())
1771 }
1772 }
1773}
1774
1775impl fmt::Display for Expression {
1776 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1777 fmt::Display::fmt(&Syntax(self.root()), f)
1778 }
1779}
1780
1781impl fmt::Display for ConversionTarget {
1782 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1783 match self {
1784 ConversionTarget::Type(kind) => write!(f, "{kind}"),
1785 ConversionTarget::Unit { unit_name } => write!(f, "{unit_name}"),
1786 }
1787 }
1788}
1789
1790impl fmt::Display for ArithmeticComputation {
1791 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1792 match self {
1793 ArithmeticComputation::Add => write!(f, "+"),
1794 ArithmeticComputation::Subtract => write!(f, "-"),
1795 ArithmeticComputation::Multiply => write!(f, "*"),
1796 ArithmeticComputation::Divide => write!(f, "/"),
1797 ArithmeticComputation::Modulo => write!(f, "mod"),
1798 ArithmeticComputation::Power => write!(f, "^"),
1799 }
1800 }
1801}
1802
1803impl fmt::Display for ComparisonComputation {
1804 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1805 match self {
1806 ComparisonComputation::GreaterThan => write!(f, ">"),
1807 ComparisonComputation::LessThan => write!(f, "<"),
1808 ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1809 ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1810 ComparisonComputation::Is => write!(f, "is"),
1811 ComparisonComputation::IsNot => write!(f, "is not"),
1812 }
1813 }
1814}
1815
1816impl fmt::Display for MathematicalComputation {
1817 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1818 match self {
1819 MathematicalComputation::Sqrt => write!(f, "sqrt"),
1820 MathematicalComputation::Sin => write!(f, "sin"),
1821 MathematicalComputation::Cos => write!(f, "cos"),
1822 MathematicalComputation::Tan => write!(f, "tan"),
1823 MathematicalComputation::Asin => write!(f, "asin"),
1824 MathematicalComputation::Acos => write!(f, "acos"),
1825 MathematicalComputation::Atan => write!(f, "atan"),
1826 MathematicalComputation::Log => write!(f, "log"),
1827 MathematicalComputation::Exp => write!(f, "exp"),
1828 MathematicalComputation::Abs => write!(f, "abs"),
1829 }
1830 }
1831}
1832
1833#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1839#[serde(rename_all = "snake_case")]
1840pub enum PrimitiveKind {
1841 Boolean,
1842 Measure,
1843 MeasureRange,
1844 Number,
1845 NumberRange,
1846 Ratio,
1847 RatioRange,
1848 Text,
1849 Date,
1850 DateRange,
1851 Time,
1852 TimeRange,
1853}
1854
1855impl std::fmt::Display for PrimitiveKind {
1856 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1857 let s = match self {
1858 PrimitiveKind::Boolean => "boolean",
1859 PrimitiveKind::Measure => "measure",
1860 PrimitiveKind::MeasureRange => "measure range",
1861 PrimitiveKind::Number => "number",
1862 PrimitiveKind::NumberRange => "number range",
1863 PrimitiveKind::Ratio => "ratio",
1864 PrimitiveKind::RatioRange => "ratio range",
1865 PrimitiveKind::Text => "text",
1866 PrimitiveKind::Date => "date",
1867 PrimitiveKind::DateRange => "date range",
1868 PrimitiveKind::Time => "time",
1869 PrimitiveKind::TimeRange => "time range",
1870 };
1871 write!(f, "{}", s)
1872 }
1873}
1874
1875#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1884pub enum ParentType {
1885 Primitive {
1886 primitive: PrimitiveKind,
1887 },
1888 Custom {
1889 name: String,
1890 },
1891 Qualified {
1894 spec_alias: String,
1895 inner: Box<ParentType>,
1896 },
1897 Ranged {
1899 inner: Box<ParentType>,
1900 },
1901 List {
1903 inner: Box<ParentType>,
1904 },
1905}
1906
1907impl ParentType {
1908 #[must_use]
1910 pub fn custom(name: impl Into<String>) -> Self {
1911 Self::Custom { name: name.into() }
1912 }
1913}
1914
1915impl std::fmt::Display for ParentType {
1916 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1917 match self {
1918 ParentType::Primitive { primitive } => write!(f, "{}", primitive),
1919 ParentType::Custom { name } => write!(f, "{name}"),
1920 ParentType::Qualified { spec_alias, inner } => {
1921 write!(f, "{spec_alias}.{inner}")
1922 }
1923 ParentType::Ranged { inner } => write!(f, "{inner} range"),
1924 ParentType::List { inner } => write!(f, "{inner} list"),
1925 }
1926 }
1927}
1928
1929pub struct AsLemmaSource<'a, T: ?Sized>(pub &'a T);
1935
1936pub fn quote_lemma_text(s: &str) -> String {
1939 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1940 format!("\"{}\"", escaped)
1941}
1942
1943fn format_decimal_source(n: &Decimal) -> String {
1948 let raw = if n.fract().is_zero() {
1949 n.trunc().to_string()
1950 } else {
1951 n.to_string()
1952 };
1953 group_digits(&raw)
1954}
1955
1956fn group_digits(s: &str) -> String {
1960 let (sign, rest) = if s.starts_with('-') || s.starts_with('+') {
1961 (&s[..1], &s[1..])
1962 } else {
1963 ("", s)
1964 };
1965
1966 let (int_part, frac_part) = match rest.find('.') {
1967 Some(pos) => (&rest[..pos], &rest[pos..]),
1968 None => (rest, ""),
1969 };
1970
1971 if int_part.len() < 4 {
1972 return s.to_string();
1973 }
1974
1975 let mut grouped = String::with_capacity(int_part.len() + int_part.len() / 3);
1976 for (i, ch) in int_part.chars().enumerate() {
1977 let digits_remaining = int_part.len() - i;
1978 if i > 0 && digits_remaining % 3 == 0 {
1979 grouped.push('_');
1980 }
1981 grouped.push(ch);
1982 }
1983
1984 format!("{}{}{}", sign, grouped, frac_part)
1985}
1986
1987impl<'a> fmt::Display for AsLemmaSource<'a, CommandArg> {
1988 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1989 use crate::literals::Value;
1990 match self.0 {
1991 CommandArg::Literal(Value::Number(d)) => {
1992 write!(f, "{}", group_digits(&d.to_string()))
1993 }
1994 CommandArg::Literal(Value::NumberWithUnit(d, unit)) => {
1995 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1996 }
1997 CommandArg::Literal(value) => write!(f, "{}", AsLemmaSource(value)),
1998 CommandArg::Label(s) => write!(f, "{}", s),
1999 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
2000 }
2001 }
2002}
2003
2004pub(crate) fn format_assignment_continuation(
2006 command: &str,
2007 key: &str,
2008 value: &impl fmt::Display,
2009) -> String {
2010 format!("{} {}: {}", command, key, value)
2011}
2012
2013pub(crate) fn format_constraint_as_source(
2015 cmd: &TypeConstraintCommand,
2016 args: &[CommandArg],
2017) -> String {
2018 if *cmd == TypeConstraintCommand::Unit {
2019 let Some(CommandArg::Label(name)) = args.first() else {
2020 return cmd.to_string();
2021 };
2022 let Some(CommandArg::UnitExpr(unit_arg)) = args.get(1) else {
2023 return format!("{} {}", cmd, name);
2024 };
2025 return format_assignment_continuation("unit", name, unit_arg);
2026 }
2027
2028 if args.is_empty() {
2029 cmd.to_string()
2030 } else {
2031 let args_str: Vec<String> = args
2032 .iter()
2033 .map(|a| format!("{}", AsLemmaSource(a)))
2034 .collect();
2035 let sep = match cmd.continuation_shape() {
2036 ContinuationShape::CommaSeparated => ", ",
2037 ContinuationShape::Single | ContinuationShape::Assignment => " ",
2038 };
2039 format!("{} {}", cmd, args_str.join(sep))
2040 }
2041}
2042
2043fn format_constraints_as_source(constraints: &[Constraint], separator: &str) -> String {
2046 constraints
2047 .iter()
2048 .map(|row| format_constraint_as_source(&row.command, &row.args))
2049 .collect::<Vec<_>>()
2050 .join(separator)
2051}
2052
2053impl<'a> fmt::Display for AsLemmaSource<'a, Value> {
2056 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2057 match self.0 {
2058 Value::Number(n) => write!(f, "{}", format_decimal_source(n)),
2059 Value::Text(s) => write!(f, "{}", quote_lemma_text(s)),
2060 Value::Date(dt) => write!(f, "{dt}"),
2061 Value::Time(t) => write!(f, "{t}"),
2062 Value::Boolean(b) => write!(f, "{}", b),
2063 Value::NumberWithUnit(n, u) => match u.as_str() {
2064 "percent" => write!(f, "{}%", format_decimal_source(n)),
2065 "permille" => write!(f, "{}%%", format_decimal_source(n)),
2066 unit => write!(f, "{} {}", format_decimal_source(n), unit),
2067 },
2068 Value::Range(left, right) => {
2069 write!(
2070 f,
2071 "{}...{}",
2072 AsLemmaSource(left.as_ref()),
2073 AsLemmaSource(right.as_ref())
2074 )
2075 }
2076 }
2077 }
2078}
2079
2080impl<'a> fmt::Display for AsLemmaSource<'a, DataValue> {
2083 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2084 match self.0 {
2085 DataValue::Definition {
2086 base,
2087 constraints,
2088 value,
2089 list: _,
2090 } => {
2091 if base.is_none() && constraints.is_none() {
2092 if let Some(v) = value {
2093 return write!(f, "{}", AsLemmaSource(v));
2094 }
2095 }
2096 let base_str = match base.as_ref() {
2097 Some(b) => format!("{}", b),
2098 None => match value {
2099 Some(v) => {
2100 if let Some(ref constraints_vec) = constraints {
2101 let constraint_str =
2102 format_constraints_as_source(constraints_vec, " -> ");
2103 return write!(f, "{} -> {}", AsLemmaSource(v), constraint_str);
2104 }
2105 return write!(f, "{}", AsLemmaSource(v));
2106 }
2107 None => String::new(),
2108 },
2109 };
2110 if let Some(ref constraints_vec) = constraints {
2111 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
2112 write!(f, "{} -> {}", base_str, constraint_str)
2113 } else {
2114 write!(f, "{}", base_str)
2115 }
2116 }
2117 DataValue::Import {
2118 spec_ref,
2119 bindings: _,
2120 effective_ref: _,
2121 }
2122 | DataValue::ImportList { spec_ref, list: _ } => {
2123 write!(f, "uses {}", spec_ref)
2124 }
2125 }
2126 }
2127}
2128
2129pub(crate) fn canonicalize_value(value: &mut Value) {
2130 if let Value::NumberWithUnit(_, unit) = value {
2131 *unit = ascii_lowercase_logical_name(std::mem::take(unit));
2132 }
2133}
2134
2135pub(crate) fn canonicalize_reference(reference: &mut Reference) {
2136 for segment in &mut reference.segments {
2137 *segment = ascii_lowercase_logical_name(std::mem::take(segment));
2138 }
2139 reference.name = ascii_lowercase_logical_name(std::mem::take(&mut reference.name));
2140}
2141
2142pub(crate) fn canonicalize_spec_ref(spec_ref: &mut SpecRef) {
2143 spec_ref.name = ascii_lowercase_logical_name(std::mem::take(&mut spec_ref.name));
2144 if let Some(qualifier) = spec_ref.repository.as_mut() {
2145 qualifier.name = ascii_lowercase_logical_name(std::mem::take(&mut qualifier.name));
2146 }
2147}
2148
2149pub(crate) fn canonicalize_parent_type(parent: &mut ParentType) {
2150 let mut current = parent;
2151 loop {
2152 current = match current {
2153 ParentType::Custom { name } => {
2154 *name = ascii_lowercase_logical_name(std::mem::take(name));
2155 return;
2156 }
2157 ParentType::Qualified { spec_alias, inner } => {
2158 *spec_alias = ascii_lowercase_logical_name(std::mem::take(spec_alias));
2159 inner.as_mut()
2160 }
2161 ParentType::Ranged { inner } | ParentType::List { inner } => inner.as_mut(),
2162 ParentType::Primitive { .. } => return,
2163 }
2164 }
2165}
2166
2167pub(crate) fn canonicalize_unit_factor(factor: &mut UnitFactor) {
2168 factor.measure_ref = ascii_lowercase_logical_name(std::mem::take(&mut factor.measure_ref));
2169}
2170
2171pub(crate) fn canonicalize_unit_arg(unit_arg: &mut UnitArg) {
2172 match unit_arg {
2173 UnitArg::Expr(_, factors) => {
2174 for factor in factors {
2175 canonicalize_unit_factor(factor);
2176 }
2177 }
2178 UnitArg::Reference(reference) => {
2179 reference.name = ascii_lowercase_logical_name(std::mem::take(&mut reference.name));
2180 for segment in &mut reference.segments {
2181 *segment = ascii_lowercase_logical_name(std::mem::take(segment));
2182 }
2183 }
2184 UnitArg::Factor(_) => {}
2185 }
2186}
2187
2188pub(crate) fn canonicalize_command_arg(command_arg: &mut CommandArg) {
2189 match command_arg {
2190 CommandArg::Literal(value) => canonicalize_value(value),
2191 CommandArg::Label(label) => {
2192 *label = ascii_lowercase_logical_name(std::mem::take(label));
2193 }
2194 CommandArg::UnitExpr(unit_arg) => canonicalize_unit_arg(unit_arg),
2195 }
2196}
2197
2198pub(crate) fn canonicalize_constraints(constraints: &mut [Constraint]) {
2199 for row in constraints {
2200 for arg in &mut row.args {
2201 canonicalize_command_arg(arg);
2202 }
2203 }
2204}
2205
2206pub(crate) fn canonicalize_expression(expression: &mut Expression) {
2207 for kind in expression.table.kinds_mut() {
2208 canonicalize_expression_kind(kind);
2209 }
2210}
2211
2212fn canonicalize_expression_kind(kind: &mut ExpressionKind) {
2214 match kind {
2215 ExpressionKind::Literal(value) => canonicalize_value(value),
2216 ExpressionKind::Reference(reference) => canonicalize_reference(reference),
2217 ExpressionKind::ListType { element } => canonicalize_parent_type(element),
2218 ExpressionKind::Member { path, .. } => canonicalize_reference(path),
2219 ExpressionKind::ListCommand { command, .. } => canonicalize_list_command(command),
2220 ExpressionKind::Now
2221 | ExpressionKind::ImplicitRowValue
2222 | ExpressionKind::DateRelative(..)
2223 | ExpressionKind::DateCalendar(..)
2224 | ExpressionKind::RangeLiteral(..)
2225 | ExpressionKind::PastFutureRange(..)
2226 | ExpressionKind::RangeContainment(..)
2227 | ExpressionKind::RangeBound(..)
2228 | ExpressionKind::Logical { .. }
2229 | ExpressionKind::Arithmetic { .. }
2230 | ExpressionKind::Comparison(..)
2231 | ExpressionKind::UnitConversion(..)
2232 | ExpressionKind::LogicalNegation(..)
2233 | ExpressionKind::MathematicalComputation(..)
2234 | ExpressionKind::StepRounding { .. }
2235 | ExpressionKind::Veto(_)
2236 | ExpressionKind::ResultIsVeto(..) => {}
2237 }
2238}
2239
2240fn canonicalize_list_command(command: &mut ListCommand) {
2241 match command {
2242 ListCommand::First
2243 | ListCommand::Last
2244 | ListCommand::Count
2245 | ListCommand::Find { .. }
2246 | ListCommand::Sum { .. }
2247 | ListCommand::Avg { .. }
2248 | ListCommand::Product { .. }
2249 | ListCommand::Min { .. }
2250 | ListCommand::Max { .. }
2251 | ListCommand::Any { .. }
2252 | ListCommand::All { .. }
2253 | ListCommand::Where { .. }
2254 | ListCommand::Sort { .. }
2255 | ListCommand::Top { .. }
2256 | ListCommand::With {
2257 rows: ListWithRows::Values(_),
2258 } => {}
2259 ListCommand::With {
2260 rows: ListWithRows::FieldPairs(pairs),
2261 } => {
2262 for (name, _) in pairs {
2263 canonicalize_reference(name);
2264 }
2265 }
2266 ListCommand::Key { fields } => {
2267 for field in fields {
2268 canonicalize_reference(field);
2269 }
2270 }
2271 }
2272}
2273
2274pub(crate) fn canonicalize_unless_clause(unless_clause: &mut UnlessClause) {
2275 canonicalize_expression(&mut unless_clause.condition);
2276 canonicalize_expression(&mut unless_clause.result);
2277}
2278
2279pub(crate) fn canonicalize_data_value(data_value: &mut DataValue) {
2280 match data_value {
2281 DataValue::Definition {
2282 base,
2283 constraints,
2284 value,
2285 list,
2286 } => {
2287 if let Some(base) = base {
2288 canonicalize_parent_type(base);
2289 }
2290 if let Some(constraints) = constraints {
2291 canonicalize_constraints(constraints);
2292 }
2293 if let Some(value) = value {
2294 canonicalize_value(value);
2295 }
2296 if let Some(list) = list {
2297 canonicalize_instance_list_body(list);
2298 }
2299 }
2300 DataValue::Import {
2301 spec_ref,
2302 bindings,
2303 effective_ref,
2304 } => {
2305 canonicalize_spec_ref(spec_ref);
2306 canonicalize_uses_bindings(bindings);
2307 if let Some(reference) = effective_ref {
2308 canonicalize_reference(reference);
2309 }
2310 }
2311 DataValue::ImportList { spec_ref, list } => {
2312 canonicalize_spec_ref(spec_ref);
2313 canonicalize_instance_list_body(list);
2314 }
2315 }
2316}
2317
2318pub(crate) fn canonicalize_instance_list_body(list: &mut InstanceListBody) {
2319 if let Some(key) = &mut list.key {
2320 for field in &mut key.fields {
2321 canonicalize_reference(field);
2322 }
2323 }
2324 for row in list.rows.iter_mut().chain(list.option_rows.iter_mut()) {
2325 match row {
2326 InstanceListRow::Fields { bindings, .. } => {
2327 for binding in bindings {
2328 canonicalize_reference(&mut binding.path);
2329 canonicalize_with_rhs(&mut binding.rhs);
2330 }
2331 }
2332 InstanceListRow::Values { values, .. } => {
2333 for value in values {
2334 canonicalize_with_rhs(value);
2335 }
2336 }
2337 }
2338 }
2339}
2340
2341fn canonicalize_with_rhs(rhs: &mut WithRhs) {
2342 match rhs {
2343 WithRhs::Literal(value) => canonicalize_value(value),
2344 WithRhs::Reference { target } => canonicalize_reference(target),
2345 }
2346}
2347
2348fn canonicalize_uses_bindings(bindings: &mut [UsesBinding]) {
2349 for binding in bindings {
2350 canonicalize_reference(&mut binding.path);
2351 match &mut binding.rhs {
2352 UsesBindingRhs::Literal(value) => canonicalize_value(value),
2353 UsesBindingRhs::References { targets } => {
2354 for target in targets {
2355 canonicalize_reference(target);
2356 }
2357 }
2358 }
2359 }
2360}
2361
2362pub(crate) fn canonicalize_lemma_data(data: &mut LemmaData) {
2363 canonicalize_reference(&mut data.reference);
2364 canonicalize_data_value(&mut data.value);
2365}
2366
2367pub(crate) fn canonicalize_lemma_rule(rule: &mut LemmaRule) {
2368 rule.name = ascii_lowercase_logical_name(std::mem::take(&mut rule.name));
2369 canonicalize_expression(&mut rule.expression);
2370 for unless_clause in &mut rule.unless_clauses {
2371 canonicalize_unless_clause(unless_clause);
2372 }
2373}
2374
2375pub(crate) fn canonicalize_lemma_spec(spec: &mut LemmaSpec) {
2376 spec.name = ascii_lowercase_logical_name(std::mem::take(&mut spec.name));
2377 for meta in &mut spec.meta_fields {
2378 meta.key = ascii_lowercase_logical_name(std::mem::take(&mut meta.key));
2379 }
2380 for data in &mut spec.data {
2381 canonicalize_lemma_data(data);
2382 }
2383 for rule in &mut spec.rules {
2384 canonicalize_lemma_rule(rule);
2385 }
2386}
2387
2388pub(crate) fn canonicalize_repository(repository: &mut LemmaRepository) {
2389 if let Some(name) = repository.name.take() {
2390 repository.name = Some(ascii_lowercase_logical_name(name));
2391 }
2392}
2393
2394#[cfg(test)]
2395mod tests {
2396 use super::*;
2397 use crate::literals::DateGranularity;
2398
2399 #[test]
2400 fn test_conversion_target_display() {
2401 assert_eq!(
2402 format!("{}", ConversionTarget::Type(ConversionTypeKind::Number)),
2403 "number"
2404 );
2405 }
2406
2407 #[test]
2408 fn test_value_number_with_unit_ratio_display() {
2409 use rust_decimal::Decimal;
2410 use std::str::FromStr;
2411 let percent =
2412 Value::NumberWithUnit(Decimal::from_str("10").unwrap(), "percent".to_string());
2413 assert_eq!(format!("{}", percent), "10%");
2414 let permille =
2415 Value::NumberWithUnit(Decimal::from_str("5").unwrap(), "permille".to_string());
2416 assert_eq!(format!("{}", permille), "5%%");
2417 }
2418
2419 #[test]
2420 fn test_datetime_value_display() {
2421 let dt = DateTimeValue {
2422 year: 2024,
2423 month: 12,
2424 day: 25,
2425 hour: 14,
2426 minute: 30,
2427 second: 45,
2428 microsecond: 0,
2429 timezone: Some(TimezoneValue { offset_minutes: 60 }),
2430
2431 granularity: DateGranularity::DateTime,
2432 };
2433 assert_eq!(format!("{}", dt), "2024-12-25T14:30:45+01:00");
2434 }
2435
2436 #[test]
2437 fn test_datetime_value_display_date_only() {
2438 let dt = DateTimeValue {
2439 year: 2026,
2440 month: 3,
2441 day: 4,
2442 hour: 0,
2443 minute: 0,
2444 second: 0,
2445 microsecond: 0,
2446 timezone: None,
2447
2448 granularity: DateGranularity::Full,
2449 };
2450 assert_eq!(format!("{}", dt), "2026-03-04");
2451 }
2452
2453 #[test]
2454 fn test_datetime_value_display_microseconds() {
2455 let dt = DateTimeValue {
2456 year: 2026,
2457 month: 2,
2458 day: 23,
2459 hour: 14,
2460 minute: 30,
2461 second: 45,
2462 microsecond: 123456,
2463 timezone: Some(TimezoneValue { offset_minutes: 0 }),
2464
2465 granularity: DateGranularity::DateTime,
2466 };
2467 assert_eq!(format!("{}", dt), "2026-02-23T14:30:45.123456Z");
2468 }
2469
2470 #[test]
2471 fn test_datetime_microsecond_in_ordering() {
2472 let a = DateTimeValue {
2473 year: 2026,
2474 month: 1,
2475 day: 1,
2476 hour: 0,
2477 minute: 0,
2478 second: 0,
2479 microsecond: 100,
2480 timezone: None,
2481
2482 granularity: DateGranularity::DateTime,
2483 };
2484 let b = DateTimeValue {
2485 year: 2026,
2486 month: 1,
2487 day: 1,
2488 hour: 0,
2489 minute: 0,
2490 second: 0,
2491 microsecond: 200,
2492 timezone: None,
2493
2494 granularity: DateGranularity::DateTime,
2495 };
2496 assert!(a < b);
2497 }
2498
2499 #[test]
2500 fn test_datetime_parse_iso_week() {
2501 let dt: DateTimeValue = "2026-W01".parse().unwrap();
2502 assert_eq!(dt.year, 2025);
2503 assert_eq!(dt.month, 12);
2504 assert_eq!(dt.day, 29);
2505 assert_eq!(dt.microsecond, 0);
2506 assert_eq!(dt.to_string(), "2026-W01");
2507 assert!(matches!(
2508 dt.granularity,
2509 DateGranularity::IsoWeek {
2510 iso_year: 2026,
2511 week: 1
2512 }
2513 ));
2514 }
2515
2516 fn text_arg(s: &str) -> CommandArg {
2521 CommandArg::Literal(crate::literals::Value::Text(s.to_string()))
2522 }
2523
2524 fn number_arg(s: &str) -> CommandArg {
2525 let d: rust_decimal::Decimal = s.parse().expect("decimal");
2526 CommandArg::Literal(crate::literals::Value::Number(d))
2527 }
2528
2529 fn boolean_arg(b: BooleanValue) -> CommandArg {
2530 CommandArg::Literal(crate::literals::Value::Boolean(b))
2531 }
2532
2533 fn measure_arg(value: &str, unit: &str) -> CommandArg {
2534 let d: rust_decimal::Decimal = value.parse().expect("decimal");
2535 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
2536 }
2537
2538 fn duration_arg(value: &str, unit: &str) -> CommandArg {
2539 let d: rust_decimal::Decimal = value.parse().expect("decimal");
2540 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
2541 }
2542
2543 #[test]
2544 fn as_lemma_source_text_default_is_quoted() {
2545 let fv = DataValue::Definition {
2546 base: Some(ParentType::Primitive {
2547 primitive: PrimitiveKind::Text,
2548 }),
2549 constraints: Some(vec![test_constraint(
2550 TypeConstraintCommand::Suggest,
2551 vec![text_arg("single")],
2552 )]),
2553 value: None,
2554
2555 list: None,
2556 };
2557 assert_eq!(
2558 format!("{}", AsLemmaSource(&fv)),
2559 "text -> suggest \"single\""
2560 );
2561 }
2562
2563 #[test]
2564 fn as_lemma_source_number_default_not_quoted() {
2565 let fv = DataValue::Definition {
2566 base: Some(ParentType::Primitive {
2567 primitive: PrimitiveKind::Number,
2568 }),
2569 constraints: Some(vec![test_constraint(
2570 TypeConstraintCommand::Suggest,
2571 vec![number_arg("10")],
2572 )]),
2573 value: None,
2574
2575 list: None,
2576 };
2577 assert_eq!(format!("{}", AsLemmaSource(&fv)), "number -> suggest 10");
2578 }
2579
2580 #[test]
2581 fn as_lemma_source_help_always_quoted() {
2582 let fv = DataValue::Definition {
2583 base: Some(ParentType::Primitive {
2584 primitive: PrimitiveKind::Number,
2585 }),
2586 constraints: Some(vec![test_constraint(
2587 TypeConstraintCommand::Help,
2588 vec![text_arg("Enter a measure")],
2589 )]),
2590 value: None,
2591
2592 list: None,
2593 };
2594 assert_eq!(
2595 format!("{}", AsLemmaSource(&fv)),
2596 "number -> help \"Enter a measure\""
2597 );
2598 }
2599
2600 #[test]
2601 fn as_lemma_source_text_option_quoted() {
2602 let fv = DataValue::Definition {
2603 base: Some(ParentType::Primitive {
2604 primitive: PrimitiveKind::Text,
2605 }),
2606 constraints: Some(vec![
2607 test_constraint(TypeConstraintCommand::Option, vec![text_arg("active")]),
2608 test_constraint(TypeConstraintCommand::Option, vec![text_arg("inactive")]),
2609 ]),
2610 value: None,
2611
2612 list: None,
2613 };
2614 assert_eq!(
2615 format!("{}", AsLemmaSource(&fv)),
2616 "text -> option \"active\" -> option \"inactive\""
2617 );
2618 }
2619
2620 #[test]
2621 fn as_lemma_source_measure_unit_not_quoted() {
2622 let fv = DataValue::Definition {
2623 base: Some(ParentType::Primitive {
2624 primitive: PrimitiveKind::Measure,
2625 }),
2626 constraints: Some(vec![
2627 test_constraint(
2628 TypeConstraintCommand::Unit,
2629 vec![
2630 CommandArg::Label("eur".to_string()),
2631 CommandArg::UnitExpr(UnitArg::Factor(decimal("1.00"))),
2632 ],
2633 ),
2634 test_constraint(
2635 TypeConstraintCommand::Unit,
2636 vec![
2637 CommandArg::Label("usd".to_string()),
2638 CommandArg::UnitExpr(UnitArg::Factor(decimal("0.91"))),
2639 ],
2640 ),
2641 ]),
2642 value: None,
2643
2644 list: None,
2645 };
2646 assert_eq!(
2647 format!("{}", AsLemmaSource(&fv)),
2648 "measure -> unit eur: 1.00 -> unit usd: 0.91"
2649 );
2650 }
2651
2652 #[test]
2653 fn as_lemma_source_measure_minimum_with_unit() {
2654 let fv = DataValue::Definition {
2655 base: Some(ParentType::Primitive {
2656 primitive: PrimitiveKind::Measure,
2657 }),
2658 constraints: Some(vec![test_constraint(
2659 TypeConstraintCommand::Minimum,
2660 vec![measure_arg("0", "eur")],
2661 )]),
2662 value: None,
2663
2664 list: None,
2665 };
2666 assert_eq!(
2667 format!("{}", AsLemmaSource(&fv)),
2668 "measure -> minimum 0 eur"
2669 );
2670 }
2671
2672 #[test]
2673 fn as_lemma_source_boolean_default() {
2674 let fv = DataValue::Definition {
2675 base: Some(ParentType::Primitive {
2676 primitive: PrimitiveKind::Boolean,
2677 }),
2678 constraints: Some(vec![test_constraint(
2679 TypeConstraintCommand::Suggest,
2680 vec![boolean_arg(BooleanValue::True)],
2681 )]),
2682 value: None,
2683
2684 list: None,
2685 };
2686 assert_eq!(format!("{}", AsLemmaSource(&fv)), "boolean -> suggest true");
2687 }
2688
2689 #[test]
2690 fn as_lemma_source_duration_default() {
2691 let fv = DataValue::Definition {
2692 base: Some(ParentType::custom("duration")),
2693 constraints: Some(vec![test_constraint(
2694 TypeConstraintCommand::Suggest,
2695 vec![duration_arg("40", "hour")],
2696 )]),
2697 value: None,
2698
2699 list: None,
2700 };
2701 assert_eq!(
2702 format!("{}", AsLemmaSource(&fv)),
2703 "duration -> suggest 40 hour"
2704 );
2705 }
2706
2707 #[test]
2708 fn as_lemma_source_named_type_default_quoted() {
2709 let fv = DataValue::Definition {
2712 base: Some(ParentType::custom("filing_status_type")),
2713 constraints: Some(vec![test_constraint(
2714 TypeConstraintCommand::Suggest,
2715 vec![text_arg("single")],
2716 )]),
2717 value: None,
2718
2719 list: None,
2720 };
2721 assert_eq!(
2722 format!("{}", AsLemmaSource(&fv)),
2723 "filing_status_type -> suggest \"single\""
2724 );
2725 }
2726
2727 #[test]
2728 fn as_lemma_source_help_escapes_quotes() {
2729 let fv = DataValue::Definition {
2730 base: Some(ParentType::Primitive {
2731 primitive: PrimitiveKind::Text,
2732 }),
2733 constraints: Some(vec![test_constraint(
2734 TypeConstraintCommand::Help,
2735 vec![text_arg("say \"hello\"")],
2736 )]),
2737 value: None,
2738
2739 list: None,
2740 };
2741 assert_eq!(
2742 format!("{}", AsLemmaSource(&fv)),
2743 "text -> help \"say \\\"hello\\\"\""
2744 );
2745 }
2746
2747 fn unit_arg_expr(prefix: Decimal, factors: &[(&str, i32)]) -> UnitArg {
2748 UnitArg::Expr(
2749 prefix,
2750 factors
2751 .iter()
2752 .map(|(measure_ref, exp)| UnitFactor {
2753 measure_ref: (*measure_ref).to_string(),
2754 exp: *exp,
2755 })
2756 .collect(),
2757 )
2758 }
2759
2760 #[test]
2761 fn unit_arg_display_metre_per_second() {
2762 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -1)]);
2763 assert_eq!(format!("{arg}"), "meter/second");
2764 assert!(
2765 !format!("{arg}").contains("second^-1"),
2766 "must not print denominator as negative exponent"
2767 );
2768 }
2769
2770 #[test]
2771 fn unit_arg_display_meter_per_second_squared() {
2772 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -2)]);
2773 assert_eq!(format!("{arg}"), "meter/second^2");
2774 }
2775
2776 #[test]
2777 fn unit_arg_display_kg_times_mps2() {
2778 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("mps2", 1)]);
2779 assert_eq!(format!("{arg}"), "kg * mps2");
2780 }
2781
2782 #[test]
2783 fn unit_arg_display_numeric_prefix_metre_per_second() {
2784 use std::str::FromStr;
2785 let prefix = Decimal::from_str("3.6").expect("decimal");
2786 let arg = unit_arg_expr(prefix, &[("meter", 1), ("second", -1)]);
2787 assert_eq!(format!("{arg}"), "3.6 meter/second");
2788 }
2789
2790 #[test]
2791 fn unit_arg_display_metre_per_second_times_kg() {
2792 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -1), ("kg", 1)]);
2793 assert_eq!(format!("{arg}"), "meter/second * kg");
2794 }
2795
2796 #[test]
2797 fn unit_arg_display_kg_meter_per_second_squared() {
2798 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("meter", 1), ("second", -2)]);
2799 assert_eq!(format!("{arg}"), "kg * meter/second^2");
2800 }
2801
2802 #[test]
2805 fn format_constraint_as_source_unit_factor_uses_assignment_colon() {
2806 let args = vec![
2807 CommandArg::Label("eur".to_string()),
2808 CommandArg::UnitExpr(UnitArg::Factor(decimal("1"))),
2809 ];
2810 assert_eq!(
2811 format_constraint_as_source(&TypeConstraintCommand::Unit, &args),
2812 "unit eur: 1"
2813 );
2814 }
2815
2816 #[test]
2817 fn format_constraint_as_source_unit_compound_uses_assignment_colon() {
2818 let arg = unit_arg_expr(decimal("3.6"), &[("meter", 1), ("second", -1)]);
2819 let args = vec![
2820 CommandArg::Label("kmh".to_string()),
2821 CommandArg::UnitExpr(arg),
2822 ];
2823 assert_eq!(
2824 format_constraint_as_source(&TypeConstraintCommand::Unit, &args),
2825 "unit kmh: 3.6 meter/second"
2826 );
2827 }
2828
2829 #[test]
2830 fn format_constraint_as_source_unit_factor_one_decimal_uses_assignment_colon() {
2831 let args = vec![
2832 CommandArg::Label("eur".to_string()),
2833 CommandArg::UnitExpr(UnitArg::Factor(decimal("1.00"))),
2834 ];
2835 assert_eq!(
2836 format_constraint_as_source(&TypeConstraintCommand::Unit, &args),
2837 "unit eur: 1.00"
2838 );
2839 }
2840
2841 #[test]
2842 fn as_lemma_source_measure_unit_uses_assignment_colon() {
2843 let fv = DataValue::Definition {
2844 base: Some(ParentType::Primitive {
2845 primitive: PrimitiveKind::Measure,
2846 }),
2847 constraints: Some(vec![
2848 test_constraint(
2849 TypeConstraintCommand::Unit,
2850 vec![
2851 CommandArg::Label("eur".to_string()),
2852 CommandArg::UnitExpr(UnitArg::Factor(decimal("1.00"))),
2853 ],
2854 ),
2855 test_constraint(
2856 TypeConstraintCommand::Unit,
2857 vec![
2858 CommandArg::Label("usd".to_string()),
2859 CommandArg::UnitExpr(UnitArg::Factor(decimal("0.91"))),
2860 ],
2861 ),
2862 ]),
2863 value: None,
2864
2865 list: None,
2866 };
2867 assert_eq!(
2868 format!("{}", AsLemmaSource(&fv)),
2869 "measure -> unit eur: 1.00 -> unit usd: 0.91"
2870 );
2871 }
2872
2873 #[test]
2874 fn as_lemma_source_time_keeps_microseconds() {
2875 let time = Value::Time(crate::literals::TimeValue {
2876 hour: 14,
2877 minute: 30,
2878 second: 45,
2879 microsecond: 250,
2880 timezone: None,
2881 });
2882 assert_eq!(format!("{}", AsLemmaSource(&time)), "14:30:45.000250");
2883 }
2884
2885 #[test]
2886 fn as_lemma_source_datetime_keeps_microseconds() {
2887 let datetime = Value::Date(DateTimeValue {
2888 year: 2024,
2889 month: 1,
2890 day: 15,
2891 hour: 14,
2892 minute: 30,
2893 second: 0,
2894 microsecond: 500000,
2895 timezone: Some(crate::literals::TimezoneValue { offset_minutes: 0 }),
2896 granularity: DateGranularity::DateTime,
2897 });
2898 assert_eq!(
2899 format!("{}", AsLemmaSource(&datetime)),
2900 "2024-01-15T14:30:00.500000Z"
2901 );
2902 }
2903
2904 fn decimal(value: &str) -> rust_decimal::Decimal {
2905 use std::str::FromStr;
2906 rust_decimal::Decimal::from_str(value).expect("decimal literal in test")
2907 }
2908}