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    /// Get the byte size of this literal value for resource limiting
266    pub fn byte_size(&self) -> usize {
267        match self {
268            LiteralValue::Text(s) | LiteralValue::Regex(s) => s.len(),
269            LiteralValue::Number(d) | LiteralValue::Percentage(d) => {
270                // Decimal internal representation size
271                std::mem::size_of_val(d)
272            }
273            LiteralValue::Boolean(_) => std::mem::size_of::<bool>(),
274            LiteralValue::Date(_) => std::mem::size_of::<DateTimeValue>(),
275            LiteralValue::Time(_) => std::mem::size_of::<TimeValue>(),
276            LiteralValue::Unit(_) => std::mem::size_of::<NumericUnit>(),
277        }
278    }
279
280    /// Convert a LiteralValue to its corresponding LemmaType
281    pub fn to_type(&self) -> LemmaType {
282        match self {
283            LiteralValue::Text(_) => LemmaType::Text,
284            LiteralValue::Number(_) => LemmaType::Number,
285            LiteralValue::Date(_) => LemmaType::Date,
286            LiteralValue::Time(_) => LemmaType::Date,
287            LiteralValue::Boolean(_) => LemmaType::Boolean,
288            LiteralValue::Percentage(_) => LemmaType::Percentage,
289            LiteralValue::Regex(_) => LemmaType::Regex,
290            LiteralValue::Unit(unit) => match unit {
291                NumericUnit::Mass(_, _) => LemmaType::Mass,
292                NumericUnit::Length(_, _) => LemmaType::Length,
293                NumericUnit::Volume(_, _) => LemmaType::Volume,
294                NumericUnit::Duration(_, _) => LemmaType::Duration,
295                NumericUnit::Temperature(_, _) => LemmaType::Temperature,
296                NumericUnit::Power(_, _) => LemmaType::Power,
297                NumericUnit::Force(_, _) => LemmaType::Force,
298                NumericUnit::Pressure(_, _) => LemmaType::Pressure,
299                NumericUnit::Energy(_, _) => LemmaType::Energy,
300                NumericUnit::Frequency(_, _) => LemmaType::Frequency,
301                NumericUnit::Data(_, _) => LemmaType::Data,
302                NumericUnit::Money(_, _) => LemmaType::Money,
303            },
304        }
305    }
306}
307
308/// A time value
309#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
310pub struct TimeValue {
311    pub hour: u8,
312    pub minute: u8,
313    pub second: u8,
314    pub timezone: Option<TimezoneValue>,
315}
316
317/// A timezone value
318#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
319pub struct TimezoneValue {
320    pub offset_hours: i8,
321    pub offset_minutes: u8,
322}
323
324/// A datetime value that preserves timezone information
325#[derive(Debug, Clone, PartialEq, Serialize)]
326pub struct DateTimeValue {
327    pub year: i32,
328    pub month: u32,
329    pub day: u32,
330    pub hour: u32,
331    pub minute: u32,
332    pub second: u32,
333    pub timezone: Option<TimezoneValue>,
334}
335
336/// Unit types for different physical quantities
337macro_rules! impl_unit_serialize {
338    ($($unit_type:ty),+) => {
339        $(
340            impl Serialize for $unit_type {
341                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
342                where
343                    S: serde::Serializer,
344                {
345                    serializer.serialize_str(&self.to_string())
346                }
347            }
348        )+
349    };
350}
351
352impl_unit_serialize!(
353    MassUnit,
354    LengthUnit,
355    VolumeUnit,
356    DurationUnit,
357    TemperatureUnit,
358    PowerUnit,
359    ForceUnit,
360    PressureUnit,
361    EnergyUnit,
362    FrequencyUnit,
363    DataUnit,
364    MoneyUnit
365);
366
367#[derive(Debug, Clone, PartialEq, Eq, Hash)]
368pub enum MassUnit {
369    Kilogram,
370    Gram,
371    Milligram,
372    Ton,
373    Pound,
374    Ounce,
375}
376
377#[derive(Debug, Clone, PartialEq, Eq, Hash)]
378pub enum LengthUnit {
379    Kilometer,
380    Mile,
381    NauticalMile,
382    Meter,
383    Decimeter,
384    Centimeter,
385    Millimeter,
386    Yard,
387    Foot,
388    Inch,
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Hash)]
392pub enum VolumeUnit {
393    CubicMeter,
394    CubicCentimeter,
395    Liter,
396    Deciliter,
397    Centiliter,
398    Milliliter,
399    Gallon,
400    Quart,
401    Pint,
402    FluidOunce,
403}
404
405#[derive(Debug, Clone, PartialEq, Eq, Hash)]
406pub enum DurationUnit {
407    Year,
408    Month,
409    Week,
410    Day,
411    Hour,
412    Minute,
413    Second,
414    Millisecond,
415    Microsecond,
416}
417
418#[derive(Debug, Clone, PartialEq, Eq, Hash)]
419pub enum TemperatureUnit {
420    Celsius,
421    Fahrenheit,
422    Kelvin,
423}
424
425#[derive(Debug, Clone, PartialEq, Eq, Hash)]
426pub enum PowerUnit {
427    Megawatt,
428    Kilowatt,
429    Watt,
430    Milliwatt,
431    Horsepower,
432}
433
434#[derive(Debug, Clone, PartialEq, Eq, Hash)]
435pub enum ForceUnit {
436    Newton,
437    Kilonewton,
438    Lbf,
439}
440
441#[derive(Debug, Clone, PartialEq, Eq, Hash)]
442pub enum PressureUnit {
443    Megapascal,
444    Kilopascal,
445    Pascal,
446    Atmosphere,
447    Bar,
448    Psi,
449    Torr,
450    Mmhg,
451}
452
453#[derive(Debug, Clone, PartialEq, Eq, Hash)]
454pub enum EnergyUnit {
455    Megajoule,
456    Kilojoule,
457    Joule,
458    Kilowatthour,
459    Watthour,
460    Kilocalorie,
461    Calorie,
462    Btu,
463}
464
465#[derive(Debug, Clone, PartialEq, Eq, Hash)]
466pub enum FrequencyUnit {
467    Hertz,
468    Kilohertz,
469    Megahertz,
470    Gigahertz,
471}
472
473#[derive(Debug, Clone, PartialEq, Eq, Hash)]
474pub enum DataUnit {
475    Petabyte,
476    Terabyte,
477    Gigabyte,
478    Megabyte,
479    Kilobyte,
480    Byte,
481    Tebibyte,
482    Gibibyte,
483    Mebibyte,
484    Kibibyte,
485}
486
487#[derive(Debug, Clone, PartialEq, Eq, Hash)]
488pub enum MoneyUnit {
489    Eur,
490    Usd,
491    Gbp,
492    Jpy,
493    Cny,
494    Chf,
495    Cad,
496    Aud,
497    Inr,
498}
499
500/// A unified type for all numeric units (physical quantities and money)
501///
502/// This provides consistent behavior for all unit types:
503/// - Comparisons always compare numeric values (ignoring units)
504/// - Same-unit arithmetic preserves the unit
505/// - Cross-unit arithmetic produces dimensionless numbers
506#[derive(Debug, Clone, PartialEq, Serialize)]
507pub enum NumericUnit {
508    Mass(Decimal, MassUnit),
509    Length(Decimal, LengthUnit),
510    Volume(Decimal, VolumeUnit),
511    Duration(Decimal, DurationUnit),
512    Temperature(Decimal, TemperatureUnit),
513    Power(Decimal, PowerUnit),
514    Force(Decimal, ForceUnit),
515    Pressure(Decimal, PressureUnit),
516    Energy(Decimal, EnergyUnit),
517    Frequency(Decimal, FrequencyUnit),
518    Data(Decimal, DataUnit),
519    Money(Decimal, MoneyUnit),
520}
521
522impl NumericUnit {
523    /// Extract the numeric value from any unit
524    pub fn value(&self) -> Decimal {
525        match self {
526            NumericUnit::Mass(v, _)
527            | NumericUnit::Length(v, _)
528            | NumericUnit::Volume(v, _)
529            | NumericUnit::Duration(v, _)
530            | NumericUnit::Temperature(v, _)
531            | NumericUnit::Power(v, _)
532            | NumericUnit::Force(v, _)
533            | NumericUnit::Pressure(v, _)
534            | NumericUnit::Energy(v, _)
535            | NumericUnit::Frequency(v, _)
536            | NumericUnit::Data(v, _)
537            | NumericUnit::Money(v, _) => *v,
538        }
539    }
540
541    /// Check if two units are the same category
542    pub fn same_category(&self, other: &NumericUnit) -> bool {
543        std::mem::discriminant(self) == std::mem::discriminant(other)
544    }
545
546    /// Create a new NumericUnit with the same unit type but different value
547    /// This is the key method that eliminates type enumeration in operations
548    pub fn with_value(&self, new_value: Decimal) -> NumericUnit {
549        match self {
550            NumericUnit::Mass(_, u) => NumericUnit::Mass(new_value, u.clone()),
551            NumericUnit::Length(_, u) => NumericUnit::Length(new_value, u.clone()),
552            NumericUnit::Volume(_, u) => NumericUnit::Volume(new_value, u.clone()),
553            NumericUnit::Duration(_, u) => NumericUnit::Duration(new_value, u.clone()),
554            NumericUnit::Temperature(_, u) => NumericUnit::Temperature(new_value, u.clone()),
555            NumericUnit::Power(_, u) => NumericUnit::Power(new_value, u.clone()),
556            NumericUnit::Force(_, u) => NumericUnit::Force(new_value, u.clone()),
557            NumericUnit::Pressure(_, u) => NumericUnit::Pressure(new_value, u.clone()),
558            NumericUnit::Energy(_, u) => NumericUnit::Energy(new_value, u.clone()),
559            NumericUnit::Frequency(_, u) => NumericUnit::Frequency(new_value, u.clone()),
560            NumericUnit::Data(_, u) => NumericUnit::Data(new_value, u.clone()),
561            NumericUnit::Money(_, u) => NumericUnit::Money(new_value, u.clone()),
562        }
563    }
564
565    /// Validate that two Money units have the same currency
566    /// Returns Ok for non-Money units or matching currencies
567    pub fn validate_same_currency(&self, other: &NumericUnit) -> Result<(), crate::LemmaError> {
568        if let (NumericUnit::Money(_, l_curr), NumericUnit::Money(_, r_curr)) = (self, other) {
569            if l_curr != r_curr {
570                return Err(crate::LemmaError::Engine(format!(
571                    "Cannot operate on different currencies: {:?} and {:?}",
572                    l_curr, r_curr
573                )));
574            }
575        }
576        Ok(())
577    }
578}
579
580impl fmt::Display for NumericUnit {
581    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
582        match self {
583            NumericUnit::Mass(v, u) => write!(f, "{} {}", v, u),
584            NumericUnit::Length(v, u) => write!(f, "{} {}", v, u),
585            NumericUnit::Volume(v, u) => write!(f, "{} {}", v, u),
586            NumericUnit::Duration(v, u) => write!(f, "{} {}", v, u),
587            NumericUnit::Temperature(v, u) => write!(f, "{} {}", v, u),
588            NumericUnit::Power(v, u) => write!(f, "{} {}", v, u),
589            NumericUnit::Force(v, u) => write!(f, "{} {}", v, u),
590            NumericUnit::Pressure(v, u) => write!(f, "{} {}", v, u),
591            NumericUnit::Energy(v, u) => write!(f, "{} {}", v, u),
592            NumericUnit::Frequency(v, u) => write!(f, "{} {}", v, u),
593            NumericUnit::Data(v, u) => write!(f, "{} {}", v, u),
594            NumericUnit::Money(v, u) => write!(f, "{} {}", v, u),
595        }
596    }
597}
598
599impl LemmaRule {
600    pub fn new(name: String, expression: Expression) -> Self {
601        Self {
602            name,
603            expression,
604            unless_clauses: Vec::new(),
605            span: None,
606        }
607    }
608
609    pub fn add_unless_clause(mut self, unless_clause: UnlessClause) -> Self {
610        self.unless_clauses.push(unless_clause);
611        self
612    }
613}
614
615impl LemmaFact {
616    pub fn new(fact_type: FactType, value: FactValue) -> Self {
617        Self {
618            fact_type,
619            value,
620            span: None,
621        }
622    }
623
624    pub fn with_span(mut self, span: Span) -> Self {
625        self.span = Some(span);
626        self
627    }
628}
629
630impl LemmaDoc {
631    pub fn new(name: String) -> Self {
632        Self {
633            name,
634            source: None,
635            start_line: 1,
636            commentary: None,
637            facts: Vec::new(),
638            rules: Vec::new(),
639        }
640    }
641
642    pub fn with_source(mut self, source: String) -> Self {
643        self.source = Some(source);
644        self
645    }
646
647    pub fn with_start_line(mut self, start_line: usize) -> Self {
648        self.start_line = start_line;
649        self
650    }
651
652    pub fn set_commentary(mut self, commentary: String) -> Self {
653        self.commentary = Some(commentary);
654        self
655    }
656
657    pub fn add_fact(mut self, fact: LemmaFact) -> Self {
658        self.facts.push(fact);
659        self
660    }
661
662    pub fn add_rule(mut self, rule: LemmaRule) -> Self {
663        self.rules.push(rule);
664        self
665    }
666
667    /// Get the expected type for a fact by path
668    /// Returns None if the fact is not found in this document or if the fact is a document reference
669    pub fn get_fact_type(&self, fact_path: &FactPath) -> Option<LemmaType> {
670        self.facts
671            .iter()
672            .find(|fact| {
673                let segments = fact_path.segments();
674                match &fact.fact_type {
675                    FactType::Local(name) => segments.len() == 1 && segments[0] == *name,
676                    FactType::Foreign(foreign_ref) => segments == foreign_ref.reference.as_slice(),
677                }
678            })
679            .and_then(|fact| match &fact.value {
680                FactValue::Literal(lit) => Some(lit.to_type()),
681                FactValue::TypeAnnotation(TypeAnnotation::LemmaType(lemma_type)) => {
682                    Some(lemma_type.clone())
683                }
684                FactValue::DocumentReference(_) => {
685                    // Document references don't have a single type
686                    // They import all facts from the referenced document
687                    None
688                }
689            })
690    }
691}
692
693impl fmt::Display for LemmaDoc {
694    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
695        write!(f, "doc {}", self.name)?;
696        writeln!(f)?;
697
698        if let Some(ref commentary) = self.commentary {
699            writeln!(f, "\"\"\"{}", commentary)?;
700            writeln!(f, "\"\"\"")?;
701        }
702
703        for fact in &self.facts {
704            write!(f, "{}", fact)?;
705        }
706
707        for rule in &self.rules {
708            write!(f, "{}", rule)?;
709        }
710
711        Ok(())
712    }
713}
714
715impl fmt::Display for LemmaFact {
716    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
717        writeln!(f, "fact {} = {}", self.fact_type, self.value)
718    }
719}
720
721impl fmt::Display for LemmaRule {
722    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
723        write!(f, "rule {} = {}", self.name, self.expression)?;
724
725        for unless_clause in &self.unless_clauses {
726            write!(
727                f,
728                " unless {} then {}",
729                unless_clause.condition, unless_clause.result
730            )?;
731        }
732
733        writeln!(f)?;
734        Ok(())
735    }
736}
737
738impl fmt::Display for Expression {
739    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
740        match &self.kind {
741            ExpressionKind::Literal(lit) => write!(f, "{}", lit),
742            ExpressionKind::FactReference(fact_ref) => write!(f, "{}", fact_ref),
743            ExpressionKind::RuleReference(rule_ref) => write!(f, "{}", rule_ref),
744            ExpressionKind::Arithmetic(left, op, right) => {
745                write!(f, "{} {} {}", left, op, right)
746            }
747            ExpressionKind::Comparison(left, op, right) => {
748                write!(f, "{} {} {}", left, op, right)
749            }
750            ExpressionKind::FactHasAnyValue(fact_ref) => {
751                write!(f, "have {}", fact_ref)
752            }
753            ExpressionKind::UnitConversion(value, target) => {
754                write!(f, "{} in {}", value, target)
755            }
756            ExpressionKind::LogicalNegation(expr, negation_type) => {
757                let prefix = match negation_type {
758                    NegationType::Not => "not",
759                    NegationType::HaveNot => "have not",
760                    NegationType::NotHave => "not have",
761                };
762                write!(f, "{} {}", prefix, expr)
763            }
764            ExpressionKind::LogicalAnd(left, right) => {
765                write!(f, "{} and {}", left, right)
766            }
767            ExpressionKind::LogicalOr(left, right) => {
768                write!(f, "{} or {}", left, right)
769            }
770            ExpressionKind::MathematicalOperator(op, operand) => {
771                let op_name = match op {
772                    MathematicalOperator::Sqrt => "sqrt",
773                    MathematicalOperator::Sin => "sin",
774                    MathematicalOperator::Cos => "cos",
775                    MathematicalOperator::Tan => "tan",
776                    MathematicalOperator::Asin => "asin",
777                    MathematicalOperator::Acos => "acos",
778                    MathematicalOperator::Atan => "atan",
779                    MathematicalOperator::Log => "log",
780                    MathematicalOperator::Exp => "exp",
781                };
782                write!(f, "{} {}", op_name, operand)
783            }
784            ExpressionKind::Veto(veto) => match &veto.message {
785                Some(msg) => write!(f, "veto \"{}\"", msg),
786                None => write!(f, "veto"),
787            },
788        }
789    }
790}
791
792impl fmt::Display for LiteralValue {
793    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
794        match self {
795            LiteralValue::Number(n) => write!(f, "{}", n),
796            LiteralValue::Text(s) => write!(f, "\"{}\"", s),
797            LiteralValue::Date(dt) => write!(f, "{}", dt),
798            LiteralValue::Boolean(b) => write!(f, "{}", b),
799            LiteralValue::Percentage(p) => write!(f, "{}%", p),
800            LiteralValue::Unit(unit) => write!(f, "{}", unit),
801            LiteralValue::Regex(s) => write!(f, "{}", s),
802            LiteralValue::Time(time) => {
803                write!(f, "time({}, {}, {})", time.hour, time.minute, time.second)
804            }
805        }
806    }
807}
808
809impl LiteralValue {
810    /// Provides a descriptive string for error messages and debugging
811    pub fn describe(&self) -> String {
812        match self {
813            LiteralValue::Text(s) => format!("text value \"{}\"", s),
814            LiteralValue::Number(n) => format!("number {}", n),
815            LiteralValue::Boolean(b) => format!("boolean {}", b),
816            LiteralValue::Percentage(p) => format!("percentage {}%", p),
817            LiteralValue::Date(_) => "date value".to_string(),
818            LiteralValue::Unit(unit) => {
819                format!(
820                    "{} value {}",
821                    LiteralValue::Unit(unit.clone()).to_type(),
822                    unit
823                )
824            }
825            LiteralValue::Regex(s) => format!("regex value {}", s),
826            LiteralValue::Time(time) => {
827                format!("time value {}:{}:{}", time.hour, time.minute, time.second)
828            }
829        }
830    }
831}
832
833impl fmt::Display for MassUnit {
834    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835        match self {
836            MassUnit::Kilogram => write!(f, "kilogram"),
837            MassUnit::Gram => write!(f, "gram"),
838            MassUnit::Milligram => write!(f, "milligram"),
839            MassUnit::Ton => write!(f, "ton"),
840            MassUnit::Pound => write!(f, "pound"),
841            MassUnit::Ounce => write!(f, "ounce"),
842        }
843    }
844}
845
846impl fmt::Display for LengthUnit {
847    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
848        match self {
849            LengthUnit::Kilometer => write!(f, "kilometer"),
850            LengthUnit::Mile => write!(f, "mile"),
851            LengthUnit::NauticalMile => write!(f, "nautical_mile"),
852            LengthUnit::Meter => write!(f, "meter"),
853            LengthUnit::Decimeter => write!(f, "decimeter"),
854            LengthUnit::Centimeter => write!(f, "centimeter"),
855            LengthUnit::Millimeter => write!(f, "millimeter"),
856            LengthUnit::Yard => write!(f, "yard"),
857            LengthUnit::Foot => write!(f, "foot"),
858            LengthUnit::Inch => write!(f, "inch"),
859        }
860    }
861}
862
863impl fmt::Display for VolumeUnit {
864    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
865        match self {
866            VolumeUnit::CubicMeter => write!(f, "cubic_meter"),
867            VolumeUnit::CubicCentimeter => write!(f, "cubic_centimeter"),
868            VolumeUnit::Liter => write!(f, "liter"),
869            VolumeUnit::Deciliter => write!(f, "deciliter"),
870            VolumeUnit::Centiliter => write!(f, "centiliter"),
871            VolumeUnit::Milliliter => write!(f, "milliliter"),
872            VolumeUnit::Gallon => write!(f, "gallon"),
873            VolumeUnit::Quart => write!(f, "quart"),
874            VolumeUnit::Pint => write!(f, "pint"),
875            VolumeUnit::FluidOunce => write!(f, "fluid_ounce"),
876        }
877    }
878}
879
880impl fmt::Display for DurationUnit {
881    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
882        match self {
883            DurationUnit::Year => write!(f, "year"),
884            DurationUnit::Month => write!(f, "month"),
885            DurationUnit::Week => write!(f, "week"),
886            DurationUnit::Day => write!(f, "day"),
887            DurationUnit::Hour => write!(f, "hour"),
888            DurationUnit::Minute => write!(f, "minute"),
889            DurationUnit::Second => write!(f, "second"),
890            DurationUnit::Millisecond => write!(f, "millisecond"),
891            DurationUnit::Microsecond => write!(f, "microsecond"),
892        }
893    }
894}
895
896impl fmt::Display for TemperatureUnit {
897    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
898        match self {
899            TemperatureUnit::Celsius => write!(f, "celsius"),
900            TemperatureUnit::Fahrenheit => write!(f, "fahrenheit"),
901            TemperatureUnit::Kelvin => write!(f, "kelvin"),
902        }
903    }
904}
905
906impl fmt::Display for PowerUnit {
907    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
908        match self {
909            PowerUnit::Megawatt => write!(f, "megawatt"),
910            PowerUnit::Kilowatt => write!(f, "kilowatt"),
911            PowerUnit::Watt => write!(f, "watt"),
912            PowerUnit::Milliwatt => write!(f, "milliwatt"),
913            PowerUnit::Horsepower => write!(f, "horsepower"),
914        }
915    }
916}
917
918impl fmt::Display for ForceUnit {
919    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
920        match self {
921            ForceUnit::Newton => write!(f, "newton"),
922            ForceUnit::Kilonewton => write!(f, "kilonewton"),
923            ForceUnit::Lbf => write!(f, "lbf"),
924        }
925    }
926}
927
928impl fmt::Display for PressureUnit {
929    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
930        match self {
931            PressureUnit::Megapascal => write!(f, "megapascal"),
932            PressureUnit::Kilopascal => write!(f, "kilopascal"),
933            PressureUnit::Pascal => write!(f, "pascal"),
934            PressureUnit::Atmosphere => write!(f, "atmosphere"),
935            PressureUnit::Bar => write!(f, "bar"),
936            PressureUnit::Psi => write!(f, "psi"),
937            PressureUnit::Torr => write!(f, "torr"),
938            PressureUnit::Mmhg => write!(f, "mmhg"),
939        }
940    }
941}
942
943impl fmt::Display for EnergyUnit {
944    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
945        match self {
946            EnergyUnit::Megajoule => write!(f, "megajoule"),
947            EnergyUnit::Kilojoule => write!(f, "kilojoule"),
948            EnergyUnit::Joule => write!(f, "joule"),
949            EnergyUnit::Kilowatthour => write!(f, "kilowatthour"),
950            EnergyUnit::Watthour => write!(f, "watthour"),
951            EnergyUnit::Kilocalorie => write!(f, "kilocalorie"),
952            EnergyUnit::Calorie => write!(f, "calorie"),
953            EnergyUnit::Btu => write!(f, "btu"),
954        }
955    }
956}
957
958impl fmt::Display for FrequencyUnit {
959    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
960        match self {
961            FrequencyUnit::Hertz => write!(f, "hertz"),
962            FrequencyUnit::Kilohertz => write!(f, "kilohertz"),
963            FrequencyUnit::Megahertz => write!(f, "megahertz"),
964            FrequencyUnit::Gigahertz => write!(f, "gigahertz"),
965        }
966    }
967}
968
969impl fmt::Display for DataUnit {
970    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
971        match self {
972            DataUnit::Petabyte => write!(f, "petabyte"),
973            DataUnit::Terabyte => write!(f, "terabyte"),
974            DataUnit::Gigabyte => write!(f, "gigabyte"),
975            DataUnit::Megabyte => write!(f, "megabyte"),
976            DataUnit::Kilobyte => write!(f, "kilobyte"),
977            DataUnit::Byte => write!(f, "byte"),
978            DataUnit::Tebibyte => write!(f, "tebibyte"),
979            DataUnit::Gibibyte => write!(f, "gibibyte"),
980            DataUnit::Mebibyte => write!(f, "mebibyte"),
981            DataUnit::Kibibyte => write!(f, "kibibyte"),
982        }
983    }
984}
985
986impl fmt::Display for MoneyUnit {
987    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
988        match self {
989            MoneyUnit::Eur => write!(f, "EUR"),
990            MoneyUnit::Usd => write!(f, "USD"),
991            MoneyUnit::Gbp => write!(f, "GBP"),
992            MoneyUnit::Jpy => write!(f, "JPY"),
993            MoneyUnit::Cny => write!(f, "CNY"),
994            MoneyUnit::Chf => write!(f, "CHF"),
995            MoneyUnit::Cad => write!(f, "CAD"),
996            MoneyUnit::Aud => write!(f, "AUD"),
997            MoneyUnit::Inr => write!(f, "INR"),
998        }
999    }
1000}
1001
1002impl fmt::Display for ConversionTarget {
1003    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1004        match self {
1005            ConversionTarget::Mass(unit) => write!(f, "{}", unit),
1006            ConversionTarget::Length(unit) => write!(f, "{}", unit),
1007            ConversionTarget::Volume(unit) => write!(f, "{}", unit),
1008            ConversionTarget::Duration(unit) => write!(f, "{}", unit),
1009            ConversionTarget::Temperature(unit) => write!(f, "{}", unit),
1010            ConversionTarget::Power(unit) => write!(f, "{}", unit),
1011            ConversionTarget::Force(unit) => write!(f, "{}", unit),
1012            ConversionTarget::Pressure(unit) => write!(f, "{}", unit),
1013            ConversionTarget::Energy(unit) => write!(f, "{}", unit),
1014            ConversionTarget::Frequency(unit) => write!(f, "{}", unit),
1015            ConversionTarget::Data(unit) => write!(f, "{}", unit),
1016            ConversionTarget::Money(unit) => write!(f, "{}", unit),
1017            ConversionTarget::Percentage => write!(f, "percentage"),
1018        }
1019    }
1020}
1021
1022impl fmt::Display for LemmaType {
1023    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1024        match self {
1025            LemmaType::Text => write!(f, "text"),
1026            LemmaType::Number => write!(f, "number"),
1027            LemmaType::Date => write!(f, "date"),
1028            LemmaType::Boolean => write!(f, "boolean"),
1029            LemmaType::Regex => write!(f, "regex"),
1030            LemmaType::Percentage => write!(f, "percentage"),
1031            LemmaType::Mass => write!(f, "mass"),
1032            LemmaType::Length => write!(f, "length"),
1033            LemmaType::Volume => write!(f, "volume"),
1034            LemmaType::Duration => write!(f, "duration"),
1035            LemmaType::Temperature => write!(f, "temperature"),
1036            LemmaType::Power => write!(f, "power"),
1037            LemmaType::Force => write!(f, "force"),
1038            LemmaType::Pressure => write!(f, "pressure"),
1039            LemmaType::Energy => write!(f, "energy"),
1040            LemmaType::Frequency => write!(f, "frequency"),
1041            LemmaType::Data => write!(f, "data"),
1042            LemmaType::Money => write!(f, "money"),
1043        }
1044    }
1045}
1046
1047impl fmt::Display for TypeAnnotation {
1048    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1049        match self {
1050            TypeAnnotation::LemmaType(lemma_type) => write!(f, "{}", lemma_type),
1051        }
1052    }
1053}
1054
1055impl LemmaType {
1056    /// Get an example value string for this type, suitable for UI help text
1057    pub fn example_value(&self) -> &'static str {
1058        match self {
1059            LemmaType::Text => "\"hello world\"",
1060            LemmaType::Number => "3.14",
1061            LemmaType::Boolean => "true",
1062            LemmaType::Money => "99.99 EUR",
1063            LemmaType::Date => "2023-12-25T14:30:00Z",
1064            LemmaType::Duration => "90 minutes",
1065            LemmaType::Mass => "5.5 kilograms",
1066            LemmaType::Length => "10 meters",
1067            LemmaType::Percentage => "50%",
1068            LemmaType::Temperature => "25 celsius",
1069            LemmaType::Regex => "/pattern/",
1070            LemmaType::Volume => "1.2 liter",
1071            LemmaType::Power => "100 watts",
1072            LemmaType::Energy => "1000 joules",
1073            LemmaType::Force => "10 newtons",
1074            LemmaType::Pressure => "101325 pascals",
1075            LemmaType::Frequency => "880 hertz",
1076            LemmaType::Data => "800 megabytes",
1077        }
1078    }
1079}
1080
1081impl TypeAnnotation {
1082    /// Get an example value string for this type annotation, suitable for UI help text
1083    pub fn example_value(&self) -> &'static str {
1084        match self {
1085            TypeAnnotation::LemmaType(lemma_type) => lemma_type.example_value(),
1086        }
1087    }
1088}
1089
1090impl fmt::Display for FactValue {
1091    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1092        match self {
1093            FactValue::Literal(lit) => write!(f, "{}", lit),
1094            FactValue::TypeAnnotation(type_ann) => write!(f, "[{}]", type_ann),
1095            FactValue::DocumentReference(doc_name) => write!(f, "doc {}", doc_name),
1096        }
1097    }
1098}
1099
1100impl fmt::Display for FactReference {
1101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1102        write!(f, "{}", self.reference.join("."))
1103    }
1104}
1105
1106impl fmt::Display for ForeignFact {
1107    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1108        write!(f, "{}", self.reference.join("."))
1109    }
1110}
1111
1112impl fmt::Display for FactType {
1113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1114        match self {
1115            FactType::Local(name) => write!(f, "{}", name),
1116            FactType::Foreign(foreign_ref) => write!(f, "{}", foreign_ref),
1117        }
1118    }
1119}
1120
1121impl fmt::Display for RuleReference {
1122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1123        write!(f, "{}?", self.reference.join("."))
1124    }
1125}
1126
1127impl fmt::Display for ArithmeticOperation {
1128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1129        match self {
1130            ArithmeticOperation::Add => write!(f, "+"),
1131            ArithmeticOperation::Subtract => write!(f, "-"),
1132            ArithmeticOperation::Multiply => write!(f, "*"),
1133            ArithmeticOperation::Divide => write!(f, "/"),
1134            ArithmeticOperation::Modulo => write!(f, "%"),
1135            ArithmeticOperation::Power => write!(f, "^"),
1136        }
1137    }
1138}
1139
1140impl fmt::Display for ComparisonOperator {
1141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1142        match self {
1143            ComparisonOperator::GreaterThan => write!(f, ">"),
1144            ComparisonOperator::LessThan => write!(f, "<"),
1145            ComparisonOperator::GreaterThanOrEqual => write!(f, ">="),
1146            ComparisonOperator::LessThanOrEqual => write!(f, "<="),
1147            ComparisonOperator::Equal => write!(f, "=="),
1148            ComparisonOperator::NotEqual => write!(f, "!="),
1149            ComparisonOperator::Is => write!(f, "is"),
1150            ComparisonOperator::IsNot => write!(f, "is not"),
1151        }
1152    }
1153}
1154
1155impl fmt::Display for TimeValue {
1156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1157        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
1158    }
1159}
1160
1161impl fmt::Display for TimezoneValue {
1162    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1163        if self.offset_hours == 0 && self.offset_minutes == 0 {
1164            write!(f, "Z")
1165        } else {
1166            let sign = if self.offset_hours >= 0 { "+" } else { "-" };
1167            let hours = self.offset_hours.abs();
1168            write!(f, "{}{:02}:{:02}", sign, hours, self.offset_minutes)
1169        }
1170    }
1171}
1172
1173impl fmt::Display for DateTimeValue {
1174    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1175        write!(
1176            f,
1177            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1178            self.year, self.month, self.day, self.hour, self.minute, self.second
1179        )?;
1180        if let Some(tz) = &self.timezone {
1181            write!(f, "{}", tz)?;
1182        }
1183        Ok(())
1184    }
1185}
1186
1187/// A structured path to a fact, avoiding string allocations
1188///
1189/// Instead of storing facts as "base.base.price" strings, we store them
1190/// as structured paths ["base", "base", "price"]. This is more efficient
1191/// and semantically clearer.
1192#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1193pub struct FactPath {
1194    segments: Vec<String>,
1195}
1196
1197impl FactPath {
1198    /// Create a new fact path from a vector of segments
1199    pub fn new(segments: Vec<String>) -> Self {
1200        Self { segments }
1201    }
1202
1203    /// Create a fact path from a slice of segments
1204    pub fn from_slice(segments: &[String]) -> Self {
1205        Self {
1206            segments: segments.to_vec(),
1207        }
1208    }
1209
1210    /// Create a fact path with a prefix prepended
1211    pub fn with_prefix(&self, prefix: &[String]) -> Self {
1212        let mut new_segments = prefix.to_vec();
1213        new_segments.extend_from_slice(&self.segments);
1214        Self {
1215            segments: new_segments,
1216        }
1217    }
1218
1219    /// Get the segments as a slice
1220    pub fn segments(&self) -> &[String] {
1221        &self.segments
1222    }
1223
1224    /// Get the segments as a mutable slice
1225    pub fn segments_mut(&mut self) -> &mut Vec<String> {
1226        &mut self.segments
1227    }
1228
1229    /// Check if the path is empty
1230    pub fn is_empty(&self) -> bool {
1231        self.segments.is_empty()
1232    }
1233
1234    /// Get the length of the path
1235    pub fn len(&self) -> usize {
1236        self.segments.len()
1237    }
1238}
1239
1240impl std::fmt::Display for FactPath {
1241    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1242        write!(f, "{}", self.segments.join("."))
1243    }
1244}
1245
1246impl From<Vec<String>> for FactPath {
1247    fn from(segments: Vec<String>) -> Self {
1248        Self::new(segments)
1249    }
1250}
1251
1252impl From<&[String]> for FactPath {
1253    fn from(segments: &[String]) -> Self {
1254        Self::from_slice(segments)
1255    }
1256}
1257
1258/// A segment in a rule path representing one document traversal
1259#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1260pub struct RulePathSegment {
1261    pub fact: String,
1262    pub doc: String,
1263}
1264
1265/// Uniquely identifies a rule by tracking the complete fact traversal path
1266#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1267pub struct RulePath {
1268    pub rule: String,
1269    pub segments: Vec<RulePathSegment>,
1270}
1271
1272impl RulePath {
1273    pub fn from_reference(
1274        reference: &[String],
1275        current_doc: &LemmaDoc,
1276        all_documents: &std::collections::HashMap<String, LemmaDoc>,
1277    ) -> Result<Self, crate::LemmaError> {
1278        let mut doc = current_doc;
1279        let mut segments = Vec::new();
1280
1281        for fact_name in &reference[..reference.len() - 1] {
1282            let fact = doc
1283                .facts
1284                .iter()
1285                .find(|f| matches!(&f.fact_type, FactType::Local(name) if name == fact_name))
1286                .ok_or_else(|| {
1287                    crate::LemmaError::Engine(format!(
1288                        "Fact {} not found in document {}",
1289                        fact_name, doc.name
1290                    ))
1291                })?;
1292
1293            let target_doc_name = match &fact.value {
1294                FactValue::DocumentReference(name) => name.clone(),
1295                _ => {
1296                    return Err(crate::LemmaError::Engine(format!(
1297                        "Fact {} is not a document reference",
1298                        fact_name
1299                    )))
1300                }
1301            };
1302
1303            segments.push(RulePathSegment {
1304                fact: fact_name.clone(),
1305                doc: target_doc_name.clone(),
1306            });
1307
1308            doc = all_documents.get(&target_doc_name).ok_or_else(|| {
1309                crate::LemmaError::Engine(format!("Document {} not found", target_doc_name))
1310            })?;
1311        }
1312
1313        // Get the rule name (last element of reference)
1314        let rule_name = reference.last().ok_or_else(|| {
1315            crate::LemmaError::Engine("Rule reference cannot be empty".to_string())
1316        })?;
1317
1318        Ok(RulePath {
1319            rule: rule_name.clone(),
1320            segments,
1321        })
1322    }
1323
1324    pub fn target_doc<'a>(&'a self, main_doc: &'a str) -> &'a str {
1325        self.segments
1326            .last()
1327            .map(|s| s.doc.as_str())
1328            .unwrap_or(main_doc)
1329    }
1330}
1331
1332impl fmt::Display for RulePath {
1333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1334        for seg in &self.segments {
1335            write!(f, "{}.", seg.fact)?;
1336        }
1337        write!(f, "{}", self.rule)
1338    }
1339}