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)]
139pub struct LemmaSpec {
140 pub name: String,
142 pub from_registry: bool,
144 pub effective_from: EffectiveDate,
145 pub attribute: Option<String>,
146 pub start_line: usize,
147 pub commentary: Option<String>,
148 pub data: Vec<LemmaData>,
149 pub rules: Vec<LemmaRule>,
150 pub meta_fields: Vec<MetaField>,
151}
152
153#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
154pub struct MetaField {
155 pub key: String,
156 pub value: MetaValue,
157 pub source_location: Source,
158}
159
160#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
161#[serde(rename_all = "snake_case")]
162pub enum MetaValue {
163 Literal(Value),
164 Unquoted(String),
165}
166
167impl fmt::Display for MetaValue {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 match self {
170 MetaValue::Literal(v) => write!(f, "{}", v),
171 MetaValue::Unquoted(s) => write!(f, "{}", s),
172 }
173 }
174}
175
176impl fmt::Display for MetaField {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 write!(f, "meta {}: {}", self.key, self.value)
179 }
180}
181
182#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
183pub struct LemmaData {
184 pub reference: Reference,
185 pub value: DataValue,
186 pub source_location: Source,
187}
188
189#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
195pub struct UnlessClause {
196 pub condition: Expression,
197 pub result: Expression,
198 pub source_location: Source,
199}
200
201#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
203pub struct LemmaRule {
204 pub name: String,
205 pub expression: Expression,
206 pub unless_clauses: Vec<UnlessClause>,
207 pub source_location: Source,
208}
209
210#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
216pub struct Expression {
217 pub kind: ExpressionKind,
218 pub source_location: Option<Source>,
219}
220
221impl Expression {
222 #[must_use]
224 pub fn new(kind: ExpressionKind, source_location: Source) -> Self {
225 Self {
226 kind,
227 source_location: Some(source_location),
228 }
229 }
230}
231
232impl PartialEq for Expression {
234 fn eq(&self, other: &Self) -> bool {
235 self.kind == other.kind
236 }
237}
238
239impl Eq for Expression {}
240
241#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
243#[serde(rename_all = "snake_case")]
244pub enum DateRelativeKind {
245 InPast,
246 InFuture,
247}
248
249#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
251#[serde(rename_all = "snake_case")]
252pub enum DateCalendarKind {
253 Current,
254 Past,
255 Future,
256 NotIn,
257}
258
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
261#[serde(rename_all = "snake_case")]
262pub enum CalendarUnit {
263 Year,
264 Month,
265 Week,
266}
267
268impl fmt::Display for DateRelativeKind {
269 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
270 match self {
271 DateRelativeKind::InPast => write!(f, "in past"),
272 DateRelativeKind::InFuture => write!(f, "in future"),
273 }
274 }
275}
276
277impl fmt::Display for DateCalendarKind {
278 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279 match self {
280 DateCalendarKind::Current => write!(f, "in calendar"),
281 DateCalendarKind::Past => write!(f, "in past calendar"),
282 DateCalendarKind::Future => write!(f, "in future calendar"),
283 DateCalendarKind::NotIn => write!(f, "not in calendar"),
284 }
285 }
286}
287
288impl fmt::Display for CalendarUnit {
289 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
290 match self {
291 CalendarUnit::Year => write!(f, "year"),
292 CalendarUnit::Month => write!(f, "month"),
293 CalendarUnit::Week => write!(f, "week"),
294 }
295 }
296}
297
298#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
300#[serde(rename_all = "snake_case")]
301pub enum ExpressionKind {
302 Literal(Value),
304 Reference(Reference),
306 UnresolvedUnitLiteral(Decimal, String),
309 Now,
311 DateRelative(DateRelativeKind, Arc<Expression>, Option<Arc<Expression>>),
314 DateCalendar(DateCalendarKind, CalendarUnit, Arc<Expression>),
317 LogicalAnd(Arc<Expression>, Arc<Expression>),
318 Arithmetic(Arc<Expression>, ArithmeticComputation, Arc<Expression>),
319 Comparison(Arc<Expression>, ComparisonComputation, Arc<Expression>),
320 UnitConversion(Arc<Expression>, ConversionTarget),
321 LogicalNegation(Arc<Expression>, NegationType),
322 MathematicalComputation(MathematicalComputation, Arc<Expression>),
323 Veto(VetoExpression),
324}
325
326#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
336pub struct Reference {
337 pub segments: Vec<String>,
338 pub name: String,
339}
340
341impl Reference {
342 #[must_use]
343 pub fn local(name: String) -> Self {
344 Self {
345 segments: Vec::new(),
346 name,
347 }
348 }
349
350 #[must_use]
351 pub fn from_path(path: Vec<String>) -> Self {
352 if path.is_empty() {
353 Self {
354 segments: Vec::new(),
355 name: String::new(),
356 }
357 } else {
358 let name = path[path.len() - 1].clone();
360 let segments = path[..path.len() - 1].to_vec();
361 Self { segments, name }
362 }
363 }
364
365 #[must_use]
366 pub fn is_local(&self) -> bool {
367 self.segments.is_empty()
368 }
369
370 #[must_use]
371 pub fn full_path(&self) -> Vec<String> {
372 let mut path = self.segments.clone();
373 path.push(self.name.clone());
374 path
375 }
376}
377
378impl fmt::Display for Reference {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 for segment in &self.segments {
381 write!(f, "{}.", segment)?;
382 }
383 write!(f, "{}", self.name)
384 }
385}
386
387#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
389#[serde(rename_all = "snake_case")]
390pub enum ArithmeticComputation {
391 Add,
392 Subtract,
393 Multiply,
394 Divide,
395 Modulo,
396 Power,
397}
398
399#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
401#[serde(rename_all = "snake_case")]
402pub enum ComparisonComputation {
403 GreaterThan,
404 LessThan,
405 GreaterThanOrEqual,
406 LessThanOrEqual,
407 Is,
408 IsNot,
409}
410
411impl ComparisonComputation {
412 #[must_use]
414 pub fn is_equal(&self) -> bool {
415 matches!(self, ComparisonComputation::Is)
416 }
417
418 #[must_use]
420 pub fn is_not_equal(&self) -> bool {
421 matches!(self, ComparisonComputation::IsNot)
422 }
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
428#[serde(rename_all = "snake_case")]
429pub enum ConversionTarget {
430 Duration(DurationUnit),
431 Unit(String),
432}
433
434#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
436#[serde(rename_all = "snake_case")]
437pub enum NegationType {
438 Not,
439}
440
441#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
449pub struct VetoExpression {
450 pub message: Option<String>,
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
455#[serde(rename_all = "snake_case")]
456pub enum MathematicalComputation {
457 Sqrt,
458 Sin,
459 Cos,
460 Tan,
461 Asin,
462 Acos,
463 Atan,
464 Log,
465 Exp,
466 Abs,
467 Floor,
468 Ceil,
469 Round,
470}
471
472#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
477pub struct SpecRef {
478 pub name: String,
480 pub from_registry: bool,
482 pub effective: Option<DateTimeValue>,
484}
485
486impl std::fmt::Display for SpecRef {
487 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
488 write!(f, "{}", self.name)?;
489 if let Some(d) = &self.effective {
490 write!(f, " {}", d)?;
491 }
492 Ok(())
493 }
494}
495
496impl SpecRef {
497 pub fn local(name: impl Into<String>) -> Self {
499 Self {
500 name: name.into(),
501 from_registry: false,
502 effective: None,
503 }
504 }
505
506 pub fn registry(name: impl Into<String>) -> Self {
508 Self {
509 name: name.into(),
510 from_registry: true,
511 effective: None,
512 }
513 }
514
515 pub fn resolution_key(&self) -> String {
516 self.name.clone()
517 }
518
519 pub fn at(&self, effective: &EffectiveDate) -> EffectiveDate {
522 self.effective
523 .clone()
524 .map_or_else(|| effective.clone(), EffectiveDate::DateTimeValue)
525 }
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
543#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
544pub enum CommandArg {
545 Literal(crate::literals::Value),
547 Label(String),
549}
550
551impl fmt::Display for CommandArg {
552 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
553 match self {
554 CommandArg::Literal(v) => write!(f, "{}", v),
555 CommandArg::Label(s) => write!(f, "{}", s),
556 }
557 }
558}
559
560#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
562#[serde(rename_all = "snake_case")]
563pub enum TypeConstraintCommand {
564 Help,
565 Default,
566 Unit,
567 Minimum,
568 Maximum,
569 Decimals,
570 Precision,
571 Option,
572 Options,
573 Length,
574}
575
576impl fmt::Display for TypeConstraintCommand {
577 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
578 let s = match self {
579 TypeConstraintCommand::Help => "help",
580 TypeConstraintCommand::Default => "default",
581 TypeConstraintCommand::Unit => "unit",
582 TypeConstraintCommand::Minimum => "minimum",
583 TypeConstraintCommand::Maximum => "maximum",
584 TypeConstraintCommand::Decimals => "decimals",
585 TypeConstraintCommand::Precision => "precision",
586 TypeConstraintCommand::Option => "option",
587 TypeConstraintCommand::Options => "options",
588 TypeConstraintCommand::Length => "length",
589 };
590 write!(f, "{}", s)
591 }
592}
593
594#[must_use]
596pub fn try_parse_type_constraint_command(s: &str) -> Option<TypeConstraintCommand> {
597 match s.trim().to_lowercase().as_str() {
598 "help" => Some(TypeConstraintCommand::Help),
599 "default" => Some(TypeConstraintCommand::Default),
600 "unit" => Some(TypeConstraintCommand::Unit),
601 "minimum" => Some(TypeConstraintCommand::Minimum),
602 "maximum" => Some(TypeConstraintCommand::Maximum),
603 "decimals" => Some(TypeConstraintCommand::Decimals),
604 "precision" => Some(TypeConstraintCommand::Precision),
605 "option" => Some(TypeConstraintCommand::Option),
606 "options" => Some(TypeConstraintCommand::Options),
607 "length" => Some(TypeConstraintCommand::Length),
608 _ => None,
609 }
610}
611
612pub type Constraint = (TypeConstraintCommand, Vec<CommandArg>);
614
615#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
616#[serde(rename_all = "snake_case")]
617pub enum DataValue {
619 Literal(Value),
621 SpecReference(SpecRef),
623 TypeDeclaration {
625 base: ParentType,
626 constraints: Option<Vec<Constraint>>,
627 from: Option<SpecRef>,
628 },
629 Reference {
646 target: Reference,
647 constraints: Option<Vec<Constraint>>,
648 },
649}
650
651fn format_constraint_chain(constraints: &[Constraint]) -> String {
654 constraints
655 .iter()
656 .map(|(cmd, args)| {
657 let args_str: Vec<String> = args.iter().map(|a| a.to_string()).collect();
658 let joined = args_str.join(" ");
659 if joined.is_empty() {
660 format!("{}", cmd)
661 } else {
662 format!("{} {}", cmd, joined)
663 }
664 })
665 .collect::<Vec<_>>()
666 .join(" -> ")
667}
668
669impl fmt::Display for DataValue {
670 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
671 match self {
672 DataValue::Literal(v) => write!(f, "{}", v),
673 DataValue::SpecReference(spec_ref) => {
674 write!(f, "with {}", spec_ref)
675 }
676 DataValue::TypeDeclaration {
677 base,
678 constraints,
679 from,
680 } => {
681 let base_str = if let Some(from_spec) = from {
682 format!("{} from {}", base, from_spec)
683 } else {
684 format!("{}", base)
685 };
686 if let Some(ref constraints_vec) = constraints {
687 let constraint_str = format_constraint_chain(constraints_vec);
688 write!(f, "{} -> {}", base_str, constraint_str)
689 } else {
690 write!(f, "{}", base_str)
691 }
692 }
693 DataValue::Reference {
694 target,
695 constraints,
696 } => {
697 if let Some(ref constraints_vec) = constraints {
698 let constraint_str = format_constraint_chain(constraints_vec);
699 write!(f, "{} -> {}", target, constraint_str)
700 } else {
701 write!(f, "{}", target)
702 }
703 }
704 }
705 }
706}
707
708impl LemmaData {
709 #[must_use]
710 pub fn new(reference: Reference, value: DataValue, source_location: Source) -> Self {
711 Self {
712 reference,
713 value,
714 source_location,
715 }
716 }
717}
718
719impl LemmaSpec {
720 #[must_use]
721 pub fn new(name: String) -> Self {
722 let from_registry = name.starts_with('@');
723 Self {
724 name,
725 from_registry,
726 effective_from: EffectiveDate::Origin,
727 attribute: None,
728 start_line: 1,
729 commentary: None,
730 data: Vec::new(),
731 rules: Vec::new(),
732 meta_fields: Vec::new(),
733 }
734 }
735
736 pub fn effective_from(&self) -> Option<&DateTimeValue> {
738 self.effective_from.as_ref()
739 }
740
741 #[must_use]
742 pub fn with_attribute(mut self, attribute: String) -> Self {
743 self.attribute = Some(attribute);
744 self
745 }
746
747 #[must_use]
748 pub fn with_start_line(mut self, start_line: usize) -> Self {
749 self.start_line = start_line;
750 self
751 }
752
753 #[must_use]
754 pub fn set_commentary(mut self, commentary: String) -> Self {
755 self.commentary = Some(commentary);
756 self
757 }
758
759 #[must_use]
760 pub fn add_data(mut self, data: LemmaData) -> Self {
761 self.data.push(data);
762 self
763 }
764
765 #[must_use]
766 pub fn add_rule(mut self, rule: LemmaRule) -> Self {
767 self.rules.push(rule);
768 self
769 }
770
771 #[must_use]
772 pub fn add_meta_field(mut self, meta: MetaField) -> Self {
773 self.meta_fields.push(meta);
774 self
775 }
776}
777
778impl PartialEq for LemmaSpec {
779 fn eq(&self, other: &Self) -> bool {
780 self.name == other.name && self.effective_from() == other.effective_from()
781 }
782}
783
784impl Eq for LemmaSpec {}
785
786impl PartialOrd for LemmaSpec {
787 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
788 Some(self.cmp(other))
789 }
790}
791
792impl Ord for LemmaSpec {
793 fn cmp(&self, other: &Self) -> Ordering {
794 (self.name.as_str(), self.effective_from())
795 .cmp(&(other.name.as_str(), other.effective_from()))
796 }
797}
798
799impl Hash for LemmaSpec {
800 fn hash<H: Hasher>(&self, state: &mut H) {
801 self.name.hash(state);
802 match self.effective_from() {
803 Some(d) => d.hash(state),
804 None => 0u8.hash(state),
805 }
806 }
807}
808
809impl fmt::Display for LemmaSpec {
810 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
811 write!(f, "spec {}", self.name)?;
812 if let EffectiveDate::DateTimeValue(ref af) = self.effective_from {
813 write!(f, " {}", af)?;
814 }
815 writeln!(f)?;
816
817 if let Some(ref commentary) = self.commentary {
818 writeln!(f, "\"\"\"")?;
819 writeln!(f, "{}", commentary)?;
820 writeln!(f, "\"\"\"")?;
821 }
822
823 if !self.data.is_empty() {
824 writeln!(f)?;
825 for data in &self.data {
826 write!(f, "{}", data)?;
827 }
828 }
829
830 if !self.rules.is_empty() {
831 writeln!(f)?;
832 for (index, rule) in self.rules.iter().enumerate() {
833 if index > 0 {
834 writeln!(f)?;
835 }
836 write!(f, "{}", rule)?;
837 }
838 }
839
840 if !self.meta_fields.is_empty() {
841 writeln!(f)?;
842 for meta in &self.meta_fields {
843 writeln!(f, "{}", meta)?;
844 }
845 }
846
847 Ok(())
848 }
849}
850
851impl fmt::Display for LemmaData {
852 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
853 writeln!(f, "data {}: {}", self.reference, self.value)
854 }
855}
856
857impl fmt::Display for LemmaRule {
858 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
859 write!(f, "rule {}: {}", self.name, self.expression)?;
860 for unless_clause in &self.unless_clauses {
861 write!(
862 f,
863 "\n unless {} then {}",
864 unless_clause.condition, unless_clause.result
865 )?;
866 }
867 writeln!(f)?;
868 Ok(())
869 }
870}
871
872pub fn expression_precedence(kind: &ExpressionKind) -> u8 {
877 match kind {
878 ExpressionKind::LogicalAnd(..) => 2,
879 ExpressionKind::LogicalNegation(..) => 3,
880 ExpressionKind::Comparison(..) => 4,
881 ExpressionKind::UnitConversion(..) => 4,
882 ExpressionKind::Arithmetic(_, op, _) => match op {
883 ArithmeticComputation::Add | ArithmeticComputation::Subtract => 5,
884 ArithmeticComputation::Multiply
885 | ArithmeticComputation::Divide
886 | ArithmeticComputation::Modulo => 6,
887 ArithmeticComputation::Power => 7,
888 },
889 ExpressionKind::MathematicalComputation(..) => 8,
890 ExpressionKind::DateRelative(..) | ExpressionKind::DateCalendar(..) => 4,
891 ExpressionKind::Literal(..)
892 | ExpressionKind::Reference(..)
893 | ExpressionKind::UnresolvedUnitLiteral(..)
894 | ExpressionKind::Now
895 | ExpressionKind::Veto(..) => 10,
896 }
897}
898
899fn write_expression_child(
900 f: &mut fmt::Formatter<'_>,
901 child: &Expression,
902 parent_prec: u8,
903) -> fmt::Result {
904 let child_prec = expression_precedence(&child.kind);
905 if child_prec < parent_prec {
906 write!(f, "({})", child)
907 } else {
908 write!(f, "{}", child)
909 }
910}
911
912impl fmt::Display for Expression {
913 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
914 match &self.kind {
915 ExpressionKind::Literal(lit) => write!(f, "{}", AsLemmaSource(lit)),
916 ExpressionKind::Reference(r) => write!(f, "{}", r),
917 ExpressionKind::Arithmetic(left, op, right) => {
918 let my_prec = expression_precedence(&self.kind);
919 write_expression_child(f, left, my_prec)?;
920 write!(f, " {} ", op)?;
921 write_expression_child(f, right, my_prec)
922 }
923 ExpressionKind::Comparison(left, op, right) => {
924 let my_prec = expression_precedence(&self.kind);
925 write_expression_child(f, left, my_prec)?;
926 write!(f, " {} ", op)?;
927 write_expression_child(f, right, my_prec)
928 }
929 ExpressionKind::UnitConversion(value, target) => {
930 let my_prec = expression_precedence(&self.kind);
931 write_expression_child(f, value, my_prec)?;
932 write!(f, " in {}", target)
933 }
934 ExpressionKind::LogicalNegation(expr, _) => {
935 let my_prec = expression_precedence(&self.kind);
936 write!(f, "not ")?;
937 write_expression_child(f, expr, my_prec)
938 }
939 ExpressionKind::LogicalAnd(left, right) => {
940 let my_prec = expression_precedence(&self.kind);
941 write_expression_child(f, left, my_prec)?;
942 write!(f, " and ")?;
943 write_expression_child(f, right, my_prec)
944 }
945 ExpressionKind::MathematicalComputation(op, operand) => {
946 let my_prec = expression_precedence(&self.kind);
947 write!(f, "{} ", op)?;
948 write_expression_child(f, operand, my_prec)
949 }
950 ExpressionKind::Veto(veto) => match &veto.message {
951 Some(msg) => write!(f, "veto {}", quote_lemma_text(msg)),
952 None => write!(f, "veto"),
953 },
954 ExpressionKind::UnresolvedUnitLiteral(number, unit_name) => {
955 write!(f, "{} {}", format_decimal_source(number), unit_name)
956 }
957 ExpressionKind::Now => write!(f, "now"),
958 ExpressionKind::DateRelative(kind, date_expr, tolerance) => {
959 write!(f, "{} {}", date_expr, kind)?;
960 if let Some(tol) = tolerance {
961 write!(f, " {}", tol)?;
962 }
963 Ok(())
964 }
965 ExpressionKind::DateCalendar(kind, unit, date_expr) => {
966 write!(f, "{} {} {}", date_expr, kind, unit)
967 }
968 }
969 }
970}
971
972impl fmt::Display for ConversionTarget {
973 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
974 match self {
975 ConversionTarget::Duration(unit) => write!(f, "{}", unit),
976 ConversionTarget::Unit(unit) => write!(f, "{}", unit),
977 }
978 }
979}
980
981impl fmt::Display for ArithmeticComputation {
982 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
983 match self {
984 ArithmeticComputation::Add => write!(f, "+"),
985 ArithmeticComputation::Subtract => write!(f, "-"),
986 ArithmeticComputation::Multiply => write!(f, "*"),
987 ArithmeticComputation::Divide => write!(f, "/"),
988 ArithmeticComputation::Modulo => write!(f, "%"),
989 ArithmeticComputation::Power => write!(f, "^"),
990 }
991 }
992}
993
994impl fmt::Display for ComparisonComputation {
995 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
996 match self {
997 ComparisonComputation::GreaterThan => write!(f, ">"),
998 ComparisonComputation::LessThan => write!(f, "<"),
999 ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1000 ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1001 ComparisonComputation::Is => write!(f, "is"),
1002 ComparisonComputation::IsNot => write!(f, "is not"),
1003 }
1004 }
1005}
1006
1007impl fmt::Display for MathematicalComputation {
1008 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1009 match self {
1010 MathematicalComputation::Sqrt => write!(f, "sqrt"),
1011 MathematicalComputation::Sin => write!(f, "sin"),
1012 MathematicalComputation::Cos => write!(f, "cos"),
1013 MathematicalComputation::Tan => write!(f, "tan"),
1014 MathematicalComputation::Asin => write!(f, "asin"),
1015 MathematicalComputation::Acos => write!(f, "acos"),
1016 MathematicalComputation::Atan => write!(f, "atan"),
1017 MathematicalComputation::Log => write!(f, "log"),
1018 MathematicalComputation::Exp => write!(f, "exp"),
1019 MathematicalComputation::Abs => write!(f, "abs"),
1020 MathematicalComputation::Floor => write!(f, "floor"),
1021 MathematicalComputation::Ceil => write!(f, "ceil"),
1022 MathematicalComputation::Round => write!(f, "round"),
1023 }
1024 }
1025}
1026
1027#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1033#[serde(rename_all = "snake_case")]
1034pub enum PrimitiveKind {
1035 Boolean,
1036 Scale,
1037 Number,
1038 Percent,
1039 Ratio,
1040 Text,
1041 Date,
1042 Time,
1043 Duration,
1044}
1045
1046impl std::fmt::Display for PrimitiveKind {
1047 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1048 let s = match self {
1049 PrimitiveKind::Boolean => "boolean",
1050 PrimitiveKind::Scale => "scale",
1051 PrimitiveKind::Number => "number",
1052 PrimitiveKind::Percent => "percent",
1053 PrimitiveKind::Ratio => "ratio",
1054 PrimitiveKind::Text => "text",
1055 PrimitiveKind::Date => "date",
1056 PrimitiveKind::Time => "time",
1057 PrimitiveKind::Duration => "duration",
1058 };
1059 write!(f, "{}", s)
1060 }
1061}
1062
1063#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
1068#[serde(tag = "kind", rename_all = "snake_case")]
1069pub enum ParentType {
1070 Primitive { primitive: PrimitiveKind },
1071 Custom { name: String },
1072}
1073
1074impl std::fmt::Display for ParentType {
1075 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1076 match self {
1077 ParentType::Primitive { primitive } => write!(f, "{}", primitive),
1078 ParentType::Custom { name } => write!(f, "{}", name),
1079 }
1080 }
1081}
1082
1083pub struct AsLemmaSource<'a, T: ?Sized>(pub &'a T);
1089
1090pub fn quote_lemma_text(s: &str) -> String {
1093 let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1094 format!("\"{}\"", escaped)
1095}
1096
1097fn format_decimal_source(n: &Decimal) -> String {
1102 let raw = if n.fract().is_zero() {
1103 n.trunc().to_string()
1104 } else {
1105 n.to_string()
1106 };
1107 group_digits(&raw)
1108}
1109
1110fn group_digits(s: &str) -> String {
1114 let (sign, rest) = if s.starts_with('-') || s.starts_with('+') {
1115 (&s[..1], &s[1..])
1116 } else {
1117 ("", s)
1118 };
1119
1120 let (int_part, frac_part) = match rest.find('.') {
1121 Some(pos) => (&rest[..pos], &rest[pos..]),
1122 None => (rest, ""),
1123 };
1124
1125 if int_part.len() < 4 {
1126 return s.to_string();
1127 }
1128
1129 let mut grouped = String::with_capacity(int_part.len() + int_part.len() / 3);
1130 for (i, ch) in int_part.chars().enumerate() {
1131 let digits_remaining = int_part.len() - i;
1132 if i > 0 && digits_remaining % 3 == 0 {
1133 grouped.push('_');
1134 }
1135 grouped.push(ch);
1136 }
1137
1138 format!("{}{}{}", sign, grouped, frac_part)
1139}
1140
1141impl<'a> fmt::Display for AsLemmaSource<'a, CommandArg> {
1142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143 use crate::literals::Value;
1144 match self.0 {
1145 CommandArg::Literal(Value::Text(s)) => write!(f, "{}", quote_lemma_text(s)),
1146 CommandArg::Literal(Value::Number(d)) => {
1147 write!(f, "{}", group_digits(&d.to_string()))
1148 }
1149 CommandArg::Literal(Value::Boolean(bv)) => write!(f, "{}", bv),
1150 CommandArg::Literal(Value::Scale(d, unit)) => {
1151 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1152 }
1153 CommandArg::Literal(Value::Duration(d, unit)) => {
1154 write!(f, "{} {}", group_digits(&d.to_string()), unit)
1155 }
1156 CommandArg::Literal(value @ Value::Ratio(_, _)) => write!(f, "{}", value),
1157 CommandArg::Literal(Value::Date(dt)) => write!(f, "{}", dt),
1158 CommandArg::Literal(Value::Time(t)) => write!(f, "{}", t),
1159 CommandArg::Label(s) => write!(f, "{}", s),
1160 }
1161 }
1162}
1163
1164fn format_constraint_as_source(cmd: &TypeConstraintCommand, args: &[CommandArg]) -> String {
1166 if args.is_empty() {
1167 cmd.to_string()
1168 } else {
1169 let args_str: Vec<String> = args
1170 .iter()
1171 .map(|a| format!("{}", AsLemmaSource(a)))
1172 .collect();
1173 format!("{} {}", cmd, args_str.join(" "))
1174 }
1175}
1176
1177fn format_constraints_as_source(constraints: &[Constraint], separator: &str) -> String {
1180 constraints
1181 .iter()
1182 .map(|(cmd, args)| format_constraint_as_source(cmd, args))
1183 .collect::<Vec<_>>()
1184 .join(separator)
1185}
1186
1187impl<'a> fmt::Display for AsLemmaSource<'a, Value> {
1190 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1191 match self.0 {
1192 Value::Number(n) => write!(f, "{}", format_decimal_source(n)),
1193 Value::Text(s) => write!(f, "{}", quote_lemma_text(s)),
1194 Value::Date(dt) => {
1195 let is_date_only =
1196 dt.hour == 0 && dt.minute == 0 && dt.second == 0 && dt.timezone.is_none();
1197 if is_date_only {
1198 write!(f, "{:04}-{:02}-{:02}", dt.year, dt.month, dt.day)
1199 } else {
1200 write!(
1201 f,
1202 "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1203 dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second
1204 )?;
1205 if let Some(tz) = &dt.timezone {
1206 write!(f, "{}", tz)?;
1207 }
1208 Ok(())
1209 }
1210 }
1211 Value::Time(t) => {
1212 write!(f, "{:02}:{:02}:{:02}", t.hour, t.minute, t.second)?;
1213 if let Some(tz) = &t.timezone {
1214 write!(f, "{}", tz)?;
1215 }
1216 Ok(())
1217 }
1218 Value::Boolean(b) => write!(f, "{}", b),
1219 Value::Scale(n, u) => write!(f, "{} {}", format_decimal_source(n), u),
1220 Value::Duration(n, u) => write!(f, "{} {}", format_decimal_source(n), u),
1221 Value::Ratio(n, unit) => match unit.as_deref() {
1222 Some("percent") => {
1223 let display_value = *n * Decimal::from(100);
1224 write!(f, "{}%", format_decimal_source(&display_value))
1225 }
1226 Some("permille") => {
1227 let display_value = *n * Decimal::from(1000);
1228 write!(f, "{}%%", format_decimal_source(&display_value))
1229 }
1230 Some(unit_name) => write!(f, "{} {}", format_decimal_source(n), unit_name),
1231 None => write!(f, "{}", format_decimal_source(n)),
1232 },
1233 }
1234 }
1235}
1236
1237impl<'a> fmt::Display for AsLemmaSource<'a, MetaValue> {
1240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1241 match self.0 {
1242 MetaValue::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1243 MetaValue::Unquoted(s) => write!(f, "{}", s),
1244 }
1245 }
1246}
1247
1248impl<'a> fmt::Display for AsLemmaSource<'a, DataValue> {
1249 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1250 match self.0 {
1251 DataValue::Literal(v) => write!(f, "{}", AsLemmaSource(v)),
1252 DataValue::SpecReference(spec_ref) => {
1253 write!(f, "with {}", spec_ref)
1254 }
1255 DataValue::TypeDeclaration {
1256 base,
1257 constraints,
1258 from,
1259 } => {
1260 let base_str = if let Some(from_spec) = from {
1261 format!("{} from {}", base, from_spec)
1262 } else {
1263 format!("{}", base)
1264 };
1265 if let Some(ref constraints_vec) = constraints {
1266 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
1267 write!(f, "{} -> {}", base_str, constraint_str)
1268 } else {
1269 write!(f, "{}", base_str)
1270 }
1271 }
1272 DataValue::Reference {
1273 target,
1274 constraints,
1275 } => {
1276 if let Some(ref constraints_vec) = constraints {
1277 let constraint_str = format_constraints_as_source(constraints_vec, " -> ");
1278 write!(f, "{} -> {}", target, constraint_str)
1279 } else {
1280 write!(f, "{}", target)
1281 }
1282 }
1283 }
1284 }
1285}
1286
1287#[cfg(test)]
1288mod tests {
1289 use super::*;
1290
1291 #[test]
1292 fn test_duration_unit_display() {
1293 assert_eq!(format!("{}", DurationUnit::Second), "seconds");
1294 assert_eq!(format!("{}", DurationUnit::Minute), "minutes");
1295 assert_eq!(format!("{}", DurationUnit::Hour), "hours");
1296 assert_eq!(format!("{}", DurationUnit::Day), "days");
1297 assert_eq!(format!("{}", DurationUnit::Week), "weeks");
1298 assert_eq!(format!("{}", DurationUnit::Millisecond), "milliseconds");
1299 assert_eq!(format!("{}", DurationUnit::Microsecond), "microseconds");
1300 }
1301
1302 #[test]
1303 fn test_conversion_target_display() {
1304 assert_eq!(
1305 format!("{}", ConversionTarget::Duration(DurationUnit::Hour)),
1306 "hours"
1307 );
1308 assert_eq!(
1309 format!("{}", ConversionTarget::Unit("usd".to_string())),
1310 "usd"
1311 );
1312 }
1313
1314 #[test]
1315 fn test_value_ratio_display() {
1316 use rust_decimal::Decimal;
1317 use std::str::FromStr;
1318 let percent = Value::Ratio(
1319 Decimal::from_str("0.10").unwrap(),
1320 Some("percent".to_string()),
1321 );
1322 assert_eq!(format!("{}", percent), "10%");
1323 let permille = Value::Ratio(
1324 Decimal::from_str("0.005").unwrap(),
1325 Some("permille".to_string()),
1326 );
1327 assert_eq!(format!("{}", permille), "5%%");
1328 }
1329
1330 #[test]
1331 fn test_datetime_value_display() {
1332 let dt = DateTimeValue {
1333 year: 2024,
1334 month: 12,
1335 day: 25,
1336 hour: 14,
1337 minute: 30,
1338 second: 45,
1339 microsecond: 0,
1340 timezone: Some(TimezoneValue {
1341 offset_hours: 1,
1342 offset_minutes: 0,
1343 }),
1344 };
1345 assert_eq!(format!("{}", dt), "2024-12-25T14:30:45+01:00");
1346 }
1347
1348 #[test]
1349 fn test_datetime_value_display_date_only() {
1350 let dt = DateTimeValue {
1351 year: 2026,
1352 month: 3,
1353 day: 4,
1354 hour: 0,
1355 minute: 0,
1356 second: 0,
1357 microsecond: 0,
1358 timezone: None,
1359 };
1360 assert_eq!(format!("{}", dt), "2026-03-04");
1361 }
1362
1363 #[test]
1364 fn test_datetime_value_display_microseconds() {
1365 let dt = DateTimeValue {
1366 year: 2026,
1367 month: 2,
1368 day: 23,
1369 hour: 14,
1370 minute: 30,
1371 second: 45,
1372 microsecond: 123456,
1373 timezone: Some(TimezoneValue {
1374 offset_hours: 0,
1375 offset_minutes: 0,
1376 }),
1377 };
1378 assert_eq!(format!("{}", dt), "2026-02-23T14:30:45.123456Z");
1379 }
1380
1381 #[test]
1382 fn test_datetime_microsecond_in_ordering() {
1383 let a = DateTimeValue {
1384 year: 2026,
1385 month: 1,
1386 day: 1,
1387 hour: 0,
1388 minute: 0,
1389 second: 0,
1390 microsecond: 100,
1391 timezone: None,
1392 };
1393 let b = DateTimeValue {
1394 year: 2026,
1395 month: 1,
1396 day: 1,
1397 hour: 0,
1398 minute: 0,
1399 second: 0,
1400 microsecond: 200,
1401 timezone: None,
1402 };
1403 assert!(a < b);
1404 }
1405
1406 #[test]
1407 fn test_datetime_parse_iso_week() {
1408 let dt: DateTimeValue = "2026-W01".parse().unwrap();
1409 assert_eq!(dt.year, 2025);
1410 assert_eq!(dt.month, 12);
1411 assert_eq!(dt.day, 29);
1412 assert_eq!(dt.microsecond, 0);
1413 }
1414
1415 #[test]
1416 fn test_negation_types() {
1417 let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
1418 let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
1419 assert_eq!(decoded, NegationType::Not);
1420 }
1421
1422 #[test]
1423 fn parent_type_primitive_serde_internally_tagged() {
1424 let p = ParentType::Primitive {
1425 primitive: PrimitiveKind::Number,
1426 };
1427 let json = serde_json::to_string(&p).expect("ParentType::Primitive must serialize");
1428 assert!(json.contains("\"kind\"") && json.contains("\"primitive\""));
1429 let back: ParentType = serde_json::from_str(&json).expect("deserialize");
1430 assert_eq!(back, p);
1431 }
1432
1433 fn text_arg(s: &str) -> CommandArg {
1438 CommandArg::Literal(crate::literals::Value::Text(s.to_string()))
1439 }
1440
1441 fn number_arg(s: &str) -> CommandArg {
1442 let d: rust_decimal::Decimal = s.parse().expect("decimal");
1443 CommandArg::Literal(crate::literals::Value::Number(d))
1444 }
1445
1446 fn boolean_arg(b: BooleanValue) -> CommandArg {
1447 CommandArg::Literal(crate::literals::Value::Boolean(b))
1448 }
1449
1450 fn scale_arg(value: &str, unit: &str) -> CommandArg {
1451 let d: rust_decimal::Decimal = value.parse().expect("decimal");
1452 CommandArg::Literal(crate::literals::Value::Scale(d, unit.to_string()))
1453 }
1454
1455 fn duration_arg(value: &str, unit: DurationUnit) -> CommandArg {
1456 let d: rust_decimal::Decimal = value.parse().expect("decimal");
1457 CommandArg::Literal(crate::literals::Value::Duration(d, unit))
1458 }
1459
1460 #[test]
1461 fn as_lemma_source_text_default_is_quoted() {
1462 let fv = DataValue::TypeDeclaration {
1463 base: ParentType::Primitive {
1464 primitive: PrimitiveKind::Text,
1465 },
1466 constraints: Some(vec![(
1467 TypeConstraintCommand::Default,
1468 vec![text_arg("single")],
1469 )]),
1470 from: None,
1471 };
1472 assert_eq!(
1473 format!("{}", AsLemmaSource(&fv)),
1474 "text -> default \"single\""
1475 );
1476 }
1477
1478 #[test]
1479 fn as_lemma_source_number_default_not_quoted() {
1480 let fv = DataValue::TypeDeclaration {
1481 base: ParentType::Primitive {
1482 primitive: PrimitiveKind::Number,
1483 },
1484 constraints: Some(vec![(
1485 TypeConstraintCommand::Default,
1486 vec![number_arg("10")],
1487 )]),
1488 from: None,
1489 };
1490 assert_eq!(format!("{}", AsLemmaSource(&fv)), "number -> default 10");
1491 }
1492
1493 #[test]
1494 fn as_lemma_source_help_always_quoted() {
1495 let fv = DataValue::TypeDeclaration {
1496 base: ParentType::Primitive {
1497 primitive: PrimitiveKind::Number,
1498 },
1499 constraints: Some(vec![(
1500 TypeConstraintCommand::Help,
1501 vec![text_arg("Enter a quantity")],
1502 )]),
1503 from: None,
1504 };
1505 assert_eq!(
1506 format!("{}", AsLemmaSource(&fv)),
1507 "number -> help \"Enter a quantity\""
1508 );
1509 }
1510
1511 #[test]
1512 fn as_lemma_source_text_option_quoted() {
1513 let fv = DataValue::TypeDeclaration {
1514 base: ParentType::Primitive {
1515 primitive: PrimitiveKind::Text,
1516 },
1517 constraints: Some(vec![
1518 (TypeConstraintCommand::Option, vec![text_arg("active")]),
1519 (TypeConstraintCommand::Option, vec![text_arg("inactive")]),
1520 ]),
1521 from: None,
1522 };
1523 assert_eq!(
1524 format!("{}", AsLemmaSource(&fv)),
1525 "text -> option \"active\" -> option \"inactive\""
1526 );
1527 }
1528
1529 #[test]
1530 fn as_lemma_source_scale_unit_not_quoted() {
1531 let fv = DataValue::TypeDeclaration {
1532 base: ParentType::Primitive {
1533 primitive: PrimitiveKind::Scale,
1534 },
1535 constraints: Some(vec![
1536 (
1537 TypeConstraintCommand::Unit,
1538 vec![CommandArg::Label("eur".to_string()), number_arg("1.00")],
1539 ),
1540 (
1541 TypeConstraintCommand::Unit,
1542 vec![CommandArg::Label("usd".to_string()), number_arg("1.10")],
1543 ),
1544 ]),
1545 from: None,
1546 };
1547 assert_eq!(
1548 format!("{}", AsLemmaSource(&fv)),
1549 "scale -> unit eur 1.00 -> unit usd 1.10"
1550 );
1551 }
1552
1553 #[test]
1554 fn as_lemma_source_scale_minimum_with_unit() {
1555 let fv = DataValue::TypeDeclaration {
1556 base: ParentType::Primitive {
1557 primitive: PrimitiveKind::Scale,
1558 },
1559 constraints: Some(vec![(
1560 TypeConstraintCommand::Minimum,
1561 vec![scale_arg("0", "eur")],
1562 )]),
1563 from: None,
1564 };
1565 assert_eq!(format!("{}", AsLemmaSource(&fv)), "scale -> minimum 0 eur");
1566 }
1567
1568 #[test]
1569 fn as_lemma_source_boolean_default() {
1570 let fv = DataValue::TypeDeclaration {
1571 base: ParentType::Primitive {
1572 primitive: PrimitiveKind::Boolean,
1573 },
1574 constraints: Some(vec![(
1575 TypeConstraintCommand::Default,
1576 vec![boolean_arg(BooleanValue::True)],
1577 )]),
1578 from: None,
1579 };
1580 assert_eq!(format!("{}", AsLemmaSource(&fv)), "boolean -> default true");
1581 }
1582
1583 #[test]
1584 fn as_lemma_source_duration_default() {
1585 let fv = DataValue::TypeDeclaration {
1586 base: ParentType::Primitive {
1587 primitive: PrimitiveKind::Duration,
1588 },
1589 constraints: Some(vec![(
1590 TypeConstraintCommand::Default,
1591 vec![duration_arg("40", DurationUnit::Hour)],
1592 )]),
1593 from: None,
1594 };
1595 assert_eq!(
1596 format!("{}", AsLemmaSource(&fv)),
1597 "duration -> default 40 hours"
1598 );
1599 }
1600
1601 #[test]
1602 fn as_lemma_source_named_type_default_quoted() {
1603 let fv = DataValue::TypeDeclaration {
1606 base: ParentType::Custom {
1607 name: "filing_status_type".to_string(),
1608 },
1609 constraints: Some(vec![(
1610 TypeConstraintCommand::Default,
1611 vec![text_arg("single")],
1612 )]),
1613 from: None,
1614 };
1615 assert_eq!(
1616 format!("{}", AsLemmaSource(&fv)),
1617 "filing_status_type -> default \"single\""
1618 );
1619 }
1620
1621 #[test]
1622 fn as_lemma_source_help_escapes_quotes() {
1623 let fv = DataValue::TypeDeclaration {
1624 base: ParentType::Primitive {
1625 primitive: PrimitiveKind::Text,
1626 },
1627 constraints: Some(vec![(
1628 TypeConstraintCommand::Help,
1629 vec![text_arg("say \"hello\"")],
1630 )]),
1631 from: None,
1632 };
1633 assert_eq!(
1634 format!("{}", AsLemmaSource(&fv)),
1635 "text -> help \"say \\\"hello\\\"\""
1636 );
1637 }
1638}