lemma/
semantic.rs

1use crate::ast::{ExpressionId, Span};
2use rust_decimal::Decimal;
3use serde::Serialize;
4use std::fmt;
5
6/// A Lemma document containing facts, rules
7#[derive(Debug, Clone, PartialEq)]
8pub struct LemmaDoc {
9    pub name: String,
10    pub source: Option<String>,
11    pub start_line: usize,
12    pub commentary: Option<String>,
13    pub facts: Vec<LemmaFact>,
14    pub rules: Vec<LemmaRule>,
15}
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct LemmaFact {
19    pub fact_type: FactType,
20    pub value: FactValue,
21    pub span: Option<Span>,
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum FactType {
26    Local(String),
27    Foreign(ForeignFact),
28}
29
30/// A fact that references another document
31#[derive(Debug, Clone, PartialEq)]
32pub struct ForeignFact {
33    pub reference: Vec<String>,
34}
35
36/// An unless clause that provides an alternative result
37///
38/// Unless clauses are evaluated in order, and the last matching condition wins.
39/// This matches natural language: "X unless A then Y, unless B then Z" - if both
40/// A and B are true, Z is returned (the last match).
41#[derive(Debug, Clone, PartialEq)]
42pub struct UnlessClause {
43    pub condition: Expression,
44    pub result: Expression,
45    pub span: Option<Span>,
46}
47
48/// A rule with a single expression and optional unless clauses
49#[derive(Debug, Clone, PartialEq)]
50pub struct LemmaRule {
51    pub name: String,
52    pub expression: Expression,
53    pub unless_clauses: Vec<UnlessClause>,
54    pub span: Option<Span>,
55}
56
57/// An expression that can be evaluated, with source location and unique ID
58#[derive(Debug, Clone, PartialEq)]
59pub struct Expression {
60    pub kind: ExpressionKind,
61    pub span: Option<Span>,
62    pub id: ExpressionId,
63}
64
65impl Expression {
66    /// Create a new expression with kind, span, and ID
67    pub fn new(kind: ExpressionKind, span: Option<Span>, id: ExpressionId) -> Self {
68        Self { kind, span, id }
69    }
70}
71
72/// The kind/type of expression
73#[derive(Debug, Clone, PartialEq)]
74pub enum ExpressionKind {
75    Literal(LiteralValue),
76    FactReference(FactReference),
77    RuleReference(RuleReference),
78    LogicalAnd(Box<Expression>, Box<Expression>),
79    LogicalOr(Box<Expression>, Box<Expression>),
80    Arithmetic(Box<Expression>, ArithmeticOperation, Box<Expression>),
81    Comparison(Box<Expression>, ComparisonOperator, Box<Expression>),
82    FactHasAnyValue(FactReference),
83    UnitConversion(Box<Expression>, ConversionTarget),
84    LogicalNegation(Box<Expression>, NegationType),
85    MathematicalOperator(MathematicalOperator, Box<Expression>),
86    Veto(VetoExpression),
87}
88
89/// Reference to a fact
90#[derive(Debug, Clone, PartialEq, Eq, Hash)]
91pub struct FactReference {
92    pub reference: Vec<String>, // ["file", "size"]
93}
94
95/// Reference to a rule
96///
97/// Rule references use a question mark suffix to distinguish them from fact references.
98/// Example: `has_license?` references the `has_license` rule.
99/// Cross-document example: `employee.is_eligible?` references the `is_eligible` rule from the `employee` document.
100#[derive(Debug, Clone, PartialEq, Eq, Hash)]
101pub struct RuleReference {
102    pub reference: Vec<String>, // ["employee", "is_eligible"] or just ["is_eligible"]
103}
104
105/// Arithmetic operations
106#[derive(Debug, Clone, PartialEq, Eq, Hash)]
107pub enum ArithmeticOperation {
108    Add,
109    Subtract,
110    Multiply,
111    Divide,
112    Modulo,
113    Power,
114}
115
116impl ArithmeticOperation {
117    /// Returns a human-readable name for the operation
118    pub fn name(&self) -> &'static str {
119        match self {
120            ArithmeticOperation::Add => "addition",
121            ArithmeticOperation::Subtract => "subtraction",
122            ArithmeticOperation::Multiply => "multiplication",
123            ArithmeticOperation::Divide => "division",
124            ArithmeticOperation::Modulo => "modulo",
125            ArithmeticOperation::Power => "exponentiation",
126        }
127    }
128}
129
130/// Comparison operators
131#[derive(Debug, Clone, PartialEq, Eq, Hash)]
132pub enum ComparisonOperator {
133    GreaterThan,
134    LessThan,
135    GreaterThanOrEqual,
136    LessThanOrEqual,
137    Equal,
138    NotEqual,
139    Is,
140    IsNot,
141}
142
143impl ComparisonOperator {
144    /// Returns a human-readable name for the operator
145    pub fn name(&self) -> &'static str {
146        match self {
147            ComparisonOperator::GreaterThan => "greater than",
148            ComparisonOperator::LessThan => "less than",
149            ComparisonOperator::GreaterThanOrEqual => "greater than or equal",
150            ComparisonOperator::LessThanOrEqual => "less than or equal",
151            ComparisonOperator::Equal => "equal",
152            ComparisonOperator::NotEqual => "not equal",
153            ComparisonOperator::Is => "is",
154            ComparisonOperator::IsNot => "is not",
155        }
156    }
157}
158
159/// The target unit for unit conversion expressions
160#[derive(Debug, Clone, PartialEq, Eq, Hash)]
161pub enum ConversionTarget {
162    Mass(MassUnit),
163    Length(LengthUnit),
164    Volume(VolumeUnit),
165    Duration(DurationUnit),
166    Temperature(TemperatureUnit),
167    Power(PowerUnit),
168    Force(ForceUnit),
169    Pressure(PressureUnit),
170    Energy(EnergyUnit),
171    Frequency(FrequencyUnit),
172    Data(DataUnit),
173    Money(MoneyUnit),
174    Percentage,
175}
176
177/// Types of logical negation
178#[derive(Debug, Clone, PartialEq, Eq, Hash)]
179pub enum NegationType {
180    Not,     // "not expression"
181    HaveNot, // "have not expression"
182    NotHave, // "not have expression"
183}
184
185/// A veto expression that prohibits any valid verdict from the rule
186///
187/// Unlike `reject` (which is just an alias for boolean `false`), a veto
188/// prevents the rule from producing any valid result. This is used for
189/// validation and constraint enforcement.
190///
191/// Example: `veto "Must be over 18"` - blocks the rule entirely with a message
192#[derive(Debug, Clone, PartialEq)]
193pub struct VetoExpression {
194    pub message: Option<String>,
195}
196
197/// Mathematical operators
198#[derive(Debug, Clone, PartialEq, Eq, Hash)]
199pub enum MathematicalOperator {
200    Sqrt, // Square root
201    Sin,  // Sine
202    Cos,  // Cosine
203    Tan,  // Tangent
204    Asin, // Arc sine
205    Acos, // Arc cosine
206    Atan, // Arc tangent
207    Log,  // Natural logarithm
208    Exp,  // Exponential (e^x)
209}
210
211#[derive(Debug, Clone, PartialEq)]
212pub enum FactValue {
213    Literal(LiteralValue),
214    DocumentReference(String),
215    TypeAnnotation(TypeAnnotation),
216}
217
218#[derive(Debug, Clone, PartialEq)]
219pub enum TypeAnnotation {
220    LemmaType(LemmaType),
221}
222
223/// A type for type annotations (both literal types and document types)
224#[derive(Debug, Clone, PartialEq, Eq, Hash)]
225pub enum LemmaType {
226    Text,
227    Number,
228    Date,
229    Boolean,
230    Regex,
231    Percentage,
232    Mass,
233    Length,
234    Volume,
235    Duration,
236    Temperature,
237    Power,
238    Energy,
239    Force,
240    Pressure,
241    Frequency,
242    Data,
243    Money,
244}
245
246/// A literal value
247#[derive(Debug, Clone, PartialEq, Serialize)]
248pub enum LiteralValue {
249    Number(Decimal),
250    Text(String),
251    Date(DateTimeValue), // Date with time and timezone information preserved
252    Time(TimeValue),     // Standalone time with optional timezone
253    Boolean(bool),
254    Percentage(Decimal),
255    Unit(NumericUnit), // All physical units and money
256    Regex(String),     // e.g., "/pattern/"
257}
258
259impl LiteralValue {
260    /// Get the display value as a string (uses the Display implementation)
261    pub fn display_value(&self) -> String {
262        self.to_string()
263    }
264
265    /// Convert a LiteralValue to its corresponding LemmaType
266    pub fn to_type(&self) -> LemmaType {
267        match self {
268            LiteralValue::Text(_) => LemmaType::Text,
269            LiteralValue::Number(_) => LemmaType::Number,
270            LiteralValue::Date(_) => LemmaType::Date,
271            LiteralValue::Time(_) => LemmaType::Date,
272            LiteralValue::Boolean(_) => LemmaType::Boolean,
273            LiteralValue::Percentage(_) => LemmaType::Percentage,
274            LiteralValue::Regex(_) => LemmaType::Regex,
275            LiteralValue::Unit(unit) => match unit {
276                NumericUnit::Mass(_, _) => LemmaType::Mass,
277                NumericUnit::Length(_, _) => LemmaType::Length,
278                NumericUnit::Volume(_, _) => LemmaType::Volume,
279                NumericUnit::Duration(_, _) => LemmaType::Duration,
280                NumericUnit::Temperature(_, _) => LemmaType::Temperature,
281                NumericUnit::Power(_, _) => LemmaType::Power,
282                NumericUnit::Force(_, _) => LemmaType::Force,
283                NumericUnit::Pressure(_, _) => LemmaType::Pressure,
284                NumericUnit::Energy(_, _) => LemmaType::Energy,
285                NumericUnit::Frequency(_, _) => LemmaType::Frequency,
286                NumericUnit::Data(_, _) => LemmaType::Data,
287                NumericUnit::Money(_, _) => LemmaType::Money,
288            },
289        }
290    }
291}
292
293/// A time value
294#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
295pub struct TimeValue {
296    pub hour: u8,
297    pub minute: u8,
298    pub second: u8,
299    pub timezone: Option<TimezoneValue>,
300}
301
302/// A timezone value
303#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
304pub struct TimezoneValue {
305    pub offset_hours: i8,
306    pub offset_minutes: u8,
307}
308
309/// A datetime value that preserves timezone information
310#[derive(Debug, Clone, PartialEq, Serialize)]
311pub struct DateTimeValue {
312    pub year: i32,
313    pub month: u32,
314    pub day: u32,
315    pub hour: u32,
316    pub minute: u32,
317    pub second: u32,
318    pub timezone: Option<TimezoneValue>,
319}
320
321/// Unit types for different physical quantities
322macro_rules! impl_unit_serialize {
323    ($($unit_type:ty),+) => {
324        $(
325            impl Serialize for $unit_type {
326                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
327                where
328                    S: serde::Serializer,
329                {
330                    serializer.serialize_str(&self.to_string())
331                }
332            }
333        )+
334    };
335}
336
337impl_unit_serialize!(
338    MassUnit,
339    LengthUnit,
340    VolumeUnit,
341    DurationUnit,
342    TemperatureUnit,
343    PowerUnit,
344    ForceUnit,
345    PressureUnit,
346    EnergyUnit,
347    FrequencyUnit,
348    DataUnit,
349    MoneyUnit
350);
351
352#[derive(Debug, Clone, PartialEq, Eq, Hash)]
353pub enum MassUnit {
354    Kilogram,
355    Gram,
356    Milligram,
357    Ton,
358    Pound,
359    Ounce,
360}
361
362#[derive(Debug, Clone, PartialEq, Eq, Hash)]
363pub enum LengthUnit {
364    Kilometer,
365    Mile,
366    NauticalMile,
367    Meter,
368    Decimeter,
369    Centimeter,
370    Millimeter,
371    Yard,
372    Foot,
373    Inch,
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, Hash)]
377pub enum VolumeUnit {
378    CubicMeter,
379    CubicCentimeter,
380    Liter,
381    Deciliter,
382    Centiliter,
383    Milliliter,
384    Gallon,
385    Quart,
386    Pint,
387    FluidOunce,
388}
389
390#[derive(Debug, Clone, PartialEq, Eq, Hash)]
391pub enum DurationUnit {
392    Year,
393    Month,
394    Week,
395    Day,
396    Hour,
397    Minute,
398    Second,
399    Millisecond,
400    Microsecond,
401}
402
403#[derive(Debug, Clone, PartialEq, Eq, Hash)]
404pub enum TemperatureUnit {
405    Celsius,
406    Fahrenheit,
407    Kelvin,
408}
409
410#[derive(Debug, Clone, PartialEq, Eq, Hash)]
411pub enum PowerUnit {
412    Megawatt,
413    Kilowatt,
414    Watt,
415    Milliwatt,
416    Horsepower,
417}
418
419#[derive(Debug, Clone, PartialEq, Eq, Hash)]
420pub enum ForceUnit {
421    Newton,
422    Kilonewton,
423    Lbf,
424}
425
426#[derive(Debug, Clone, PartialEq, Eq, Hash)]
427pub enum PressureUnit {
428    Megapascal,
429    Kilopascal,
430    Pascal,
431    Atmosphere,
432    Bar,
433    Psi,
434    Torr,
435    Mmhg,
436}
437
438#[derive(Debug, Clone, PartialEq, Eq, Hash)]
439pub enum EnergyUnit {
440    Megajoule,
441    Kilojoule,
442    Joule,
443    Kilowatthour,
444    Watthour,
445    Kilocalorie,
446    Calorie,
447    Btu,
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, Hash)]
451pub enum FrequencyUnit {
452    Hertz,
453    Kilohertz,
454    Megahertz,
455    Gigahertz,
456}
457
458#[derive(Debug, Clone, PartialEq, Eq, Hash)]
459pub enum DataUnit {
460    Petabyte,
461    Terabyte,
462    Gigabyte,
463    Megabyte,
464    Kilobyte,
465    Byte,
466    Tebibyte,
467    Gibibyte,
468    Mebibyte,
469    Kibibyte,
470}
471
472#[derive(Debug, Clone, PartialEq, Eq, Hash)]
473pub enum MoneyUnit {
474    Eur,
475    Usd,
476    Gbp,
477    Jpy,
478    Cny,
479    Chf,
480    Cad,
481    Aud,
482    Inr,
483}
484
485/// A unified type for all numeric units (physical quantities and money)
486///
487/// This provides consistent behavior for all unit types:
488/// - Comparisons always compare numeric values (ignoring units)
489/// - Same-unit arithmetic preserves the unit
490/// - Cross-unit arithmetic produces dimensionless numbers
491#[derive(Debug, Clone, PartialEq, Serialize)]
492pub enum NumericUnit {
493    Mass(Decimal, MassUnit),
494    Length(Decimal, LengthUnit),
495    Volume(Decimal, VolumeUnit),
496    Duration(Decimal, DurationUnit),
497    Temperature(Decimal, TemperatureUnit),
498    Power(Decimal, PowerUnit),
499    Force(Decimal, ForceUnit),
500    Pressure(Decimal, PressureUnit),
501    Energy(Decimal, EnergyUnit),
502    Frequency(Decimal, FrequencyUnit),
503    Data(Decimal, DataUnit),
504    Money(Decimal, MoneyUnit),
505}
506
507impl NumericUnit {
508    /// Extract the numeric value from any unit
509    pub fn value(&self) -> Decimal {
510        match self {
511            NumericUnit::Mass(v, _)
512            | NumericUnit::Length(v, _)
513            | NumericUnit::Volume(v, _)
514            | NumericUnit::Duration(v, _)
515            | NumericUnit::Temperature(v, _)
516            | NumericUnit::Power(v, _)
517            | NumericUnit::Force(v, _)
518            | NumericUnit::Pressure(v, _)
519            | NumericUnit::Energy(v, _)
520            | NumericUnit::Frequency(v, _)
521            | NumericUnit::Data(v, _)
522            | NumericUnit::Money(v, _) => *v,
523        }
524    }
525
526    /// Check if two units are the same category
527    pub fn same_category(&self, other: &NumericUnit) -> bool {
528        std::mem::discriminant(self) == std::mem::discriminant(other)
529    }
530
531    /// Create a new NumericUnit with the same unit type but different value
532    /// This is the key method that eliminates type enumeration in operations
533    pub fn with_value(&self, new_value: Decimal) -> NumericUnit {
534        match self {
535            NumericUnit::Mass(_, u) => NumericUnit::Mass(new_value, u.clone()),
536            NumericUnit::Length(_, u) => NumericUnit::Length(new_value, u.clone()),
537            NumericUnit::Volume(_, u) => NumericUnit::Volume(new_value, u.clone()),
538            NumericUnit::Duration(_, u) => NumericUnit::Duration(new_value, u.clone()),
539            NumericUnit::Temperature(_, u) => NumericUnit::Temperature(new_value, u.clone()),
540            NumericUnit::Power(_, u) => NumericUnit::Power(new_value, u.clone()),
541            NumericUnit::Force(_, u) => NumericUnit::Force(new_value, u.clone()),
542            NumericUnit::Pressure(_, u) => NumericUnit::Pressure(new_value, u.clone()),
543            NumericUnit::Energy(_, u) => NumericUnit::Energy(new_value, u.clone()),
544            NumericUnit::Frequency(_, u) => NumericUnit::Frequency(new_value, u.clone()),
545            NumericUnit::Data(_, u) => NumericUnit::Data(new_value, u.clone()),
546            NumericUnit::Money(_, u) => NumericUnit::Money(new_value, u.clone()),
547        }
548    }
549
550    /// Validate that two Money units have the same currency
551    /// Returns Ok for non-Money units or matching currencies
552    pub fn validate_same_currency(&self, other: &NumericUnit) -> Result<(), crate::LemmaError> {
553        if let (NumericUnit::Money(_, l_curr), NumericUnit::Money(_, r_curr)) = (self, other) {
554            if l_curr != r_curr {
555                return Err(crate::LemmaError::Engine(format!(
556                    "Cannot operate on different currencies: {:?} and {:?}",
557                    l_curr, r_curr
558                )));
559            }
560        }
561        Ok(())
562    }
563}
564
565impl fmt::Display for NumericUnit {
566    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567        match self {
568            NumericUnit::Mass(v, u) => write!(f, "{} {}", v, u),
569            NumericUnit::Length(v, u) => write!(f, "{} {}", v, u),
570            NumericUnit::Volume(v, u) => write!(f, "{} {}", v, u),
571            NumericUnit::Duration(v, u) => write!(f, "{} {}", v, u),
572            NumericUnit::Temperature(v, u) => write!(f, "{} {}", v, u),
573            NumericUnit::Power(v, u) => write!(f, "{} {}", v, u),
574            NumericUnit::Force(v, u) => write!(f, "{} {}", v, u),
575            NumericUnit::Pressure(v, u) => write!(f, "{} {}", v, u),
576            NumericUnit::Energy(v, u) => write!(f, "{} {}", v, u),
577            NumericUnit::Frequency(v, u) => write!(f, "{} {}", v, u),
578            NumericUnit::Data(v, u) => write!(f, "{} {}", v, u),
579            NumericUnit::Money(v, u) => write!(f, "{} {}", v, u),
580        }
581    }
582}
583
584impl LemmaRule {
585    pub fn new(name: String, expression: Expression) -> Self {
586        Self {
587            name,
588            expression,
589            unless_clauses: Vec::new(),
590            span: None,
591        }
592    }
593
594    pub fn add_unless_clause(mut self, unless_clause: UnlessClause) -> Self {
595        self.unless_clauses.push(unless_clause);
596        self
597    }
598}
599
600impl LemmaFact {
601    pub fn new(fact_type: FactType, value: FactValue) -> Self {
602        Self {
603            fact_type,
604            value,
605            span: None,
606        }
607    }
608
609    pub fn with_span(mut self, span: Span) -> Self {
610        self.span = Some(span);
611        self
612    }
613}
614
615impl LemmaDoc {
616    pub fn new(name: String) -> Self {
617        Self {
618            name,
619            source: None,
620            start_line: 1,
621            commentary: None,
622            facts: Vec::new(),
623            rules: Vec::new(),
624        }
625    }
626
627    pub fn with_source(mut self, source: String) -> Self {
628        self.source = Some(source);
629        self
630    }
631
632    pub fn with_start_line(mut self, start_line: usize) -> Self {
633        self.start_line = start_line;
634        self
635    }
636
637    pub fn set_commentary(mut self, commentary: String) -> Self {
638        self.commentary = Some(commentary);
639        self
640    }
641
642    pub fn add_fact(mut self, fact: LemmaFact) -> Self {
643        self.facts.push(fact);
644        self
645    }
646
647    pub fn add_rule(mut self, rule: LemmaRule) -> Self {
648        self.rules.push(rule);
649        self
650    }
651}
652
653impl fmt::Display for LemmaDoc {
654    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
655        write!(f, "doc {}", self.name)?;
656        writeln!(f)?;
657
658        if let Some(ref commentary) = self.commentary {
659            writeln!(f, "\"\"\"{}", commentary)?;
660            writeln!(f, "\"\"\"")?;
661        }
662
663        for fact in &self.facts {
664            write!(f, "{}", fact)?;
665        }
666
667        for rule in &self.rules {
668            write!(f, "{}", rule)?;
669        }
670
671        Ok(())
672    }
673}
674
675impl fmt::Display for LemmaFact {
676    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677        writeln!(f, "fact {} = {}", self.fact_type, self.value)
678    }
679}
680
681impl fmt::Display for LemmaRule {
682    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
683        write!(f, "rule {} = {}", self.name, self.expression)?;
684
685        for unless_clause in &self.unless_clauses {
686            write!(
687                f,
688                " unless {} then {}",
689                unless_clause.condition, unless_clause.result
690            )?;
691        }
692
693        writeln!(f)?;
694        Ok(())
695    }
696}
697
698impl fmt::Display for Expression {
699    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
700        match &self.kind {
701            ExpressionKind::Literal(lit) => write!(f, "{}", lit),
702            ExpressionKind::FactReference(fact_ref) => write!(f, "{}", fact_ref),
703            ExpressionKind::RuleReference(rule_ref) => write!(f, "{}", rule_ref),
704            ExpressionKind::Arithmetic(left, op, right) => {
705                write!(f, "{} {} {}", left, op, right)
706            }
707            ExpressionKind::Comparison(left, op, right) => {
708                write!(f, "{} {} {}", left, op, right)
709            }
710            ExpressionKind::FactHasAnyValue(fact_ref) => {
711                write!(f, "have {}", fact_ref)
712            }
713            ExpressionKind::UnitConversion(value, target) => {
714                write!(f, "{} in {}", value, target)
715            }
716            ExpressionKind::LogicalNegation(expr, negation_type) => {
717                let prefix = match negation_type {
718                    NegationType::Not => "not",
719                    NegationType::HaveNot => "have not",
720                    NegationType::NotHave => "not have",
721                };
722                write!(f, "{} {}", prefix, expr)
723            }
724            ExpressionKind::LogicalAnd(left, right) => {
725                write!(f, "{} and {}", left, right)
726            }
727            ExpressionKind::LogicalOr(left, right) => {
728                write!(f, "{} or {}", left, right)
729            }
730            ExpressionKind::MathematicalOperator(op, operand) => {
731                let op_name = match op {
732                    MathematicalOperator::Sqrt => "sqrt",
733                    MathematicalOperator::Sin => "sin",
734                    MathematicalOperator::Cos => "cos",
735                    MathematicalOperator::Tan => "tan",
736                    MathematicalOperator::Asin => "asin",
737                    MathematicalOperator::Acos => "acos",
738                    MathematicalOperator::Atan => "atan",
739                    MathematicalOperator::Log => "log",
740                    MathematicalOperator::Exp => "exp",
741                };
742                write!(f, "{} {}", op_name, operand)
743            }
744            ExpressionKind::Veto(veto) => match &veto.message {
745                Some(msg) => write!(f, "veto \"{}\"", msg),
746                None => write!(f, "veto"),
747            },
748        }
749    }
750}
751
752impl fmt::Display for LiteralValue {
753    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
754        match self {
755            LiteralValue::Number(n) => write!(f, "{}", n),
756            LiteralValue::Text(s) => write!(f, "\"{}\"", s),
757            LiteralValue::Date(dt) => write!(f, "{}", dt),
758            LiteralValue::Boolean(b) => write!(f, "{}", b),
759            LiteralValue::Percentage(p) => write!(f, "{}%", p),
760            LiteralValue::Unit(unit) => write!(f, "{}", unit),
761            LiteralValue::Regex(s) => write!(f, "{}", s),
762            LiteralValue::Time(time) => {
763                write!(f, "time({}, {}, {})", time.hour, time.minute, time.second)
764            }
765        }
766    }
767}
768
769impl LiteralValue {
770    /// Provides a descriptive string for error messages and debugging
771    pub fn describe(&self) -> String {
772        match self {
773            LiteralValue::Text(s) => format!("text value \"{}\"", s),
774            LiteralValue::Number(n) => format!("number {}", n),
775            LiteralValue::Boolean(b) => format!("boolean {}", b),
776            LiteralValue::Percentage(p) => format!("percentage {}%", p),
777            LiteralValue::Date(_) => "date value".to_string(),
778            LiteralValue::Unit(unit) => {
779                format!(
780                    "{} value {}",
781                    LiteralValue::Unit(unit.clone()).to_type(),
782                    unit
783                )
784            }
785            LiteralValue::Regex(s) => format!("regex value {}", s),
786            LiteralValue::Time(time) => {
787                format!("time value {}:{}:{}", time.hour, time.minute, time.second)
788            }
789        }
790    }
791}
792
793impl fmt::Display for MassUnit {
794    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
795        match self {
796            MassUnit::Kilogram => write!(f, "kilogram"),
797            MassUnit::Gram => write!(f, "gram"),
798            MassUnit::Milligram => write!(f, "milligram"),
799            MassUnit::Ton => write!(f, "ton"),
800            MassUnit::Pound => write!(f, "pound"),
801            MassUnit::Ounce => write!(f, "ounce"),
802        }
803    }
804}
805
806impl fmt::Display for LengthUnit {
807    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
808        match self {
809            LengthUnit::Kilometer => write!(f, "kilometer"),
810            LengthUnit::Mile => write!(f, "mile"),
811            LengthUnit::NauticalMile => write!(f, "nautical_mile"),
812            LengthUnit::Meter => write!(f, "meter"),
813            LengthUnit::Decimeter => write!(f, "decimeter"),
814            LengthUnit::Centimeter => write!(f, "centimeter"),
815            LengthUnit::Millimeter => write!(f, "millimeter"),
816            LengthUnit::Yard => write!(f, "yard"),
817            LengthUnit::Foot => write!(f, "foot"),
818            LengthUnit::Inch => write!(f, "inch"),
819        }
820    }
821}
822
823impl fmt::Display for VolumeUnit {
824    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
825        match self {
826            VolumeUnit::CubicMeter => write!(f, "cubic_meter"),
827            VolumeUnit::CubicCentimeter => write!(f, "cubic_centimeter"),
828            VolumeUnit::Liter => write!(f, "liter"),
829            VolumeUnit::Deciliter => write!(f, "deciliter"),
830            VolumeUnit::Centiliter => write!(f, "centiliter"),
831            VolumeUnit::Milliliter => write!(f, "milliliter"),
832            VolumeUnit::Gallon => write!(f, "gallon"),
833            VolumeUnit::Quart => write!(f, "quart"),
834            VolumeUnit::Pint => write!(f, "pint"),
835            VolumeUnit::FluidOunce => write!(f, "fluid_ounce"),
836        }
837    }
838}
839
840impl fmt::Display for DurationUnit {
841    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
842        match self {
843            DurationUnit::Year => write!(f, "year"),
844            DurationUnit::Month => write!(f, "month"),
845            DurationUnit::Week => write!(f, "week"),
846            DurationUnit::Day => write!(f, "day"),
847            DurationUnit::Hour => write!(f, "hour"),
848            DurationUnit::Minute => write!(f, "minute"),
849            DurationUnit::Second => write!(f, "second"),
850            DurationUnit::Millisecond => write!(f, "millisecond"),
851            DurationUnit::Microsecond => write!(f, "microsecond"),
852        }
853    }
854}
855
856impl fmt::Display for TemperatureUnit {
857    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
858        match self {
859            TemperatureUnit::Celsius => write!(f, "celsius"),
860            TemperatureUnit::Fahrenheit => write!(f, "fahrenheit"),
861            TemperatureUnit::Kelvin => write!(f, "kelvin"),
862        }
863    }
864}
865
866impl fmt::Display for PowerUnit {
867    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
868        match self {
869            PowerUnit::Megawatt => write!(f, "megawatt"),
870            PowerUnit::Kilowatt => write!(f, "kilowatt"),
871            PowerUnit::Watt => write!(f, "watt"),
872            PowerUnit::Milliwatt => write!(f, "milliwatt"),
873            PowerUnit::Horsepower => write!(f, "horsepower"),
874        }
875    }
876}
877
878impl fmt::Display for ForceUnit {
879    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
880        match self {
881            ForceUnit::Newton => write!(f, "newton"),
882            ForceUnit::Kilonewton => write!(f, "kilonewton"),
883            ForceUnit::Lbf => write!(f, "lbf"),
884        }
885    }
886}
887
888impl fmt::Display for PressureUnit {
889    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
890        match self {
891            PressureUnit::Megapascal => write!(f, "megapascal"),
892            PressureUnit::Kilopascal => write!(f, "kilopascal"),
893            PressureUnit::Pascal => write!(f, "pascal"),
894            PressureUnit::Atmosphere => write!(f, "atmosphere"),
895            PressureUnit::Bar => write!(f, "bar"),
896            PressureUnit::Psi => write!(f, "psi"),
897            PressureUnit::Torr => write!(f, "torr"),
898            PressureUnit::Mmhg => write!(f, "mmhg"),
899        }
900    }
901}
902
903impl fmt::Display for EnergyUnit {
904    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
905        match self {
906            EnergyUnit::Megajoule => write!(f, "megajoule"),
907            EnergyUnit::Kilojoule => write!(f, "kilojoule"),
908            EnergyUnit::Joule => write!(f, "joule"),
909            EnergyUnit::Kilowatthour => write!(f, "kilowatthour"),
910            EnergyUnit::Watthour => write!(f, "watthour"),
911            EnergyUnit::Kilocalorie => write!(f, "kilocalorie"),
912            EnergyUnit::Calorie => write!(f, "calorie"),
913            EnergyUnit::Btu => write!(f, "btu"),
914        }
915    }
916}
917
918impl fmt::Display for FrequencyUnit {
919    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
920        match self {
921            FrequencyUnit::Hertz => write!(f, "hertz"),
922            FrequencyUnit::Kilohertz => write!(f, "kilohertz"),
923            FrequencyUnit::Megahertz => write!(f, "megahertz"),
924            FrequencyUnit::Gigahertz => write!(f, "gigahertz"),
925        }
926    }
927}
928
929impl fmt::Display for DataUnit {
930    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
931        match self {
932            DataUnit::Petabyte => write!(f, "petabyte"),
933            DataUnit::Terabyte => write!(f, "terabyte"),
934            DataUnit::Gigabyte => write!(f, "gigabyte"),
935            DataUnit::Megabyte => write!(f, "megabyte"),
936            DataUnit::Kilobyte => write!(f, "kilobyte"),
937            DataUnit::Byte => write!(f, "byte"),
938            DataUnit::Tebibyte => write!(f, "tebibyte"),
939            DataUnit::Gibibyte => write!(f, "gibibyte"),
940            DataUnit::Mebibyte => write!(f, "mebibyte"),
941            DataUnit::Kibibyte => write!(f, "kibibyte"),
942        }
943    }
944}
945
946impl fmt::Display for MoneyUnit {
947    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
948        match self {
949            MoneyUnit::Eur => write!(f, "EUR"),
950            MoneyUnit::Usd => write!(f, "USD"),
951            MoneyUnit::Gbp => write!(f, "GBP"),
952            MoneyUnit::Jpy => write!(f, "JPY"),
953            MoneyUnit::Cny => write!(f, "CNY"),
954            MoneyUnit::Chf => write!(f, "CHF"),
955            MoneyUnit::Cad => write!(f, "CAD"),
956            MoneyUnit::Aud => write!(f, "AUD"),
957            MoneyUnit::Inr => write!(f, "INR"),
958        }
959    }
960}
961
962impl fmt::Display for ConversionTarget {
963    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
964        match self {
965            ConversionTarget::Mass(unit) => write!(f, "{}", unit),
966            ConversionTarget::Length(unit) => write!(f, "{}", unit),
967            ConversionTarget::Volume(unit) => write!(f, "{}", unit),
968            ConversionTarget::Duration(unit) => write!(f, "{}", unit),
969            ConversionTarget::Temperature(unit) => write!(f, "{}", unit),
970            ConversionTarget::Power(unit) => write!(f, "{}", unit),
971            ConversionTarget::Force(unit) => write!(f, "{}", unit),
972            ConversionTarget::Pressure(unit) => write!(f, "{}", unit),
973            ConversionTarget::Energy(unit) => write!(f, "{}", unit),
974            ConversionTarget::Frequency(unit) => write!(f, "{}", unit),
975            ConversionTarget::Data(unit) => write!(f, "{}", unit),
976            ConversionTarget::Money(unit) => write!(f, "{}", unit),
977            ConversionTarget::Percentage => write!(f, "percentage"),
978        }
979    }
980}
981
982impl fmt::Display for LemmaType {
983    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
984        match self {
985            LemmaType::Text => write!(f, "text"),
986            LemmaType::Number => write!(f, "number"),
987            LemmaType::Date => write!(f, "date"),
988            LemmaType::Boolean => write!(f, "boolean"),
989            LemmaType::Regex => write!(f, "regex"),
990            LemmaType::Percentage => write!(f, "percentage"),
991            LemmaType::Mass => write!(f, "mass"),
992            LemmaType::Length => write!(f, "length"),
993            LemmaType::Volume => write!(f, "volume"),
994            LemmaType::Duration => write!(f, "duration"),
995            LemmaType::Temperature => write!(f, "temperature"),
996            LemmaType::Power => write!(f, "power"),
997            LemmaType::Force => write!(f, "force"),
998            LemmaType::Pressure => write!(f, "pressure"),
999            LemmaType::Energy => write!(f, "energy"),
1000            LemmaType::Frequency => write!(f, "frequency"),
1001            LemmaType::Data => write!(f, "data"),
1002            LemmaType::Money => write!(f, "money"),
1003        }
1004    }
1005}
1006
1007impl fmt::Display for TypeAnnotation {
1008    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1009        match self {
1010            TypeAnnotation::LemmaType(lemma_type) => write!(f, "{}", lemma_type),
1011        }
1012    }
1013}
1014
1015impl fmt::Display for FactValue {
1016    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1017        match self {
1018            FactValue::Literal(lit) => write!(f, "{}", lit),
1019            FactValue::TypeAnnotation(type_ann) => write!(f, "[{}]", type_ann),
1020            FactValue::DocumentReference(doc_name) => write!(f, "doc {}", doc_name),
1021        }
1022    }
1023}
1024
1025impl fmt::Display for FactReference {
1026    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1027        write!(f, "{}", self.reference.join("."))
1028    }
1029}
1030
1031impl fmt::Display for ForeignFact {
1032    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1033        write!(f, "{}", self.reference.join("."))
1034    }
1035}
1036
1037impl fmt::Display for FactType {
1038    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1039        match self {
1040            FactType::Local(name) => write!(f, "{}", name),
1041            FactType::Foreign(foreign_ref) => write!(f, "{}", foreign_ref),
1042        }
1043    }
1044}
1045
1046impl fmt::Display for RuleReference {
1047    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1048        write!(f, "{}?", self.reference.join("."))
1049    }
1050}
1051
1052impl fmt::Display for ArithmeticOperation {
1053    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1054        match self {
1055            ArithmeticOperation::Add => write!(f, "+"),
1056            ArithmeticOperation::Subtract => write!(f, "-"),
1057            ArithmeticOperation::Multiply => write!(f, "*"),
1058            ArithmeticOperation::Divide => write!(f, "/"),
1059            ArithmeticOperation::Modulo => write!(f, "%"),
1060            ArithmeticOperation::Power => write!(f, "^"),
1061        }
1062    }
1063}
1064
1065impl fmt::Display for ComparisonOperator {
1066    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1067        match self {
1068            ComparisonOperator::GreaterThan => write!(f, ">"),
1069            ComparisonOperator::LessThan => write!(f, "<"),
1070            ComparisonOperator::GreaterThanOrEqual => write!(f, ">="),
1071            ComparisonOperator::LessThanOrEqual => write!(f, "<="),
1072            ComparisonOperator::Equal => write!(f, "=="),
1073            ComparisonOperator::NotEqual => write!(f, "!="),
1074            ComparisonOperator::Is => write!(f, "is"),
1075            ComparisonOperator::IsNot => write!(f, "is not"),
1076        }
1077    }
1078}
1079
1080impl fmt::Display for TimeValue {
1081    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1082        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
1083    }
1084}
1085
1086impl fmt::Display for TimezoneValue {
1087    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1088        if self.offset_hours == 0 && self.offset_minutes == 0 {
1089            write!(f, "Z")
1090        } else {
1091            let sign = if self.offset_hours >= 0 { "+" } else { "-" };
1092            let hours = self.offset_hours.abs();
1093            write!(f, "{}{:02}:{:02}", sign, hours, self.offset_minutes)
1094        }
1095    }
1096}
1097
1098impl fmt::Display for DateTimeValue {
1099    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1100        write!(
1101            f,
1102            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1103            self.year, self.month, self.day, self.hour, self.minute, self.second
1104        )?;
1105        if let Some(tz) = &self.timezone {
1106            write!(f, "{}", tz)?;
1107        }
1108        Ok(())
1109    }
1110}