1#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
15pub struct Span {
16 pub start: usize,
17 pub end: usize,
18 pub line: usize,
19 pub col: usize,
20}
21
22pub struct DepthTracker {
24 depth: usize,
25 max_depth: usize,
26}
27
28impl DepthTracker {
29 pub fn with_max_depth(max_depth: usize) -> Self {
30 Self {
31 depth: 0,
32 max_depth,
33 }
34 }
35
36 pub fn push_depth(&mut self) -> Result<(), usize> {
38 self.depth += 1;
39 if self.depth > self.max_depth {
40 return Err(self.depth);
41 }
42 Ok(())
43 }
44
45 pub fn pop_depth(&mut self) {
46 if self.depth > 0 {
47 self.depth -= 1;
48 }
49 }
50
51 pub fn max_depth(&self) -> usize {
52 self.max_depth
53 }
54}
55
56impl Default for DepthTracker {
57 fn default() -> Self {
58 Self {
59 depth: 0,
60 max_depth: 5,
61 }
62 }
63}
64
65use crate::parsing::source::Source;
70use rust_decimal::Decimal;
71use serde::Serialize;
72use std::cmp::Ordering;
73use std::fmt;
74use std::hash::{Hash, Hasher};
75use std::sync::Arc;
76
77pub use crate::literals::{
78 BooleanValue, DateTimeValue, DurationUnit, TimeValue, TimezoneValue, Value,
79};
80
81#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
82pub enum EffectiveDate {
83 Origin,
84 DateTimeValue(crate::DateTimeValue),
85}
86
87impl EffectiveDate {
88 pub fn as_ref(&self) -> Option<&crate::DateTimeValue> {
89 match self {
90 EffectiveDate::Origin => None,
91 EffectiveDate::DateTimeValue(dt) => Some(dt),
92 }
93 }
94
95 pub fn from_option(opt: Option<crate::DateTimeValue>) -> Self {
96 match opt {
97 None => EffectiveDate::Origin,
98 Some(dt) => EffectiveDate::DateTimeValue(dt),
99 }
100 }
101
102 pub fn to_option(&self) -> Option<crate::DateTimeValue> {
103 match self {
104 EffectiveDate::Origin => None,
105 EffectiveDate::DateTimeValue(dt) => Some(dt.clone()),
106 }
107 }
108
109 pub fn is_origin(&self) -> bool {
110 matches!(self, EffectiveDate::Origin)
111 }
112}
113
114impl PartialOrd for EffectiveDate {
115 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
116 Some(self.cmp(other))
117 }
118}
119
120impl Ord for EffectiveDate {
121 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
123 self.as_ref().cmp(&other.as_ref())
124 }
125}
126
127impl fmt::Display for EffectiveDate {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 match self {
130 EffectiveDate::Origin => Ok(()),
131 EffectiveDate::DateTimeValue(dt) => write!(f, "{}", dt),
132 }
133 }
134}
135
136#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
150pub struct LemmaRepository {
151 pub name: Option<String>,
153 pub dependency: Option<String>,
156 pub start_line: usize,
157 pub source_type: Option<crate::parsing::source::SourceType>,
158}
159
160impl LemmaRepository {
161 #[must_use]
162 pub fn new(name: Option<String>) -> Self {
163 Self {
164 name,
165 dependency: None,
166 start_line: 1,
167 source_type: None,
168 }
169 }
170
171 #[must_use]
172 pub fn with_start_line(mut self, start_line: usize) -> Self {
173 self.start_line = start_line;
174 self
175 }
176
177 #[must_use]
178 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
179 self.source_type = Some(source_type);
180 self
181 }
182
183 #[must_use]
184 pub fn with_dependency(mut self, dependency_id: impl Into<String>) -> Self {
185 self.dependency = Some(dependency_id.into());
186 self
187 }
188
189 #[must_use]
193 pub fn identity(&self) -> Option<&str> {
194 self.name.as_deref()
195 }
196}
197
198impl PartialEq for LemmaRepository {
199 fn eq(&self, other: &Self) -> bool {
200 self.name == other.name
201 }
202}
203
204impl Eq for LemmaRepository {}
205
206impl PartialOrd for LemmaRepository {
207 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
208 Some(self.cmp(other))
209 }
210}
211
212impl Ord for LemmaRepository {
213 fn cmp(&self, other: &Self) -> Ordering {
214 self.name.cmp(&other.name)
215 }
216}
217
218impl Hash for LemmaRepository {
219 fn hash<H: Hasher>(&self, state: &mut H) {
220 self.name.hash(state);
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
228pub struct RepositoryQualifier {
229 pub name: String,
230}
231
232impl RepositoryQualifier {
233 #[must_use]
234 pub fn new(name: impl Into<String>) -> Self {
235 Self { name: name.into() }
236 }
237
238 #[must_use]
240 pub fn is_registry(&self) -> bool {
241 self.name.starts_with('@')
242 }
243}
244
245impl fmt::Display for RepositoryQualifier {
246 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
247 write!(f, "{}", self.name)
248 }
249}
250
251#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
263pub struct LemmaSpec {
264 pub name: String,
265 pub effective_from: EffectiveDate,
266 pub source_type: Option<crate::parsing::source::SourceType>,
267 pub start_line: usize,
268 pub commentary: Option<String>,
269 pub data: Vec<LemmaData>,
270 pub rules: Vec<LemmaRule>,
271 pub meta_fields: Vec<MetaField>,
272}
273
274#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
275pub struct MetaField {
276 pub key: String,
277 pub value: MetaValue,
278 pub source_location: Source,
279}
280
281#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
282#[serde(rename_all = "snake_case")]
283pub enum MetaValue {
284 Literal(Value),
285 Unquoted(String),
286}
287
288impl fmt::Display for MetaValue {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 match self {
291 MetaValue::Literal(v) => write!(f, "{}", v),
292 MetaValue::Unquoted(s) => write!(f, "{}", s),
293 }
294 }
295}
296
297impl fmt::Display for MetaField {
298 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299 write!(f, "meta {}: {}", self.key, self.value)
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
304pub struct LemmaData {
305 pub reference: Reference,
306 pub value: DataValue,
307 pub source_location: Source,
308}
309
310#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
316pub struct UnlessClause {
317 pub condition: Expression,
318 pub result: Expression,
319 pub source_location: Source,
320}
321
322#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
324pub struct LemmaRule {
325 pub name: String,
326 pub expression: Expression,
327 pub unless_clauses: Vec<UnlessClause>,
328 pub source_location: Source,
329}
330
331#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
337pub struct Expression {
338 pub kind: ExpressionKind,
339 pub source_location: Option<Source>,
340}
341
342impl Expression {
343 #[must_use]
345 pub fn new(kind: ExpressionKind, source_location: Source) -> Self {
346 Self {
347 kind,
348 source_location: Some(source_location),
349 }
350 }
351}
352
353impl PartialEq for Expression {
355 fn eq(&self, other: &Self) -> bool {
356 self.kind == other.kind
357 }
358}
359
360impl Eq for Expression {}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
364#[serde(rename_all = "snake_case")]
365pub enum DateRelativeKind {
366 InPast,
367 InFuture,
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
372#[serde(rename_all = "snake_case")]
373pub enum DateCalendarKind {
374 Current,
375 Past,
376 Future,
377 NotIn,
378}
379
380#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
382#[serde(rename_all = "snake_case")]
383pub enum CalendarUnit {
384 Year,
385 Month,
386 Week,
387}
388
389impl fmt::Display for DateRelativeKind {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 match self {
392 DateRelativeKind::InPast => write!(f, "in past"),
393 DateRelativeKind::InFuture => write!(f, "in future"),
394 }
395 }
396}
397
398impl fmt::Display for DateCalendarKind {
399 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
400 match self {
401 DateCalendarKind::Current => write!(f, "in calendar"),
402 DateCalendarKind::Past => write!(f, "in past calendar"),
403 DateCalendarKind::Future => write!(f, "in future calendar"),
404 DateCalendarKind::NotIn => write!(f, "not in calendar"),
405 }
406 }
407}
408
409impl fmt::Display for CalendarUnit {
410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 match self {
412 CalendarUnit::Year => write!(f, "year"),
413 CalendarUnit::Month => write!(f, "month"),
414 CalendarUnit::Week => write!(f, "week"),
415 }
416 }
417}
418
419#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
421#[serde(rename_all = "snake_case")]
422pub enum ExpressionKind {
423 Literal(Value),
425 Reference(Reference),
427 UnresolvedUnitLiteral(Decimal, String),
430 Now,
432 DateRelative(DateRelativeKind, Arc<Expression>, Option<Arc<Expression>>),
435 DateCalendar(DateCalendarKind, CalendarUnit, Arc<Expression>),
438 LogicalAnd(Arc<Expression>, Arc<Expression>),
439 Arithmetic(Arc<Expression>, ArithmeticComputation, Arc<Expression>),
440 Comparison(Arc<Expression>, ComparisonComputation, Arc<Expression>),
441 UnitConversion(Arc<Expression>, ConversionTarget),
442 LogicalNegation(Arc<Expression>, NegationType),
443 MathematicalComputation(MathematicalComputation, Arc<Expression>),
444 Veto(VetoExpression),
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
457pub struct Reference {
458 pub segments: Vec<String>,
459 pub name: String,
460}
461
462impl Reference {
463 #[must_use]
464 pub fn local(name: String) -> Self {
465 Self {
466 segments: Vec::new(),
467 name,
468 }
469 }
470
471 #[must_use]
472 pub fn from_path(path: Vec<String>) -> Self {
473 if path.is_empty() {
474 Self {
475 segments: Vec::new(),
476 name: String::new(),
477 }
478 } else {
479 let name = path[path.len() - 1].clone();
481 let segments = path[..path.len() - 1].to_vec();
482 Self { segments, name }
483 }
484 }
485
486 #[must_use]
487 pub fn is_local(&self) -> bool {
488 self.segments.is_empty()
489 }
490
491 #[must_use]
492 pub fn full_path(&self) -> Vec<String> {
493 let mut path = self.segments.clone();
494 path.push(self.name.clone());
495 path
496 }
497}
498
499impl fmt::Display for Reference {
500 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
501 for segment in &self.segments {
502 write!(f, "{}.", segment)?;
503 }
504 write!(f, "{}", self.name)
505 }
506}
507
508#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
510#[serde(rename_all = "snake_case")]
511pub enum ArithmeticComputation {
512 Add,
513 Subtract,
514 Multiply,
515 Divide,
516 Modulo,
517 Power,
518}
519
520#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
522#[serde(rename_all = "snake_case")]
523pub enum ComparisonComputation {
524 GreaterThan,
525 LessThan,
526 GreaterThanOrEqual,
527 LessThanOrEqual,
528 Is,
529 IsNot,
530}
531
532impl ComparisonComputation {
533 #[must_use]
535 pub fn is_equal(&self) -> bool {
536 matches!(self, ComparisonComputation::Is)
537 }
538
539 #[must_use]
541 pub fn is_not_equal(&self) -> bool {
542 matches!(self, ComparisonComputation::IsNot)
543 }
544}
545
546#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
549#[serde(rename_all = "snake_case")]
550pub enum ConversionTarget {
551 Duration(DurationUnit),
552 Unit(String),
553}
554
555#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
557#[serde(rename_all = "snake_case")]
558pub enum NegationType {
559 Not,
560}
561
562#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
570pub struct VetoExpression {
571 pub message: Option<String>,
572}
573
574#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
576#[serde(rename_all = "snake_case")]
577pub enum MathematicalComputation {
578 Sqrt,
579 Sin,
580 Cos,
581 Tan,
582 Asin,
583 Acos,
584 Atan,
585 Log,
586 Exp,
587 Abs,
588 Floor,
589 Ceil,
590 Round,
591}
592
593#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
601pub struct SpecRef {
602 pub repository: Option<RepositoryQualifier>,
605 pub name: String,
607 pub effective: Option<DateTimeValue>,
609 #[serde(default, skip_serializing_if = "Option::is_none")]
611 pub repository_span: Option<Span>,
612 #[serde(default, skip_serializing_if = "Option::is_none")]
614 pub target_span: Option<Span>,
615}
616
617impl std::fmt::Display for SpecRef {
618 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
619 if let Some(qualifier) = &self.repository {
620 write!(f, "{} ", qualifier)?;
621 }
622 write!(f, "{}", self.name)?;
623 if let Some(d) = &self.effective {
624 write!(f, " {}", d)?;
625 }
626 Ok(())
627 }
628}
629
630impl SpecRef {
631 pub fn same_repository(name: impl Into<String>) -> Self {
633 Self {
634 name: name.into(),
635 repository: None,
636 effective: None,
637 repository_span: None,
638 target_span: None,
639 }
640 }
641
642 pub fn cross_repository(name: impl Into<String>, qualifier: RepositoryQualifier) -> Self {
644 Self {
645 name: name.into(),
646 repository: Some(qualifier),
647 effective: None,
648 repository_span: None,
649 target_span: None,
650 }
651 }
652
653 pub fn at(&self, effective: &EffectiveDate) -> EffectiveDate {
656 self.effective
657 .clone()
658 .map_or_else(|| effective.clone(), EffectiveDate::DateTimeValue)
659 }
660}
661
662#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
677#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
678pub enum CommandArg {
679 Literal(crate::literals::Value),
681 Label(String),
683}
684
685impl fmt::Display for CommandArg {
686 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
687 match self {
688 CommandArg::Literal(v) => write!(f, "{}", v),
689 CommandArg::Label(s) => write!(f, "{}", s),
690 }
691 }
692}
693
694#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
696#[serde(rename_all = "snake_case")]
697pub enum TypeConstraintCommand {
698 Help,
699 Default,
700 Unit,
701 Minimum,
702 Maximum,
703 Decimals,
704 Precision,
705 Option,
706 Options,
707 Length,
708}
709
710impl fmt::Display for TypeConstraintCommand {
711 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
712 let s = match self {
713 TypeConstraintCommand::Help => "help",
714 TypeConstraintCommand::Default => "default",
715 TypeConstraintCommand::Unit => "unit",
716 TypeConstraintCommand::Minimum => "minimum",
717 TypeConstraintCommand::Maximum => "maximum",
718 TypeConstraintCommand::Decimals => "decimals",
719 TypeConstraintCommand::Precision => "precision",
720 TypeConstraintCommand::Option => "option",
721 TypeConstraintCommand::Options => "options",
722 TypeConstraintCommand::Length => "length",
723 };
724 write!(f, "{}", s)
725 }
726}
727
728#[must_use]
730pub fn try_parse_type_constraint_command(s: &str) -> Option<TypeConstraintCommand> {
731 match s.trim().to_lowercase().as_str() {
732 "help" => Some(TypeConstraintCommand::Help),
733 "default" => Some(TypeConstraintCommand::Default),
734 "unit" => Some(TypeConstraintCommand::Unit),
735 "minimum" => Some(TypeConstraintCommand::Minimum),
736 "maximum" => Some(TypeConstraintCommand::Maximum),
737 "decimals" => Some(TypeConstraintCommand::Decimals),
738 "precision" => Some(TypeConstraintCommand::Precision),
739 "option" => Some(TypeConstraintCommand::Option),
740 "options" => Some(TypeConstraintCommand::Options),
741 "length" => Some(TypeConstraintCommand::Length),
742 _ => None,
743 }
744}
745
746pub type Constraint = (TypeConstraintCommand, Vec<CommandArg>);
748
749#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
750#[serde(rename_all = "snake_case")]
751pub enum DataValue {
753 Definition {
761 #[serde(default, skip_serializing_if = "Option::is_none")]
762 base: Option<ParentType>,
763 constraints: Option<Vec<Constraint>>,
764 #[serde(default, skip_serializing_if = "Option::is_none")]
765 from: Option<SpecRef>,
766 #[serde(default, skip_serializing_if = "Option::is_none")]
767 value: Option<Value>,
768 },
769 Import(SpecRef),
771 Reference {
787 target: Reference,
788 constraints: Option<Vec<Constraint>>,
789 },
790}
791
792impl DataValue {
793 #[must_use]
795 pub fn is_definition_literal_only(&self) -> bool {
796 matches!(
797 self,
798 DataValue::Definition {
799 base: None,
800 constraints: None,
801 from: None,
802 value: Some(_),
803 }
804 )
805 }
806
807 #[must_use]
809 pub fn definition_needs_type_resolution(&self) -> bool {
810 match self {
811 DataValue::Definition { base: Some(_), .. }
812 | DataValue::Definition { from: Some(_), .. }
813 | DataValue::Definition {
814 constraints: Some(_),
815 ..
816 } => true,
817 DataValue::Definition {
818 base: None,
819 constraints: None,
820 from: None,
821 value: Some(v),
822 } => !matches!(v, Value::Scale(_, _) | Value::Ratio(_, _)),
823 DataValue::Import(_) | DataValue::Reference { .. } | DataValue::Definition { .. } => {
824 false
825 }
826 }
827 }
828}
829
830fn format_constraint_chain(constraints: &[Constraint]) -> String {
833 constraints
834 .iter()
835 .map(|(cmd, args)| {
836 let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
837 let joined = args_str.join(" ");
838 if joined.is_empty() {
839 format!("{}", cmd)
840 } else {
841 format!("{} {}", cmd, joined)
842 }
843 })
844 .collect::<Vec<_>>()
845 .join(" -> ")
846}
847
848impl fmt::Display for DataValue {
849 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
850 match self {
851 DataValue::Definition {
852 base,
853 constraints,
854 from,
855 value,
856 } => {
857 if base.is_none() && from.is_none() && constraints.is_none() {
858 return match value {
859 Some(v) => write!(f, "{}", v),
860 None => Ok(()),
861 };
862 }
863 let base_str = match (base.as_ref(), from.as_ref()) {
864 (Some(b), Some(spec)) => format!("{b} from {spec}"),
865 (Some(b), None) => format!("{b}"),
866 (None, Some(spec)) => format!("<type> from {spec}"),
867 (None, None) => match value {
868 Some(v) => {
869 if let Some(ref constraints_vec) = constraints {
870 let constraint_str = format_constraint_chain(constraints_vec);
871 return write!(f, "{v} -> {constraint_str}");
872 }
873 return write!(f, "{v}");
874 }
875 None => String::new(),
876 },
877 };
878 if let Some(ref constraints_vec) = constraints {
879 let constraint_str = format_constraint_chain(constraints_vec);
880 write!(f, "{base_str} -> {constraint_str}")
881 } else {
882 write!(f, "{base_str}")
883 }
884 }
885 DataValue::Import(spec_ref) => {
886 write!(f, "with {}", spec_ref)
887 }
888 DataValue::Reference {
889 target,
890 constraints,
891 } => {
892 write!(f, "{}", target)?;
893 if let Some(ref constraints_vec) = constraints {
894 let constraint_str = format_constraint_chain(constraints_vec);
895 write!(f, " -> {}", constraint_str)?;
896 }
897 Ok(())
898 }
899 }
900 }
901}
902
903impl LemmaData {
904 #[must_use]
905 pub fn new(reference: Reference, value: DataValue, source_location: Source) -> Self {
906 Self {
907 reference,
908 value,
909 source_location,
910 }
911 }
912}
913
914impl LemmaSpec {
915 #[must_use]
916 pub fn new(name: String) -> Self {
917 Self {
918 name,
919 effective_from: EffectiveDate::Origin,
920 source_type: None,
921 start_line: 1,
922 commentary: None,
923 data: Vec::new(),
924 rules: Vec::new(),
925 meta_fields: Vec::new(),
926 }
927 }
928
929 pub fn effective_from(&self) -> Option<&DateTimeValue> {
931 self.effective_from.as_ref()
932 }
933
934 #[must_use]
935 pub fn with_source_type(mut self, source_type: crate::parsing::source::SourceType) -> Self {
936 self.source_type = Some(source_type);
937 self
938 }
939
940 #[must_use]
941 pub fn with_start_line(mut self, start_line: usize) -> Self {
942 self.start_line = start_line;
943 self
944 }
945
946 #[must_use]
947 pub fn set_commentary(mut self, commentary: String) -> Self {
948 self.commentary = Some(commentary);
949 self
950 }
951
952 #[must_use]
953 pub fn add_data(mut self, data: LemmaData) -> Self {
954 self.data.push(data);
955 self
956 }
957
958 #[must_use]
959 pub fn add_rule(mut self, rule: LemmaRule) -> Self {
960 self.rules.push(rule);
961 self
962 }
963
964 #[must_use]
965 pub fn add_meta_field(mut self, meta: MetaField) -> Self {
966 self.meta_fields.push(meta);
967 self
968 }
969}
970
971impl fmt::Display for LemmaSpec {
972 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
973 write!(f, "spec {}", self.name)?;
974 if let EffectiveDate::DateTimeValue(ref af) = self.effective_from {
975 write!(f, " {}", af)?;
976 }
977 writeln!(f)?;
978
979 if let Some(ref commentary) = self.commentary {
980 writeln!(f, "\"\"\"")?;
981 writeln!(f, "{}", commentary)?;
982 writeln!(f, "\"\"\"")?;
983 }
984
985 if !self.data.is_empty() {
986 writeln!(f)?;
987 for data in &self.data {
988 write!(f, "{}", data)?;
989 }
990 }
991
992 if !self.rules.is_empty() {
993 writeln!(f)?;
994 for (index, rule) in self.rules.iter().enumerate() {
995 if index > 0 {
996 writeln!(f)?;
997 }
998 write!(f, "{}", rule)?;
999 }
1000 }
1001
1002 if !self.meta_fields.is_empty() {
1003 writeln!(f)?;
1004 for meta in &self.meta_fields {
1005 writeln!(f, "{}", meta)?;
1006 }
1007 }
1008
1009 Ok(())
1010 }
1011}
1012
1013impl fmt::Display for LemmaData {
1014 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1015 writeln!(f, "data {}: {}", self.reference, self.value)
1016 }
1017}
1018
1019impl fmt::Display for LemmaRule {
1020 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1021 write!(f, "rule {}: {}", self.name, self.expression)?;
1022 for unless_clause in &self.unless_clauses {
1023 write!(
1024 f,
1025 "\n unless {} then {}",
1026 unless_clause.condition, unless_clause.result
1027 )?;
1028 }
1029 writeln!(f)?;
1030 Ok(())
1031 }
1032}
1033
1034pub fn expression_precedence(kind: &ExpressionKind) -> u8 {
1039 match kind {
1040 ExpressionKind::LogicalAnd(..) => 2,
1041 ExpressionKind::LogicalNegation(..) => 3,
1042 ExpressionKind::Comparison(..) => 4,
1043 ExpressionKind::UnitConversion(..) => 4,
1044 ExpressionKind::Arithmetic(_, op, _) => match op {
1045 ArithmeticComputation::Add | ArithmeticComputation::Subtract => 5,
1046 ArithmeticComputation::Multiply
1047 | ArithmeticComputation::Divide
1048 | ArithmeticComputation::Modulo => 6,
1049 ArithmeticComputation::Power => 7,
1050 },
1051 ExpressionKind::MathematicalComputation(..) => 8,
1052 ExpressionKind::DateRelative(..) | ExpressionKind::DateCalendar(..) => 4,
1053 ExpressionKind::Literal(..)
1054 | ExpressionKind::Reference(..)
1055 | ExpressionKind::UnresolvedUnitLiteral(..)
1056 | ExpressionKind::Now
1057 | ExpressionKind::Veto(..) => 10,
1058 }
1059}
1060
1061fn write_expression_child(
1062 f: &mut fmt::Formatter<'_>,
1063 child: &Expression,
1064 parent_prec: u8,
1065) -> fmt::Result {
1066 let child_prec = expression_precedence(&child.kind);
1067 if child_prec < parent_prec {
1068 write!(f, "({})", child)
1069 } else {
1070 write!(f, "{}", child)
1071 }
1072}
1073
1074impl fmt::Display for Expression {
1075 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1076 match &self.kind {
1077 ExpressionKind::Literal(lit) => write!(f, "{}", AsLemmaSource(lit)),
1078 ExpressionKind::Reference(r) => write!(f, "{}", r),
1079 ExpressionKind::Arithmetic(left, op, right) => {
1080 let my_prec = expression_precedence(&self.kind);
1081 write_expression_child(f, left, my_prec)?;
1082 write!(f, " {} ", op)?;
1083 write_expression_child(f, right, my_prec)
1084 }
1085 ExpressionKind::Comparison(left, op, right) => {
1086 let my_prec = expression_precedence(&self.kind);
1087 write_expression_child(f, left, my_prec)?;
1088 write!(f, " {} ", op)?;
1089 write_expression_child(f, right, my_prec)
1090 }
1091 ExpressionKind::UnitConversion(value, target) => {
1092 let my_prec = expression_precedence(&self.kind);
1093 write_expression_child(f, value, my_prec)?;
1094 write!(f, " in {}", target)
1095 }
1096 ExpressionKind::LogicalNegation(expr, _) => {
1097 let my_prec = expression_precedence(&self.kind);
1098 write!(f, "not ")?;
1099 write_expression_child(f, expr, my_prec)
1100 }
1101 ExpressionKind::LogicalAnd(left, right) => {
1102 let my_prec = expression_precedence(&self.kind);
1103 write_expression_child(f, left, my_prec)?;
1104 write!(f, " and ")?;
1105 write_expression_child(f, right, my_prec)
1106 }
1107 ExpressionKind::MathematicalComputation(op, operand) => {
1108 let my_prec = expression_precedence(&self.kind);
1109 write!(f, "{} ", op)?;
1110 write_expression_child(f, operand, my_prec)
1111 }
1112 ExpressionKind::Veto(veto) => match &veto.message {
1113 Some(msg) => write!(f, "veto {}", quote_lemma_text(msg)),
1114 None => write!(f, "veto"),
1115 },
1116 ExpressionKind::UnresolvedUnitLiteral(number, unit_name) => {
1117 write!(f, "{} {}", format_decimal_source(number), unit_name)
1118 }
1119 ExpressionKind::Now => write!(f, "now"),
1120 ExpressionKind::DateRelative(kind, date_expr, tolerance) => {
1121 write!(f, "{} {}", date_expr, kind)?;
1122 if let Some(tol) = tolerance {
1123 write!(f, " {}", tol)?;
1124 }
1125 Ok(())
1126 }
1127 ExpressionKind::DateCalendar(kind, unit, date_expr) => {
1128 write!(f, "{} {} {}", date_expr, kind, unit)
1129 }
1130 }
1131 }
1132}
1133
1134impl fmt::Display for ConversionTarget {
1135 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1136 match self {
1137 ConversionTarget::Duration(unit) => write!(f, "{}", unit),
1138 ConversionTarget::Unit(unit) => write!(f, "{}", unit),
1139 }
1140 }
1141}
1142
1143impl fmt::Display for ArithmeticComputation {
1144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1145 match self {
1146 ArithmeticComputation::Add => write!(f, "+"),
1147 ArithmeticComputation::Subtract => write!(f, "-"),
1148 ArithmeticComputation::Multiply => write!(f, "*"),
1149 ArithmeticComputation::Divide => write!(f, "/"),
1150 ArithmeticComputation::Modulo => write!(f, "%"),
1151 ArithmeticComputation::Power => write!(f, "^"),
1152 }
1153 }
1154}
1155
1156impl fmt::Display for ComparisonComputation {
1157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1158 match self {
1159 ComparisonComputation::GreaterThan => write!(f, ">"),
1160 ComparisonComputation::LessThan => write!(f, "<"),
1161 ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1162 ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1163 ComparisonComputation::Is => write!(f, "is"),
1164 ComparisonComputation::IsNot => write!(f, "is not"),
1165 }
1166 }
1167}
1168
1169impl fmt::Display for MathematicalComputation {
1170 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1171 match self {
1172 MathematicalComputation::Sqrt => write!(f, "sqrt"),
1173 MathematicalComputation::Sin => write!(f, "sin"),
1174 MathematicalComputation::Cos => write!(f, "cos"),
1175 MathematicalComputation::Tan => write!(f, "tan"),
1176 MathematicalComputation::Asin => write!(f, "asin"),
1177 MathematicalComputation::Acos => write!(f, "acos"),
1178 MathematicalComputation::Atan => write!(f, "atan"),
1179 MathematicalComputation::Log => write!(f, "log"),
1180 MathematicalComputation::Exp => write!(f, "exp"),
1181 MathematicalComputation::Abs => write!(f, "abs"),
1182 MathematicalComputation::Floor => write!(f, "floor"),
1183 MathematicalComputation::Ceil => write!(f, "ceil"),
1184 MathematicalComputation::Round => write!(f, "round"),
1185 }
1186 }
1187}
1188
1189#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1195#[serde(rename_all = "snake_case")]
1196pub enum PrimitiveKind {
1197 Boolean,
1198 Scale,
1199 Number,
1200 Percent,
1201 Ratio,
1202 Text,
1203 Date,
1204 Time,
1205 Duration,
1206}
1207
1208impl std::fmt::Display for PrimitiveKind {
1209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1210 let s = match self {
1211 PrimitiveKind::Boolean => "boolean",
1212 PrimitiveKind::Scale => "scale",
1213 PrimitiveKind::Number => "number",
1214 PrimitiveKind::Percent => "percent",
1215 PrimitiveKind::Ratio => "ratio",
1216 PrimitiveKind::Text => "text",
1217 PrimitiveKind::Date => "date",
1218 PrimitiveKind::Time => "time",
1219 PrimitiveKind::Duration => "duration",
1220 };
1221 write!(f, "{}", s)
1222 }
1223}
1224
1225#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1230#[serde(tag = "kind", rename_all = "snake_case")]
1231pub enum ParentType {
1232 Primitive { primitive: PrimitiveKind },
1233 Custom { name: String },
1234}
1235
1236impl std::fmt::Display for ParentType {
1237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1238 match self {
1239 ParentType::Primitive { primitive } => write!(f, "{}", primitive),
1240 ParentType::Custom { name } => write!(f, "{}", name),
1241 }
1242 }
1243}
1244
1245pub struct AsLemmaSource<'a, T: ?Sized>(pub &'a T);
1251
1252pub fn quote_lemma_text(s: &str) -> String {
1255 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1256 format!("\"{}\"", escaped)
1257}
1258
1259fn format_decimal_source(n: &Decimal) -> String {
1264 let raw = if n.fract().is_zero() {
1265 n.trunc().to_string()
1266 } else {
1267 n.to_string()
1268 };
1269 group_digits(&raw)
1270}
1271
1272fn group_digits(s: &str) -> String {
1276 let (sign, rest) = if s.starts_with('-') || s.starts_with('+') {
1277 (&s[..1], &s[1..])
1278 } else {
1279 ("", s)
1280 };
1281
1282 let (int_part, frac_part) = match rest.find('.') {
1283 Some(pos) => (&rest[..pos], &rest[pos..]),
1284 None => (rest, ""),
1285 };
1286
1287 if int_part.len() < 4 {
1288 return s.to_string();
1289 }
1290
1291 let mut grouped = String::with_capacity(int_part.len() + int_part.len() / 3);
1292 for (i, ch) in int_part.chars().enumerate() {
1293 let digits_remaining = int_part.len() - i;
1294 if i > 0 && digits_remaining % 3 == 0 {
1295 grouped.push('_');
1296 }
1297 grouped.push(ch);
1298 }
1299
1300 format!("{}{}{}", sign, grouped, frac_part)
1301}
1302
1303impl<'a> fmt::Display for AsLemmaSource<'a, CommandArg> {
1304 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1305 use crate::literals::Value;
1306 match self.0 {
1307 CommandArg::Literal(Value::Text(s)) => write!(f, "{}", quote_lemma_text(s)),
1308 CommandArg::Literal(Value::Number(d)) => {
1309 write!(f, "{}", group_digits(&d.to_string()))
1310 }
1311 CommandArg::Literal(Value::Boolean(bv)) => write!(f, "{}", bv),
1312 CommandArg::Literal(Value::Scale(d, unit)) => {
1313 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1314 }
1315 CommandArg::Literal(Value::Duration(d, unit)) => {
1316 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1317 }
1318 CommandArg::Literal(value @ Value::Ratio(_, _)) => write!(f, "{}", value),
1319 CommandArg::Literal(Value::Date(dt)) => write!(f, "{}", dt),
1320 CommandArg::Literal(Value::Time(t)) => write!(f, "{}", t),
1321 CommandArg::Label(s) => write!(f, "{}", s),
1322 }
1323 }
1324}
1325
1326pub(crate) fn format_constraint_as_source(
1328 cmd: &TypeConstraintCommand,
1329 args: &[CommandArg],
1330) -> String {
1331 if args.is_empty() {
1332 cmd.to_string()
1333 } else {
1334 let args_str: Vec<String> = args
1335 .iter()
1336 .map(|a| format!("{}", AsLemmaSource(a)))
1337 .collect();
1338 format!("{} {}", cmd, args_str.join(" "))
1339 }
1340}
1341
1342fn format_constraints_as_source(constraints: &[Constraint], separator: &str) -> String {
1345 constraints
1346 .iter()
1347 .map(|(cmd, args)| format_constraint_as_source(cmd, args))
1348 .collect::<Vec<_>>()
1349 .join(separator)
1350}
1351
1352impl<'a> fmt::Display for AsLemmaSource<'a, Value> {
1355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1356 match self.0 {
1357 Value::Number(n) => write!(f, "{}", format_decimal_source(n)),
1358 Value::Text(s) => write!(f, "{}", quote_lemma_text(s)),
1359 Value::Date(dt) => {
1360 let is_date_only =
1361 dt.hour == 0 && dt.minute == 0 && dt.second == 0 && dt.timezone.is_none();
1362 if is_date_only {
1363 write!(f, "{:04}-{:02}-{:02}", dt.year, dt.month, dt.day)
1364 } else {
1365 write!(
1366 f,
1367 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1368 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
1369 )?;
1370 if let Some(tz) = &dt.timezone {
1371 write!(f, "{}", tz)?;
1372 }
1373 Ok(())
1374 }
1375 }
1376 Value::Time(t) => {
1377 write!(f, "{:02}:{:02}:{:02}", t.hour, t.minute, t.second)?;
1378 if let Some(tz) = &t.timezone {
1379 write!(f, "{}", tz)?;
1380 }
1381 Ok(())
1382 }
1383 Value::Boolean(b) => write!(f, "{}", b),
1384 Value::Scale(n, u) => write!(f, "{} {}", format_decimal_source(n), u),
1385 Value::Duration(n, u) => write!(f, "{} {}", format_decimal_source(n), u),
1386 Value::Ratio(n, unit) => match unit.as_deref() {
1387 Some("percent") => {
1388 let display_value = *n * Decimal::from(100);
1389 write!(f, "{}%", format_decimal_source(&display_value))
1390 }
1391 Some("permille") => {
1392 let display_value = *n * Decimal::from(1000);
1393 write!(f, "{}%%", format_decimal_source(&display_value))
1394 }
1395 Some(unit_name) => write!(f, "{} {}", format_decimal_source(n), unit_name),
1396 None => write!(f, "{}", format_decimal_source(n)),
1397 },
1398 }
1399 }
1400}
1401
1402impl<'a> fmt::Display for AsLemmaSource<'a, MetaValue> {
1405 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1406 match self.0 {
1407 MetaValue::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1408 MetaValue::Unquoted(s) => write!(f, "{}", s),
1409 }
1410 }
1411}
1412
1413impl<'a> fmt::Display for AsLemmaSource<'a, DataValue> {
1414 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1415 match self.0 {
1416 DataValue::Definition {
1417 base,
1418 constraints,
1419 from,
1420 value,
1421 } => {
1422 if base.is_none() && from.is_none() && constraints.is_none() {
1423 if let Some(v) = value {
1424 return write!(f, "{}", AsLemmaSource(v));
1425 }
1426 }
1427 let base_str = match (base.as_ref(), from.as_ref()) {
1428 (Some(b), Some(spec)) => format!("{} from {}", b, spec),
1429 (Some(b), None) => format!("{}", b),
1430 (None, Some(spec)) => format!("<type> from {}", spec),
1431 (None, None) => match value {
1432 Some(v) => {
1433 if let Some(ref constraints_vec) = constraints {
1434 let constraint_str =
1435 format_constraints_as_source(constraints_vec, " -> ");
1436 return write!(f, "{} -> {}", AsLemmaSource(v), constraint_str);
1437 }
1438 return write!(f, "{}", AsLemmaSource(v));
1439 }
1440 None => String::new(),
1441 },
1442 };
1443 if let Some(ref constraints_vec) = constraints {
1444 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
1445 write!(f, "{} -> {}", base_str, constraint_str)
1446 } else {
1447 write!(f, "{}", base_str)
1448 }
1449 }
1450 DataValue::Import(spec_ref) => {
1451 write!(f, "with {}", spec_ref)
1452 }
1453 DataValue::Reference {
1454 target,
1455 constraints,
1456 } => {
1457 write!(f, "{}", target)?;
1458 if let Some(ref constraints_vec) = constraints {
1459 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
1460 write!(f, " -> {}", constraint_str)?;
1461 }
1462 Ok(())
1463 }
1464 }
1465 }
1466}
1467
1468#[cfg(test)]
1469mod tests {
1470 use super::*;
1471
1472 #[test]
1473 fn test_duration_unit_display() {
1474 assert_eq!(format!("{}", DurationUnit::Second), "seconds");
1475 assert_eq!(format!("{}", DurationUnit::Minute), "minutes");
1476 assert_eq!(format!("{}", DurationUnit::Hour), "hours");
1477 assert_eq!(format!("{}", DurationUnit::Day), "days");
1478 assert_eq!(format!("{}", DurationUnit::Week), "weeks");
1479 assert_eq!(format!("{}", DurationUnit::Millisecond), "milliseconds");
1480 assert_eq!(format!("{}", DurationUnit::Microsecond), "microseconds");
1481 }
1482
1483 #[test]
1484 fn test_conversion_target_display() {
1485 assert_eq!(
1486 format!("{}", ConversionTarget::Duration(DurationUnit::Hour)),
1487 "hours"
1488 );
1489 assert_eq!(
1490 format!("{}", ConversionTarget::Unit("usd".to_string())),
1491 "usd"
1492 );
1493 }
1494
1495 #[test]
1496 fn test_value_ratio_display() {
1497 use rust_decimal::Decimal;
1498 use std::str::FromStr;
1499 let percent = Value::Ratio(
1500 Decimal::from_str("0.10").unwrap(),
1501 Some("percent".to_string()),
1502 );
1503 assert_eq!(format!("{}", percent), "10%");
1504 let permille = Value::Ratio(
1505 Decimal::from_str("0.005").unwrap(),
1506 Some("permille".to_string()),
1507 );
1508 assert_eq!(format!("{}", permille), "5%%");
1509 }
1510
1511 #[test]
1512 fn test_datetime_value_display() {
1513 let dt = DateTimeValue {
1514 year: 2024,
1515 month: 12,
1516 day: 25,
1517 hour: 14,
1518 minute: 30,
1519 second: 45,
1520 microsecond: 0,
1521 timezone: Some(TimezoneValue {
1522 offset_hours: 1,
1523 offset_minutes: 0,
1524 }),
1525 };
1526 assert_eq!(format!("{}", dt), "2024-12-25T14:30:45+01:00");
1527 }
1528
1529 #[test]
1530 fn test_datetime_value_display_date_only() {
1531 let dt = DateTimeValue {
1532 year: 2026,
1533 month: 3,
1534 day: 4,
1535 hour: 0,
1536 minute: 0,
1537 second: 0,
1538 microsecond: 0,
1539 timezone: None,
1540 };
1541 assert_eq!(format!("{}", dt), "2026-03-04");
1542 }
1543
1544 #[test]
1545 fn test_datetime_value_display_microseconds() {
1546 let dt = DateTimeValue {
1547 year: 2026,
1548 month: 2,
1549 day: 23,
1550 hour: 14,
1551 minute: 30,
1552 second: 45,
1553 microsecond: 123456,
1554 timezone: Some(TimezoneValue {
1555 offset_hours: 0,
1556 offset_minutes: 0,
1557 }),
1558 };
1559 assert_eq!(format!("{}", dt), "2026-02-23T14:30:45.123456Z");
1560 }
1561
1562 #[test]
1563 fn test_datetime_microsecond_in_ordering() {
1564 let a = DateTimeValue {
1565 year: 2026,
1566 month: 1,
1567 day: 1,
1568 hour: 0,
1569 minute: 0,
1570 second: 0,
1571 microsecond: 100,
1572 timezone: None,
1573 };
1574 let b = DateTimeValue {
1575 year: 2026,
1576 month: 1,
1577 day: 1,
1578 hour: 0,
1579 minute: 0,
1580 second: 0,
1581 microsecond: 200,
1582 timezone: None,
1583 };
1584 assert!(a < b);
1585 }
1586
1587 #[test]
1588 fn test_datetime_parse_iso_week() {
1589 let dt: DateTimeValue = "2026-W01".parse().unwrap();
1590 assert_eq!(dt.year, 2025);
1591 assert_eq!(dt.month, 12);
1592 assert_eq!(dt.day, 29);
1593 assert_eq!(dt.microsecond, 0);
1594 }
1595
1596 #[test]
1597 fn test_negation_types() {
1598 let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
1599 let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
1600 assert_eq!(decoded, NegationType::Not);
1601 }
1602
1603 #[test]
1604 fn parent_type_primitive_serde_internally_tagged() {
1605 let p = ParentType::Primitive {
1606 primitive: PrimitiveKind::Number,
1607 };
1608 let json = serde_json::to_string(&p).expect("ParentType::Primitive must serialize");
1609 assert!(json.contains("\"kind\"") && json.contains("\"primitive\""));
1610 let back: ParentType = serde_json::from_str(&json).expect("deserialize");
1611 assert_eq!(back, p);
1612 }
1613
1614 fn text_arg(s: &str) -> CommandArg {
1619 CommandArg::Literal(crate::literals::Value::Text(s.to_string()))
1620 }
1621
1622 fn number_arg(s: &str) -> CommandArg {
1623 let d: rust_decimal::Decimal = s.parse().expect("decimal");
1624 CommandArg::Literal(crate::literals::Value::Number(d))
1625 }
1626
1627 fn boolean_arg(b: BooleanValue) -> CommandArg {
1628 CommandArg::Literal(crate::literals::Value::Boolean(b))
1629 }
1630
1631 fn scale_arg(value: &str, unit: &str) -> CommandArg {
1632 let d: rust_decimal::Decimal = value.parse().expect("decimal");
1633 CommandArg::Literal(crate::literals::Value::Scale(d, unit.to_string()))
1634 }
1635
1636 fn duration_arg(value: &str, unit: DurationUnit) -> CommandArg {
1637 let d: rust_decimal::Decimal = value.parse().expect("decimal");
1638 CommandArg::Literal(crate::literals::Value::Duration(d, unit))
1639 }
1640
1641 #[test]
1642 fn as_lemma_source_text_default_is_quoted() {
1643 let fv = DataValue::Definition {
1644 base: Some(ParentType::Primitive {
1645 primitive: PrimitiveKind::Text,
1646 }),
1647 constraints: Some(vec![(
1648 TypeConstraintCommand::Default,
1649 vec![text_arg("single")],
1650 )]),
1651 from: None,
1652 value: None,
1653 };
1654 assert_eq!(
1655 format!("{}", AsLemmaSource(&fv)),
1656 "text -> default \"single\""
1657 );
1658 }
1659
1660 #[test]
1661 fn as_lemma_source_number_default_not_quoted() {
1662 let fv = DataValue::Definition {
1663 base: Some(ParentType::Primitive {
1664 primitive: PrimitiveKind::Number,
1665 }),
1666 constraints: Some(vec![(
1667 TypeConstraintCommand::Default,
1668 vec![number_arg("10")],
1669 )]),
1670 from: None,
1671 value: None,
1672 };
1673 assert_eq!(format!("{}", AsLemmaSource(&fv)), "number -> default 10");
1674 }
1675
1676 #[test]
1677 fn as_lemma_source_help_always_quoted() {
1678 let fv = DataValue::Definition {
1679 base: Some(ParentType::Primitive {
1680 primitive: PrimitiveKind::Number,
1681 }),
1682 constraints: Some(vec![(
1683 TypeConstraintCommand::Help,
1684 vec![text_arg("Enter a quantity")],
1685 )]),
1686 from: None,
1687 value: None,
1688 };
1689 assert_eq!(
1690 format!("{}", AsLemmaSource(&fv)),
1691 "number -> help \"Enter a quantity\""
1692 );
1693 }
1694
1695 #[test]
1696 fn as_lemma_source_text_option_quoted() {
1697 let fv = DataValue::Definition {
1698 base: Some(ParentType::Primitive {
1699 primitive: PrimitiveKind::Text,
1700 }),
1701 constraints: Some(vec![
1702 (TypeConstraintCommand::Option, vec![text_arg("active")]),
1703 (TypeConstraintCommand::Option, vec![text_arg("inactive")]),
1704 ]),
1705 from: None,
1706 value: None,
1707 };
1708 assert_eq!(
1709 format!("{}", AsLemmaSource(&fv)),
1710 "text -> option \"active\" -> option \"inactive\""
1711 );
1712 }
1713
1714 #[test]
1715 fn as_lemma_source_scale_unit_not_quoted() {
1716 let fv = DataValue::Definition {
1717 base: Some(ParentType::Primitive {
1718 primitive: PrimitiveKind::Scale,
1719 }),
1720 constraints: Some(vec![
1721 (
1722 TypeConstraintCommand::Unit,
1723 vec![CommandArg::Label("eur".to_string()), number_arg("1.00")],
1724 ),
1725 (
1726 TypeConstraintCommand::Unit,
1727 vec![CommandArg::Label("usd".to_string()), number_arg("1.10")],
1728 ),
1729 ]),
1730 from: None,
1731 value: None,
1732 };
1733 assert_eq!(
1734 format!("{}", AsLemmaSource(&fv)),
1735 "scale -> unit eur 1.00 -> unit usd 1.10"
1736 );
1737 }
1738
1739 #[test]
1740 fn as_lemma_source_scale_minimum_with_unit() {
1741 let fv = DataValue::Definition {
1742 base: Some(ParentType::Primitive {
1743 primitive: PrimitiveKind::Scale,
1744 }),
1745 constraints: Some(vec![(
1746 TypeConstraintCommand::Minimum,
1747 vec![scale_arg("0", "eur")],
1748 )]),
1749 from: None,
1750 value: None,
1751 };
1752 assert_eq!(format!("{}", AsLemmaSource(&fv)), "scale -> minimum 0 eur");
1753 }
1754
1755 #[test]
1756 fn as_lemma_source_boolean_default() {
1757 let fv = DataValue::Definition {
1758 base: Some(ParentType::Primitive {
1759 primitive: PrimitiveKind::Boolean,
1760 }),
1761 constraints: Some(vec![(
1762 TypeConstraintCommand::Default,
1763 vec![boolean_arg(BooleanValue::True)],
1764 )]),
1765 from: None,
1766 value: None,
1767 };
1768 assert_eq!(format!("{}", AsLemmaSource(&fv)), "boolean -> default true");
1769 }
1770
1771 #[test]
1772 fn as_lemma_source_duration_default() {
1773 let fv = DataValue::Definition {
1774 base: Some(ParentType::Primitive {
1775 primitive: PrimitiveKind::Duration,
1776 }),
1777 constraints: Some(vec![(
1778 TypeConstraintCommand::Default,
1779 vec![duration_arg("40", DurationUnit::Hour)],
1780 )]),
1781 from: None,
1782 value: None,
1783 };
1784 assert_eq!(
1785 format!("{}", AsLemmaSource(&fv)),
1786 "duration -> default 40 hours"
1787 );
1788 }
1789
1790 #[test]
1791 fn as_lemma_source_named_type_default_quoted() {
1792 let fv = DataValue::Definition {
1795 base: Some(ParentType::Custom {
1796 name: "filing_status_type".to_string(),
1797 }),
1798 constraints: Some(vec![(
1799 TypeConstraintCommand::Default,
1800 vec![text_arg("single")],
1801 )]),
1802 from: None,
1803 value: None,
1804 };
1805 assert_eq!(
1806 format!("{}", AsLemmaSource(&fv)),
1807 "filing_status_type -> default \"single\""
1808 );
1809 }
1810
1811 #[test]
1812 fn as_lemma_source_help_escapes_quotes() {
1813 let fv = DataValue::Definition {
1814 base: Some(ParentType::Primitive {
1815 primitive: PrimitiveKind::Text,
1816 }),
1817 constraints: Some(vec![(
1818 TypeConstraintCommand::Help,
1819 vec![text_arg("say \"hello\"")],
1820 )]),
1821 from: None,
1822 value: None,
1823 };
1824 assert_eq!(
1825 format!("{}", AsLemmaSource(&fv)),
1826 "text -> help \"say \\\"hello\\\"\""
1827 );
1828 }
1829}