Skip to main content

lemma/
semantic.rs

1use crate::error::LemmaError;
2use crate::parsing::ast::Span;
3use crate::parsing::source::Source;
4use chrono::{Datelike, Timelike};
5use rust_decimal::Decimal;
6use serde::Serialize;
7use std::fmt;
8use std::hash::{Hash, Hasher};
9use std::str::FromStr;
10use std::sync::{Arc, OnceLock};
11
12/// A Lemma document containing facts and rules
13#[derive(Debug, Clone, PartialEq)]
14pub struct LemmaDoc {
15    pub name: String,
16    pub attribute: Option<String>,
17    pub start_line: usize,
18    pub commentary: Option<String>,
19    pub types: Vec<TypeDef>,
20    pub facts: Vec<LemmaFact>,
21    pub rules: Vec<LemmaRule>,
22}
23
24#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
25pub struct LemmaFact {
26    pub reference: FactReference,
27    pub value: FactValue,
28    pub source_location: Option<Source>,
29}
30
31/// An unless clause that provides an alternative result
32///
33/// Unless clauses are evaluated in order, and the last matching condition wins.
34/// This matches natural language: "X unless A then Y, unless B then Z" - if both
35/// A and B are true, Z is returned (the last match).
36#[derive(Debug, Clone, PartialEq, serde::Serialize)]
37pub struct UnlessClause {
38    pub condition: Expression,
39    pub result: Expression,
40    pub source_location: Option<Source>,
41}
42
43/// A rule with a single expression and optional unless clauses
44#[derive(Debug, Clone, PartialEq, serde::Serialize)]
45pub struct LemmaRule {
46    pub name: String,
47    pub expression: Expression,
48    pub unless_clauses: Vec<UnlessClause>,
49    pub source_location: Option<Source>,
50}
51
52/// An expression that can be evaluated, with source location
53///
54/// Expressions use semantic equality and hashing - two expressions with the same
55/// structure (kind) are equal/hash-equal regardless of source location.
56#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
57pub struct Expression {
58    pub kind: ExpressionKind,
59    pub source_location: Option<Source>,
60}
61
62impl Expression {
63    /// Create a new expression with kind and source location
64    #[must_use]
65    pub fn new(kind: ExpressionKind, source_location: Option<Source>) -> Self {
66        Self {
67            kind,
68            source_location,
69        }
70    }
71
72    /// Get the source text for this expression from the given sources map
73    ///
74    /// Returns `None` if the expression has no source location or the source is not found.
75    pub fn get_source_text(
76        &self,
77        sources: &std::collections::HashMap<String, String>,
78    ) -> Option<String> {
79        self.source_location.as_ref().and_then(|loc| {
80            sources
81                .get(&loc.attribute)
82                .and_then(|source| loc.extract_text(source))
83        })
84    }
85
86    /// Collect all FactPath references from this expression tree.
87    pub fn collect_fact_paths(&self, facts: &mut std::collections::HashSet<FactPath>) {
88        match &self.kind {
89            ExpressionKind::FactPath(fp) => {
90                facts.insert(fp.clone());
91            }
92            ExpressionKind::LogicalAnd(left, right)
93            | ExpressionKind::LogicalOr(left, right)
94            | ExpressionKind::Arithmetic(left, _, right)
95            | ExpressionKind::Comparison(left, _, right) => {
96                left.collect_fact_paths(facts);
97                right.collect_fact_paths(facts);
98            }
99            ExpressionKind::UnitConversion(inner, _)
100            | ExpressionKind::LogicalNegation(inner, _)
101            | ExpressionKind::MathematicalComputation(_, inner) => {
102                inner.collect_fact_paths(facts);
103            }
104            ExpressionKind::Literal(_)
105            | ExpressionKind::Reference(_)
106            | ExpressionKind::UnresolvedUnitLiteral(_, _)
107            | ExpressionKind::FactReference(_)
108            | ExpressionKind::RuleReference(_)
109            | ExpressionKind::Veto(_)
110            | ExpressionKind::RulePath(_) => {}
111        }
112    }
113
114    /// Compute semantic hash - hashes the expression structure, ignoring source location
115    fn semantic_hash<H: Hasher>(&self, state: &mut H) {
116        self.kind.semantic_hash(state);
117    }
118}
119
120/// Semantic equality - compares expressions by structure only, ignoring source location
121impl PartialEq for Expression {
122    fn eq(&self, other: &Self) -> bool {
123        self.kind == other.kind
124    }
125}
126
127impl Eq for Expression {}
128
129/// Semantic hashing - hashes expression structure only, ignoring source location
130impl Hash for Expression {
131    fn hash<H: Hasher>(&self, state: &mut H) {
132        self.semantic_hash(state);
133    }
134}
135
136/// The kind/type of expression
137#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum ExpressionKind {
140    Literal(LiteralValue),
141    /// Unresolved reference from parser (resolved during planning)
142    Reference(Reference),
143    /// Unresolved unit literal from parser (resolved during planning)
144    /// Contains (number, unit_name) - the unit name will be resolved to its type during semantic analysis
145    UnresolvedUnitLiteral(Decimal, String),
146    /// Resolved fact reference (converted from Reference during planning)
147    FactReference(FactReference),
148    RuleReference(RuleReference),
149    LogicalAnd(Arc<Expression>, Arc<Expression>),
150    LogicalOr(Arc<Expression>, Arc<Expression>),
151    Arithmetic(Arc<Expression>, ArithmeticComputation, Arc<Expression>),
152    Comparison(Arc<Expression>, ComparisonComputation, Arc<Expression>),
153    UnitConversion(Arc<Expression>, ConversionTarget),
154    LogicalNegation(Arc<Expression>, NegationType),
155    MathematicalComputation(MathematicalComputation, Arc<Expression>),
156    Veto(VetoExpression),
157    /// Resolved fact path (used after planning, converted from FactReference)
158    FactPath(FactPath),
159    /// Resolved rule path (used after planning, converted from RuleReference)
160    RulePath(RulePath),
161}
162
163impl ExpressionKind {
164    /// Compute semantic hash for expression kinds
165    fn semantic_hash<H: Hasher>(&self, state: &mut H) {
166        // Hash discriminant first
167        std::mem::discriminant(self).hash(state);
168
169        match self {
170            ExpressionKind::Literal(lit) => lit.semantic_hash(state),
171            ExpressionKind::Reference(r) => r.hash(state),
172            ExpressionKind::UnresolvedUnitLiteral(_, _) => {
173                unreachable!("UnresolvedUnitLiteral found during hashing - this indicates a bug: unresolved units should be resolved during planning");
174            }
175            ExpressionKind::FactReference(fr) => fr.hash(state),
176            ExpressionKind::RuleReference(rr) => rr.hash(state),
177            ExpressionKind::LogicalAnd(left, right) | ExpressionKind::LogicalOr(left, right) => {
178                left.semantic_hash(state);
179                right.semantic_hash(state);
180            }
181            ExpressionKind::Arithmetic(left, op, right) => {
182                left.semantic_hash(state);
183                op.hash(state);
184                right.semantic_hash(state);
185            }
186            ExpressionKind::Comparison(left, op, right) => {
187                left.semantic_hash(state);
188                op.hash(state);
189                right.semantic_hash(state);
190            }
191            ExpressionKind::UnitConversion(expr, target) => {
192                expr.semantic_hash(state);
193                target.hash(state);
194            }
195            ExpressionKind::LogicalNegation(expr, neg_type) => {
196                expr.semantic_hash(state);
197                neg_type.hash(state);
198            }
199            ExpressionKind::MathematicalComputation(op, expr) => {
200                op.hash(state);
201                expr.semantic_hash(state);
202            }
203            ExpressionKind::Veto(veto) => veto.semantic_hash(state),
204            ExpressionKind::FactPath(fp) => fp.hash(state),
205            ExpressionKind::RulePath(rp) => rp.hash(state),
206        }
207    }
208}
209
210/// Unresolved reference from parser
211///
212/// During parsing, identifiers are captured as References.
213/// During planning, they are resolved to FactReference.
214/// Examples:
215/// - Local reference "age": segments=[], name="age"
216/// - Cross-document "employee.salary": segments=["employee"], name="salary"
217#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
218pub struct Reference {
219    pub segments: Vec<String>,
220    pub name: String,
221}
222
223/// Reference to a fact (resolved from Reference during planning)
224///
225/// Fact references use dot notation to traverse documents.
226/// Examples:
227/// - Local fact "age": segments=[], fact="age"
228/// - Cross-document "employee.salary": segments=["employee"], fact="salary"
229#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
230pub struct FactReference {
231    pub segments: Vec<String>,
232    pub fact: String,
233}
234
235/// Reference to a rule
236///
237/// Rule references use a question mark suffix to distinguish them from fact references.
238/// Examples:
239/// - Local rule "has_license?": segments=[], rule="has_license"
240/// - Cross-document "employee.is_eligible?": segments=["employee"], rule="is_eligible"
241#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
242pub struct RuleReference {
243    pub segments: Vec<String>,
244    pub rule: String,
245}
246
247/// A single segment in a path traversal
248///
249/// Used in both FactPath and RulePath to represent document traversal.
250/// Each segment contains a fact name that points to a document.
251#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
252pub struct PathSegment {
253    /// Fact name at this segment
254    pub fact: String,
255
256    /// Document name this fact points to
257    pub doc: String,
258}
259
260/// A resolved path to a fact, with document traversal segments
261///
262/// Used after planning to represent fully resolved fact references.
263/// Public because used in ExecutionPlan and evaluation.
264#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
265pub struct FactPath {
266    /// Path segments: each segment is a fact name that points to a document
267    pub segments: Vec<PathSegment>,
268
269    /// Final fact name
270    pub fact: String,
271}
272
273impl FactPath {
274    /// Returns true if this is a local fact (no document traversal)
275    #[must_use]
276    pub fn is_local(&self) -> bool {
277        self.segments.is_empty()
278    }
279
280    /// Create a new FactPath from segments and fact name
281    #[must_use]
282    pub fn new(segments: Vec<PathSegment>, fact: String) -> Self {
283        Self { segments, fact }
284    }
285
286    /// Create a local fact path (no document traversal)
287    #[must_use]
288    pub fn local(fact: String) -> Self {
289        Self {
290            segments: Vec::new(),
291            fact,
292        }
293    }
294
295    /// Create a FactPath from a full path of strings
296    ///
297    /// The last element becomes the fact name, all others become segments.
298    /// Segment doc fields are left empty since we only have fact names.
299    /// This is for backward compatibility with tests.
300    #[must_use]
301    pub fn from_path(mut path: Vec<String>) -> Self {
302        if path.is_empty() {
303            return Self {
304                segments: Vec::new(),
305                fact: String::new(),
306            };
307        }
308        let fact = path.pop().unwrap_or_default();
309        let segments = path
310            .into_iter()
311            .map(|fact_name| PathSegment {
312                fact: fact_name,
313                doc: String::new(),
314            })
315            .collect();
316        Self { segments, fact }
317    }
318
319    /// Get all path segments as fact names including the final fact name
320    #[must_use]
321    pub fn full_path(&self) -> Vec<String> {
322        let mut path: Vec<String> = self.segments.iter().map(|s| s.fact.clone()).collect();
323        path.push(self.fact.clone());
324        path
325    }
326}
327
328/// A resolved path to a rule, with document traversal segments
329///
330/// Used after planning to represent fully resolved rule references.
331/// Public because used in ExecutionPlan and evaluation.
332#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
333pub struct RulePath {
334    /// Path segments: each segment is a fact name that points to a document
335    pub segments: Vec<PathSegment>,
336
337    /// Final rule name
338    pub rule: String,
339}
340
341impl RulePath {
342    /// Returns true if this is a local rule (no document traversal)
343    #[must_use]
344    pub fn is_local(&self) -> bool {
345        self.segments.is_empty()
346    }
347
348    /// Create a local rule path (no document traversal)
349    #[must_use]
350    pub fn local(rule: String) -> Self {
351        Self {
352            segments: Vec::new(),
353            rule,
354        }
355    }
356}
357
358impl RuleReference {
359    /// Create from a full path (last element becomes rule)
360    pub fn from_path(mut full_path: Vec<String>) -> Self {
361        let rule = full_path.pop().unwrap_or_default();
362        Self {
363            segments: full_path,
364            rule,
365        }
366    }
367
368    /// Returns true if this is a local rule reference (no path segments)
369    #[must_use]
370    pub fn is_local(&self) -> bool {
371        self.segments.is_empty()
372    }
373
374    /// Get all path segments including the rule name
375    #[must_use]
376    pub fn full_path(&self) -> Vec<String> {
377        let mut path = self.segments.clone();
378        path.push(self.rule.clone());
379        path
380    }
381}
382
383/// Arithmetic computations
384#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
385#[serde(rename_all = "snake_case")]
386pub enum ArithmeticComputation {
387    Add,
388    Subtract,
389    Multiply,
390    Divide,
391    Modulo,
392    Power,
393}
394
395impl ArithmeticComputation {
396    /// Returns a human-readable name for the computation
397    #[must_use]
398    pub fn name(&self) -> &'static str {
399        match self {
400            ArithmeticComputation::Add => "addition",
401            ArithmeticComputation::Subtract => "subtraction",
402            ArithmeticComputation::Multiply => "multiplication",
403            ArithmeticComputation::Divide => "division",
404            ArithmeticComputation::Modulo => "modulo",
405            ArithmeticComputation::Power => "exponentiation",
406        }
407    }
408
409    /// Returns the operator symbol
410    #[must_use]
411    pub fn symbol(&self) -> &'static str {
412        match self {
413            ArithmeticComputation::Add => "+",
414            ArithmeticComputation::Subtract => "-",
415            ArithmeticComputation::Multiply => "*",
416            ArithmeticComputation::Divide => "/",
417            ArithmeticComputation::Modulo => "%",
418            ArithmeticComputation::Power => "^",
419        }
420    }
421}
422
423/// Comparison computations
424#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
425#[serde(rename_all = "snake_case")]
426pub enum ComparisonComputation {
427    GreaterThan,
428    LessThan,
429    GreaterThanOrEqual,
430    LessThanOrEqual,
431    Equal,
432    NotEqual,
433    Is,
434    IsNot,
435}
436
437impl ComparisonComputation {
438    /// Returns a human-readable name for the computation
439    #[must_use]
440    pub fn name(&self) -> &'static str {
441        match self {
442            ComparisonComputation::GreaterThan => "greater than",
443            ComparisonComputation::LessThan => "less than",
444            ComparisonComputation::GreaterThanOrEqual => "greater than or equal",
445            ComparisonComputation::LessThanOrEqual => "less than or equal",
446            ComparisonComputation::Equal => "equal",
447            ComparisonComputation::NotEqual => "not equal",
448            ComparisonComputation::Is => "is",
449            ComparisonComputation::IsNot => "is not",
450        }
451    }
452
453    /// Returns the operator symbol
454    #[must_use]
455    pub fn symbol(&self) -> &'static str {
456        match self {
457            ComparisonComputation::GreaterThan => ">",
458            ComparisonComputation::LessThan => "<",
459            ComparisonComputation::GreaterThanOrEqual => ">=",
460            ComparisonComputation::LessThanOrEqual => "<=",
461            ComparisonComputation::Equal => "==",
462            ComparisonComputation::NotEqual => "!=",
463            ComparisonComputation::Is => "is",
464            ComparisonComputation::IsNot => "is not",
465        }
466    }
467
468    /// Check if this is an equality comparison (== or is)
469    #[must_use]
470    pub fn is_equal(&self) -> bool {
471        matches!(
472            self,
473            ComparisonComputation::Equal | ComparisonComputation::Is
474        )
475    }
476
477    /// Check if this is an inequality comparison (!= or is not)
478    #[must_use]
479    pub fn is_not_equal(&self) -> bool {
480        matches!(
481            self,
482            ComparisonComputation::NotEqual | ComparisonComputation::IsNot
483        )
484    }
485}
486
487/// The target unit for unit conversion expressions
488#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
489#[serde(rename_all = "snake_case")]
490pub enum ConversionTarget {
491    Duration(DurationUnit),
492    Percentage,
493    ScaleUnit(String),
494}
495
496/// Types of logical negation
497#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
498pub enum NegationType {
499    Not,
500}
501
502/// Logical computations
503#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
504#[serde(rename_all = "snake_case")]
505pub enum LogicalComputation {
506    And,
507    Or,
508    Not,
509}
510
511/// A veto expression that prohibits any valid verdict from the rule
512///
513/// Unlike `reject` (which is just an alias for boolean `false`), a veto
514/// prevents the rule from producing any valid result. This is used for
515/// validation and constraint enforcement.
516///
517/// Example: `veto "Must be over 18"` - blocks the rule entirely with a message
518#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
519pub struct VetoExpression {
520    pub message: Option<String>,
521}
522
523impl VetoExpression {
524    fn semantic_hash<H: Hasher>(&self, state: &mut H) {
525        self.message.hash(state);
526    }
527}
528
529/// Mathematical computations
530#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
531#[serde(rename_all = "snake_case")]
532pub enum MathematicalComputation {
533    Sqrt,
534    Sin,
535    Cos,
536    Tan,
537    Asin,
538    Acos,
539    Atan,
540    Log,
541    Exp,
542    Abs,
543    Floor,
544    Ceil,
545    Round,
546}
547
548#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
549#[serde(rename_all = "snake_case")]
550pub enum FactValue {
551    Literal(LiteralValue),
552    DocumentReference(String),
553    TypeDeclaration {
554        base: String,
555        overrides: Option<Vec<(String, Vec<String>)>>,
556        from: Option<String>,
557    },
558}
559
560/// A type for type declarations
561/// Boolean value with original input preserved
562#[derive(
563    Debug,
564    Clone,
565    PartialEq,
566    Serialize,
567    serde::Deserialize,
568    strum_macros::EnumString,
569    strum_macros::Display,
570)]
571#[strum(ascii_case_insensitive, serialize_all = "lowercase")]
572pub enum BooleanValue {
573    True,
574    False,
575    Yes,
576    No,
577    Accept,
578    Reject,
579}
580
581impl From<BooleanValue> for bool {
582    fn from(value: BooleanValue) -> bool {
583        match value {
584            BooleanValue::True | BooleanValue::Yes | BooleanValue::Accept => true,
585            BooleanValue::False | BooleanValue::No | BooleanValue::Reject => false,
586        }
587    }
588}
589
590impl From<&BooleanValue> for bool {
591    fn from(value: &BooleanValue) -> bool {
592        match value {
593            BooleanValue::True | BooleanValue::Yes | BooleanValue::Accept => true,
594            BooleanValue::False | BooleanValue::No | BooleanValue::Reject => false,
595        }
596    }
597}
598
599impl From<bool> for BooleanValue {
600    fn from(value: bool) -> BooleanValue {
601        if value {
602            BooleanValue::True
603        } else {
604            BooleanValue::False
605        }
606    }
607}
608
609impl std::ops::Not for BooleanValue {
610    type Output = BooleanValue;
611
612    fn not(self) -> Self::Output {
613        if self.into() {
614            BooleanValue::False
615        } else {
616            BooleanValue::True
617        }
618    }
619}
620
621impl std::ops::Not for &BooleanValue {
622    type Output = BooleanValue;
623
624    fn not(self) -> Self::Output {
625        if self.into() {
626            BooleanValue::False
627        } else {
628            BooleanValue::True
629        }
630    }
631}
632
633/// The actual value data (without type information)
634#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
635#[serde(rename_all = "snake_case")]
636pub enum Value {
637    Number(Decimal),
638    Scale(Decimal, Option<String>), // value, optional unit name (e.g., "eur", "usd", "kilogram")
639    Text(String),
640    Date(DateTimeValue),
641    Time(TimeValue),
642    Boolean(BooleanValue),
643    Duration(Decimal, DurationUnit),
644    Ratio(Decimal, Option<String>), // value, optional unit name (e.g., "percent", "permille")
645}
646
647/// A literal value with its type
648///
649/// Every literal value knows its type - no distinction between standard and custom types.
650#[derive(Debug, Clone, PartialEq, Serialize, serde::Deserialize)]
651pub struct LiteralValue {
652    pub value: Value,
653    pub lemma_type: LemmaType,
654}
655
656impl LiteralValue {
657    /// Create a Number literal value from any type that can convert to Decimal
658    /// Uses STANDARD_NUMBER as the type
659    pub fn number<T: Into<Decimal>>(value: T) -> Self {
660        LiteralValue {
661            value: Value::Number(value.into()),
662            lemma_type: standard_number().clone(),
663        }
664    }
665
666    /// Create a Number literal value with a custom type
667    pub fn number_with_type<T: Into<Decimal>>(value: T, lemma_type: LemmaType) -> Self {
668        LiteralValue {
669            value: Value::Number(value.into()),
670            lemma_type,
671        }
672    }
673
674    /// Create a Scale literal value
675    /// Uses the provided type (must be a Scale type)
676    pub fn scale<T: Into<Decimal>>(value: T, unit: Option<String>) -> Self {
677        LiteralValue {
678            value: Value::Scale(value.into(), unit),
679            lemma_type: crate::semantic::standard_scale().clone(),
680        }
681    }
682
683    /// Create a Scale literal value with a custom type
684    pub fn scale_with_type<T: Into<Decimal>>(
685        value: T,
686        unit: Option<String>,
687        lemma_type: LemmaType,
688    ) -> Self {
689        LiteralValue {
690            value: Value::Scale(value.into(), unit),
691            lemma_type,
692        }
693    }
694
695    /// Create a Text literal value
696    /// Uses STANDARD_TEXT as the type
697    pub fn text(value: String) -> Self {
698        LiteralValue {
699            value: Value::Text(value),
700            lemma_type: standard_text().clone(),
701        }
702    }
703
704    /// Create a Text literal value with a custom type
705    pub fn text_with_type(value: String, lemma_type: LemmaType) -> Self {
706        LiteralValue {
707            value: Value::Text(value),
708            lemma_type,
709        }
710    }
711
712    /// Create a Boolean literal value
713    /// Uses STANDARD_BOOLEAN as the type
714    pub fn boolean(value: BooleanValue) -> Self {
715        let canonical: BooleanValue = bool::from(&value).into();
716        LiteralValue {
717            value: Value::Boolean(canonical),
718            lemma_type: standard_boolean().clone(),
719        }
720    }
721
722    /// Create a Boolean literal value with a custom type
723    pub fn boolean_with_type(value: BooleanValue, lemma_type: LemmaType) -> Self {
724        let canonical: BooleanValue = bool::from(&value).into();
725        LiteralValue {
726            value: Value::Boolean(canonical),
727            lemma_type,
728        }
729    }
730
731    /// Create a Date literal value
732    /// Uses STANDARD_DATE as the type
733    pub fn date(value: DateTimeValue) -> Self {
734        LiteralValue {
735            value: Value::Date(value),
736            lemma_type: standard_date().clone(),
737        }
738    }
739
740    /// Create a Date literal value with a custom type
741    pub fn date_with_type(value: DateTimeValue, lemma_type: LemmaType) -> Self {
742        LiteralValue {
743            value: Value::Date(value),
744            lemma_type,
745        }
746    }
747
748    /// Create a Time literal value
749    /// Uses STANDARD_TIME as the type
750    pub fn time(value: TimeValue) -> Self {
751        LiteralValue {
752            value: Value::Time(value),
753            lemma_type: standard_time().clone(),
754        }
755    }
756
757    /// Create a Time literal value with a custom type
758    pub fn time_with_type(value: TimeValue, lemma_type: LemmaType) -> Self {
759        LiteralValue {
760            value: Value::Time(value),
761            lemma_type,
762        }
763    }
764
765    /// Create a Duration literal value
766    /// Uses STANDARD_DURATION as the type
767    pub fn duration(value: Decimal, unit: DurationUnit) -> Self {
768        LiteralValue {
769            value: Value::Duration(value, unit),
770            lemma_type: standard_duration().clone(),
771        }
772    }
773
774    /// Create a Duration literal value with a custom type
775    pub fn duration_with_type(value: Decimal, unit: DurationUnit, lemma_type: LemmaType) -> Self {
776        LiteralValue {
777            value: Value::Duration(value, unit),
778            lemma_type,
779        }
780    }
781
782    /// Create a Ratio literal value
783    /// Uses STANDARD_RATIO as the type
784    pub fn ratio<T: Into<Decimal>>(value: T, unit: Option<String>) -> Self {
785        LiteralValue {
786            value: Value::Ratio(value.into(), unit),
787            lemma_type: standard_ratio().clone(),
788        }
789    }
790
791    /// Create a Ratio literal value with a custom type
792    pub fn ratio_with_type<T: Into<Decimal>>(
793        value: T,
794        unit: Option<String>,
795        lemma_type: LemmaType,
796    ) -> Self {
797        LiteralValue {
798            value: Value::Ratio(value.into(), unit),
799            lemma_type,
800        }
801    }
802
803    /// Get the type of this literal value
804    pub fn get_type(&self) -> &LemmaType {
805        &self.lemma_type
806    }
807
808    /// Get the display value as a string (uses the Display implementation)
809    #[must_use]
810    pub fn display_value(&self) -> String {
811        self.to_string()
812    }
813
814    /// Get the byte size of this literal value for resource limiting
815    pub fn byte_size(&self) -> usize {
816        match &self.value {
817            Value::Text(s) => s.len(),
818            Value::Number(d) => std::mem::size_of_val(d),
819            Value::Scale(d, _) => std::mem::size_of_val(d),
820            Value::Boolean(_) => std::mem::size_of::<bool>(),
821            Value::Date(_) => std::mem::size_of::<DateTimeValue>(),
822            Value::Time(_) => std::mem::size_of::<TimeValue>(),
823            Value::Duration(value, _) => std::mem::size_of_val(value),
824            Value::Ratio(value, _) => std::mem::size_of_val(value),
825        }
826    }
827
828    /// Compute semantic hash for literal values
829    /// Uses string representation for Decimal to avoid Hash trait requirement
830    fn semantic_hash<H: Hasher>(&self, state: &mut H) {
831        std::mem::discriminant(&self.value).hash(state);
832        match &self.value {
833            Value::Number(d) | Value::Scale(d, _) | Value::Ratio(d, _) => {
834                d.to_string().hash(state);
835            }
836            Value::Text(s) => s.hash(state),
837            Value::Boolean(b) => std::mem::discriminant(b).hash(state),
838            Value::Date(dt) => {
839                dt.year.hash(state);
840                dt.month.hash(state);
841                dt.day.hash(state);
842                dt.hour.hash(state);
843                dt.minute.hash(state);
844                dt.second.hash(state);
845                if let Some(tz) = &dt.timezone {
846                    tz.offset_hours.hash(state);
847                    tz.offset_minutes.hash(state);
848                }
849            }
850            Value::Time(t) => {
851                t.hour.hash(state);
852                t.minute.hash(state);
853                t.second.hash(state);
854                if let Some(tz) = &t.timezone {
855                    tz.offset_hours.hash(state);
856                    tz.offset_minutes.hash(state);
857                }
858            }
859            Value::Duration(value, unit) => {
860                value.to_string().hash(state);
861                std::mem::discriminant(unit).hash(state);
862            }
863        }
864    }
865}
866
867/// A time value
868#[derive(
869    Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, serde::Deserialize,
870)]
871pub struct TimeValue {
872    pub hour: u8,
873    pub minute: u8,
874    pub second: u8,
875    pub timezone: Option<TimezoneValue>,
876}
877
878/// A timezone value
879#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, serde::Deserialize)]
880pub struct TimezoneValue {
881    pub offset_hours: i8,
882    pub offset_minutes: u8,
883}
884
885/// A datetime value that preserves timezone information
886#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, serde::Deserialize)]
887pub struct DateTimeValue {
888    pub year: i32,
889    pub month: u32,
890    pub day: u32,
891    pub hour: u32,
892    pub minute: u32,
893    pub second: u32,
894    pub timezone: Option<TimezoneValue>,
895}
896
897/// Unit types for different physical quantities
898macro_rules! impl_unit_serialize {
899    ($($unit_type:ty),+) => {
900        $(
901            impl Serialize for $unit_type {
902                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
903                where
904                    S: serde::Serializer,
905                {
906                    serializer.serialize_str(&self.to_string())
907                }
908            }
909        )+
910    };
911}
912
913impl_unit_serialize!(DurationUnit);
914
915#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Deserialize, strum_macros::EnumString)]
916#[strum(serialize_all = "lowercase")]
917pub enum DurationUnit {
918    Year,
919    Month,
920    Week,
921    Day,
922    Hour,
923    Minute,
924    Second,
925    Millisecond,
926    Microsecond,
927}
928
929impl fmt::Display for DurationUnit {
930    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
931        let s = match self {
932            DurationUnit::Year => "years",
933            DurationUnit::Month => "months",
934            DurationUnit::Week => "weeks",
935            DurationUnit::Day => "days",
936            DurationUnit::Hour => "hours",
937            DurationUnit::Minute => "minutes",
938            DurationUnit::Second => "seconds",
939            DurationUnit::Millisecond => "milliseconds",
940            DurationUnit::Microsecond => "microseconds",
941        };
942        write!(f, "{}", s)
943    }
944}
945
946impl Reference {
947    #[must_use]
948    pub fn new(segments: Vec<String>, name: String) -> Self {
949        Self { segments, name }
950    }
951
952    #[must_use]
953    pub fn local(name: String) -> Self {
954        Self {
955            segments: Vec::new(),
956            name,
957        }
958    }
959
960    #[must_use]
961    pub fn from_path(path: Vec<String>) -> Self {
962        if path.is_empty() {
963            Self {
964                segments: Vec::new(),
965                name: String::new(),
966            }
967        } else {
968            // Safe: path is non-empty.
969            let name = path[path.len() - 1].clone();
970            let segments = path[..path.len() - 1].to_vec();
971            Self { segments, name }
972        }
973    }
974
975    #[must_use]
976    pub fn is_local(&self) -> bool {
977        self.segments.is_empty()
978    }
979
980    #[must_use]
981    pub fn full_path(&self) -> Vec<String> {
982        let mut path = self.segments.clone();
983        path.push(self.name.clone());
984        path
985    }
986
987    /// Convert to FactReference (used during planning resolution)
988    #[must_use]
989    pub fn to_fact_reference(&self) -> FactReference {
990        FactReference {
991            segments: self.segments.clone(),
992            fact: self.name.clone(),
993        }
994    }
995}
996
997impl FactReference {
998    /// Create a new FactReference from segments and fact name
999    #[must_use]
1000    pub fn new(segments: Vec<String>, fact: String) -> Self {
1001        Self { segments, fact }
1002    }
1003
1004    /// Create a FactReference from a single fact name (local reference)
1005    #[must_use]
1006    pub fn local(fact: String) -> Self {
1007        Self {
1008            segments: Vec::new(),
1009            fact,
1010        }
1011    }
1012
1013    /// Create a FactReference from a Vec<String> path (for backward compatibility during migration)
1014    #[must_use]
1015    pub fn from_path(path: Vec<String>) -> Self {
1016        if path.is_empty() {
1017            Self {
1018                segments: Vec::new(),
1019                fact: String::new(),
1020            }
1021        } else {
1022            // Safe: path is non-empty.
1023            let fact = path[path.len() - 1].clone();
1024            let segments = path[..path.len() - 1].to_vec();
1025            Self { segments, fact }
1026        }
1027    }
1028
1029    /// Returns true if this is a local reference (no path segments)
1030    #[must_use]
1031    pub fn is_local(&self) -> bool {
1032        self.segments.is_empty()
1033    }
1034
1035    /// Get all path segments including the fact name
1036    #[must_use]
1037    pub fn full_path(&self) -> Vec<String> {
1038        let mut path = self.segments.clone();
1039        path.push(self.fact.clone());
1040        path
1041    }
1042}
1043
1044impl LemmaFact {
1045    #[must_use]
1046    pub fn new(reference: FactReference, value: FactValue) -> Self {
1047        Self {
1048            reference,
1049            value,
1050            source_location: None,
1051        }
1052    }
1053
1054    #[must_use]
1055    pub fn with_source_location(mut self, source_location: Source) -> Self {
1056        self.source_location = Some(source_location);
1057        self
1058    }
1059
1060    /// Returns true if this fact is local (not a cross-document reference)
1061    #[must_use]
1062    pub fn is_local(&self) -> bool {
1063        self.reference.is_local()
1064    }
1065}
1066
1067impl LemmaDoc {
1068    #[must_use]
1069    pub fn new(name: String) -> Self {
1070        Self {
1071            name,
1072            attribute: None,
1073            start_line: 1,
1074            commentary: None,
1075            types: Vec::new(),
1076            facts: Vec::new(),
1077            rules: Vec::new(),
1078        }
1079    }
1080
1081    #[must_use]
1082    pub fn with_attribute(mut self, attribute: String) -> Self {
1083        self.attribute = Some(attribute);
1084        self
1085    }
1086
1087    #[must_use]
1088    pub fn with_start_line(mut self, start_line: usize) -> Self {
1089        self.start_line = start_line;
1090        self
1091    }
1092
1093    #[must_use]
1094    pub fn set_commentary(mut self, commentary: String) -> Self {
1095        self.commentary = Some(commentary);
1096        self
1097    }
1098
1099    #[must_use]
1100    pub fn add_fact(mut self, fact: LemmaFact) -> Self {
1101        self.facts.push(fact);
1102        self
1103    }
1104
1105    #[must_use]
1106    pub fn add_rule(mut self, rule: LemmaRule) -> Self {
1107        self.rules.push(rule);
1108        self
1109    }
1110
1111    #[must_use]
1112    pub fn add_type(mut self, type_def: TypeDef) -> Self {
1113        self.types.push(type_def);
1114        self
1115    }
1116
1117    /// Get the expected type for a fact by path
1118    /// Returns None if the fact is not found in this document or if the fact is a document reference
1119    pub fn get_fact_type(&self, fact_ref: &[String]) -> Option<LemmaType> {
1120        let fact_path: Vec<String> = fact_ref.to_vec();
1121        let fact_name = fact_path.last()?.clone();
1122        let segments: Vec<String> = fact_path[..fact_path.len().saturating_sub(1)].to_vec();
1123        let target_ref = FactReference {
1124            segments,
1125            fact: fact_name,
1126        };
1127        self.facts
1128            .iter()
1129            .find(|fact| fact.reference == target_ref)
1130            .and_then(|fact| match &fact.value {
1131                FactValue::Literal(lit) => Some(lit.get_type().clone()),
1132                FactValue::TypeDeclaration { .. } => {
1133                    // Type resolution happens during planning phase
1134                    None
1135                }
1136                FactValue::DocumentReference(_) => None,
1137            })
1138    }
1139}
1140
1141impl fmt::Display for LemmaDoc {
1142    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1143        write!(f, "doc {}", self.name)?;
1144        writeln!(f)?;
1145
1146        if let Some(ref commentary) = self.commentary {
1147            writeln!(f, "\"\"\"{}", commentary)?;
1148            writeln!(f, "\"\"\"")?;
1149        }
1150
1151        for fact in &self.facts {
1152            write!(f, "{}", fact)?;
1153        }
1154
1155        for rule in &self.rules {
1156            write!(f, "{}", rule)?;
1157        }
1158
1159        Ok(())
1160    }
1161}
1162
1163impl fmt::Display for FactReference {
1164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1165        for segment in &self.segments {
1166            write!(f, "{}.", segment)?;
1167        }
1168        write!(f, "{}", self.fact)
1169    }
1170}
1171
1172impl fmt::Display for LemmaFact {
1173    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1174        writeln!(f, "fact {} = {}", self.reference, self.value)
1175    }
1176}
1177
1178impl fmt::Display for LemmaRule {
1179    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1180        write!(f, "rule {} = {}", self.name, self.expression)?;
1181
1182        for unless_clause in &self.unless_clauses {
1183            write!(
1184                f,
1185                " unless {} then {}",
1186                unless_clause.condition, unless_clause.result
1187            )?;
1188        }
1189
1190        writeln!(f)?;
1191        Ok(())
1192    }
1193}
1194
1195impl fmt::Display for Expression {
1196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1197        match &self.kind {
1198            ExpressionKind::Literal(lit) => write!(f, "{}", lit),
1199            ExpressionKind::Reference(r) => {
1200                if r.segments.is_empty() {
1201                    write!(f, "{}", r.name)
1202                } else {
1203                    write!(f, "{}.{}", r.segments.join("."), r.name)
1204                }
1205            }
1206            ExpressionKind::FactReference(fact_ref) => write!(f, "{}", fact_ref),
1207            ExpressionKind::FactPath(fact_path) => write!(f, "{}", fact_path),
1208            ExpressionKind::RuleReference(rule_ref) => write!(f, "{}", rule_ref),
1209            ExpressionKind::RulePath(rule_path) => write!(f, "{}", rule_path),
1210            ExpressionKind::Arithmetic(left, op, right) => {
1211                write!(f, "{} {} {}", left, op, right)
1212            }
1213            ExpressionKind::Comparison(left, op, right) => {
1214                write!(f, "{} {} {}", left, op, right)
1215            }
1216            ExpressionKind::UnitConversion(value, target) => {
1217                write!(f, "{} in {}", value, target)
1218            }
1219            ExpressionKind::LogicalNegation(expr, _) => {
1220                write!(f, "not {}", expr)
1221            }
1222            ExpressionKind::LogicalAnd(left, right) => {
1223                write!(f, "{} and {}", left, right)
1224            }
1225            ExpressionKind::LogicalOr(left, right) => {
1226                write!(f, "{} or {}", left, right)
1227            }
1228            ExpressionKind::MathematicalComputation(op, operand) => {
1229                let op_name = match op {
1230                    MathematicalComputation::Sqrt => "sqrt",
1231                    MathematicalComputation::Sin => "sin",
1232                    MathematicalComputation::Cos => "cos",
1233                    MathematicalComputation::Tan => "tan",
1234                    MathematicalComputation::Asin => "asin",
1235                    MathematicalComputation::Acos => "acos",
1236                    MathematicalComputation::Atan => "atan",
1237                    MathematicalComputation::Log => "log",
1238                    MathematicalComputation::Exp => "exp",
1239                    MathematicalComputation::Abs => "abs",
1240                    MathematicalComputation::Floor => "floor",
1241                    MathematicalComputation::Ceil => "ceil",
1242                    MathematicalComputation::Round => "round",
1243                };
1244                write!(f, "{} {}", op_name, operand)
1245            }
1246            ExpressionKind::Veto(veto) => match &veto.message {
1247                Some(msg) => write!(f, "veto \"{}\"", msg),
1248                None => write!(f, "veto"),
1249            },
1250            ExpressionKind::UnresolvedUnitLiteral(number, unit_name) => {
1251                write!(f, "{} {}", number, unit_name)
1252            }
1253        }
1254    }
1255}
1256
1257impl fmt::Display for LiteralValue {
1258    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1259        match &self.value {
1260            Value::Number(n) => {
1261                // Get decimals from type specification if available
1262                let decimals_opt = match &self.lemma_type.specifications {
1263                    TypeSpecification::Number { decimals, .. } => *decimals,
1264                    _ => None,
1265                };
1266
1267                if let Some(decimals) = decimals_opt {
1268                    // Format with fixed decimal places, always showing all decimals
1269                    let rounded = n.round_dp(decimals as u32);
1270                    let mut s = rounded.to_string();
1271                    // Ensure we have the right number of decimal places
1272                    if let Some(dot_pos) = s.find('.') {
1273                        let current_decimals = s.len() - dot_pos - 1;
1274                        if current_decimals < decimals as usize {
1275                            // Pad with zeros
1276                            s.push_str(&"0".repeat(decimals as usize - current_decimals));
1277                        } else if current_decimals > decimals as usize {
1278                            // This shouldn't happen due to round_dp, but handle it
1279                            let truncate_pos = dot_pos + 1 + decimals as usize;
1280                            s = s[..truncate_pos].to_string();
1281                        }
1282                    } else {
1283                        // No decimal point, add it with zeros
1284                        s.push('.');
1285                        s.push_str(&"0".repeat(decimals as usize));
1286                    }
1287                    write!(f, "{}", s)
1288                } else {
1289                    // No decimals specified: normalize (remove trailing zeros)
1290                    let normalized = n.normalize();
1291                    if normalized.fract().is_zero() {
1292                        let int_part = normalized.trunc().to_string();
1293                        let formatted = int_part
1294                            .chars()
1295                            .rev()
1296                            .enumerate()
1297                            .flat_map(|(i, c)| {
1298                                if i > 0 && i % 3 == 0 && c != '-' {
1299                                    vec![',', c]
1300                                } else {
1301                                    vec![c]
1302                                }
1303                            })
1304                            .collect::<String>()
1305                            .chars()
1306                            .rev()
1307                            .collect::<String>();
1308                        write!(f, "{}", formatted)
1309                    } else {
1310                        write!(f, "{}", normalized)
1311                    }
1312                }
1313            }
1314            Value::Text(s) => {
1315                let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
1316                write!(f, "\"{}\"", escaped)
1317            }
1318            Value::Date(dt) => write!(f, "{}", dt),
1319            Value::Boolean(b) => write!(f, "{}", b),
1320            Value::Time(time) => {
1321                write!(f, "time({}, {}, {})", time.hour, time.minute, time.second)
1322            }
1323            Value::Scale(n, unit_opt) => {
1324                // Format the number part (same as Number)
1325                let decimals_opt = match &self.lemma_type.specifications {
1326                    TypeSpecification::Scale { decimals, .. } => *decimals,
1327                    _ => None,
1328                };
1329
1330                let number_str = if let Some(decimals) = decimals_opt {
1331                    // Format with fixed decimal places
1332                    let rounded = n.round_dp(decimals as u32);
1333                    let mut s = rounded.to_string();
1334                    if let Some(dot_pos) = s.find('.') {
1335                        let current_decimals = s.len() - dot_pos - 1;
1336                        if current_decimals < decimals as usize {
1337                            s.push_str(&"0".repeat(decimals as usize - current_decimals));
1338                        }
1339                    } else {
1340                        s.push('.');
1341                        s.push_str(&"0".repeat(decimals as usize));
1342                    }
1343                    s
1344                } else {
1345                    // No decimals specified: normalize (remove trailing zeros)
1346                    let normalized = n.normalize();
1347                    if normalized.fract().is_zero() {
1348                        normalized.trunc().to_string()
1349                    } else {
1350                        normalized.to_string()
1351                    }
1352                };
1353
1354                // Append unit if present
1355                if let Some(unit) = unit_opt {
1356                    write!(f, "{} {}", number_str, unit)
1357                } else {
1358                    write!(f, "{}", number_str)
1359                }
1360            }
1361            Value::Duration(value, unit) => write!(f, "{} {}", value, unit),
1362            Value::Ratio(r, unit_opt) => {
1363                // Use tracked unit if present
1364                if let Some(unit) = unit_opt {
1365                    if unit == "percent" {
1366                        // Display as percent: convert ratio (0.50) to percent (50%)
1367                        let percentage_value = *r * rust_decimal::Decimal::from(100);
1368                        let rounded = percentage_value.round_dp(2);
1369                        if rounded.fract().is_zero() {
1370                            write!(f, "{}%", rounded.trunc())
1371                        } else {
1372                            write!(f, "{}%", rounded)
1373                        }
1374                    } else {
1375                        // Display with unit
1376                        let normalized = r.normalize();
1377                        if normalized.fract().is_zero() {
1378                            write!(f, "{} {}", normalized.trunc(), unit)
1379                        } else {
1380                            write!(f, "{} {}", normalized, unit)
1381                        }
1382                    }
1383                } else {
1384                    // Display as regular ratio (no unit)
1385                    let normalized = r.normalize();
1386                    if normalized.fract().is_zero() {
1387                        write!(f, "{}", normalized.trunc())
1388                    } else {
1389                        write!(f, "{}", normalized)
1390                    }
1391                }
1392            }
1393        }
1394    }
1395}
1396
1397impl fmt::Display for ConversionTarget {
1398    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1399        match self {
1400            ConversionTarget::Duration(unit) => write!(f, "{}", unit),
1401            ConversionTarget::Percentage => write!(f, "percent"),
1402            ConversionTarget::ScaleUnit(unit) => write!(f, "{}", unit),
1403        }
1404    }
1405}
1406
1407impl fmt::Display for FactValue {
1408    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1409        match self {
1410            FactValue::Literal(lit) => write!(f, "{}", lit),
1411            FactValue::TypeDeclaration {
1412                base,
1413                overrides,
1414                from,
1415            } => {
1416                let base_str = if let Some(from_doc) = from {
1417                    format!("{} from {}", base, from_doc)
1418                } else {
1419                    base.clone()
1420                };
1421
1422                if let Some(ref overrides_vec) = overrides {
1423                    let override_str = overrides_vec
1424                        .iter()
1425                        .map(|(cmd, args)| {
1426                            let args_str = args.join(" ");
1427                            if args_str.is_empty() {
1428                                cmd.clone()
1429                            } else {
1430                                format!("{} {}", cmd, args_str)
1431                            }
1432                        })
1433                        .collect::<Vec<_>>()
1434                        .join(" -> ");
1435                    write!(f, "[{} -> {}]", base_str, override_str)
1436                } else {
1437                    write!(f, "[{}]", base_str)
1438                }
1439            }
1440            FactValue::DocumentReference(doc_name) => write!(f, "doc {}", doc_name),
1441        }
1442    }
1443}
1444
1445impl fmt::Display for ArithmeticComputation {
1446    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1447        match self {
1448            ArithmeticComputation::Add => write!(f, "+"),
1449            ArithmeticComputation::Subtract => write!(f, "-"),
1450            ArithmeticComputation::Multiply => write!(f, "*"),
1451            ArithmeticComputation::Divide => write!(f, "/"),
1452            ArithmeticComputation::Modulo => write!(f, "%"),
1453            ArithmeticComputation::Power => write!(f, "^"),
1454        }
1455    }
1456}
1457
1458impl fmt::Display for ComparisonComputation {
1459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1460        match self {
1461            ComparisonComputation::GreaterThan => write!(f, ">"),
1462            ComparisonComputation::LessThan => write!(f, "<"),
1463            ComparisonComputation::GreaterThanOrEqual => write!(f, ">="),
1464            ComparisonComputation::LessThanOrEqual => write!(f, "<="),
1465            ComparisonComputation::Equal => write!(f, "=="),
1466            ComparisonComputation::NotEqual => write!(f, "!="),
1467            ComparisonComputation::Is => write!(f, "is"),
1468            ComparisonComputation::IsNot => write!(f, "is not"),
1469        }
1470    }
1471}
1472
1473impl fmt::Display for MathematicalComputation {
1474    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1475        match self {
1476            MathematicalComputation::Sqrt => write!(f, "sqrt"),
1477            MathematicalComputation::Sin => write!(f, "sin"),
1478            MathematicalComputation::Cos => write!(f, "cos"),
1479            MathematicalComputation::Tan => write!(f, "tan"),
1480            MathematicalComputation::Asin => write!(f, "asin"),
1481            MathematicalComputation::Acos => write!(f, "acos"),
1482            MathematicalComputation::Atan => write!(f, "atan"),
1483            MathematicalComputation::Log => write!(f, "log"),
1484            MathematicalComputation::Exp => write!(f, "exp"),
1485            MathematicalComputation::Abs => write!(f, "abs"),
1486            MathematicalComputation::Floor => write!(f, "floor"),
1487            MathematicalComputation::Ceil => write!(f, "ceil"),
1488            MathematicalComputation::Round => write!(f, "round"),
1489        }
1490    }
1491}
1492
1493impl fmt::Display for TimeValue {
1494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1495        write!(f, "{:02}:{:02}:{:02}", self.hour, self.minute, self.second)
1496    }
1497}
1498
1499impl fmt::Display for TimezoneValue {
1500    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1501        if self.offset_hours == 0 && self.offset_minutes == 0 {
1502            write!(f, "Z")
1503        } else {
1504            let sign = if self.offset_hours >= 0 { "+" } else { "-" };
1505            let hours = self.offset_hours.abs();
1506            write!(f, "{}{:02}:{:02}", sign, hours, self.offset_minutes)
1507        }
1508    }
1509}
1510
1511impl fmt::Display for DateTimeValue {
1512    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1513        write!(
1514            f,
1515            "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}",
1516            self.year, self.month, self.day, self.hour, self.minute, self.second
1517        )?;
1518        if let Some(tz) = &self.timezone {
1519            write!(f, "{}", tz)?;
1520        }
1521        Ok(())
1522    }
1523}
1524
1525impl fmt::Display for RuleReference {
1526    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1527        if self.segments.is_empty() {
1528            write!(f, "{}?", self.rule)
1529        } else {
1530            write!(f, "{}.{}?", self.segments.join("."), self.rule)
1531        }
1532    }
1533}
1534
1535impl fmt::Display for FactPath {
1536    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1537        for segment in &self.segments {
1538            write!(f, "{}.", segment.fact)?;
1539        }
1540        write!(f, "{}", self.fact)
1541    }
1542}
1543
1544impl fmt::Display for RulePath {
1545    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1546        for segment in &self.segments {
1547            write!(f, "{}.", segment.fact)?;
1548        }
1549        write!(f, "{}?", self.rule)
1550    }
1551}
1552
1553/// A unit for Number and Ratio types
1554#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
1555pub struct Unit {
1556    pub name: String,
1557    pub value: Decimal,
1558}
1559
1560/// Type specifications that define the foundational types available in Lemma,
1561/// including their default values and constraints.
1562#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
1563pub enum TypeSpecification {
1564    Boolean {
1565        help: Option<String>,
1566        default: Option<bool>,
1567    },
1568    Scale {
1569        minimum: Option<Decimal>,
1570        maximum: Option<Decimal>,
1571        decimals: Option<u8>,
1572        precision: Option<Decimal>,
1573        units: Vec<Unit>,
1574        help: Option<String>,
1575        default: Option<(Decimal, String)>,
1576    },
1577    Number {
1578        minimum: Option<Decimal>,
1579        maximum: Option<Decimal>,
1580        decimals: Option<u8>,
1581        precision: Option<Decimal>,
1582        help: Option<String>,
1583        default: Option<Decimal>,
1584    },
1585    Ratio {
1586        minimum: Option<Decimal>,
1587        maximum: Option<Decimal>,
1588        units: Vec<Unit>,
1589        help: Option<String>,
1590        default: Option<Decimal>,
1591    },
1592    Text {
1593        minimum: Option<usize>,
1594        maximum: Option<usize>,
1595        length: Option<usize>,
1596        options: Vec<String>,
1597        help: Option<String>,
1598        default: Option<String>,
1599    },
1600    Date {
1601        minimum: Option<DateTimeValue>,
1602        maximum: Option<DateTimeValue>,
1603        help: Option<String>,
1604        default: Option<DateTimeValue>,
1605    },
1606    Time {
1607        minimum: Option<TimeValue>,
1608        maximum: Option<TimeValue>,
1609        help: Option<String>,
1610        default: Option<TimeValue>,
1611    },
1612    Duration {
1613        help: Option<String>,
1614        default: Option<(Decimal, DurationUnit)>,
1615    },
1616    Veto {
1617        message: Option<String>,
1618    },
1619}
1620
1621impl TypeSpecification {
1622    /// Create a Boolean type with no defaults
1623    pub fn boolean() -> Self {
1624        TypeSpecification::Boolean {
1625            help: None,
1626            default: None,
1627        }
1628    }
1629
1630    /// Create a Scale type with default specifications (can have units)
1631    pub fn scale() -> Self {
1632        TypeSpecification::Scale {
1633            minimum: None,
1634            maximum: None,
1635            decimals: None,
1636            precision: None,
1637            units: vec![],
1638            help: None,
1639            default: None,
1640        }
1641    }
1642
1643    /// Create a Number type with default specifications (dimensionless, no units)
1644    pub fn number() -> Self {
1645        TypeSpecification::Number {
1646            minimum: None,
1647            maximum: None,
1648            decimals: None,
1649            precision: None,
1650            help: None,
1651            default: None,
1652        }
1653    }
1654
1655    /// Create a Ratio type with default units
1656    /// Default units: percent = 100, permille = 1000
1657    pub fn ratio() -> Self {
1658        TypeSpecification::Ratio {
1659            minimum: None,
1660            maximum: None,
1661            units: vec![
1662                Unit {
1663                    name: "percent".to_string(),
1664                    value: Decimal::from(100),
1665                },
1666                Unit {
1667                    name: "permille".to_string(),
1668                    value: Decimal::from(1000),
1669                },
1670            ],
1671            help: None,
1672            default: None,
1673        }
1674    }
1675
1676    /// Create a Text type with default specifications
1677    pub fn text() -> Self {
1678        TypeSpecification::Text {
1679            minimum: None,
1680            maximum: None,
1681            length: None,
1682            options: vec![],
1683            help: None,
1684            default: None,
1685        }
1686    }
1687
1688    /// Create a Date type with default specifications
1689    pub fn date() -> Self {
1690        TypeSpecification::Date {
1691            minimum: None,
1692            maximum: None,
1693            help: None,
1694            default: None,
1695        }
1696    }
1697
1698    /// Create a Time type with default specifications
1699    pub fn time() -> Self {
1700        TypeSpecification::Time {
1701            minimum: None,
1702            maximum: None,
1703            help: None,
1704            default: None,
1705        }
1706    }
1707
1708    /// Create a Duration type with default specifications
1709    pub fn duration() -> Self {
1710        TypeSpecification::Duration {
1711            help: None,
1712            default: None,
1713        }
1714    }
1715
1716    /// Create a Veto type (internal use only - not user-declarable)
1717    pub fn veto() -> Self {
1718        TypeSpecification::Veto { message: None }
1719    }
1720
1721    /// Apply a single override command to this specification
1722    pub fn apply_override(mut self, command: &str, args: &[String]) -> Result<Self, String> {
1723        match &mut self {
1724            TypeSpecification::Boolean { help, default } => match command {
1725                "help" => *help = args.first().cloned(),
1726                "default" => {
1727                    let d = args
1728                        .first()
1729                        .ok_or_else(|| "default requires an argument".to_string())?
1730                        .parse::<BooleanValue>()
1731                        .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1732                    *default = Some(d.into());
1733                }
1734                _ => {
1735                    return Err(format!(
1736                        "Invalid command '{}' for boolean type. Valid commands: help, default",
1737                        command
1738                    ));
1739                }
1740            },
1741            TypeSpecification::Scale {
1742                decimals,
1743                minimum,
1744                maximum,
1745                precision,
1746                units,
1747                help,
1748                default,
1749            } => match command {
1750                "decimals" => {
1751                    let d = args
1752                        .first()
1753                        .ok_or_else(|| "decimals requires an argument".to_string())?
1754                        .parse::<u8>()
1755                        .map_err(|_| format!("invalid decimals value: {:?}", args.first()))?;
1756                    *decimals = Some(d);
1757                }
1758                "unit" if args.len() >= 2 => {
1759                    let unit_name = args[0].clone();
1760                    // Check if unit name already exists in the current type
1761                    if units.iter().any(|u| u.name == unit_name) {
1762                        return Err(format!(
1763                            "Duplicate unit name '{}' in type definition. Unit names must be unique within a type.",
1764                            unit_name
1765                        ));
1766                    }
1767                    let value = args[1]
1768                        .parse::<Decimal>()
1769                        .map_err(|_| format!("invalid unit value: {}", args[1]))?;
1770                    units.push(Unit {
1771                        name: unit_name,
1772                        value,
1773                    });
1774                }
1775                "minimum" => {
1776                    let m = args
1777                        .first()
1778                        .ok_or_else(|| "minimum requires an argument".to_string())?
1779                        .parse::<Decimal>()
1780                        .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1781                    *minimum = Some(m);
1782                }
1783                "maximum" => {
1784                    let m = args
1785                        .first()
1786                        .ok_or_else(|| "maximum requires an argument".to_string())?
1787                        .parse::<Decimal>()
1788                        .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1789                    *maximum = Some(m);
1790                }
1791                "precision" => {
1792                    let p = args
1793                        .first()
1794                        .ok_or_else(|| "precision requires an argument".to_string())?
1795                        .parse::<Decimal>()
1796                        .map_err(|_| format!("invalid precision value: {:?}", args.first()))?;
1797                    *precision = Some(p);
1798                }
1799                "help" => *help = args.first().cloned(),
1800                "default" => {
1801                    if args.len() < 2 {
1802                        return Err(
1803                            "default requires a value and unit (e.g., 'default 1 kilogram')"
1804                                .to_string(),
1805                        );
1806                    }
1807                    let value = args[0]
1808                        .parse::<Decimal>()
1809                        .map_err(|_| format!("invalid default value: {:?}", args[0]))?;
1810                    let unit_name = args[1].clone();
1811                    // Validate that the unit exists
1812                    if !units.iter().any(|u| u.name == unit_name) {
1813                        return Err(format!(
1814                            "Invalid unit '{}' for default. Valid units: {}",
1815                            unit_name,
1816                            units
1817                                .iter()
1818                                .map(|u| u.name.clone())
1819                                .collect::<Vec<_>>()
1820                                .join(", ")
1821                        ));
1822                    }
1823                    *default = Some((value, unit_name));
1824                }
1825                _ => {
1826                    return Err(format!(
1827                        "Invalid command '{}' for scale type. Valid commands: unit, minimum, maximum, decimals, precision, help, default",
1828                        command
1829                    ));
1830                }
1831            },
1832            TypeSpecification::Number {
1833                decimals,
1834                minimum,
1835                maximum,
1836                precision,
1837                help,
1838                default,
1839            } => match command {
1840                "decimals" => {
1841                    let d = args
1842                        .first()
1843                        .ok_or_else(|| "decimals requires an argument".to_string())?
1844                        .parse::<u8>()
1845                        .map_err(|_| format!("invalid decimals value: {:?}", args.first()))?;
1846                    *decimals = Some(d);
1847                }
1848                "unit" => {
1849                    return Err(
1850                        "Invalid command 'unit' for number type. Number types are dimensionless and cannot have units. Use 'scale' type instead.".to_string()
1851                    );
1852                }
1853                "minimum" => {
1854                    let m = args
1855                        .first()
1856                        .ok_or_else(|| "minimum requires an argument".to_string())?
1857                        .parse::<Decimal>()
1858                        .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1859                    *minimum = Some(m);
1860                }
1861                "maximum" => {
1862                    let m = args
1863                        .first()
1864                        .ok_or_else(|| "maximum requires an argument".to_string())?
1865                        .parse::<Decimal>()
1866                        .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1867                    *maximum = Some(m);
1868                }
1869                "precision" => {
1870                    let p = args
1871                        .first()
1872                        .ok_or_else(|| "precision requires an argument".to_string())?
1873                        .parse::<Decimal>()
1874                        .map_err(|_| format!("invalid precision value: {:?}", args.first()))?;
1875                    *precision = Some(p);
1876                }
1877                "help" => *help = args.first().cloned(),
1878                "default" => {
1879                    let d = args
1880                        .first()
1881                        .ok_or_else(|| "default requires an argument".to_string())?
1882                        .parse::<Decimal>()
1883                        .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1884                    *default = Some(d);
1885                }
1886                _ => {
1887                    return Err(format!(
1888                        "Invalid command '{}' for number type. Valid commands: minimum, maximum, decimals, precision, help, default",
1889                        command
1890                    ));
1891                }
1892            },
1893            TypeSpecification::Ratio {
1894                minimum,
1895                maximum,
1896                units,
1897                help,
1898                default,
1899            } => match command {
1900                "unit" if args.len() >= 2 => {
1901                    let value = args[1]
1902                        .parse::<Decimal>()
1903                        .map_err(|_| format!("invalid unit value: {}", args[1]))?;
1904                    units.push(Unit {
1905                        name: args[0].clone(),
1906                        value,
1907                    });
1908                }
1909                "minimum" => {
1910                    let m = args
1911                        .first()
1912                        .ok_or_else(|| "minimum requires an argument".to_string())?
1913                        .parse::<Decimal>()
1914                        .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1915                    *minimum = Some(m);
1916                }
1917                "maximum" => {
1918                    let m = args
1919                        .first()
1920                        .ok_or_else(|| "maximum requires an argument".to_string())?
1921                        .parse::<Decimal>()
1922                        .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1923                    *maximum = Some(m);
1924                }
1925                "help" => *help = args.first().cloned(),
1926                "default" => {
1927                    let d = args
1928                        .first()
1929                        .ok_or_else(|| "default requires an argument".to_string())?
1930                        .parse::<Decimal>()
1931                        .map_err(|_| format!("invalid default value: {:?}", args.first()))?;
1932                    *default = Some(d);
1933                }
1934                _ => {
1935                    return Err(format!(
1936                        "Invalid command '{}' for ratio type. Valid commands: unit, minimum, maximum, help, default",
1937                        command
1938                    ));
1939                }
1940            },
1941            TypeSpecification::Text {
1942                minimum,
1943                maximum,
1944                length,
1945                options,
1946                help,
1947                default,
1948            } => match command {
1949                "option" if args.len() == 1 => {
1950                    options.push(strip_surrounding_quotes(&args[0]));
1951                }
1952                "options" => {
1953                    *options = args.iter().map(|s| strip_surrounding_quotes(s)).collect();
1954                }
1955                "minimum" => {
1956                    let m = args
1957                        .first()
1958                        .ok_or_else(|| "minimum requires an argument".to_string())?
1959                        .parse::<usize>()
1960                        .map_err(|_| format!("invalid minimum value: {:?}", args.first()))?;
1961                    *minimum = Some(m);
1962                }
1963                "maximum" => {
1964                    let m = args
1965                        .first()
1966                        .ok_or_else(|| "maximum requires an argument".to_string())?
1967                        .parse::<usize>()
1968                        .map_err(|_| format!("invalid maximum value: {:?}", args.first()))?;
1969                    *maximum = Some(m);
1970                }
1971                "length" => {
1972                    let l = args
1973                        .first()
1974                        .ok_or_else(|| "length requires an argument".to_string())?
1975                        .parse::<usize>()
1976                        .map_err(|_| format!("invalid length value: {:?}", args.first()))?;
1977                    *length = Some(l);
1978                }
1979                "help" => *help = args.first().cloned(),
1980                "default" => {
1981                    let arg = args
1982                        .first()
1983                        .ok_or_else(|| "default requires an argument".to_string())?;
1984                    *default = Some(strip_surrounding_quotes(arg));
1985                }
1986                _ => {
1987                    return Err(format!(
1988                        "Invalid command '{}' for text type. Valid commands: options, minimum, maximum, length, help, default",
1989                        command
1990                    ));
1991                }
1992            },
1993            TypeSpecification::Date {
1994                minimum,
1995                maximum,
1996                help,
1997                default,
1998            } => match command {
1999                "minimum" => {
2000                    let arg = args
2001                        .first()
2002                        .ok_or_else(|| "default requires an argument".to_string())?;
2003                    let span = Span {
2004                        start: 0,
2005                        end: 0,
2006                        line: 1,
2007                        col: 0,
2008                    };
2009                    let Value::Date(date) = &standard_date()
2010                        .parse_value(arg, span.clone(), "<internal>", "<internal>")
2011                        .map_err(|_| format!("invalid default date value: {}", arg))?
2012                        .value
2013                    else {
2014                        return Err(format!("invalid default date value: {}", arg));
2015                    };
2016                    *minimum = Some(date.clone());
2017                }
2018                "maximum" => {
2019                    let arg = args
2020                        .first()
2021                        .ok_or_else(|| "default requires an argument".to_string())?;
2022                    let span = Span {
2023                        start: 0,
2024                        end: 0,
2025                        line: 1,
2026                        col: 0,
2027                    };
2028                    let Value::Date(date) = standard_date()
2029                        .parse_value(arg, span, "<internal>", "<internal>")
2030                        .map_err(|_| format!("invalid default date value: {}", arg))?
2031                        .value
2032                    else {
2033                        return Err(format!("invalid default date value: {}", arg));
2034                    };
2035                    *maximum = Some(date);
2036                }
2037                "help" => *help = args.first().cloned(),
2038                "default" => {
2039                    let arg = args
2040                        .first()
2041                        .ok_or_else(|| "default requires an argument".to_string())?;
2042                    let span = Span {
2043                        start: 0,
2044                        end: 0,
2045                        line: 1,
2046                        col: 0,
2047                    };
2048                    let Value::Date(date) = standard_date()
2049                        .parse_value(arg, span, "<internal>", "<internal>")
2050                        .map_err(|_| format!("invalid default date value: {}", arg))?
2051                        .value
2052                    else {
2053                        return Err(format!("invalid default date value: {}", arg));
2054                    };
2055                    *default = Some(date);
2056                }
2057                _ => {
2058                    return Err(format!(
2059                        "Invalid command '{}' for date type. Valid commands: minimum, maximum, help, default",
2060                        command
2061                    ));
2062                }
2063            },
2064            TypeSpecification::Time {
2065                minimum,
2066                maximum,
2067                help,
2068                default,
2069            } => match command {
2070                "minimum" => {
2071                    let arg = args
2072                        .first()
2073                        .ok_or_else(|| "minimum requires an argument".to_string())?;
2074                    let span = Span {
2075                        start: 0,
2076                        end: 0,
2077                        line: 1,
2078                        col: 0,
2079                    };
2080                    let Value::Time(time) = &standard_time()
2081                        .parse_value(arg, span.clone(), "<internal>", "<internal>")
2082                        .map_err(|_| format!("invalid minimum time value: {}", arg))?
2083                        .value
2084                    else {
2085                        return Err(format!("invalid minimum time value: {}", arg));
2086                    };
2087                    *minimum = Some(time.clone());
2088                }
2089                "maximum" => {
2090                    let arg = args
2091                        .first()
2092                        .ok_or_else(|| "maximum requires an argument".to_string())?;
2093                    let span = Span {
2094                        start: 0,
2095                        end: 0,
2096                        line: 1,
2097                        col: 0,
2098                    };
2099                    let Value::Time(time) = &standard_time()
2100                        .parse_value(arg, span.clone(), "<internal>", "<internal>")
2101                        .map_err(|_| format!("invalid maximum time value: {}", arg))?
2102                        .value
2103                    else {
2104                        return Err(format!("invalid maximum time value: {}", arg));
2105                    };
2106                    *maximum = Some(time.clone());
2107                }
2108                "help" => *help = args.first().cloned(),
2109                "default" => {
2110                    let arg = args
2111                        .first()
2112                        .ok_or_else(|| "default requires an argument".to_string())?;
2113                    let span = Span {
2114                        start: 0,
2115                        end: 0,
2116                        line: 1,
2117                        col: 0,
2118                    };
2119                    let Value::Time(time) = &standard_time()
2120                        .parse_value(arg, span, "<internal>", "<internal>")
2121                        .map_err(|_| format!("invalid default time value: {}", arg))?
2122                        .value
2123                    else {
2124                        return Err(format!("invalid default time value: {}", arg));
2125                    };
2126                    *default = Some(time.clone());
2127                }
2128                _ => {
2129                    return Err(format!(
2130                        "Invalid command '{}' for time type. Valid commands: minimum, maximum, help, default",
2131                        command
2132                    ));
2133                }
2134            },
2135            TypeSpecification::Duration { help, default } => match command {
2136                "help" => *help = args.first().cloned(),
2137                "default" if args.len() >= 2 => {
2138                    let value = args[0]
2139                        .parse::<Decimal>()
2140                        .map_err(|_| format!("invalid duration value: {}", args[0]))?;
2141                    let unit = args[1]
2142                        .parse::<DurationUnit>()
2143                        .map_err(|_| format!("invalid duration unit: {}", args[1]))?;
2144                    *default = Some((value, unit));
2145                }
2146                _ => {
2147                    return Err(format!(
2148                        "Invalid command '{}' for duration type. Valid commands: help, default",
2149                        command
2150                    ));
2151                }
2152            },
2153            TypeSpecification::Veto { .. } => {
2154                return Err(format!(
2155                    "Invalid command '{}' for veto type. Veto is not a user-declarable type and cannot have overrides",
2156                    command
2157                ));
2158            }
2159        }
2160        Ok(self)
2161    }
2162}
2163
2164/// User-defined type as it appears in the source (AST)
2165///
2166/// Supports these variants:
2167/// - Basic type: `type money = number`
2168/// - Basic type extension: `type money = number -> decimals 2 -> unit EUR 1.00 -> unit USD 1.18`
2169/// - From another custom type: `type currency from lemma`
2170/// - Shorthand with overrides: `type currency from lemma -> maximum 1000`
2171/// - Inline type definitions: `fact age = [number -> minimum 0 -> maximum 120]`
2172#[derive(Clone, Debug, PartialEq)]
2173pub enum TypeDef {
2174    /// Regular named type definition
2175    /// Example: `type money = number -> decimals 2`
2176    Regular {
2177        source_location: Source,
2178        name: String,
2179        parent: String,
2180        overrides: Option<Vec<(String, Vec<String>)>>,
2181    },
2182    /// Imported type from another document
2183    /// Example: `type currency from lemma` or `type currency from lemma -> maximum 1000`
2184    Import {
2185        source_location: Source,
2186        name: String,
2187        source_type: String,
2188        from: String,
2189        overrides: Option<Vec<(String, Vec<String>)>>,
2190    },
2191    /// Inline type definition
2192    /// Example: `fact age = [number -> minimum 0 -> maximum 120]`
2193    /// Example: `fact age = [age from lemma]`
2194    /// Example: `fact age = [age from lemma -> minimum 18]`
2195    Inline {
2196        source_location: Source,
2197        parent: String,
2198        overrides: Option<Vec<(String, Vec<String>)>>,
2199        fact_ref: FactReference,
2200        from: Option<String>,
2201    },
2202}
2203
2204impl TypeDef {
2205    pub fn source_location(&self) -> &Source {
2206        match self {
2207            TypeDef::Regular {
2208                source_location, ..
2209            }
2210            | TypeDef::Import {
2211                source_location, ..
2212            }
2213            | TypeDef::Inline {
2214                source_location, ..
2215            } => source_location,
2216        }
2217    }
2218}
2219
2220/// A fully resolved type used during evaluation
2221///
2222/// This combines the type specifications with all overrides already applied.
2223#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, serde::Deserialize)]
2224pub struct LemmaType {
2225    pub name: Option<String>,
2226    pub specifications: TypeSpecification,
2227}
2228
2229impl LemmaType {
2230    /// Create a new LemmaType with the given name and specifications
2231    pub fn new(name: String, specifications: TypeSpecification) -> Self {
2232        Self {
2233            name: Some(name),
2234            specifications,
2235        }
2236    }
2237
2238    /// Create a new LemmaType without a name (for standard types and inline fact definitions)
2239    pub fn without_name(specifications: TypeSpecification) -> Self {
2240        Self {
2241            name: None,
2242            specifications,
2243        }
2244    }
2245
2246    /// Get the name of this type, deriving from specifications if name is None
2247    pub fn name(&self) -> &str {
2248        match &self.name {
2249            Some(n) => n.as_str(),
2250            None => match &self.specifications {
2251                TypeSpecification::Boolean { .. } => "boolean",
2252                TypeSpecification::Scale { .. } => "scale",
2253                TypeSpecification::Number { .. } => "number",
2254                TypeSpecification::Text { .. } => "text",
2255                TypeSpecification::Date { .. } => "date",
2256                TypeSpecification::Time { .. } => "time",
2257                TypeSpecification::Duration { .. } => "duration",
2258                TypeSpecification::Ratio { .. } => "ratio",
2259                TypeSpecification::Veto { .. } => "veto",
2260            },
2261        }
2262    }
2263
2264    /// Check if this type is boolean
2265    pub fn is_boolean(&self) -> bool {
2266        matches!(&self.specifications, TypeSpecification::Boolean { .. })
2267    }
2268
2269    /// Check if this type is scale (has units)
2270    pub fn is_scale(&self) -> bool {
2271        matches!(&self.specifications, TypeSpecification::Scale { .. })
2272    }
2273
2274    /// Check if this type is number (dimensionless)
2275    pub fn is_number(&self) -> bool {
2276        matches!(&self.specifications, TypeSpecification::Number { .. })
2277    }
2278
2279    /// Check if this type is numeric (either scale or number)
2280    pub fn is_numeric(&self) -> bool {
2281        matches!(
2282            &self.specifications,
2283            TypeSpecification::Scale { .. } | TypeSpecification::Number { .. }
2284        )
2285    }
2286
2287    /// Check if this type is text
2288    pub fn is_text(&self) -> bool {
2289        matches!(&self.specifications, TypeSpecification::Text { .. })
2290    }
2291
2292    /// Check if this type is date
2293    pub fn is_date(&self) -> bool {
2294        matches!(&self.specifications, TypeSpecification::Date { .. })
2295    }
2296
2297    /// Check if this type is time
2298    pub fn is_time(&self) -> bool {
2299        matches!(&self.specifications, TypeSpecification::Time { .. })
2300    }
2301
2302    /// Check if this type is duration
2303    pub fn is_duration(&self) -> bool {
2304        matches!(&self.specifications, TypeSpecification::Duration { .. })
2305    }
2306
2307    /// Check if this type is ratio
2308    pub fn is_ratio(&self) -> bool {
2309        matches!(&self.specifications, TypeSpecification::Ratio { .. })
2310    }
2311
2312    /// Check if this type is veto
2313    pub fn is_veto(&self) -> bool {
2314        matches!(&self.specifications, TypeSpecification::Veto { .. })
2315    }
2316
2317    /// Check if two types have the same standard type specification (ignoring constraints/overrides)
2318    /// This compares only the TypeSpecification variant (Boolean, Number, Scale, Text, etc.)
2319    /// and ignores all constraints like minimum, maximum, decimals, units, options, etc.
2320    pub fn has_same_base_type(&self, other: &LemmaType) -> bool {
2321        use TypeSpecification::*;
2322        matches!(
2323            (&self.specifications, &other.specifications),
2324            (Boolean { .. }, Boolean { .. })
2325                | (Number { .. }, Number { .. })
2326                | (Scale { .. }, Scale { .. })
2327                | (Text { .. }, Text { .. })
2328                | (Date { .. }, Date { .. })
2329                | (Time { .. }, Time { .. })
2330                | (Duration { .. }, Duration { .. })
2331                | (Ratio { .. }, Ratio { .. })
2332                | (Veto { .. }, Veto { .. })
2333        )
2334    }
2335
2336    /// Create a Veto LemmaType (internal use only - not user-declarable)
2337    pub fn veto_type() -> Self {
2338        Self::without_name(TypeSpecification::veto())
2339    }
2340
2341    /// Create a LiteralValue from this type's default value, if one exists
2342    pub fn create_default_value(&self) -> Option<LiteralValue> {
2343        use TypeSpecification::*;
2344        match &self.specifications {
2345            Boolean { default, .. } => default
2346                .map(|b| LiteralValue::boolean_with_type(BooleanValue::from(b), self.clone())),
2347            Text { default, .. } => default
2348                .as_ref()
2349                .map(|s| LiteralValue::text_with_type(s.clone(), self.clone())),
2350            Number { default, .. } => {
2351                default.map(|d| LiteralValue::number_with_type(d, self.clone()))
2352            }
2353            Scale { default, .. } => default.as_ref().map(|(value, unit_name)| {
2354                LiteralValue::scale_with_type(*value, Some(unit_name.clone()), self.clone())
2355            }),
2356            Ratio { default, .. } => {
2357                default.map(|d| LiteralValue::ratio_with_type(d, None, self.clone()))
2358            }
2359            Date { default, .. } => default
2360                .as_ref()
2361                .map(|d| LiteralValue::date_with_type(d.clone(), self.clone())),
2362            Time { default, .. } => default
2363                .as_ref()
2364                .map(|t| LiteralValue::time_with_type(t.clone(), self.clone())),
2365            Duration { default, .. } => default
2366                .as_ref()
2367                .map(|(v, u)| LiteralValue::duration_with_type(*v, u.clone(), self.clone())),
2368            Veto { .. } => None,
2369        }
2370    }
2371
2372    /// Parse a raw string value into a LiteralValue according to this type.
2373    ///
2374    /// Parsing errors are anchored to the provided source location (`span`, `attribute`, `doc_name`).
2375    pub fn parse_value(
2376        &self,
2377        raw: &str,
2378        span: Span,
2379        attribute: &str,
2380        doc_name: &str,
2381    ) -> Result<LiteralValue, LemmaError> {
2382        let value = match &self.specifications {
2383            TypeSpecification::Boolean { .. } => {
2384                Self::parse_boolean_value(raw, span.clone(), attribute, doc_name)?
2385            }
2386            TypeSpecification::Scale { .. } => {
2387                Self::parse_scale_value(raw, self, span.clone(), attribute, doc_name)?
2388            }
2389            TypeSpecification::Number { .. } => {
2390                Self::parse_number_value(raw, span.clone(), attribute, doc_name)?
2391            }
2392            TypeSpecification::Text { .. } => Self::parse_text_value(raw)?,
2393            TypeSpecification::Date { .. } => {
2394                Self::parse_date_value(raw, span.clone(), attribute, doc_name)?
2395            }
2396            TypeSpecification::Time { .. } => {
2397                Self::parse_time_value(raw, span.clone(), attribute, doc_name)?
2398            }
2399            TypeSpecification::Duration { .. } => {
2400                Self::parse_duration_value(raw, span.clone(), attribute, doc_name)?
2401            }
2402            TypeSpecification::Ratio { .. } => {
2403                Self::parse_ratio_value(raw, span.clone(), attribute, doc_name)?
2404            }
2405            TypeSpecification::Veto { .. } => {
2406                return Err(LemmaError::engine(
2407                    "Cannot parse value for veto type - veto is not a user-declarable type",
2408                    span,
2409                    attribute,
2410                    Arc::from(raw),
2411                    doc_name,
2412                    1,
2413                    None::<String>,
2414                ));
2415            }
2416        };
2417        // Create LiteralValue with the appropriate helper method based on value type
2418        Ok(match &value {
2419            Value::Number(n) => LiteralValue::number_with_type(*n, self.clone()),
2420            Value::Scale(n, u) => LiteralValue::scale_with_type(*n, u.clone(), self.clone()),
2421            Value::Text(s) => LiteralValue::text_with_type(s.clone(), self.clone()),
2422            Value::Boolean(b) => LiteralValue::boolean_with_type(b.clone(), self.clone()),
2423            Value::Date(d) => LiteralValue::date_with_type(d.clone(), self.clone()),
2424            Value::Time(t) => LiteralValue::time_with_type(t.clone(), self.clone()),
2425            Value::Duration(v, u) => LiteralValue::duration_with_type(*v, u.clone(), self.clone()),
2426            Value::Ratio(r, u) => LiteralValue::ratio_with_type(*r, u.clone(), self.clone()),
2427        })
2428    }
2429
2430    fn parse_text_value(raw: &str) -> Result<Value, LemmaError> {
2431        Ok(Value::Text(raw.to_string()))
2432    }
2433
2434    fn parse_scale_value(
2435        raw: &str,
2436        lemma_type: &LemmaType,
2437        span: Span,
2438        attribute: &str,
2439        doc_name: &str,
2440    ) -> Result<Value, LemmaError> {
2441        let trimmed = raw.trim();
2442
2443        // Parse number and optional unit from string
2444        // Handles: "50", "50 celsius", "50celsius", "1,234.56 celsius", etc.
2445
2446        // First, try to find where the number part ends
2447        // Numbers can contain: digits, decimal point, sign, underscore, comma
2448        let mut number_end = 0;
2449        let chars: Vec<char> = trimmed.chars().collect();
2450        let mut has_decimal = false;
2451
2452        // Skip leading sign
2453        let start = if chars.first().is_some_and(|c| *c == '+' || *c == '-') {
2454            1
2455        } else {
2456            0
2457        };
2458
2459        for (i, &ch) in chars.iter().enumerate().skip(start) {
2460            match ch {
2461                '0'..='9' => number_end = i + 1,
2462                '.' if !has_decimal => {
2463                    has_decimal = true;
2464                    number_end = i + 1;
2465                }
2466                '_' | ',' => {
2467                    // Thousand separators - continue scanning
2468                    number_end = i + 1;
2469                }
2470                _ => {
2471                    // Non-numeric character - number ends here
2472                    break;
2473                }
2474            }
2475        }
2476
2477        // Extract number and unit parts
2478        let number_part = trimmed[..number_end].trim();
2479        let unit_part = trimmed[number_end..].trim();
2480
2481        // Clean number part (remove separators for parsing)
2482        let clean_number = number_part.replace(['_', ','], "");
2483        let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2484            LemmaError::engine(
2485                format!("Invalid scale string: '{}' is not a valid number", raw),
2486                span.clone(),
2487                attribute,
2488                Arc::from(raw),
2489                doc_name,
2490                1,
2491                None::<String>,
2492            )
2493        })?;
2494
2495        // Validate unit against type definition
2496        let allowed_units = match &lemma_type.specifications {
2497            TypeSpecification::Scale { units, .. } => units,
2498            _ => unreachable!(
2499                "BUG: parse_scale_value called with non-scale type {}",
2500                lemma_type.name()
2501            ),
2502        };
2503
2504        if unit_part.is_empty() && !allowed_units.is_empty() {
2505            let valid: Vec<String> = allowed_units.iter().map(|u| u.name.clone()).collect();
2506            return Err(LemmaError::engine(
2507                format!(
2508                    "Missing unit for scale type. Valid units: {}",
2509                    valid.join(", ")
2510                ),
2511                span.clone(),
2512                attribute,
2513                Arc::from(raw),
2514                doc_name,
2515                1,
2516                None::<String>,
2517            ));
2518        }
2519
2520        let unit = if unit_part.is_empty() {
2521            None
2522        } else {
2523            let unit_matched = allowed_units
2524                .iter()
2525                .find(|u| u.name.eq_ignore_ascii_case(unit_part));
2526
2527            if let Some(unit_def) = unit_matched {
2528                Some(unit_def.name.clone())
2529            } else {
2530                let valid: Vec<String> = allowed_units.iter().map(|u| u.name.clone()).collect();
2531                return Err(LemmaError::engine(
2532                    format!(
2533                        "Invalid unit '{}' for scale type. Valid units: {}",
2534                        unit_part,
2535                        valid.join(", ")
2536                    ),
2537                    span,
2538                    attribute,
2539                    Arc::from(raw),
2540                    doc_name,
2541                    1,
2542                    None::<String>,
2543                ));
2544            }
2545        };
2546
2547        Ok(Value::Scale(decimal, unit))
2548    }
2549
2550    fn parse_number_value(
2551        raw: &str,
2552        span: Span,
2553        attribute: &str,
2554        doc_name: &str,
2555    ) -> Result<Value, LemmaError> {
2556        let clean_number = raw.replace(['_', ','], "");
2557        let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2558            LemmaError::engine(
2559                format!("Invalid number: '{}'. Expected a valid decimal number (e.g., 42, 3.14, 1_000_000)", raw),
2560                span,
2561                attribute,
2562                Arc::from(raw),
2563                doc_name,
2564                1,
2565                None::<String>,
2566            )
2567        })?;
2568        Ok(Value::Number(decimal))
2569    }
2570
2571    fn parse_boolean_value(
2572        raw: &str,
2573        span: Span,
2574        attribute: &str,
2575        doc_name: &str,
2576    ) -> Result<Value, LemmaError> {
2577        let boolean_value: BooleanValue = raw.parse().map_err(|_| {
2578            LemmaError::engine(
2579                format!(
2580                    "Invalid boolean: '{}'. Expected one of: true, false, yes, no, accept, reject",
2581                    raw
2582                ),
2583                span,
2584                attribute,
2585                Arc::from(raw),
2586                doc_name,
2587                1,
2588                None::<String>,
2589            )
2590        })?;
2591        Ok(Value::Boolean(boolean_value))
2592    }
2593
2594    fn parse_date_value(
2595        raw: &str,
2596        span: Span,
2597        attribute: &str,
2598        doc_name: &str,
2599    ) -> Result<Value, LemmaError> {
2600        let datetime_str = raw.trim();
2601
2602        if let Ok(dt) = datetime_str.parse::<chrono::DateTime<chrono::FixedOffset>>() {
2603            let offset = dt.offset().local_minus_utc();
2604            return Ok(Value::Date(DateTimeValue {
2605                year: dt.year(),
2606                month: dt.month(),
2607                day: dt.day(),
2608                hour: dt.hour(),
2609                minute: dt.minute(),
2610                second: dt.second(),
2611                timezone: Some(TimezoneValue {
2612                    offset_hours: (offset / 3600) as i8,
2613                    offset_minutes: ((offset % 3600) / 60) as u8,
2614                }),
2615            }));
2616        }
2617
2618        if let Ok(dt) = datetime_str.parse::<chrono::NaiveDateTime>() {
2619            return Ok(Value::Date(DateTimeValue {
2620                year: dt.year(),
2621                month: dt.month(),
2622                day: dt.day(),
2623                hour: dt.hour(),
2624                minute: dt.minute(),
2625                second: dt.second(),
2626                timezone: None,
2627            }));
2628        }
2629
2630        if let Ok(d) = datetime_str.parse::<chrono::NaiveDate>() {
2631            return Ok(Value::Date(DateTimeValue {
2632                year: d.year(),
2633                month: d.month(),
2634                day: d.day(),
2635                hour: 0,
2636                minute: 0,
2637                second: 0,
2638                timezone: None,
2639            }));
2640        }
2641
2642        Err(LemmaError::engine(
2643            format!("Invalid date/time format: '{}'. Expected one of: YYYY-MM-DD, YYYY-MM-DDTHH:MM:SS, or YYYY-MM-DDTHH:MM:SSZ", raw),
2644            span,
2645            attribute,
2646            Arc::from(raw),
2647            doc_name,
2648            1,
2649            None::<String>,
2650        ))
2651    }
2652
2653    fn parse_time_value(
2654        raw: &str,
2655        span: Span,
2656        attribute: &str,
2657        doc_name: &str,
2658    ) -> Result<Value, LemmaError> {
2659        let time_str = raw.trim();
2660
2661        // Try parsing with timezone (HH:MM:SSZ or HH:MM:SS+HH:MM)
2662        if let Ok(dt) = time_str.parse::<chrono::DateTime<chrono::FixedOffset>>() {
2663            let offset = dt.offset().local_minus_utc();
2664            return Ok(Value::Time(TimeValue {
2665                hour: dt.hour() as u8,
2666                minute: dt.minute() as u8,
2667                second: dt.second() as u8,
2668                timezone: Some(TimezoneValue {
2669                    offset_hours: (offset / 3600) as i8,
2670                    offset_minutes: ((offset % 3600) / 60) as u8,
2671                }),
2672            }));
2673        }
2674
2675        // Try parsing as NaiveTime (HH:MM:SS or HH:MM)
2676        if let Ok(nt) = time_str.parse::<chrono::NaiveTime>() {
2677            return Ok(Value::Time(TimeValue {
2678                hour: nt.hour() as u8,
2679                minute: nt.minute() as u8,
2680                second: nt.second() as u8,
2681                timezone: None,
2682            }));
2683        }
2684
2685        // Try parsing manually for formats like "14:30" or "14:30:00"
2686        let parts: Vec<&str> = time_str.split(':').collect();
2687        if parts.len() == 2 || parts.len() == 3 {
2688            if let (Ok(hour_u32), Ok(minute_u32)) =
2689                (parts[0].parse::<u32>(), parts[1].parse::<u32>())
2690            {
2691                if hour_u32 < 24 && minute_u32 < 60 {
2692                    let second_u32 = if parts.len() == 3 {
2693                        let Ok(s) = parts[2].parse::<u32>() else {
2694                            return Err(LemmaError::engine(
2695                                format!(
2696                                    "Invalid time format: '{}'. Expected: HH:MM or HH:MM:SS (e.g., 14:30 or 14:30:00)",
2697                                    raw
2698                                ),
2699                                span.clone(),
2700                                attribute,
2701                                Arc::from(raw),
2702                                doc_name,
2703                                1,
2704                                None::<String>,
2705                            ));
2706                        };
2707                        s
2708                    } else {
2709                        0
2710                    };
2711                    if second_u32 < 60 {
2712                        return Ok(Value::Time(TimeValue {
2713                            hour: hour_u32 as u8,
2714                            minute: minute_u32 as u8,
2715                            second: second_u32 as u8,
2716                            timezone: None,
2717                        }));
2718                    }
2719                }
2720            }
2721        }
2722
2723        Err(LemmaError::engine(
2724            format!(
2725                "Invalid time format: '{}'. Expected: HH:MM or HH:MM:SS (e.g., 14:30 or 14:30:00)",
2726                raw
2727            ),
2728            span,
2729            attribute,
2730            Arc::from(raw),
2731            doc_name,
2732            1,
2733            None::<String>,
2734        ))
2735    }
2736
2737    fn parse_duration_value(
2738        raw: &str,
2739        span: Span,
2740        attribute: &str,
2741        doc_name: &str,
2742    ) -> Result<Value, LemmaError> {
2743        // Parse duration like "90 minutes" or "2 hours"
2744        let parts: Vec<&str> = raw.split_whitespace().collect();
2745        if parts.len() != 2 {
2746            return Err(LemmaError::engine(
2747                format!(
2748                    "Invalid duration: '{}'. Expected format: NUMBER UNIT (e.g., '90 minutes')",
2749                    raw
2750                ),
2751                span.clone(),
2752                attribute,
2753                Arc::from(raw),
2754                doc_name,
2755                1,
2756                None::<String>,
2757            ));
2758        }
2759
2760        let number_str = parts[0].replace(['_', ','], "");
2761        let value = Decimal::from_str(&number_str).map_err(|_| {
2762            LemmaError::engine(
2763                format!("Invalid number in duration: '{}'", parts[0]),
2764                span.clone(),
2765                attribute,
2766                Arc::from(raw),
2767                doc_name,
2768                1,
2769                None::<String>,
2770            )
2771        })?;
2772        let unit = parts[1];
2773
2774        // Parse duration unit
2775        let unit_lower = unit.to_lowercase();
2776        let duration_unit = match unit_lower.as_str() {
2777            "year" | "years" => DurationUnit::Year,
2778            "month" | "months" => DurationUnit::Month,
2779            "week" | "weeks" => DurationUnit::Week,
2780            "day" | "days" => DurationUnit::Day,
2781            "hour" | "hours" => DurationUnit::Hour,
2782            "minute" | "minutes" => DurationUnit::Minute,
2783            "second" | "seconds" => DurationUnit::Second,
2784            "millisecond" | "milliseconds" => DurationUnit::Millisecond,
2785            "microsecond" | "microseconds" => DurationUnit::Microsecond,
2786            _ => {
2787                return Err(LemmaError::engine(
2788                    format!("Unknown duration unit: '{}'. Expected one of: years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds", unit),
2789                    span,
2790                    attribute,
2791                    Arc::from(raw),
2792                    doc_name,
2793                    1,
2794                    None::<String>,
2795                ));
2796            }
2797        };
2798        Ok(Value::Duration(value, duration_unit))
2799    }
2800
2801    fn parse_ratio_value(
2802        raw: &str,
2803        span: Span,
2804        attribute: &str,
2805        doc_name: &str,
2806    ) -> Result<Value, LemmaError> {
2807        // Parse ratio as a decimal number
2808        let clean_number = raw.replace(['_', ','], "");
2809        let decimal = Decimal::from_str(&clean_number).map_err(|_| {
2810            LemmaError::engine(
2811                format!("Invalid ratio: '{}'. Expected a valid decimal number", raw),
2812                span,
2813                attribute,
2814                Arc::from(raw),
2815                doc_name,
2816                1,
2817                None::<String>,
2818            )
2819        })?;
2820        Ok(Value::Ratio(decimal, None))
2821    }
2822}
2823
2824// Private statics for lazy initialization
2825static STANDARD_BOOLEAN: OnceLock<LemmaType> = OnceLock::new();
2826static STANDARD_SCALE: OnceLock<LemmaType> = OnceLock::new();
2827static STANDARD_NUMBER: OnceLock<LemmaType> = OnceLock::new();
2828static STANDARD_TEXT: OnceLock<LemmaType> = OnceLock::new();
2829static STANDARD_DATE: OnceLock<LemmaType> = OnceLock::new();
2830static STANDARD_TIME: OnceLock<LemmaType> = OnceLock::new();
2831static STANDARD_DURATION: OnceLock<LemmaType> = OnceLock::new();
2832static STANDARD_RATIO: OnceLock<LemmaType> = OnceLock::new();
2833
2834/// Get the standard boolean type
2835pub fn standard_boolean() -> &'static LemmaType {
2836    STANDARD_BOOLEAN.get_or_init(|| LemmaType {
2837        name: None,
2838        specifications: TypeSpecification::Boolean {
2839            help: None,
2840            default: None,
2841        },
2842    })
2843}
2844
2845/// Get the standard scale type (can have units)
2846pub fn standard_scale() -> &'static LemmaType {
2847    STANDARD_SCALE.get_or_init(|| LemmaType {
2848        name: None,
2849        specifications: TypeSpecification::Scale {
2850            minimum: None,
2851            maximum: None,
2852            decimals: None,
2853            precision: None,
2854            units: Vec::new(),
2855            help: None,
2856            default: None,
2857        },
2858    })
2859}
2860
2861/// Get the standard number type (dimensionless, no units)
2862pub fn standard_number() -> &'static LemmaType {
2863    STANDARD_NUMBER.get_or_init(|| LemmaType {
2864        name: None,
2865        specifications: TypeSpecification::Number {
2866            minimum: None,
2867            maximum: None,
2868            decimals: None,
2869            precision: None,
2870            help: None,
2871            default: None,
2872        },
2873    })
2874}
2875
2876/// Get the standard text type
2877pub fn standard_text() -> &'static LemmaType {
2878    STANDARD_TEXT.get_or_init(|| LemmaType {
2879        name: None,
2880        specifications: TypeSpecification::Text {
2881            minimum: None,
2882            maximum: None,
2883            length: None,
2884            options: Vec::new(),
2885            help: None,
2886            default: None,
2887        },
2888    })
2889}
2890
2891/// Get the standard date type
2892pub fn standard_date() -> &'static LemmaType {
2893    STANDARD_DATE.get_or_init(|| LemmaType {
2894        name: None,
2895        specifications: TypeSpecification::Date {
2896            minimum: None,
2897            maximum: None,
2898            help: None,
2899            default: None,
2900        },
2901    })
2902}
2903
2904/// Get the standard time type
2905pub fn standard_time() -> &'static LemmaType {
2906    STANDARD_TIME.get_or_init(|| LemmaType {
2907        name: None,
2908        specifications: TypeSpecification::Time {
2909            minimum: None,
2910            maximum: None,
2911            help: None,
2912            default: None,
2913        },
2914    })
2915}
2916
2917/// Get the standard duration type
2918pub fn standard_duration() -> &'static LemmaType {
2919    STANDARD_DURATION.get_or_init(|| LemmaType {
2920        name: None,
2921        specifications: TypeSpecification::Duration {
2922            help: None,
2923            default: None,
2924        },
2925    })
2926}
2927
2928/// Get the standard ratio type
2929pub fn standard_ratio() -> &'static LemmaType {
2930    STANDARD_RATIO.get_or_init(|| LemmaType {
2931        name: None,
2932        specifications: TypeSpecification::Ratio {
2933            minimum: None,
2934            maximum: None,
2935            units: vec![
2936                Unit {
2937                    name: "percent".to_string(),
2938                    value: Decimal::from(100),
2939                },
2940                Unit {
2941                    name: "permille".to_string(),
2942                    value: Decimal::from(1000),
2943                },
2944            ],
2945            help: None,
2946            default: None,
2947        },
2948    })
2949}
2950
2951// Helper macros to initialize standard types
2952
2953impl LemmaType {
2954    /// Get an example value string for this type, suitable for UI help text
2955    pub fn example_value(&self) -> &'static str {
2956        match &self.specifications {
2957            TypeSpecification::Text { .. } => "\"hello world\"",
2958            TypeSpecification::Scale { .. } => "3.14",
2959            TypeSpecification::Number { .. } => "3.14",
2960            TypeSpecification::Boolean { .. } => "true",
2961            TypeSpecification::Date { .. } => "2023-12-25T14:30:00Z",
2962            TypeSpecification::Veto { .. } => "veto",
2963            TypeSpecification::Time { .. } => "14:30:00",
2964            TypeSpecification::Duration { .. } => "90 minutes",
2965            TypeSpecification::Ratio { .. } => "50%",
2966        }
2967    }
2968}
2969
2970fn strip_surrounding_quotes(s: &str) -> String {
2971    let bytes = s.as_bytes();
2972    if bytes.len() >= 2 {
2973        let first = bytes[0];
2974        let last = bytes[bytes.len() - 1];
2975        if (first == b'"' && last == b'"') || (first == b'\'' && last == b'\'') {
2976            return s[1..bytes.len() - 1].to_string();
2977        }
2978    }
2979    s.to_string()
2980}
2981
2982impl fmt::Display for LemmaType {
2983    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2984        write!(f, "{}", self.name())
2985    }
2986}
2987
2988#[cfg(test)]
2989mod tests {
2990    use super::*;
2991    use rust_decimal::Decimal;
2992    use std::str::FromStr;
2993
2994    #[test]
2995    fn test_arithmetic_operation_name() {
2996        assert_eq!(ArithmeticComputation::Add.name(), "addition");
2997        assert_eq!(ArithmeticComputation::Subtract.name(), "subtraction");
2998        assert_eq!(ArithmeticComputation::Multiply.name(), "multiplication");
2999        assert_eq!(ArithmeticComputation::Divide.name(), "division");
3000        assert_eq!(ArithmeticComputation::Modulo.name(), "modulo");
3001        assert_eq!(ArithmeticComputation::Power.name(), "exponentiation");
3002    }
3003
3004    #[test]
3005    fn test_comparison_operator_name() {
3006        assert_eq!(ComparisonComputation::GreaterThan.name(), "greater than");
3007        assert_eq!(ComparisonComputation::LessThan.name(), "less than");
3008        assert_eq!(
3009            ComparisonComputation::GreaterThanOrEqual.name(),
3010            "greater than or equal"
3011        );
3012        assert_eq!(
3013            ComparisonComputation::LessThanOrEqual.name(),
3014            "less than or equal"
3015        );
3016        assert_eq!(ComparisonComputation::Equal.name(), "equal");
3017        assert_eq!(ComparisonComputation::NotEqual.name(), "not equal");
3018        assert_eq!(ComparisonComputation::Is.name(), "is");
3019        assert_eq!(ComparisonComputation::IsNot.name(), "is not");
3020    }
3021
3022    #[test]
3023    fn test_literal_value_to_standard_type() {
3024        let one = Decimal::from_str("1").unwrap();
3025
3026        assert_eq!(LiteralValue::text("".to_string()).lemma_type.name(), "text");
3027        assert_eq!(LiteralValue::number(one).lemma_type.name(), "number");
3028        assert_eq!(
3029            LiteralValue::boolean(BooleanValue::True).lemma_type.name(),
3030            "boolean"
3031        );
3032
3033        let dt = DateTimeValue {
3034            year: 2024,
3035            month: 1,
3036            day: 1,
3037            hour: 0,
3038            minute: 0,
3039            second: 0,
3040            timezone: None,
3041        };
3042        assert_eq!(LiteralValue::date(dt).lemma_type.name(), "date");
3043        assert_eq!(
3044            LiteralValue::ratio(one / Decimal::from(100), Some("percent".to_string()))
3045                .lemma_type
3046                .name(),
3047            "ratio"
3048        );
3049        assert_eq!(
3050            LiteralValue::duration(one, DurationUnit::Second)
3051                .lemma_type
3052                .name(),
3053            "duration"
3054        );
3055    }
3056
3057    #[test]
3058    fn test_arithmetic_operation_display() {
3059        assert_eq!(format!("{}", ArithmeticComputation::Add), "+");
3060        assert_eq!(format!("{}", ArithmeticComputation::Subtract), "-");
3061        assert_eq!(format!("{}", ArithmeticComputation::Multiply), "*");
3062        assert_eq!(format!("{}", ArithmeticComputation::Divide), "/");
3063        assert_eq!(format!("{}", ArithmeticComputation::Modulo), "%");
3064        assert_eq!(format!("{}", ArithmeticComputation::Power), "^");
3065    }
3066
3067    #[test]
3068    fn test_comparison_operator_display() {
3069        assert_eq!(format!("{}", ComparisonComputation::GreaterThan), ">");
3070        assert_eq!(format!("{}", ComparisonComputation::LessThan), "<");
3071        assert_eq!(
3072            format!("{}", ComparisonComputation::GreaterThanOrEqual),
3073            ">="
3074        );
3075        assert_eq!(format!("{}", ComparisonComputation::LessThanOrEqual), "<=");
3076        assert_eq!(format!("{}", ComparisonComputation::Equal), "==");
3077        assert_eq!(format!("{}", ComparisonComputation::NotEqual), "!=");
3078        assert_eq!(format!("{}", ComparisonComputation::Is), "is");
3079        assert_eq!(format!("{}", ComparisonComputation::IsNot), "is not");
3080    }
3081
3082    #[test]
3083    fn test_duration_unit_display() {
3084        assert_eq!(format!("{}", DurationUnit::Second), "seconds");
3085        assert_eq!(format!("{}", DurationUnit::Minute), "minutes");
3086        assert_eq!(format!("{}", DurationUnit::Hour), "hours");
3087        assert_eq!(format!("{}", DurationUnit::Day), "days");
3088        assert_eq!(format!("{}", DurationUnit::Week), "weeks");
3089        assert_eq!(format!("{}", DurationUnit::Millisecond), "milliseconds");
3090        assert_eq!(format!("{}", DurationUnit::Microsecond), "microseconds");
3091    }
3092
3093    #[test]
3094    fn test_conversion_target_display() {
3095        assert_eq!(format!("{}", ConversionTarget::Percentage), "percent");
3096        assert_eq!(
3097            format!("{}", ConversionTarget::Duration(DurationUnit::Hour)),
3098            "hours"
3099        );
3100        assert_eq!(
3101            format!("{}", ConversionTarget::ScaleUnit("usd".to_string())),
3102            "usd"
3103        );
3104    }
3105
3106    #[test]
3107    fn test_doc_type_display() {
3108        assert_eq!(format!("{}", standard_text()), "text");
3109        assert_eq!(format!("{}", standard_number()), "number");
3110        assert_eq!(format!("{}", standard_date()), "date");
3111        assert_eq!(format!("{}", standard_boolean()), "boolean");
3112        assert_eq!(format!("{}", standard_duration()), "duration");
3113    }
3114
3115    #[test]
3116    fn test_type_constructor() {
3117        let specs = TypeSpecification::number();
3118        let lemma_type = LemmaType::new("dice".to_string(), specs);
3119        assert_eq!(lemma_type.name(), "dice");
3120    }
3121
3122    #[test]
3123    fn test_type_display() {
3124        let specs = TypeSpecification::text();
3125        let lemma_type = LemmaType::new("name".to_string(), specs);
3126        assert_eq!(format!("{}", lemma_type), "name");
3127    }
3128
3129    #[test]
3130    fn test_type_equality() {
3131        let specs1 = TypeSpecification::number();
3132        let specs2 = TypeSpecification::number();
3133        let lemma_type1 = LemmaType::new("dice".to_string(), specs1);
3134        let lemma_type2 = LemmaType::new("dice".to_string(), specs2);
3135        assert_eq!(lemma_type1, lemma_type2);
3136    }
3137
3138    #[test]
3139    fn test_type_serialization() {
3140        let specs = TypeSpecification::number();
3141        let lemma_type = LemmaType::new("dice".to_string(), specs);
3142        let serialized = serde_json::to_string(&lemma_type).unwrap();
3143        let deserialized: LemmaType = serde_json::from_str(&serialized).unwrap();
3144        assert_eq!(lemma_type, deserialized);
3145    }
3146
3147    #[test]
3148    fn test_literal_value_display_value() {
3149        let ten = Decimal::from_str("10").unwrap();
3150
3151        assert_eq!(
3152            LiteralValue::text("hello".to_string()).display_value(),
3153            "\"hello\""
3154        );
3155        assert_eq!(LiteralValue::number(ten).display_value(), "10");
3156        assert_eq!(
3157            LiteralValue::boolean(BooleanValue::True).display_value(),
3158            "true"
3159        );
3160        assert_eq!(
3161            LiteralValue::boolean(BooleanValue::False).display_value(),
3162            "false"
3163        );
3164        // 10% stored as 0.10 ratio with "percent" unit
3165        let ten_percent_ratio = LiteralValue::ratio(
3166            Decimal::from_str("0.10").unwrap(),
3167            Some("percent".to_string()),
3168        );
3169        // ratio with "percent" unit should display as percent
3170        assert_eq!(ten_percent_ratio.display_value(), "10%");
3171
3172        let time = TimeValue {
3173            hour: 14,
3174            minute: 30,
3175            second: 0,
3176            timezone: None,
3177        };
3178        let time_display = LiteralValue::time(time).display_value();
3179        assert!(time_display.contains("14"));
3180        assert!(time_display.contains("30"));
3181    }
3182
3183    #[test]
3184    fn test_literal_value_time_type() {
3185        let time = TimeValue {
3186            hour: 14,
3187            minute: 30,
3188            second: 0,
3189            timezone: None,
3190        };
3191        assert_eq!(LiteralValue::time(time).lemma_type.name(), "time");
3192    }
3193
3194    #[test]
3195    fn test_datetime_value_display() {
3196        let dt = DateTimeValue {
3197            year: 2024,
3198            month: 12,
3199            day: 25,
3200            hour: 14,
3201            minute: 30,
3202            second: 45,
3203            timezone: Some(TimezoneValue {
3204                offset_hours: 1,
3205                offset_minutes: 0,
3206            }),
3207        };
3208        let display = format!("{}", dt);
3209        assert!(display.contains("2024"));
3210        assert!(display.contains("12"));
3211        assert!(display.contains("25"));
3212    }
3213
3214    #[test]
3215    fn test_time_value_display() {
3216        let time = TimeValue {
3217            hour: 14,
3218            minute: 30,
3219            second: 45,
3220            timezone: Some(TimezoneValue {
3221                offset_hours: -5,
3222                offset_minutes: 30,
3223            }),
3224        };
3225        let display = format!("{}", time);
3226        assert!(display.contains("14"));
3227        assert!(display.contains("30"));
3228        assert!(display.contains("45"));
3229    }
3230
3231    #[test]
3232    fn test_timezone_value() {
3233        let tz_positive = TimezoneValue {
3234            offset_hours: 5,
3235            offset_minutes: 30,
3236        };
3237        assert_eq!(tz_positive.offset_hours, 5);
3238        assert_eq!(tz_positive.offset_minutes, 30);
3239
3240        let tz_negative = TimezoneValue {
3241            offset_hours: -8,
3242            offset_minutes: 0,
3243        };
3244        assert_eq!(tz_negative.offset_hours, -8);
3245    }
3246
3247    #[test]
3248    fn test_negation_types() {
3249        let json = serde_json::to_string(&NegationType::Not).expect("serialize NegationType");
3250        let decoded: NegationType = serde_json::from_str(&json).expect("deserialize NegationType");
3251        assert_eq!(decoded, NegationType::Not);
3252    }
3253
3254    #[test]
3255    fn test_veto_expression() {
3256        let veto_with_message = VetoExpression {
3257            message: Some("Must be over 18".to_string()),
3258        };
3259        assert_eq!(
3260            veto_with_message.message,
3261            Some("Must be over 18".to_string())
3262        );
3263
3264        let veto_without_message = VetoExpression { message: None };
3265        assert!(veto_without_message.message.is_none());
3266    }
3267
3268    #[test]
3269    fn test_expression_get_source_text_with_location() {
3270        use crate::{Expression, ExpressionKind, LiteralValue, Source, Span};
3271        use std::collections::HashMap;
3272
3273        let source = "fact value = 42";
3274        let mut sources = HashMap::new();
3275        sources.insert("test.lemma".to_string(), source.to_string());
3276
3277        let span = Span {
3278            start: 13,
3279            end: 15,
3280            line: 1,
3281            col: 13,
3282        };
3283        let source_location = Some(Source::new("test.lemma", span, "test"));
3284        let expr = Expression::new(
3285            ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3286            source_location,
3287        );
3288
3289        assert_eq!(expr.get_source_text(&sources), Some("42".to_string()));
3290    }
3291
3292    #[test]
3293    fn test_expression_get_source_text_no_location() {
3294        use crate::{Expression, ExpressionKind, LiteralValue};
3295        use std::collections::HashMap;
3296
3297        let mut sources = HashMap::new();
3298        sources.insert("test.lemma".to_string(), "fact value = 42".to_string());
3299
3300        let expr = Expression::new(
3301            ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3302            None,
3303        );
3304
3305        assert_eq!(expr.get_source_text(&sources), None);
3306    }
3307
3308    #[test]
3309    fn test_expression_get_source_text_source_not_found() {
3310        use crate::{Expression, ExpressionKind, LiteralValue, Source, Span};
3311        use std::collections::HashMap;
3312
3313        let sources = HashMap::new();
3314        let span = Span {
3315            start: 0,
3316            end: 5,
3317            line: 1,
3318            col: 0,
3319        };
3320        let source_location = Some(Source::new("missing.lemma", span, "test"));
3321        let expr = Expression::new(
3322            ExpressionKind::Literal(LiteralValue::number(rust_decimal::Decimal::new(42, 0))),
3323            source_location,
3324        );
3325
3326        assert_eq!(expr.get_source_text(&sources), None);
3327    }
3328}