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, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
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, serde::Deserialize)]
101pub enum EffectiveDate {
102 Origin,
103 DateTimeValue(crate::DateTimeValue),
104}
105
106impl EffectiveDate {
107 pub fn as_ref(&self) -> Option<&crate::DateTimeValue> {
108 match self {
109 EffectiveDate::Origin => None,
110 EffectiveDate::DateTimeValue(dt) => Some(dt),
111 }
112 }
113
114 pub fn from_option(opt: Option<crate::DateTimeValue>) -> Self {
115 match opt {
116 None => EffectiveDate::Origin,
117 Some(dt) => EffectiveDate::DateTimeValue(dt),
118 }
119 }
120
121 pub fn to_option(&self) -> Option<crate::DateTimeValue> {
122 match self {
123 EffectiveDate::Origin => None,
124 EffectiveDate::DateTimeValue(dt) => Some(dt.clone()),
125 }
126 }
127
128 pub fn is_origin(&self) -> bool {
129 matches!(self, EffectiveDate::Origin)
130 }
131}
132
133impl PartialOrd for EffectiveDate {
134 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
135 Some(self.cmp(other))
136 }
137}
138
139impl Ord for EffectiveDate {
140 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
142 self.as_ref().cmp(&other.as_ref())
143 }
144}
145
146impl fmt::Display for EffectiveDate {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match self {
149 EffectiveDate::Origin => Ok(()),
150 EffectiveDate::DateTimeValue(dt) => write!(f, "{}", dt),
151 }
152 }
153}
154
155#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
169pub struct LemmaRepository {
170 pub name: Option<String>,
172 pub dependency: Option<String>,
175 pub start_line: usize,
176 pub source_type: Option<crate::parsing::source::SourceType>,
177}
178
179impl LemmaRepository {
180 #[must_use]
181 pub fn new(name: Option<String>) -> Self {
182 Self {
183 name: name.map(ascii_lowercase_logical_name),
184 dependency: None,
185 start_line: 1,
186 source_type: None,
187 }
188 }
189
190 #[must_use]
191 pub fn with_start_line(mut self, start_line: usize) -> Self {
192 self.start_line = start_line;
193 self
194 }
195
196 #[must_use]
197 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
198 self.source_type = Some(source_type);
199 self
200 }
201
202 #[must_use]
203 pub fn with_dependency(mut self, dependency_id: impl Into<String>) -> Self {
204 self.dependency = Some(dependency_id.into());
205 self
206 }
207}
208
209impl PartialEq for LemmaRepository {
210 fn eq(&self, other: &Self) -> bool {
211 self.name == other.name
212 }
213}
214
215impl Eq for LemmaRepository {}
216
217impl PartialOrd for LemmaRepository {
218 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
219 Some(self.cmp(other))
220 }
221}
222
223impl Ord for LemmaRepository {
224 fn cmp(&self, other: &Self) -> Ordering {
225 self.name.cmp(&other.name)
226 }
227}
228
229impl Hash for LemmaRepository {
230 fn hash<H: Hasher>(&self, state: &mut H) {
231 self.name.hash(state);
232 }
233}
234
235#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
239pub struct RepositoryQualifier {
240 pub name: String,
241}
242
243impl RepositoryQualifier {
244 #[must_use]
245 pub fn new(name: impl Into<String>) -> Self {
246 Self {
247 name: ascii_lowercase_logical_name(name.into()),
248 }
249 }
250
251 #[must_use]
253 pub fn is_registry(&self) -> bool {
254 self.name.starts_with('@')
255 }
256}
257
258impl fmt::Display for RepositoryQualifier {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 write!(f, "{}", self.name)
261 }
262}
263
264#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
271pub struct LemmaSpec {
272 pub name: String,
273 pub effective_from: EffectiveDate,
274 pub source_type: Option<crate::parsing::source::SourceType>,
275 pub start_line: usize,
276 pub commentary: Option<String>,
277 pub data: Vec<LemmaData>,
278 pub rules: Vec<LemmaRule>,
279 pub meta_fields: Vec<MetaField>,
280}
281
282#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
283pub struct MetaField {
284 pub key: String,
285 pub value: Value,
286 pub source_location: Source,
287}
288
289impl fmt::Display for MetaField {
290 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291 write!(f, "meta {}: {}", self.key, AsLemmaSource(&self.value))
292 }
293}
294
295#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
296pub struct LemmaData {
297 pub reference: Reference,
298 pub value: DataValue,
299 pub source_location: Source,
300}
301
302#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
308pub struct UnlessClause {
309 pub condition: Expression,
310 pub result: Expression,
311 pub source_location: Source,
312}
313
314#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
316pub struct LemmaRule {
317 pub name: String,
318 pub expression: Expression,
319 pub unless_clauses: Vec<UnlessClause>,
320 pub source_location: Source,
321}
322
323pub type Expression = ExprTree<ExpressionKind>;
328
329pub use crate::parsing::arena::{ExprId, ExprTable, ExprTree};
330
331#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
334pub struct ChainStep<Op> {
335 pub op: Op,
336 pub operand: ExprId,
337 pub source_location: Source,
338}
339
340impl<Op: PartialEq> PartialEq for ChainStep<Op> {
342 fn eq(&self, other: &Self) -> bool {
343 self.op == other.op && self.operand == other.operand
344 }
345}
346
347impl<Op: Eq> Eq for ChainStep<Op> {}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
351#[serde(rename_all = "snake_case")]
352pub enum DateRelativeKind {
353 InPast,
354 InFuture,
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
359#[serde(rename_all = "snake_case")]
360pub enum DateCalendarKind {
361 Current,
362 Past,
363 Future,
364 NotIn,
365}
366
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
369#[serde(rename_all = "snake_case")]
370pub enum CalendarPeriodUnit {
371 Year,
372 Month,
373 Week,
374}
375
376impl CalendarPeriodUnit {
377 #[must_use]
378 pub fn from_keyword(s: &str) -> Option<Self> {
379 match s.trim().to_lowercase().as_str() {
380 "year" => Some(Self::Year),
381 "month" => Some(Self::Month),
382 "week" => Some(Self::Week),
383 _ => None,
384 }
385 }
386}
387
388impl fmt::Display for DateRelativeKind {
389 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
390 match self {
391 DateRelativeKind::InPast => write!(f, "in past"),
392 DateRelativeKind::InFuture => write!(f, "in future"),
393 }
394 }
395}
396
397impl fmt::Display for DateCalendarKind {
398 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
399 match self {
400 DateCalendarKind::Current => write!(f, "in calendar"),
401 DateCalendarKind::Past => write!(f, "in past calendar"),
402 DateCalendarKind::Future => write!(f, "in future calendar"),
403 DateCalendarKind::NotIn => write!(f, "not in calendar"),
404 }
405 }
406}
407
408impl fmt::Display for CalendarPeriodUnit {
409 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
410 match self {
411 CalendarPeriodUnit::Year => write!(f, "year"),
412 CalendarPeriodUnit::Month => write!(f, "month"),
413 CalendarPeriodUnit::Week => write!(f, "week"),
414 }
415 }
416}
417
418#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
420#[serde(rename_all = "snake_case")]
421pub enum ExpressionKind {
422 Literal(Value),
424 Reference(Reference),
426 Now,
428 DateRelative(DateRelativeKind, ExprId),
431 DateCalendar(DateCalendarKind, CalendarPeriodUnit, ExprId),
434 RangeLiteral(ExprId, ExprId),
436 PastFutureRange(DateRelativeKind, ExprId),
438 RangeContainment(ExprId, ExprId),
440 RangeBound(RangeBound, ExprId),
442 LogicalAnd {
444 first: ExprId,
445 steps: Vec<ChainStep<()>>,
446 },
447 Arithmetic {
449 first: ExprId,
450 steps: Vec<ChainStep<ArithmeticComputation>>,
451 },
452 Comparison(ExprId, ComparisonComputation, ExprId),
453 UnitConversion(ExprId, ConversionTarget),
454 LogicalNegation(ExprId, NegationType),
455 MathematicalComputation(MathematicalComputation, ExprId),
456 Veto(VetoExpression),
457 ResultIsVeto(ExprId),
459 ListType {
461 element: ParentType,
462 },
463 ListCommand {
465 list: ExprId,
466 command: ListCommand,
467 },
468 Member {
470 base: ExprId,
471 path: Reference,
472 },
473}
474
475impl ExprKind for ExpressionKind {
476 fn for_each_child(&self, mut f: impl FnMut(ExprId)) {
477 match self {
478 ExpressionKind::Literal(_)
479 | ExpressionKind::Reference(_)
480 | ExpressionKind::Now
481 | ExpressionKind::Veto(_)
482 | ExpressionKind::ListType { .. } => {}
483 ExpressionKind::DateRelative(_, inner)
484 | ExpressionKind::DateCalendar(_, _, inner)
485 | ExpressionKind::PastFutureRange(_, inner)
486 | ExpressionKind::RangeBound(_, inner)
487 | ExpressionKind::UnitConversion(inner, _)
488 | ExpressionKind::LogicalNegation(inner, _)
489 | ExpressionKind::MathematicalComputation(_, inner)
490 | ExpressionKind::ResultIsVeto(inner)
491 | ExpressionKind::Member { base: inner, .. } => f(*inner),
492 ExpressionKind::RangeLiteral(left, right)
493 | ExpressionKind::RangeContainment(left, right)
494 | ExpressionKind::Comparison(left, _, right) => {
495 f(*left);
496 f(*right);
497 }
498 ExpressionKind::LogicalAnd { first, steps } => {
499 f(*first);
500 steps.iter().for_each(|step| f(step.operand));
501 }
502 ExpressionKind::Arithmetic { first, steps } => {
503 f(*first);
504 steps.iter().for_each(|step| f(step.operand));
505 }
506 ExpressionKind::ListCommand { list, command } => {
507 f(*list);
508 command.for_each_child(f);
509 }
510 }
511 }
512
513 fn children_mut(&mut self) -> Vec<&mut ExprId> {
514 match self {
515 ExpressionKind::Literal(_)
516 | ExpressionKind::Reference(_)
517 | ExpressionKind::Now
518 | ExpressionKind::Veto(_)
519 | ExpressionKind::ListType { .. } => Vec::new(),
520 ExpressionKind::DateRelative(_, inner)
521 | ExpressionKind::DateCalendar(_, _, inner)
522 | ExpressionKind::PastFutureRange(_, inner)
523 | ExpressionKind::RangeBound(_, inner)
524 | ExpressionKind::UnitConversion(inner, _)
525 | ExpressionKind::LogicalNegation(inner, _)
526 | ExpressionKind::MathematicalComputation(_, inner)
527 | ExpressionKind::ResultIsVeto(inner)
528 | ExpressionKind::Member { base: inner, .. } => vec![inner],
529 ExpressionKind::RangeLiteral(left, right)
530 | ExpressionKind::RangeContainment(left, right)
531 | ExpressionKind::Comparison(left, _, right) => vec![left, right],
532 ExpressionKind::LogicalAnd { first, steps } => std::iter::once(first)
533 .chain(steps.iter_mut().map(|step| &mut step.operand))
534 .collect(),
535 ExpressionKind::Arithmetic { first, steps } => std::iter::once(first)
536 .chain(steps.iter_mut().map(|step| &mut step.operand))
537 .collect(),
538 ExpressionKind::ListCommand { list, command } => std::iter::once(list)
539 .chain(command.children_mut())
540 .collect(),
541 }
542 }
543}
544
545impl ListCommand {
546 fn for_each_child(&self, mut f: impl FnMut(ExprId)) {
547 match self {
548 ListCommand::First | ListCommand::Last | ListCommand::Count => {}
549 ListCommand::Find { keys } => keys.iter().copied().for_each(f),
550 ListCommand::Sum { argument }
551 | ListCommand::Avg { argument }
552 | ListCommand::Product { argument }
553 | ListCommand::Min { argument }
554 | ListCommand::Max { argument }
555 | ListCommand::Sort { argument, .. } => argument.iter().copied().for_each(f),
556 ListCommand::Any { form } | ListCommand::All { form } | ListCommand::Where { form } => {
557 match form {
558 ListPredicate::Value { right, .. } => f(*right),
559 ListPredicate::Instance { expr } => f(*expr),
560 }
561 }
562 ListCommand::Top { n } => f(*n),
563 ListCommand::With { rows } => match rows {
564 ListWithRows::Values(values) => values.iter().copied().for_each(f),
565 ListWithRows::FieldPairs(pairs) => pairs.iter().for_each(|(_, value)| f(*value)),
566 },
567 }
568 }
569
570 fn children_mut(&mut self) -> Vec<&mut ExprId> {
571 match self {
572 ListCommand::First | ListCommand::Last | ListCommand::Count => Vec::new(),
573 ListCommand::Find { keys } => keys.iter_mut().collect(),
574 ListCommand::Sum { argument }
575 | ListCommand::Avg { argument }
576 | ListCommand::Product { argument }
577 | ListCommand::Min { argument }
578 | ListCommand::Max { argument }
579 | ListCommand::Sort { argument, .. } => argument.iter_mut().collect(),
580 ListCommand::Any { form } | ListCommand::All { form } | ListCommand::Where { form } => {
581 match form {
582 ListPredicate::Value { right, .. } => vec![right],
583 ListPredicate::Instance { expr } => vec![expr],
584 }
585 }
586 ListCommand::Top { n } => vec![n],
587 ListCommand::With { rows } => match rows {
588 ListWithRows::Values(values) => values.iter_mut().collect(),
589 ListWithRows::FieldPairs(pairs) => {
590 pairs.iter_mut().map(|(_, value)| value).collect()
591 }
592 },
593 }
594 }
595}
596
597#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
599pub enum ListCommand {
600 Find {
601 keys: Vec<ExprId>,
602 },
603 First,
604 Last,
605 Sum {
606 argument: Option<ExprId>,
607 },
608 Avg {
609 argument: Option<ExprId>,
610 },
611 Product {
612 argument: Option<ExprId>,
613 },
614 Count,
615 Min {
616 argument: Option<ExprId>,
617 },
618 Max {
619 argument: Option<ExprId>,
620 },
621 Any {
622 form: ListPredicate,
623 },
624 All {
625 form: ListPredicate,
626 },
627 Where {
628 form: ListPredicate,
629 },
630 Sort {
631 descending: bool,
632 argument: Option<ExprId>,
633 },
634 Top {
635 n: ExprId,
636 },
637 With {
638 rows: ListWithRows,
639 },
640}
641
642#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
644#[serde(rename_all = "snake_case")]
645pub enum RowCondition {
646 Compare(ComparisonComputation),
647 In,
648}
649
650impl fmt::Display for RowCondition {
651 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
652 match self {
653 RowCondition::Compare(op) => write!(f, "{op}"),
654 RowCondition::In => write!(f, "in"),
655 }
656 }
657}
658
659#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
661pub enum ListPredicate {
662 Value { op: RowCondition, right: ExprId },
663 Instance { expr: ExprId },
664}
665
666#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
668pub enum ListWithRows {
669 Values(Vec<ExprId>),
670 FieldPairs(Vec<(Reference, ExprId)>),
671}
672
673#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
683pub struct Reference {
684 pub segments: Vec<String>,
685 pub name: String,
686}
687
688impl Reference {
689 #[must_use]
690 pub fn local(name: String) -> Self {
691 Self {
692 segments: Vec::new(),
693 name: ascii_lowercase_logical_name(name),
694 }
695 }
696
697 #[must_use]
698 pub fn from_path(path: Vec<String>) -> Self {
699 if path.is_empty() {
700 Self {
701 segments: Vec::new(),
702 name: String::new(),
703 }
704 } else {
705 let name = ascii_lowercase_logical_name(path[path.len() - 1].clone());
707 let segments = path[..path.len() - 1]
708 .iter()
709 .map(|segment| ascii_lowercase_logical_name(segment.clone()))
710 .collect();
711 Self { segments, name }
712 }
713 }
714
715 #[must_use]
716 pub fn is_local(&self) -> bool {
717 self.segments.is_empty()
718 }
719
720 #[must_use]
721 pub fn full_path(&self) -> Vec<String> {
722 let mut path = self.segments.clone();
723 path.push(self.name.clone());
724 path
725 }
726}
727
728impl fmt::Display for Reference {
729 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
730 for segment in &self.segments {
731 write!(f, "{}.", segment)?;
732 }
733 write!(f, "{}", self.name)
734 }
735}
736
737#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
739#[serde(rename_all = "snake_case")]
740pub enum ArithmeticComputation {
741 Add,
742 Subtract,
743 Multiply,
744 Divide,
745 Modulo,
746 Power,
747}
748
749#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
751#[serde(rename_all = "snake_case")]
752pub enum ComparisonComputation {
753 GreaterThan,
754 LessThan,
755 GreaterThanOrEqual,
756 LessThanOrEqual,
757 Is,
758 IsNot,
759}
760
761impl ComparisonComputation {
762 #[must_use]
764 pub fn is_equal(&self) -> bool {
765 matches!(self, ComparisonComputation::Is)
766 }
767
768 #[must_use]
770 pub fn is_not_equal(&self) -> bool {
771 matches!(self, ComparisonComputation::IsNot)
772 }
773}
774
775#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
777#[serde(rename_all = "snake_case")]
778pub enum ConversionTarget {
779 Type(PrimitiveKind),
780 Unit { unit_name: String },
781}
782
783#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
785#[serde(rename_all = "snake_case")]
786pub enum NegationType {
787 Not,
788}
789
790#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
797pub struct VetoExpression {
798 pub message: Option<String>,
799}
800
801#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
803#[serde(rename_all = "snake_case")]
804pub enum MathematicalComputation {
805 Sqrt,
806 Sin,
807 Cos,
808 Tan,
809 Asin,
810 Acos,
811 Atan,
812 Log,
813 Exp,
814 Abs,
815 Floor,
816 Ceil,
817 Round,
818}
819
820#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
822#[serde(rename_all = "snake_case")]
823pub enum RangeBound {
824 Lower,
825 Upper,
826}
827
828impl fmt::Display for RangeBound {
829 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830 match self {
831 RangeBound::Lower => write!(f, "lower"),
832 RangeBound::Upper => write!(f, "upper"),
833 }
834 }
835}
836
837#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
843pub struct SpecRef {
844 pub repository: Option<RepositoryQualifier>,
847 pub name: String,
849 pub effective: Option<DateTimeValue>,
851 pub repository_span: Option<Span>,
853 pub target_span: Option<Span>,
855}
856
857impl std::fmt::Display for SpecRef {
858 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
859 if let Some(qualifier) = &self.repository {
860 write!(f, "{} ", qualifier)?;
861 }
862 write!(f, "{}", self.name)?;
863 if let Some(d) = &self.effective {
864 write!(f, " {}", d)?;
865 }
866 Ok(())
867 }
868}
869
870impl SpecRef {
871 pub fn same_repository(name: impl Into<String>) -> Self {
873 Self {
874 name: ascii_lowercase_logical_name(name.into()),
875 repository: None,
876 effective: None,
877 repository_span: None,
878 target_span: None,
879 }
880 }
881
882 pub fn cross_repository(name: impl Into<String>, qualifier: RepositoryQualifier) -> Self {
884 Self {
885 name: ascii_lowercase_logical_name(name.into()),
886 repository: Some(qualifier),
887 effective: None,
888 repository_span: None,
889 target_span: None,
890 }
891 }
892
893 pub fn at(&self, effective: &EffectiveDate) -> EffectiveDate {
896 self.effective
897 .clone()
898 .map_or_else(|| effective.clone(), EffectiveDate::DateTimeValue)
899 }
900
901 pub fn resolved_instant(
904 &self,
905 consumer_effective_from: Option<&DateTimeValue>,
906 ) -> Option<DateTimeValue> {
907 self.effective
908 .clone()
909 .or_else(|| consumer_effective_from.cloned())
910 }
911}
912
913#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
921pub struct UnitFactor {
922 pub measure_ref: String,
923 pub exp: i32,
924}
925
926#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
935pub enum UnitArg {
936 Factor(#[serde(with = "crate::literals::decimal_binary_serde")] Decimal),
937 Expr(
938 #[serde(with = "crate::literals::decimal_binary_serde")] Decimal,
939 Vec<UnitFactor>,
940 ),
941}
942
943impl fmt::Display for UnitArg {
944 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
945 match self {
946 UnitArg::Factor(v) => write!(f, "{}", v),
947 UnitArg::Expr(prefix, factors) => {
948 if *prefix != Decimal::ONE {
949 write!(f, "{} ", prefix)?;
950 }
951 for (index, factor) in factors.iter().enumerate() {
952 if factor.exp == 0 {
953 unreachable!("BUG: unit factor exponent cannot be zero");
954 }
955 if factor.exp > 0 {
956 if index > 0 {
957 write!(f, " * ")?;
958 }
959 write!(f, "{}", factor.measure_ref)?;
960 if factor.exp != 1 {
961 write!(f, "^{}", factor.exp)?;
962 }
963 } else {
964 let denominator_started =
965 factors[..index].iter().any(|prior| prior.exp < 0);
966 if denominator_started {
967 write!(f, " * ")?;
968 } else {
969 write!(f, "/")?;
970 }
971 write!(f, "{}", factor.measure_ref)?;
972 let positive_exp = factor
973 .exp
974 .checked_neg()
975 .expect("BUG: negative unit factor exponent");
976 if positive_exp != 1 {
977 write!(f, "^{}", positive_exp)?;
978 }
979 }
980 }
981 Ok(())
982 }
983 }
984 }
985}
986
987#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1005pub enum CommandArg {
1006 Literal(crate::literals::Value),
1008 Label(String),
1010 UnitExpr(UnitArg),
1012}
1013
1014impl fmt::Display for CommandArg {
1015 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1016 match self {
1017 CommandArg::Literal(v) => write!(f, "{}", v),
1018 CommandArg::Label(s) => write!(f, "{}", s),
1019 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
1020 }
1021 }
1022}
1023
1024#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1026#[serde(rename_all = "snake_case")]
1027pub enum TypeConstraintCommand {
1028 Help,
1029 Suggest,
1030 With,
1031 Unit,
1032 Trait,
1033 Minimum,
1034 Maximum,
1035 Lower,
1036 Upper,
1037 Decimals,
1038 Option,
1039 Options,
1040 Length,
1041}
1042
1043impl fmt::Display for TypeConstraintCommand {
1044 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1045 let s = match self {
1046 TypeConstraintCommand::Help => "help",
1047 TypeConstraintCommand::Suggest => "suggest",
1048 TypeConstraintCommand::With => "with",
1049 TypeConstraintCommand::Unit => "unit",
1050 TypeConstraintCommand::Trait => "trait",
1051 TypeConstraintCommand::Minimum => "minimum",
1052 TypeConstraintCommand::Maximum => "maximum",
1053 TypeConstraintCommand::Lower => "lower",
1054 TypeConstraintCommand::Upper => "upper",
1055 TypeConstraintCommand::Decimals => "decimals",
1056 TypeConstraintCommand::Option => "option",
1057 TypeConstraintCommand::Options => "options",
1058 TypeConstraintCommand::Length => "length",
1059 };
1060 write!(f, "{}", s)
1061 }
1062}
1063
1064#[must_use]
1066pub fn try_parse_type_constraint_command(s: &str) -> Option<TypeConstraintCommand> {
1067 match s.trim() {
1069 s if s.eq_ignore_ascii_case("help") => Some(TypeConstraintCommand::Help),
1070 s if s.eq_ignore_ascii_case("suggest") => Some(TypeConstraintCommand::Suggest),
1071 s if s.eq_ignore_ascii_case("with") => Some(TypeConstraintCommand::With),
1072 s if s.eq_ignore_ascii_case("unit") => Some(TypeConstraintCommand::Unit),
1073 s if s.eq_ignore_ascii_case("trait") => Some(TypeConstraintCommand::Trait),
1074 s if s.eq_ignore_ascii_case("minimum") => Some(TypeConstraintCommand::Minimum),
1075 s if s.eq_ignore_ascii_case("maximum") => Some(TypeConstraintCommand::Maximum),
1076 s if s.eq_ignore_ascii_case("lower") => Some(TypeConstraintCommand::Lower),
1077 s if s.eq_ignore_ascii_case("upper") => Some(TypeConstraintCommand::Upper),
1078 s if s.eq_ignore_ascii_case("decimals") => Some(TypeConstraintCommand::Decimals),
1079 s if s.eq_ignore_ascii_case("option") => Some(TypeConstraintCommand::Option),
1080 s if s.eq_ignore_ascii_case("options") => Some(TypeConstraintCommand::Options),
1081 s if s.eq_ignore_ascii_case("length") => Some(TypeConstraintCommand::Length),
1082 _ => None,
1083 }
1084}
1085
1086#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1088pub enum ContinuationShape {
1089 Assignment,
1090 Single,
1091 CommaSeparated,
1092}
1093
1094impl TypeConstraintCommand {
1095 #[must_use]
1096 pub fn continuation_shape(self) -> ContinuationShape {
1097 match self {
1098 TypeConstraintCommand::Unit => ContinuationShape::Assignment,
1099 TypeConstraintCommand::With | TypeConstraintCommand::Options => {
1100 ContinuationShape::CommaSeparated
1101 }
1102 TypeConstraintCommand::Help
1103 | TypeConstraintCommand::Suggest
1104 | TypeConstraintCommand::Trait
1105 | TypeConstraintCommand::Minimum
1106 | TypeConstraintCommand::Maximum
1107 | TypeConstraintCommand::Lower
1108 | TypeConstraintCommand::Upper
1109 | TypeConstraintCommand::Decimals
1110 | TypeConstraintCommand::Option
1111 | TypeConstraintCommand::Length => ContinuationShape::Single,
1112 }
1113 }
1114
1115 pub const ALL: &[Self] = &[
1116 Self::Help,
1117 Self::Suggest,
1118 Self::With,
1119 Self::Unit,
1120 Self::Trait,
1121 Self::Minimum,
1122 Self::Maximum,
1123 Self::Lower,
1124 Self::Upper,
1125 Self::Decimals,
1126 Self::Option,
1127 Self::Options,
1128 Self::Length,
1129 ];
1130
1131 #[must_use]
1133 pub fn is_data_constraint(self) -> bool {
1134 true
1135 }
1136}
1137
1138#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1140pub struct Constraint {
1141 pub command: TypeConstraintCommand,
1142 pub args: Vec<CommandArg>,
1143 pub source_location: crate::parsing::source::Source,
1144 pub deprecated_without_colon: bool,
1146}
1147
1148impl Constraint {
1149 #[must_use]
1150 pub fn new(
1151 command: TypeConstraintCommand,
1152 args: Vec<CommandArg>,
1153 source_location: crate::parsing::source::Source,
1154 ) -> Self {
1155 Self {
1156 command,
1157 args,
1158 source_location,
1159 deprecated_without_colon: false,
1160 }
1161 }
1162}
1163
1164#[cfg(test)]
1165pub(crate) fn test_constraint(command: TypeConstraintCommand, args: Vec<CommandArg>) -> Constraint {
1166 Constraint::new(
1167 command,
1168 args,
1169 crate::parsing::source::Source::new(
1170 crate::parsing::source::SourceType::Volatile,
1171 Span {
1172 start: 0,
1173 end: 0,
1174 line: 1,
1175 col: 0,
1176 },
1177 ),
1178 )
1179}
1180
1181#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1183#[serde(rename_all = "snake_case")]
1184pub enum WithRhs {
1185 Literal(Value),
1186 Reference { target: Reference },
1187}
1188
1189#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1191pub struct UsesBinding {
1192 pub path: Reference,
1194 pub rhs: WithRhs,
1195 pub source_location: Source,
1196}
1197
1198#[must_use]
1200pub fn prefix_reference(alias: &str, relative: &Reference) -> Reference {
1201 let mut segments = vec![ascii_lowercase_logical_name(alias.to_string())];
1202 segments.extend(relative.segments.iter().cloned());
1203 Reference {
1204 segments,
1205 name: relative.name.clone(),
1206 }
1207}
1208
1209#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1210#[serde(rename_all = "snake_case")]
1211pub enum DataValue {
1213 Definition {
1221 base: Option<ParentType>,
1222 constraints: Option<Vec<Constraint>>,
1223 value: Option<Value>,
1224 },
1225 Import {
1227 spec_ref: SpecRef,
1228 bindings: Vec<UsesBinding>,
1229 },
1230 ImportList { spec_ref: SpecRef, list: UsesList },
1232}
1233
1234#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1236pub struct UsesList {
1237 pub key: Option<UsesListKey>,
1238 pub rows: Vec<UsesListRow>,
1239}
1240
1241#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1243pub struct UsesListKey {
1244 pub fields: Vec<Reference>,
1245 pub source_location: Source,
1246}
1247
1248#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1250#[serde(rename_all = "snake_case")]
1251pub enum UsesListRow {
1252 Fields {
1254 bindings: Vec<UsesBinding>,
1255 source_location: Source,
1256 },
1257 Values {
1259 values: Vec<WithRhs>,
1260 source_location: Source,
1261 },
1262}
1263
1264impl DataValue {
1265 #[must_use]
1266 pub fn import(spec_ref: SpecRef) -> Self {
1267 Self::Import {
1268 spec_ref,
1269 bindings: Vec::new(),
1270 }
1271 }
1272
1273 #[must_use]
1275 pub fn is_definition_literal_only(&self) -> bool {
1276 matches!(
1277 self,
1278 DataValue::Definition {
1279 base: None,
1280 constraints: None,
1281 value: Some(_),
1282 }
1283 )
1284 }
1285
1286 #[must_use]
1288 pub fn definition_needs_type_resolution(&self) -> bool {
1289 match self {
1290 DataValue::Definition { base: Some(_), .. }
1291 | DataValue::Definition {
1292 constraints: Some(_),
1293 ..
1294 } => true,
1295 DataValue::Definition {
1296 base: None,
1297 constraints: None,
1298 value: Some(v),
1299 } => !matches!(v, Value::NumberWithUnit(_, _)),
1300 DataValue::Import { .. }
1301 | DataValue::ImportList { .. }
1302 | DataValue::Definition { .. } => false,
1303 }
1304 }
1305
1306 #[must_use]
1309 pub fn uses_spec_ref(&self) -> Option<&SpecRef> {
1310 match self {
1311 DataValue::Import { spec_ref, .. } | DataValue::ImportList { spec_ref, .. } => {
1312 Some(spec_ref)
1313 }
1314 DataValue::Definition { .. } => None,
1315 }
1316 }
1317}
1318
1319impl LemmaData {
1320 #[must_use]
1321 pub fn new(reference: Reference, value: DataValue, source_location: Source) -> Self {
1322 Self {
1323 reference,
1324 value,
1325 source_location,
1326 }
1327 }
1328}
1329
1330impl LemmaSpec {
1331 #[must_use]
1332 pub fn new(name: String) -> Self {
1333 Self {
1334 name: ascii_lowercase_logical_name(name),
1335 effective_from: EffectiveDate::Origin,
1336 source_type: None,
1337 start_line: 1,
1338 commentary: None,
1339 data: Vec::new(),
1340 rules: Vec::new(),
1341 meta_fields: Vec::new(),
1342 }
1343 }
1344
1345 pub fn effective_from(&self) -> Option<&DateTimeValue> {
1347 self.effective_from.as_ref()
1348 }
1349
1350 #[must_use]
1351 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
1352 self.source_type = Some(source_type);
1353 self
1354 }
1355
1356 #[must_use]
1357 pub fn with_start_line(mut self, start_line: usize) -> Self {
1358 self.start_line = start_line;
1359 self
1360 }
1361
1362 #[must_use]
1363 pub fn set_commentary(mut self, commentary: String) -> Self {
1364 self.commentary = Some(commentary);
1365 self
1366 }
1367
1368 #[must_use]
1369 pub fn add_data(mut self, data: LemmaData) -> Self {
1370 self.data.push(data);
1371 self
1372 }
1373
1374 #[must_use]
1375 pub fn add_rule(mut self, rule: LemmaRule) -> Self {
1376 self.rules.push(rule);
1377 self
1378 }
1379
1380 #[must_use]
1381 pub fn add_meta_field(mut self, meta: MetaField) -> Self {
1382 self.meta_fields.push(meta);
1383 self
1384 }
1385}
1386
1387fn value_starts_with_sign(value: &crate::literals::Value) -> bool {
1388 let mut current = value;
1389 loop {
1390 match current {
1391 crate::literals::Value::Number(n) | crate::literals::Value::NumberWithUnit(n, _) => {
1392 return n.is_sign_negative()
1393 }
1394 crate::literals::Value::Range(left, _) => current = left.as_ref(),
1395 _ => return false,
1396 }
1397 }
1398}
1399
1400#[derive(Clone, Copy)]
1402pub(crate) enum AstAtom<'a> {
1403 Literal(&'a crate::literals::Value),
1404 Reference(&'a Reference),
1405 Now,
1406 Veto(&'a VetoExpression),
1407 ListType(&'a ParentType),
1408}
1409
1410impl fmt::Display for AstAtom<'_> {
1411 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1412 match self {
1413 AstAtom::Literal(value) => write!(f, "{}", AsLemmaSource(*value)),
1414 AstAtom::Reference(reference) => write!(f, "{reference}"),
1415 AstAtom::Now => write!(f, "now"),
1416 AstAtom::Veto(veto) => write!(f, "{veto}"),
1417 AstAtom::ListType(element) => write!(f, "{element}"),
1418 }
1419 }
1420}
1421
1422impl fmt::Display for VetoExpression {
1423 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1424 match &self.message {
1425 Some(msg) => write!(f, "veto {}", quote_lemma_text(msg)),
1426 None => write!(f, "veto"),
1427 }
1428 }
1429}
1430
1431impl<'a> SyntaxNode<'a> for ExprRef<'a, ExpressionKind> {
1432 type Atom = AstAtom<'a>;
1433
1434 fn view(self) -> SyntaxView<'a, Self> {
1435 let atom = |atom| SyntaxView::Atom {
1436 atom,
1437 starts_with_sign: false,
1438 };
1439 match self.kind() {
1440 ExpressionKind::Literal(value) => SyntaxView::Atom {
1441 atom: AstAtom::Literal(value),
1442 starts_with_sign: value_starts_with_sign(value),
1443 },
1444 ExpressionKind::Reference(reference) => atom(AstAtom::Reference(reference)),
1445 ExpressionKind::Now => atom(AstAtom::Now),
1446 ExpressionKind::Veto(veto) => atom(AstAtom::Veto(veto)),
1447 ExpressionKind::ListType { element } => atom(AstAtom::ListType(element)),
1448 ExpressionKind::DateRelative(kind, date) => {
1449 SyntaxView::DateRelative(kind, self.child(*date))
1450 }
1451 ExpressionKind::DateCalendar(kind, unit, date) => {
1452 SyntaxView::DateCalendar(kind, unit, self.child(*date))
1453 }
1454 ExpressionKind::RangeLiteral(left, right) => {
1455 SyntaxView::RangeLiteral(self.child(*left), self.child(*right))
1456 }
1457 ExpressionKind::PastFutureRange(kind, offset) => {
1458 SyntaxView::PastFutureRange(kind, self.child(*offset))
1459 }
1460 ExpressionKind::RangeContainment(value, range) => {
1461 SyntaxView::RangeContainment(self.child(*value), self.child(*range))
1462 }
1463 ExpressionKind::RangeBound(bound, operand) => {
1464 SyntaxView::Prefix(bound, self.child(*operand))
1465 }
1466 ExpressionKind::LogicalAnd { first, steps } => SyntaxView::Conjunction(
1467 std::iter::once(*first)
1468 .chain(steps.iter().map(|step| step.operand))
1469 .map(|id| self.child(id))
1470 .collect(),
1471 ),
1472 ExpressionKind::Arithmetic { first, steps } => {
1473 let level = match steps.first() {
1474 Some(step) => &step.op,
1475 None => panic!("BUG: arithmetic chain without steps"),
1476 };
1477 SyntaxView::Chain {
1478 level,
1479 operands: std::iter::once(*first)
1480 .chain(steps.iter().map(|step| step.operand))
1481 .map(|id| self.child(id))
1482 .collect(),
1483 ops: steps.iter().map(|step| &step.op).collect(),
1484 }
1485 }
1486 ExpressionKind::Comparison(left, op, right) => {
1487 SyntaxView::Comparison(self.child(*left), op, self.child(*right))
1488 }
1489 ExpressionKind::UnitConversion(value, target) => {
1490 SyntaxView::UnitConversion(self.child(*value), target)
1491 }
1492 ExpressionKind::LogicalNegation(operand, NegationType::Not) => {
1493 match self.child(*operand).kind() {
1494 ExpressionKind::ResultIsVeto(inner) => {
1495 SyntaxView::IsNotVeto(self.child(*inner))
1496 }
1497 _ => SyntaxView::Not(self.child(*operand)),
1498 }
1499 }
1500 ExpressionKind::MathematicalComputation(op, operand) => {
1501 SyntaxView::Prefix(op, self.child(*operand))
1502 }
1503 ExpressionKind::ResultIsVeto(operand) => SyntaxView::IsVeto(self.child(*operand)),
1504 ExpressionKind::ListCommand { list, command } => SyntaxView::ListCommand {
1505 list: self.child(*list),
1506 command: command_view(self.table, command),
1507 },
1508 ExpressionKind::Member { base, path } => SyntaxView::Member {
1509 base: self.child(*base),
1510 path: AstAtom::Reference(path),
1511 parenthesize_base: true,
1512 },
1513 }
1514 }
1515}
1516
1517fn predicate_view<'a>(
1518 table: &'a ExprTable<ExpressionKind>,
1519 form: &'a ListPredicate,
1520) -> PredicateView<'a, ExprRef<'a, ExpressionKind>> {
1521 let child = |id: ExprId| ExprRef { table, id };
1522 match form {
1523 ListPredicate::Value { op, right } => PredicateView::Value {
1524 op,
1525 right: child(*right),
1526 },
1527 ListPredicate::Instance { expr } => PredicateView::Instance(child(*expr)),
1528 }
1529}
1530
1531pub(crate) fn display_list_command(
1533 table: &ExprTable<ExpressionKind>,
1534 command: &ListCommand,
1535) -> String {
1536 CommandSyntax(&command_view(table, command)).to_string()
1537}
1538
1539fn command_view<'a>(
1540 table: &'a ExprTable<ExpressionKind>,
1541 command: &'a ListCommand,
1542) -> CommandView<'a, ExprRef<'a, ExpressionKind>> {
1543 let child = |id: &ExprId| ExprRef { table, id: *id };
1544 match command {
1545 ListCommand::Find { keys } => CommandView::Find(keys.iter().map(child).collect()),
1546 ListCommand::First => CommandView::First,
1547 ListCommand::Last => CommandView::Last,
1548 ListCommand::Count => CommandView::Count,
1549 ListCommand::Sum { argument } => CommandView::Reduce("sum", argument.as_ref().map(child)),
1550 ListCommand::Avg { argument } => CommandView::Reduce("avg", argument.as_ref().map(child)),
1551 ListCommand::Product { argument } => {
1552 CommandView::Reduce("product", argument.as_ref().map(child))
1553 }
1554 ListCommand::Min { argument } => CommandView::Reduce("min", argument.as_ref().map(child)),
1555 ListCommand::Max { argument } => CommandView::Reduce("max", argument.as_ref().map(child)),
1556 ListCommand::Any { form } => CommandView::Predicate("any", predicate_view(table, form)),
1557 ListCommand::All { form } => CommandView::Predicate("all", predicate_view(table, form)),
1558 ListCommand::Where { form } => CommandView::Predicate("where", predicate_view(table, form)),
1559 ListCommand::Sort {
1560 descending,
1561 argument,
1562 } => CommandView::Sort {
1563 descending: *descending,
1564 argument: argument.as_ref().map(child),
1565 },
1566 ListCommand::Top { n } => CommandView::Top(child(n)),
1567 ListCommand::With { rows } => CommandView::With(match rows {
1568 ListWithRows::Values(values) => {
1569 values.iter().map(|value| (None, child(value))).collect()
1570 }
1571 ListWithRows::FieldPairs(pairs) => pairs
1572 .iter()
1573 .map(|(name, value)| (Some(AstAtom::Reference(name)), child(value)))
1574 .collect(),
1575 }),
1576 }
1577}
1578
1579impl fmt::Display for Expression {
1580 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1581 fmt::Display::fmt(&Syntax(self.root()), f)
1582 }
1583}
1584
1585impl fmt::Display for ConversionTarget {
1586 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1587 match self {
1588 ConversionTarget::Type(kind) => write!(f, "{kind}"),
1589 ConversionTarget::Unit { unit_name } => write!(f, "{unit_name}"),
1590 }
1591 }
1592}
1593
1594impl fmt::Display for ArithmeticComputation {
1595 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1596 match self {
1597 ArithmeticComputation::Add => write!(f, "+"),
1598 ArithmeticComputation::Subtract => write!(f, "-"),
1599 ArithmeticComputation::Multiply => write!(f, "*"),
1600 ArithmeticComputation::Divide => write!(f, "/"),
1601 ArithmeticComputation::Modulo => write!(f, "mod"),
1602 ArithmeticComputation::Power => write!(f, "^"),
1603 }
1604 }
1605}
1606
1607impl fmt::Display for ComparisonComputation {
1608 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1609 match self {
1610 ComparisonComputation::GreaterThan => write!(f, ">"),
1611 ComparisonComputation::LessThan => write!(f, "<"),
1612 ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1613 ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1614 ComparisonComputation::Is => write!(f, "is"),
1615 ComparisonComputation::IsNot => write!(f, "is not"),
1616 }
1617 }
1618}
1619
1620impl fmt::Display for MathematicalComputation {
1621 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1622 match self {
1623 MathematicalComputation::Sqrt => write!(f, "sqrt"),
1624 MathematicalComputation::Sin => write!(f, "sin"),
1625 MathematicalComputation::Cos => write!(f, "cos"),
1626 MathematicalComputation::Tan => write!(f, "tan"),
1627 MathematicalComputation::Asin => write!(f, "asin"),
1628 MathematicalComputation::Acos => write!(f, "acos"),
1629 MathematicalComputation::Atan => write!(f, "atan"),
1630 MathematicalComputation::Log => write!(f, "log"),
1631 MathematicalComputation::Exp => write!(f, "exp"),
1632 MathematicalComputation::Abs => write!(f, "abs"),
1633 MathematicalComputation::Floor => write!(f, "floor"),
1634 MathematicalComputation::Ceil => write!(f, "ceil"),
1635 MathematicalComputation::Round => write!(f, "round"),
1636 }
1637 }
1638}
1639
1640#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1646#[serde(rename_all = "snake_case")]
1647pub enum PrimitiveKind {
1648 Boolean,
1649 Measure,
1650 MeasureRange,
1651 Number,
1652 NumberRange,
1653 Ratio,
1654 RatioRange,
1655 Text,
1656 Date,
1657 DateRange,
1658 Time,
1659 TimeRange,
1660}
1661
1662impl std::fmt::Display for PrimitiveKind {
1663 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1664 let s = match self {
1665 PrimitiveKind::Boolean => "boolean",
1666 PrimitiveKind::Measure => "measure",
1667 PrimitiveKind::MeasureRange => "measure range",
1668 PrimitiveKind::Number => "number",
1669 PrimitiveKind::NumberRange => "number range",
1670 PrimitiveKind::Ratio => "ratio",
1671 PrimitiveKind::RatioRange => "ratio range",
1672 PrimitiveKind::Text => "text",
1673 PrimitiveKind::Date => "date",
1674 PrimitiveKind::DateRange => "date range",
1675 PrimitiveKind::Time => "time",
1676 PrimitiveKind::TimeRange => "time range",
1677 };
1678 write!(f, "{}", s)
1679 }
1680}
1681
1682#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1687pub enum ParentType {
1688 Primitive {
1689 primitive: PrimitiveKind,
1690 },
1691 Custom {
1692 name: String,
1693 },
1694 Qualified {
1697 spec_alias: String,
1698 inner: Box<ParentType>,
1699 },
1700 Ranged {
1702 inner: Box<ParentType>,
1703 },
1704 List {
1706 inner: Box<ParentType>,
1707 },
1708}
1709
1710impl std::fmt::Display for ParentType {
1711 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1712 match self {
1713 ParentType::Primitive { primitive } => write!(f, "{}", primitive),
1714 ParentType::Custom { name } => write!(f, "{}", name),
1715 ParentType::Qualified { spec_alias, inner } => {
1716 write!(f, "{spec_alias}.{inner}")
1717 }
1718 ParentType::Ranged { inner } => write!(f, "{inner} range"),
1719 ParentType::List { inner } => write!(f, "{inner} list"),
1720 }
1721 }
1722}
1723
1724pub struct AsLemmaSource<'a, T: ?Sized>(pub &'a T);
1730
1731pub fn quote_lemma_text(s: &str) -> String {
1734 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1735 format!("\"{}\"", escaped)
1736}
1737
1738fn format_decimal_source(n: &Decimal) -> String {
1743 let raw = if n.fract().is_zero() {
1744 n.trunc().to_string()
1745 } else {
1746 n.to_string()
1747 };
1748 group_digits(&raw)
1749}
1750
1751fn group_digits(s: &str) -> String {
1755 let (sign, rest) = if s.starts_with('-') || s.starts_with('+') {
1756 (&s[..1], &s[1..])
1757 } else {
1758 ("", s)
1759 };
1760
1761 let (int_part, frac_part) = match rest.find('.') {
1762 Some(pos) => (&rest[..pos], &rest[pos..]),
1763 None => (rest, ""),
1764 };
1765
1766 if int_part.len() < 4 {
1767 return s.to_string();
1768 }
1769
1770 let mut grouped = String::with_capacity(int_part.len() + int_part.len() / 3);
1771 for (i, ch) in int_part.chars().enumerate() {
1772 let digits_remaining = int_part.len() - i;
1773 if i > 0 && digits_remaining % 3 == 0 {
1774 grouped.push('_');
1775 }
1776 grouped.push(ch);
1777 }
1778
1779 format!("{}{}{}", sign, grouped, frac_part)
1780}
1781
1782impl<'a> fmt::Display for AsLemmaSource<'a, CommandArg> {
1783 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1784 use crate::literals::Value;
1785 match self.0 {
1786 CommandArg::Literal(Value::Number(d)) => {
1787 write!(f, "{}", group_digits(&d.to_string()))
1788 }
1789 CommandArg::Literal(Value::NumberWithUnit(d, unit)) => {
1790 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1791 }
1792 CommandArg::Literal(value) => write!(f, "{}", AsLemmaSource(value)),
1793 CommandArg::Label(s) => write!(f, "{}", s),
1794 CommandArg::UnitExpr(unit_arg) => write!(f, "{}", unit_arg),
1795 }
1796 }
1797}
1798
1799pub(crate) fn format_assignment_continuation(
1801 command: &str,
1802 key: &str,
1803 value: &impl fmt::Display,
1804) -> String {
1805 format!("{} {}: {}", command, key, value)
1806}
1807
1808pub(crate) fn format_constraint_as_source(
1810 cmd: &TypeConstraintCommand,
1811 args: &[CommandArg],
1812) -> String {
1813 if *cmd == TypeConstraintCommand::Unit {
1814 let Some(CommandArg::Label(name)) = args.first() else {
1815 return cmd.to_string();
1816 };
1817 let Some(CommandArg::UnitExpr(unit_arg)) = args.get(1) else {
1818 return format!("{} {}", cmd, name);
1819 };
1820 return format_assignment_continuation("unit", name, unit_arg);
1821 }
1822
1823 if args.is_empty() {
1824 cmd.to_string()
1825 } else {
1826 let args_str: Vec<String> = args
1827 .iter()
1828 .map(|a| format!("{}", AsLemmaSource(a)))
1829 .collect();
1830 let sep = match cmd.continuation_shape() {
1831 ContinuationShape::CommaSeparated => ", ",
1832 ContinuationShape::Single | ContinuationShape::Assignment => " ",
1833 };
1834 format!("{} {}", cmd, args_str.join(sep))
1835 }
1836}
1837
1838fn format_constraints_as_source(constraints: &[Constraint], separator: &str) -> String {
1841 constraints
1842 .iter()
1843 .map(|row| format_constraint_as_source(&row.command, &row.args))
1844 .collect::<Vec<_>>()
1845 .join(separator)
1846}
1847
1848impl<'a> fmt::Display for AsLemmaSource<'a, Value> {
1851 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1852 match self.0 {
1853 Value::Number(n) => write!(f, "{}", format_decimal_source(n)),
1854 Value::Text(s) => write!(f, "{}", quote_lemma_text(s)),
1855 Value::Date(dt) => write!(f, "{dt}"),
1856 Value::Time(t) => write!(f, "{t}"),
1857 Value::Boolean(b) => write!(f, "{}", b),
1858 Value::NumberWithUnit(n, u) => match u.as_str() {
1859 "percent" => write!(f, "{}%", format_decimal_source(n)),
1860 "permille" => write!(f, "{}%%", format_decimal_source(n)),
1861 unit => write!(f, "{} {}", format_decimal_source(n), unit),
1862 },
1863 Value::Range(left, right) => {
1864 write!(
1865 f,
1866 "{}...{}",
1867 AsLemmaSource(left.as_ref()),
1868 AsLemmaSource(right.as_ref())
1869 )
1870 }
1871 }
1872 }
1873}
1874
1875impl<'a> fmt::Display for AsLemmaSource<'a, DataValue> {
1878 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1879 match self.0 {
1880 DataValue::Definition {
1881 base,
1882 constraints,
1883 value,
1884 } => {
1885 if base.is_none() && constraints.is_none() {
1886 if let Some(v) = value {
1887 return write!(f, "{}", AsLemmaSource(v));
1888 }
1889 }
1890 let base_str = match base.as_ref() {
1891 Some(b) => format!("{}", b),
1892 None => match value {
1893 Some(v) => {
1894 if let Some(ref constraints_vec) = constraints {
1895 let constraint_str =
1896 format_constraints_as_source(constraints_vec, " -> ");
1897 return write!(f, "{} -> {}", AsLemmaSource(v), constraint_str);
1898 }
1899 return write!(f, "{}", AsLemmaSource(v));
1900 }
1901 None => String::new(),
1902 },
1903 };
1904 if let Some(ref constraints_vec) = constraints {
1905 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
1906 write!(f, "{} -> {}", base_str, constraint_str)
1907 } else {
1908 write!(f, "{}", base_str)
1909 }
1910 }
1911 DataValue::Import {
1912 spec_ref,
1913 bindings: _,
1914 } => {
1915 write!(f, "uses {}", spec_ref)
1916 }
1917 DataValue::ImportList { spec_ref, list: _ } => {
1918 write!(f, "uses {} list", spec_ref)
1919 }
1920 }
1921 }
1922}
1923
1924pub(crate) fn canonicalize_value(value: &mut Value) {
1925 if let Value::NumberWithUnit(_, unit) = value {
1926 *unit = ascii_lowercase_logical_name(std::mem::take(unit));
1927 }
1928}
1929
1930pub(crate) fn canonicalize_reference(reference: &mut Reference) {
1931 for segment in &mut reference.segments {
1932 *segment = ascii_lowercase_logical_name(std::mem::take(segment));
1933 }
1934 reference.name = ascii_lowercase_logical_name(std::mem::take(&mut reference.name));
1935}
1936
1937pub(crate) fn canonicalize_spec_ref(spec_ref: &mut SpecRef) {
1938 spec_ref.name = ascii_lowercase_logical_name(std::mem::take(&mut spec_ref.name));
1939 if let Some(qualifier) = spec_ref.repository.as_mut() {
1940 qualifier.name = ascii_lowercase_logical_name(std::mem::take(&mut qualifier.name));
1941 }
1942}
1943
1944pub(crate) fn canonicalize_parent_type(parent: &mut ParentType) {
1945 let mut current = parent;
1946 loop {
1947 current = match current {
1948 ParentType::Custom { name } => {
1949 *name = ascii_lowercase_logical_name(std::mem::take(name));
1950 return;
1951 }
1952 ParentType::Qualified { spec_alias, inner } => {
1953 *spec_alias = ascii_lowercase_logical_name(std::mem::take(spec_alias));
1954 inner.as_mut()
1955 }
1956 ParentType::Ranged { inner } | ParentType::List { inner } => inner.as_mut(),
1957 ParentType::Primitive { .. } => return,
1958 }
1959 }
1960}
1961
1962pub(crate) fn canonicalize_unit_factor(factor: &mut UnitFactor) {
1963 factor.measure_ref = ascii_lowercase_logical_name(std::mem::take(&mut factor.measure_ref));
1964}
1965
1966pub(crate) fn canonicalize_unit_arg(unit_arg: &mut UnitArg) {
1967 if let UnitArg::Expr(_, factors) = unit_arg {
1968 for factor in factors {
1969 canonicalize_unit_factor(factor);
1970 }
1971 }
1972}
1973
1974pub(crate) fn canonicalize_command_arg(command_arg: &mut CommandArg) {
1975 match command_arg {
1976 CommandArg::Literal(value) => canonicalize_value(value),
1977 CommandArg::Label(label) => {
1978 *label = ascii_lowercase_logical_name(std::mem::take(label));
1979 }
1980 CommandArg::UnitExpr(unit_arg) => canonicalize_unit_arg(unit_arg),
1981 }
1982}
1983
1984pub(crate) fn canonicalize_constraints(constraints: &mut [Constraint]) {
1985 for row in constraints {
1986 for arg in &mut row.args {
1987 canonicalize_command_arg(arg);
1988 }
1989 }
1990}
1991
1992pub(crate) fn canonicalize_expression(expression: &mut Expression) {
1993 for kind in expression.table.kinds_mut() {
1994 canonicalize_expression_kind(kind);
1995 }
1996}
1997
1998fn canonicalize_expression_kind(kind: &mut ExpressionKind) {
2000 match kind {
2001 ExpressionKind::Literal(value) => canonicalize_value(value),
2002 ExpressionKind::Reference(reference) => canonicalize_reference(reference),
2003 ExpressionKind::ListType { element } => canonicalize_parent_type(element),
2004 ExpressionKind::Member { path, .. } => canonicalize_reference(path),
2005 ExpressionKind::ListCommand { command, .. } => canonicalize_list_command(command),
2006 ExpressionKind::Now
2007 | ExpressionKind::DateRelative(..)
2008 | ExpressionKind::DateCalendar(..)
2009 | ExpressionKind::RangeLiteral(..)
2010 | ExpressionKind::PastFutureRange(..)
2011 | ExpressionKind::RangeContainment(..)
2012 | ExpressionKind::RangeBound(..)
2013 | ExpressionKind::LogicalAnd { .. }
2014 | ExpressionKind::Arithmetic { .. }
2015 | ExpressionKind::Comparison(..)
2016 | ExpressionKind::UnitConversion(..)
2017 | ExpressionKind::LogicalNegation(..)
2018 | ExpressionKind::MathematicalComputation(..)
2019 | ExpressionKind::Veto(_)
2020 | ExpressionKind::ResultIsVeto(..) => {}
2021 }
2022}
2023
2024fn canonicalize_list_command(command: &mut ListCommand) {
2025 match command {
2026 ListCommand::First
2027 | ListCommand::Last
2028 | ListCommand::Count
2029 | ListCommand::Find { .. }
2030 | ListCommand::Sum { .. }
2031 | ListCommand::Avg { .. }
2032 | ListCommand::Product { .. }
2033 | ListCommand::Min { .. }
2034 | ListCommand::Max { .. }
2035 | ListCommand::Any { .. }
2036 | ListCommand::All { .. }
2037 | ListCommand::Where { .. }
2038 | ListCommand::Sort { .. }
2039 | ListCommand::Top { .. }
2040 | ListCommand::With {
2041 rows: ListWithRows::Values(_),
2042 } => {}
2043 ListCommand::With {
2044 rows: ListWithRows::FieldPairs(pairs),
2045 } => {
2046 for (name, _) in pairs {
2047 canonicalize_reference(name);
2048 }
2049 }
2050 }
2051}
2052
2053pub(crate) fn canonicalize_unless_clause(unless_clause: &mut UnlessClause) {
2054 canonicalize_expression(&mut unless_clause.condition);
2055 canonicalize_expression(&mut unless_clause.result);
2056}
2057
2058pub(crate) fn canonicalize_data_value(data_value: &mut DataValue) {
2059 match data_value {
2060 DataValue::Definition {
2061 base,
2062 constraints,
2063 value,
2064 } => {
2065 if let Some(base) = base {
2066 canonicalize_parent_type(base);
2067 }
2068 if let Some(constraints) = constraints {
2069 canonicalize_constraints(constraints);
2070 }
2071 if let Some(value) = value {
2072 canonicalize_value(value);
2073 }
2074 }
2075 DataValue::Import { spec_ref, bindings } => {
2076 canonicalize_spec_ref(spec_ref);
2077 canonicalize_uses_bindings(bindings);
2078 }
2079 DataValue::ImportList { spec_ref, list } => {
2080 canonicalize_spec_ref(spec_ref);
2081 if let Some(key) = &mut list.key {
2082 for field in &mut key.fields {
2083 canonicalize_reference(field);
2084 }
2085 }
2086 for row in &mut list.rows {
2087 match row {
2088 UsesListRow::Fields { bindings, .. } => canonicalize_uses_bindings(bindings),
2089 UsesListRow::Values { values, .. } => {
2090 for value in values {
2091 canonicalize_with_rhs(value);
2092 }
2093 }
2094 }
2095 }
2096 }
2097 }
2098}
2099
2100fn canonicalize_with_rhs(rhs: &mut WithRhs) {
2101 match rhs {
2102 WithRhs::Literal(value) => canonicalize_value(value),
2103 WithRhs::Reference { target } => canonicalize_reference(target),
2104 }
2105}
2106
2107fn canonicalize_uses_bindings(bindings: &mut [UsesBinding]) {
2108 for binding in bindings {
2109 canonicalize_reference(&mut binding.path);
2110 canonicalize_with_rhs(&mut binding.rhs);
2111 }
2112}
2113
2114pub(crate) fn canonicalize_lemma_data(data: &mut LemmaData) {
2115 canonicalize_reference(&mut data.reference);
2116 canonicalize_data_value(&mut data.value);
2117}
2118
2119pub(crate) fn canonicalize_lemma_rule(rule: &mut LemmaRule) {
2120 rule.name = ascii_lowercase_logical_name(std::mem::take(&mut rule.name));
2121 canonicalize_expression(&mut rule.expression);
2122 for unless_clause in &mut rule.unless_clauses {
2123 canonicalize_unless_clause(unless_clause);
2124 }
2125}
2126
2127pub(crate) fn canonicalize_lemma_spec(spec: &mut LemmaSpec) {
2128 spec.name = ascii_lowercase_logical_name(std::mem::take(&mut spec.name));
2129 for meta in &mut spec.meta_fields {
2130 meta.key = ascii_lowercase_logical_name(std::mem::take(&mut meta.key));
2131 }
2132 for data in &mut spec.data {
2133 canonicalize_lemma_data(data);
2134 }
2135 for rule in &mut spec.rules {
2136 canonicalize_lemma_rule(rule);
2137 }
2138}
2139
2140pub(crate) fn canonicalize_repository(repository: &mut LemmaRepository) {
2141 if let Some(name) = repository.name.take() {
2142 repository.name = Some(ascii_lowercase_logical_name(name));
2143 }
2144}
2145
2146#[cfg(test)]
2147mod tests {
2148 use super::*;
2149 use crate::literals::DateGranularity;
2150
2151 #[test]
2152 fn test_conversion_target_display() {
2153 assert_eq!(
2154 format!("{}", ConversionTarget::Type(PrimitiveKind::Number)),
2155 "number"
2156 );
2157 }
2158
2159 #[test]
2160 fn test_value_number_with_unit_ratio_display() {
2161 use rust_decimal::Decimal;
2162 use std::str::FromStr;
2163 let percent =
2164 Value::NumberWithUnit(Decimal::from_str("10").unwrap(), "percent".to_string());
2165 assert_eq!(format!("{}", percent), "10%");
2166 let permille =
2167 Value::NumberWithUnit(Decimal::from_str("5").unwrap(), "permille".to_string());
2168 assert_eq!(format!("{}", permille), "5%%");
2169 }
2170
2171 #[test]
2172 fn test_datetime_value_display() {
2173 let dt = DateTimeValue {
2174 year: 2024,
2175 month: 12,
2176 day: 25,
2177 hour: 14,
2178 minute: 30,
2179 second: 45,
2180 microsecond: 0,
2181 timezone: Some(TimezoneValue { offset_minutes: 60 }),
2182
2183 granularity: DateGranularity::DateTime,
2184 };
2185 assert_eq!(format!("{}", dt), "2024-12-25T14:30:45+01:00");
2186 }
2187
2188 #[test]
2189 fn test_datetime_value_display_date_only() {
2190 let dt = DateTimeValue {
2191 year: 2026,
2192 month: 3,
2193 day: 4,
2194 hour: 0,
2195 minute: 0,
2196 second: 0,
2197 microsecond: 0,
2198 timezone: None,
2199
2200 granularity: DateGranularity::Full,
2201 };
2202 assert_eq!(format!("{}", dt), "2026-03-04");
2203 }
2204
2205 #[test]
2206 fn test_datetime_value_display_microseconds() {
2207 let dt = DateTimeValue {
2208 year: 2026,
2209 month: 2,
2210 day: 23,
2211 hour: 14,
2212 minute: 30,
2213 second: 45,
2214 microsecond: 123456,
2215 timezone: Some(TimezoneValue { offset_minutes: 0 }),
2216
2217 granularity: DateGranularity::DateTime,
2218 };
2219 assert_eq!(format!("{}", dt), "2026-02-23T14:30:45.123456Z");
2220 }
2221
2222 #[test]
2223 fn test_datetime_microsecond_in_ordering() {
2224 let a = DateTimeValue {
2225 year: 2026,
2226 month: 1,
2227 day: 1,
2228 hour: 0,
2229 minute: 0,
2230 second: 0,
2231 microsecond: 100,
2232 timezone: None,
2233
2234 granularity: DateGranularity::DateTime,
2235 };
2236 let b = DateTimeValue {
2237 year: 2026,
2238 month: 1,
2239 day: 1,
2240 hour: 0,
2241 minute: 0,
2242 second: 0,
2243 microsecond: 200,
2244 timezone: None,
2245
2246 granularity: DateGranularity::DateTime,
2247 };
2248 assert!(a < b);
2249 }
2250
2251 #[test]
2252 fn test_datetime_parse_iso_week() {
2253 let dt: DateTimeValue = "2026-W01".parse().unwrap();
2254 assert_eq!(dt.year, 2025);
2255 assert_eq!(dt.month, 12);
2256 assert_eq!(dt.day, 29);
2257 assert_eq!(dt.microsecond, 0);
2258 assert_eq!(dt.to_string(), "2026-W01");
2259 assert!(matches!(
2260 dt.granularity,
2261 DateGranularity::IsoWeek {
2262 iso_year: 2026,
2263 week: 1
2264 }
2265 ));
2266 }
2267
2268 #[test]
2269 fn test_negation_types() {
2270 let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
2271 let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
2272 assert_eq!(decoded, NegationType::Not);
2273 }
2274
2275 #[test]
2276 fn parent_type_primitive_serde_externally_tagged() {
2277 let p = ParentType::Primitive {
2278 primitive: PrimitiveKind::Number,
2279 };
2280 let json = serde_json::to_string(&p).expect("ParentType::Primitive must serialize");
2281 assert!(json.contains("\"Primitive\"") && json.contains("\"primitive\""));
2282 let back: ParentType = serde_json::from_str(&json).expect("deserialize");
2283 assert_eq!(back, p);
2284 }
2285
2286 fn text_arg(s: &str) -> CommandArg {
2291 CommandArg::Literal(crate::literals::Value::Text(s.to_string()))
2292 }
2293
2294 fn number_arg(s: &str) -> CommandArg {
2295 let d: rust_decimal::Decimal = s.parse().expect("decimal");
2296 CommandArg::Literal(crate::literals::Value::Number(d))
2297 }
2298
2299 fn boolean_arg(b: BooleanValue) -> CommandArg {
2300 CommandArg::Literal(crate::literals::Value::Boolean(b))
2301 }
2302
2303 fn measure_arg(value: &str, unit: &str) -> CommandArg {
2304 let d: rust_decimal::Decimal = value.parse().expect("decimal");
2305 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
2306 }
2307
2308 fn duration_arg(value: &str, unit: &str) -> CommandArg {
2309 let d: rust_decimal::Decimal = value.parse().expect("decimal");
2310 CommandArg::Literal(crate::literals::Value::NumberWithUnit(d, unit.to_string()))
2311 }
2312
2313 #[test]
2314 fn as_lemma_source_text_default_is_quoted() {
2315 let fv = DataValue::Definition {
2316 base: Some(ParentType::Primitive {
2317 primitive: PrimitiveKind::Text,
2318 }),
2319 constraints: Some(vec![test_constraint(
2320 TypeConstraintCommand::Suggest,
2321 vec![text_arg("single")],
2322 )]),
2323 value: None,
2324 };
2325 assert_eq!(
2326 format!("{}", AsLemmaSource(&fv)),
2327 "text -> suggest \"single\""
2328 );
2329 }
2330
2331 #[test]
2332 fn as_lemma_source_number_default_not_quoted() {
2333 let fv = DataValue::Definition {
2334 base: Some(ParentType::Primitive {
2335 primitive: PrimitiveKind::Number,
2336 }),
2337 constraints: Some(vec![test_constraint(
2338 TypeConstraintCommand::Suggest,
2339 vec![number_arg("10")],
2340 )]),
2341 value: None,
2342 };
2343 assert_eq!(format!("{}", AsLemmaSource(&fv)), "number -> suggest 10");
2344 }
2345
2346 #[test]
2347 fn as_lemma_source_help_always_quoted() {
2348 let fv = DataValue::Definition {
2349 base: Some(ParentType::Primitive {
2350 primitive: PrimitiveKind::Number,
2351 }),
2352 constraints: Some(vec![test_constraint(
2353 TypeConstraintCommand::Help,
2354 vec![text_arg("Enter a measure")],
2355 )]),
2356 value: None,
2357 };
2358 assert_eq!(
2359 format!("{}", AsLemmaSource(&fv)),
2360 "number -> help \"Enter a measure\""
2361 );
2362 }
2363
2364 #[test]
2365 fn as_lemma_source_text_option_quoted() {
2366 let fv = DataValue::Definition {
2367 base: Some(ParentType::Primitive {
2368 primitive: PrimitiveKind::Text,
2369 }),
2370 constraints: Some(vec![
2371 test_constraint(TypeConstraintCommand::Option, vec![text_arg("active")]),
2372 test_constraint(TypeConstraintCommand::Option, vec![text_arg("inactive")]),
2373 ]),
2374 value: None,
2375 };
2376 assert_eq!(
2377 format!("{}", AsLemmaSource(&fv)),
2378 "text -> option \"active\" -> option \"inactive\""
2379 );
2380 }
2381
2382 #[test]
2383 fn as_lemma_source_measure_unit_not_quoted() {
2384 let fv = DataValue::Definition {
2385 base: Some(ParentType::Primitive {
2386 primitive: PrimitiveKind::Measure,
2387 }),
2388 constraints: Some(vec![
2389 test_constraint(
2390 TypeConstraintCommand::Unit,
2391 vec![
2392 CommandArg::Label("eur".to_string()),
2393 CommandArg::UnitExpr(UnitArg::Factor(decimal("1.00"))),
2394 ],
2395 ),
2396 test_constraint(
2397 TypeConstraintCommand::Unit,
2398 vec![
2399 CommandArg::Label("usd".to_string()),
2400 CommandArg::UnitExpr(UnitArg::Factor(decimal("0.91"))),
2401 ],
2402 ),
2403 ]),
2404 value: None,
2405 };
2406 assert_eq!(
2407 format!("{}", AsLemmaSource(&fv)),
2408 "measure -> unit eur: 1.00 -> unit usd: 0.91"
2409 );
2410 }
2411
2412 #[test]
2413 fn as_lemma_source_measure_minimum_with_unit() {
2414 let fv = DataValue::Definition {
2415 base: Some(ParentType::Primitive {
2416 primitive: PrimitiveKind::Measure,
2417 }),
2418 constraints: Some(vec![test_constraint(
2419 TypeConstraintCommand::Minimum,
2420 vec![measure_arg("0", "eur")],
2421 )]),
2422 value: None,
2423 };
2424 assert_eq!(
2425 format!("{}", AsLemmaSource(&fv)),
2426 "measure -> minimum 0 eur"
2427 );
2428 }
2429
2430 #[test]
2431 fn as_lemma_source_boolean_default() {
2432 let fv = DataValue::Definition {
2433 base: Some(ParentType::Primitive {
2434 primitive: PrimitiveKind::Boolean,
2435 }),
2436 constraints: Some(vec![test_constraint(
2437 TypeConstraintCommand::Suggest,
2438 vec![boolean_arg(BooleanValue::True)],
2439 )]),
2440 value: None,
2441 };
2442 assert_eq!(format!("{}", AsLemmaSource(&fv)), "boolean -> suggest true");
2443 }
2444
2445 #[test]
2446 fn as_lemma_source_duration_default() {
2447 let fv = DataValue::Definition {
2448 base: Some(ParentType::Custom {
2449 name: "duration".to_string(),
2450 }),
2451 constraints: Some(vec![test_constraint(
2452 TypeConstraintCommand::Suggest,
2453 vec![duration_arg("40", "hour")],
2454 )]),
2455 value: None,
2456 };
2457 assert_eq!(
2458 format!("{}", AsLemmaSource(&fv)),
2459 "duration -> suggest 40 hour"
2460 );
2461 }
2462
2463 #[test]
2464 fn as_lemma_source_named_type_default_quoted() {
2465 let fv = DataValue::Definition {
2468 base: Some(ParentType::Custom {
2469 name: "filing_status_type".to_string(),
2470 }),
2471 constraints: Some(vec![test_constraint(
2472 TypeConstraintCommand::Suggest,
2473 vec![text_arg("single")],
2474 )]),
2475 value: None,
2476 };
2477 assert_eq!(
2478 format!("{}", AsLemmaSource(&fv)),
2479 "filing_status_type -> suggest \"single\""
2480 );
2481 }
2482
2483 #[test]
2484 fn as_lemma_source_help_escapes_quotes() {
2485 let fv = DataValue::Definition {
2486 base: Some(ParentType::Primitive {
2487 primitive: PrimitiveKind::Text,
2488 }),
2489 constraints: Some(vec![test_constraint(
2490 TypeConstraintCommand::Help,
2491 vec![text_arg("say \"hello\"")],
2492 )]),
2493 value: None,
2494 };
2495 assert_eq!(
2496 format!("{}", AsLemmaSource(&fv)),
2497 "text -> help \"say \\\"hello\\\"\""
2498 );
2499 }
2500
2501 fn unit_arg_expr(prefix: Decimal, factors: &[(&str, i32)]) -> UnitArg {
2502 UnitArg::Expr(
2503 prefix,
2504 factors
2505 .iter()
2506 .map(|(measure_ref, exp)| UnitFactor {
2507 measure_ref: (*measure_ref).to_string(),
2508 exp: *exp,
2509 })
2510 .collect(),
2511 )
2512 }
2513
2514 #[test]
2515 fn unit_arg_display_metre_per_second() {
2516 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -1)]);
2517 assert_eq!(format!("{arg}"), "meter/second");
2518 assert!(
2519 !format!("{arg}").contains("second^-1"),
2520 "must not print denominator as negative exponent"
2521 );
2522 }
2523
2524 #[test]
2525 fn unit_arg_display_meter_per_second_squared() {
2526 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -2)]);
2527 assert_eq!(format!("{arg}"), "meter/second^2");
2528 }
2529
2530 #[test]
2531 fn unit_arg_display_kg_times_mps2() {
2532 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("mps2", 1)]);
2533 assert_eq!(format!("{arg}"), "kg * mps2");
2534 }
2535
2536 #[test]
2537 fn unit_arg_display_numeric_prefix_metre_per_second() {
2538 use std::str::FromStr;
2539 let prefix = Decimal::from_str("3.6").expect("decimal");
2540 let arg = unit_arg_expr(prefix, &[("meter", 1), ("second", -1)]);
2541 assert_eq!(format!("{arg}"), "3.6 meter/second");
2542 }
2543
2544 #[test]
2545 fn unit_arg_display_metre_per_second_times_kg() {
2546 let arg = unit_arg_expr(Decimal::ONE, &[("meter", 1), ("second", -1), ("kg", 1)]);
2547 assert_eq!(format!("{arg}"), "meter/second * kg");
2548 }
2549
2550 #[test]
2551 fn unit_arg_display_kg_meter_per_second_squared() {
2552 let arg = unit_arg_expr(Decimal::ONE, &[("kg", 1), ("meter", 1), ("second", -2)]);
2553 assert_eq!(format!("{arg}"), "kg * meter/second^2");
2554 }
2555
2556 #[test]
2559 fn format_constraint_as_source_unit_factor_uses_assignment_colon() {
2560 let args = vec![
2561 CommandArg::Label("eur".to_string()),
2562 CommandArg::UnitExpr(UnitArg::Factor(decimal("1"))),
2563 ];
2564 assert_eq!(
2565 format_constraint_as_source(&TypeConstraintCommand::Unit, &args),
2566 "unit eur: 1"
2567 );
2568 }
2569
2570 #[test]
2571 fn format_constraint_as_source_unit_compound_uses_assignment_colon() {
2572 let arg = unit_arg_expr(decimal("3.6"), &[("meter", 1), ("second", -1)]);
2573 let args = vec![
2574 CommandArg::Label("kmh".to_string()),
2575 CommandArg::UnitExpr(arg),
2576 ];
2577 assert_eq!(
2578 format_constraint_as_source(&TypeConstraintCommand::Unit, &args),
2579 "unit kmh: 3.6 meter/second"
2580 );
2581 }
2582
2583 #[test]
2584 fn format_constraint_as_source_unit_factor_one_decimal_uses_assignment_colon() {
2585 let args = vec![
2586 CommandArg::Label("eur".to_string()),
2587 CommandArg::UnitExpr(UnitArg::Factor(decimal("1.00"))),
2588 ];
2589 assert_eq!(
2590 format_constraint_as_source(&TypeConstraintCommand::Unit, &args),
2591 "unit eur: 1.00"
2592 );
2593 }
2594
2595 #[test]
2596 fn as_lemma_source_measure_unit_uses_assignment_colon() {
2597 let fv = DataValue::Definition {
2598 base: Some(ParentType::Primitive {
2599 primitive: PrimitiveKind::Measure,
2600 }),
2601 constraints: Some(vec![
2602 test_constraint(
2603 TypeConstraintCommand::Unit,
2604 vec![
2605 CommandArg::Label("eur".to_string()),
2606 CommandArg::UnitExpr(UnitArg::Factor(decimal("1.00"))),
2607 ],
2608 ),
2609 test_constraint(
2610 TypeConstraintCommand::Unit,
2611 vec![
2612 CommandArg::Label("usd".to_string()),
2613 CommandArg::UnitExpr(UnitArg::Factor(decimal("0.91"))),
2614 ],
2615 ),
2616 ]),
2617 value: None,
2618 };
2619 assert_eq!(
2620 format!("{}", AsLemmaSource(&fv)),
2621 "measure -> unit eur: 1.00 -> unit usd: 0.91"
2622 );
2623 }
2624
2625 #[test]
2626 fn as_lemma_source_time_keeps_microseconds() {
2627 let time = Value::Time(crate::literals::TimeValue {
2628 hour: 14,
2629 minute: 30,
2630 second: 45,
2631 microsecond: 250,
2632 timezone: None,
2633 });
2634 assert_eq!(format!("{}", AsLemmaSource(&time)), "14:30:45.000250");
2635 }
2636
2637 #[test]
2638 fn as_lemma_source_datetime_keeps_microseconds() {
2639 let datetime = Value::Date(DateTimeValue {
2640 year: 2024,
2641 month: 1,
2642 day: 15,
2643 hour: 14,
2644 minute: 30,
2645 second: 0,
2646 microsecond: 500000,
2647 timezone: Some(crate::literals::TimezoneValue { offset_minutes: 0 }),
2648 granularity: DateGranularity::DateTime,
2649 });
2650 assert_eq!(
2651 format!("{}", AsLemmaSource(&datetime)),
2652 "2024-01-15T14:30:00.500000Z"
2653 );
2654 }
2655
2656 fn decimal(value: &str) -> rust_decimal::Decimal {
2657 use std::str::FromStr;
2658 rust_decimal::Decimal::from_str(value).expect("decimal literal in test")
2659 }
2660}