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