Skip to main content

lemma/parsing/
parser.rs

1use crate::error::Error;
2use crate::limits::ResourceLimits;
3use crate::parsing::ast::*;
4use crate::parsing::lexer::{
5    can_be_label, can_be_reference_segment, is_boolean_keyword, is_duration_unit, is_math_function,
6    is_spec_body_keyword, is_structural_keyword, is_type_keyword, Lexer, Token, TokenKind,
7};
8use crate::parsing::source::Source;
9use rust_decimal::Decimal;
10use std::str::FromStr;
11use std::sync::Arc;
12
13type TypeArrowChain = (String, Option<SpecRef>, Option<Vec<Constraint>>);
14
15pub struct ParseResult {
16    pub specs: Vec<LemmaSpec>,
17    pub expression_count: usize,
18}
19
20pub fn parse(
21    content: &str,
22    attribute: &str,
23    limits: &ResourceLimits,
24) -> Result<ParseResult, Error> {
25    if content.len() > limits.max_file_size_bytes {
26        return Err(Error::resource_limit_exceeded(
27            "max_file_size_bytes",
28            format!(
29                "{} bytes ({} MB)",
30                limits.max_file_size_bytes,
31                limits.max_file_size_bytes / (1024 * 1024)
32            ),
33            format!(
34                "{} bytes ({:.2} MB)",
35                content.len(),
36                content.len() as f64 / (1024.0 * 1024.0)
37            ),
38            "Reduce file size or split into multiple specs",
39            None,
40        ));
41    }
42
43    let mut parser = Parser::new(content, attribute, limits);
44    let specs = parser.parse_file()?;
45    Ok(ParseResult {
46        specs,
47        expression_count: parser.expression_count,
48    })
49}
50
51struct Parser {
52    lexer: Lexer,
53    depth_tracker: DepthTracker,
54    expression_count: usize,
55    max_expression_count: usize,
56}
57
58impl Parser {
59    fn new(content: &str, attribute: &str, limits: &ResourceLimits) -> Self {
60        Parser {
61            lexer: Lexer::new(content, attribute),
62            depth_tracker: DepthTracker::with_max_depth(limits.max_expression_depth),
63            expression_count: 0,
64            max_expression_count: limits.max_expression_count,
65        }
66    }
67
68    fn source_text(&self) -> Arc<str> {
69        self.lexer.source_text()
70    }
71
72    fn attribute(&self) -> String {
73        self.lexer.attribute().to_string()
74    }
75
76    fn peek(&mut self) -> Result<&Token, Error> {
77        self.lexer.peek()
78    }
79
80    fn next(&mut self) -> Result<Token, Error> {
81        self.lexer.next_token()
82    }
83
84    fn at(&mut self, kind: &TokenKind) -> Result<bool, Error> {
85        Ok(&self.peek()?.kind == kind)
86    }
87
88    fn at_any(&mut self, kinds: &[TokenKind]) -> Result<bool, Error> {
89        let current = &self.peek()?.kind;
90        Ok(kinds.contains(current))
91    }
92
93    fn expect(&mut self, kind: &TokenKind) -> Result<Token, Error> {
94        let token = self.next()?;
95        if &token.kind == kind {
96            Ok(token)
97        } else {
98            Err(self.error_at_token(&token, format!("Expected {}, found {}", kind, token.kind)))
99        }
100    }
101
102    fn error_at_token(&self, token: &Token, message: impl Into<String>) -> Error {
103        Error::parsing(
104            message,
105            Source::new(
106                self.lexer.attribute(),
107                token.span.clone(),
108                self.source_text(),
109            ),
110            None::<String>,
111        )
112    }
113
114    fn error_at_token_with_suggestion(
115        &self,
116        token: &Token,
117        message: impl Into<String>,
118        suggestion: impl Into<String>,
119    ) -> Error {
120        Error::parsing(
121            message,
122            Source::new(
123                self.lexer.attribute(),
124                token.span.clone(),
125                self.source_text(),
126            ),
127            Some(suggestion),
128        )
129    }
130
131    /// Parse a content hash (8 alphanumeric chars) after a `~` token.
132    /// Bypasses normal tokenization to avoid misinterpreting digit+letter
133    /// sequences as scientific notation (e.g. `7e20848b`).
134    fn parse_content_hash(&mut self) -> Result<String, Error> {
135        let hash_span = self.lexer.current_span();
136        let hash = self.lexer.scan_raw_alphanumeric()?;
137        if hash.len() != 8 {
138            return Err(Error::parsing(
139                format!(
140                    "Expected an 8-character alphanumeric content hash after '~', found '{}'",
141                    hash
142                ),
143                self.make_source(hash_span),
144                None::<String>,
145            ));
146        }
147        Ok(hash)
148    }
149
150    fn make_source(&self, span: Span) -> Source {
151        Source::new(self.lexer.attribute(), span, self.source_text())
152    }
153
154    fn span_from(&self, start: &Span) -> Span {
155        // Create a span from start to the current lexer position.
156        // We peek to get the current position.
157        Span {
158            start: start.start,
159            end: start.end.max(start.start),
160            line: start.line,
161            col: start.col,
162        }
163    }
164
165    fn span_covering(&self, start: &Span, end: &Span) -> Span {
166        Span {
167            start: start.start,
168            end: end.end,
169            line: start.line,
170            col: start.col,
171        }
172    }
173
174    // ========================================================================
175    // Top-level: file and spec
176    // ========================================================================
177
178    fn parse_file(&mut self) -> Result<Vec<LemmaSpec>, Error> {
179        let mut specs = Vec::new();
180        loop {
181            if self.at(&TokenKind::Eof)? {
182                break;
183            }
184            if self.at(&TokenKind::Spec)? {
185                specs.push(self.parse_spec()?);
186            } else {
187                let token = self.next()?;
188                return Err(self.error_at_token_with_suggestion(
189                    &token,
190                    format!(
191                        "Expected a spec declaration (e.g. 'spec my_spec'), found {}",
192                        token.kind
193                    ),
194                    "A Lemma file must start with 'spec <name>'",
195                ));
196            }
197        }
198        Ok(specs)
199    }
200
201    fn parse_spec(&mut self) -> Result<LemmaSpec, Error> {
202        let spec_token = self.expect(&TokenKind::Spec)?;
203        let start_line = spec_token.span.line;
204
205        let (name, _name_span) = self.parse_spec_name()?;
206
207        let effective_from = self.try_parse_effective_from()?;
208
209        let commentary = self.try_parse_commentary()?;
210
211        let attribute = self.attribute();
212        let mut spec = LemmaSpec::new(name.clone())
213            .with_attribute(attribute)
214            .with_start_line(start_line);
215        spec.effective_from = effective_from;
216
217        if let Some(commentary_text) = commentary {
218            spec = spec.set_commentary(commentary_text);
219        }
220
221        // First pass: collect type definitions
222        // We need to peek and handle type definitions first, but since we consume tokens
223        // linearly, we'll collect all items in one pass.
224        let mut facts = Vec::new();
225        let mut rules = Vec::new();
226        let mut types = Vec::new();
227        let mut meta_fields = Vec::new();
228
229        loop {
230            let peek_kind = self.peek()?.kind.clone();
231            match peek_kind {
232                TokenKind::Fact => {
233                    let fact = self.parse_fact()?;
234                    facts.push(fact);
235                }
236                TokenKind::Rule => {
237                    let rule = self.parse_rule()?;
238                    rules.push(rule);
239                }
240                TokenKind::Type => {
241                    let type_def = self.parse_type_def()?;
242                    types.push(type_def);
243                }
244                TokenKind::Meta => {
245                    let meta = self.parse_meta()?;
246                    meta_fields.push(meta);
247                }
248                TokenKind::Spec | TokenKind::Eof => break,
249                _ => {
250                    let token = self.next()?;
251                    return Err(self.error_at_token_with_suggestion(
252                        &token,
253                        format!(
254                            "Expected 'fact', 'rule', 'type', 'meta', or a new 'spec', found '{}'",
255                            token.text
256                        ),
257                        "Check the spelling or add the appropriate keyword",
258                    ));
259                }
260            }
261        }
262
263        for type_def in types {
264            spec = spec.add_type(type_def);
265        }
266        for fact in facts {
267            spec = spec.add_fact(fact);
268        }
269        for rule in rules {
270            spec = spec.add_rule(rule);
271        }
272        for meta in meta_fields {
273            spec = spec.add_meta_field(meta);
274        }
275
276        Ok(spec)
277    }
278
279    /// Parse a spec name: optional @ prefix, then identifier segments separated by /
280    /// Allows: "myspec", "contracts/employment/jack", "@user/workspace/spec"
281    fn parse_spec_name(&mut self) -> Result<(String, Span), Error> {
282        let mut name = String::new();
283        let start_span;
284
285        if self.at(&TokenKind::At)? {
286            let at_tok = self.next()?;
287            start_span = at_tok.span.clone();
288            name.push('@');
289        } else {
290            start_span = self.peek()?.span.clone();
291        }
292
293        // First segment must be an identifier or a keyword that can serve as name
294        let first = self.next()?;
295        if !first.kind.is_identifier_like() {
296            return Err(self.error_at_token(
297                &first,
298                format!("Expected a spec name, found {}", first.kind),
299            ));
300        }
301        name.push_str(&first.text);
302        let mut end_span = first.span.clone();
303
304        // Continue consuming / identifier segments
305        while self.at(&TokenKind::Slash)? {
306            self.next()?; // consume /
307            name.push('/');
308            let seg = self.next()?;
309            if !seg.kind.is_identifier_like() {
310                return Err(self.error_at_token(
311                    &seg,
312                    format!(
313                        "Expected identifier after '/' in spec name, found {}",
314                        seg.kind
315                    ),
316                ));
317            }
318            name.push_str(&seg.text);
319            end_span = seg.span.clone();
320        }
321
322        // Check for hyphen-containing spec names like "my-spec"
323        while self.at(&TokenKind::Minus)? {
324            // Only consume if the next token after minus is an identifier
325            // (hyphenated names like "my-spec")
326            let minus_span = self.peek()?.span.clone();
327            self.next()?; // consume -
328            if let Ok(peeked) = self.peek() {
329                if peeked.kind.is_identifier_like() {
330                    let seg = self.next()?;
331                    name.push('-');
332                    name.push_str(&seg.text);
333                    end_span = seg.span.clone();
334                    // Could be followed by /
335                    while self.at(&TokenKind::Slash)? {
336                        self.next()?; // consume /
337                        name.push('/');
338                        let seg2 = self.next()?;
339                        if !seg2.kind.is_identifier_like() {
340                            return Err(self.error_at_token(
341                                &seg2,
342                                format!(
343                                    "Expected identifier after '/' in spec name, found {}",
344                                    seg2.kind
345                                ),
346                            ));
347                        }
348                        name.push_str(&seg2.text);
349                        end_span = seg2.span.clone();
350                    }
351                } else {
352                    // The minus wasn't part of the name; this is an error
353                    let span = self.span_covering(&start_span, &minus_span);
354                    return Err(Error::parsing(
355                        "Trailing '-' after spec name",
356                        self.make_source(span),
357                        None::<String>,
358                    ));
359                }
360            }
361        }
362
363        let full_span = self.span_covering(&start_span, &end_span);
364        Ok((name, full_span))
365    }
366
367    fn try_parse_effective_from(&mut self) -> Result<Option<DateTimeValue>, Error> {
368        // effective_from is a date/time token right after the spec name.
369        // It's tricky because it looks like a number (e.g. 2026-03-04).
370        // In the old grammar it was a special atomic rule.
371        // We'll check if the next token is a NumberLit that looks like a year.
372        if !self.at(&TokenKind::NumberLit)? {
373            return Ok(None);
374        }
375
376        let peeked = self.peek()?;
377        let peeked_text = peeked.text.clone();
378        let peeked_span = peeked.span.clone();
379
380        // Check if it could be a date: 4-digit number followed by -
381        if peeked_text.len() == 4 && peeked_text.chars().all(|c| c.is_ascii_digit()) {
382            // Collect the full datetime string by consuming tokens
383            let mut dt_str = String::new();
384            let num_tok = self.next()?; // consume the year number
385            dt_str.push_str(&num_tok.text);
386
387            // Try to consume -MM-DD and optional T... parts
388            while self.at(&TokenKind::Minus)? {
389                self.next()?; // consume -
390                dt_str.push('-');
391                let part = self.next()?;
392                dt_str.push_str(&part.text);
393            }
394
395            // Check for T (time part)
396            if self.at(&TokenKind::Identifier)? {
397                let peeked = self.peek()?;
398                if peeked.text.starts_with('T') || peeked.text.starts_with('t') {
399                    let time_part = self.next()?;
400                    dt_str.push_str(&time_part.text);
401                    // Consume any : separated parts
402                    while self.at(&TokenKind::Colon)? {
403                        self.next()?;
404                        dt_str.push(':');
405                        let part = self.next()?;
406                        dt_str.push_str(&part.text);
407                    }
408                    // Check for timezone (+ or Z)
409                    if self.at(&TokenKind::Plus)? {
410                        self.next()?;
411                        dt_str.push('+');
412                        let tz_part = self.next()?;
413                        dt_str.push_str(&tz_part.text);
414                        if self.at(&TokenKind::Colon)? {
415                            self.next()?;
416                            dt_str.push(':');
417                            let tz_min = self.next()?;
418                            dt_str.push_str(&tz_min.text);
419                        }
420                    }
421                }
422            }
423
424            // Try to parse as datetime
425            if let Some(dtv) = DateTimeValue::parse(&dt_str) {
426                return Ok(Some(dtv));
427            }
428
429            return Err(Error::parsing(
430                format!("Invalid date/time in spec declaration: '{}'", dt_str),
431                self.make_source(peeked_span),
432                None::<String>,
433            ));
434        }
435
436        Ok(None)
437    }
438
439    fn try_parse_commentary(&mut self) -> Result<Option<String>, Error> {
440        if !self.at(&TokenKind::Commentary)? {
441            return Ok(None);
442        }
443        let token = self.next()?;
444        let trimmed = token.text.trim().to_string();
445        if trimmed.is_empty() {
446            Ok(None)
447        } else {
448            Ok(Some(trimmed))
449        }
450    }
451
452    // ========================================================================
453    // Fact parsing
454    // ========================================================================
455
456    fn parse_fact(&mut self) -> Result<LemmaFact, Error> {
457        let fact_token = self.expect(&TokenKind::Fact)?;
458        let start_span = fact_token.span.clone();
459
460        // Parse fact reference (single segment = definition, multi-segment = binding)
461        let reference = self.parse_reference()?;
462
463        self.expect(&TokenKind::Colon)?;
464
465        let value = self.parse_fact_value()?;
466
467        let end_span = self.peek()?.span.clone();
468        let span = self.span_covering(&start_span, &end_span);
469        let source = self.make_source(span);
470
471        Ok(LemmaFact::new(reference, value, source))
472    }
473
474    fn parse_reference(&mut self) -> Result<Reference, Error> {
475        let mut segments = Vec::new();
476
477        let first = self.next()?;
478        // Structural keywords (spec, fact, rule, unless, ...) cannot be names.
479        // Type keywords (duration, number, date, ...) CAN be names per the grammar.
480        if is_structural_keyword(&first.kind) {
481            return Err(self.error_at_token_with_suggestion(
482                &first,
483                format!(
484                    "'{}' is a reserved keyword and cannot be used as a name",
485                    first.text
486                ),
487                "Choose a different name that is not a reserved keyword",
488            ));
489        }
490
491        if !can_be_reference_segment(&first.kind) {
492            return Err(self.error_at_token(
493                &first,
494                format!("Expected an identifier, found {}", first.kind),
495            ));
496        }
497
498        segments.push(first.text.clone());
499
500        // Consume . separated segments
501        while self.at(&TokenKind::Dot)? {
502            self.next()?; // consume .
503            let seg = self.next()?;
504            if !can_be_reference_segment(&seg.kind) {
505                return Err(self.error_at_token(
506                    &seg,
507                    format!("Expected an identifier after '.', found {}", seg.kind),
508                ));
509            }
510            segments.push(seg.text.clone());
511        }
512
513        Ok(Reference::from_path(segments))
514    }
515
516    fn parse_fact_value(&mut self) -> Result<FactValue, Error> {
517        // Check for type declaration: [type_name] or [type_arrow_chain]
518        if self.at(&TokenKind::LBracket)? {
519            return self.parse_type_declaration_or_inline();
520        }
521
522        // Check for spec reference: spec <name>
523        if self.at(&TokenKind::Spec)? {
524            return self.parse_fact_spec_reference();
525        }
526
527        // Otherwise, it's a literal value
528        let value = self.parse_literal_value()?;
529        Ok(FactValue::Literal(value))
530    }
531
532    fn parse_type_declaration_or_inline(&mut self) -> Result<FactValue, Error> {
533        self.expect(&TokenKind::LBracket)?;
534
535        // Parse the type name (could be a standard type or custom type)
536        let (base_name, from_spec, constraints) = self.parse_type_arrow_chain()?;
537
538        self.expect(&TokenKind::RBracket)?;
539
540        Ok(FactValue::TypeDeclaration {
541            base: base_name,
542            constraints,
543            from: from_spec,
544        })
545    }
546
547    fn parse_fact_spec_reference(&mut self) -> Result<FactValue, Error> {
548        self.expect(&TokenKind::Spec)?;
549
550        let (name, _name_span) = self.parse_spec_name()?;
551        let is_registry = name.starts_with('@');
552
553        let mut hash_pin = None;
554        if self.at(&TokenKind::Tilde)? {
555            self.next()?; // consume ~
556            hash_pin = Some(self.parse_content_hash()?);
557        }
558
559        let mut effective = None;
560        // Check for effective datetime after spec reference
561        if self.at(&TokenKind::NumberLit)? {
562            let peeked = self.peek()?;
563            if peeked.text.len() == 4 && peeked.text.chars().all(|c| c.is_ascii_digit()) {
564                // Could be a datetime effective
565                effective = self.try_parse_effective_from()?;
566            }
567        }
568
569        Ok(FactValue::SpecReference(SpecRef {
570            name,
571            is_registry,
572            hash_pin,
573            effective,
574        }))
575    }
576
577    // ========================================================================
578    // Rule parsing
579    // ========================================================================
580
581    fn parse_rule(&mut self) -> Result<LemmaRule, Error> {
582        let rule_token = self.expect(&TokenKind::Rule)?;
583        let start_span = rule_token.span.clone();
584
585        let name_tok = self.next()?;
586        if is_structural_keyword(&name_tok.kind) {
587            return Err(self.error_at_token_with_suggestion(
588                &name_tok,
589                format!(
590                    "'{}' is a reserved keyword and cannot be used as a rule name",
591                    name_tok.text
592                ),
593                "Choose a different name that is not a reserved keyword",
594            ));
595        }
596        if !can_be_label(&name_tok.kind) && !is_type_keyword(&name_tok.kind) {
597            return Err(self.error_at_token(
598                &name_tok,
599                format!("Expected a rule name, found {}", name_tok.kind),
600            ));
601        }
602        let rule_name = name_tok.text.clone();
603
604        self.expect(&TokenKind::Colon)?;
605
606        // Parse the base expression or veto
607        let expression = if self.at(&TokenKind::Veto)? {
608            self.parse_veto_expression()?
609        } else {
610            self.parse_expression()?
611        };
612
613        // Parse unless clauses
614        let mut unless_clauses = Vec::new();
615        while self.at(&TokenKind::Unless)? {
616            unless_clauses.push(self.parse_unless_clause()?);
617        }
618
619        let end_span = if let Some(last_unless) = unless_clauses.last() {
620            last_unless.source_location.span.clone()
621        } else if let Some(ref loc) = expression.source_location {
622            loc.span.clone()
623        } else {
624            start_span.clone()
625        };
626
627        let span = self.span_covering(&start_span, &end_span);
628        Ok(LemmaRule {
629            name: rule_name,
630            expression,
631            unless_clauses,
632            source_location: self.make_source(span),
633        })
634    }
635
636    fn parse_veto_expression(&mut self) -> Result<Expression, Error> {
637        let veto_tok = self.expect(&TokenKind::Veto)?;
638        let start_span = veto_tok.span.clone();
639
640        let message = if self.at(&TokenKind::StringLit)? {
641            let str_tok = self.next()?;
642            let content = unquote_string(&str_tok.text);
643            Some(content)
644        } else {
645            None
646        };
647
648        let span = self.span_from(&start_span);
649        self.new_expression(
650            ExpressionKind::Veto(VetoExpression { message }),
651            self.make_source(span),
652        )
653    }
654
655    fn parse_unless_clause(&mut self) -> Result<UnlessClause, Error> {
656        let unless_tok = self.expect(&TokenKind::Unless)?;
657        let start_span = unless_tok.span.clone();
658
659        let condition = self.parse_expression()?;
660
661        self.expect(&TokenKind::Then)?;
662
663        let result = if self.at(&TokenKind::Veto)? {
664            self.parse_veto_expression()?
665        } else {
666            self.parse_expression()?
667        };
668
669        let end_span = result
670            .source_location
671            .as_ref()
672            .map(|s| s.span.clone())
673            .unwrap_or_else(|| start_span.clone());
674        let span = self.span_covering(&start_span, &end_span);
675
676        Ok(UnlessClause {
677            condition,
678            result,
679            source_location: self.make_source(span),
680        })
681    }
682
683    // ========================================================================
684    // Type definitions
685    // ========================================================================
686
687    fn parse_type_def(&mut self) -> Result<TypeDef, Error> {
688        let type_tok = self.expect(&TokenKind::Type)?;
689        let start_span = type_tok.span.clone();
690
691        // Parse type name
692        let name_tok = self.next()?;
693        let type_name = name_tok.text.clone();
694
695        // Check if this is a type import (type X from Y) or a type definition (type X: Y)
696        if self.at(&TokenKind::From)? {
697            return self.parse_type_import(type_name, start_span);
698        }
699
700        // Regular type definition: type X: Y -> ...
701        if self.at(&TokenKind::Colon)? {
702            self.next()?; // consume :
703        } else {
704            // Could also be an import without us seeing 'from' yet if there are two type names
705            // e.g. "type money from other_spec"
706            let peek = self.peek()?.clone();
707            return Err(self.error_at_token(
708                &peek,
709                format!(
710                    "Expected ':' or 'from' after type name '{}', found {}",
711                    type_name, peek.kind
712                ),
713            ));
714        }
715
716        let (parent, _from, constraints) = self.parse_type_arrow_chain()?;
717
718        let end_span = self.peek()?.span.clone();
719        let span = self.span_covering(&start_span, &end_span);
720        Ok(TypeDef::Regular {
721            source_location: self.make_source(span),
722            name: type_name,
723            parent,
724            constraints,
725        })
726    }
727
728    fn parse_type_import(&mut self, type_name: String, start_span: Span) -> Result<TypeDef, Error> {
729        self.expect(&TokenKind::From)?;
730
731        let (from_name, _from_span) = self.parse_spec_name()?;
732        let is_registry = from_name.starts_with('@');
733
734        let mut hash_pin = None;
735        // Check for hash after spec name (space separated, 8 alphanumeric chars)
736        if self.at(&TokenKind::Identifier)? || self.at(&TokenKind::NumberLit)? {
737            let peeked = self.peek()?;
738            let peeked_text = peeked.text.clone();
739            if peeked_text.len() == 8 && peeked_text.chars().all(|c| c.is_ascii_alphanumeric()) {
740                let hash_tok = self.next()?;
741                hash_pin = Some(hash_tok.text.clone());
742            }
743        }
744
745        let from = SpecRef {
746            name: from_name,
747            is_registry,
748            hash_pin,
749            effective: None,
750        };
751
752        // Check for arrow chain constraints after import
753        let constraints = if self.at(&TokenKind::Arrow)? {
754            let (_, _, constraints) = self.parse_remaining_arrow_chain()?;
755            constraints
756        } else {
757            None
758        };
759
760        let end_span = self.peek()?.span.clone();
761        let span = self.span_covering(&start_span, &end_span);
762
763        let source_type = type_name.clone();
764
765        Ok(TypeDef::Import {
766            source_location: self.make_source(span),
767            name: type_name,
768            source_type,
769            from,
770            constraints,
771        })
772    }
773
774    /// Parse a type arrow chain: type_name (-> command)* or type_name from spec (-> command)*
775    fn parse_type_arrow_chain(&mut self) -> Result<TypeArrowChain, Error> {
776        // Parse the base type name (standard or custom)
777        let name_tok = self.next()?;
778        let base_name = if is_type_keyword(&name_tok.kind) {
779            name_tok.text.to_lowercase()
780        } else if can_be_label(&name_tok.kind) {
781            name_tok.text.clone()
782        } else {
783            return Err(self.error_at_token(
784                &name_tok,
785                format!("Expected a type name, found {}", name_tok.kind),
786            ));
787        };
788
789        // Check for 'from' (inline type import)
790        let from_spec = if self.at(&TokenKind::From)? {
791            self.next()?; // consume from
792            let (from_name, _) = self.parse_spec_name()?;
793            let is_registry = from_name.starts_with('@');
794            let mut hash_pin = None;
795            // Check for hash
796            if self.at(&TokenKind::Identifier)? || self.at(&TokenKind::NumberLit)? {
797                let peeked = self.peek()?;
798                let peeked_text = peeked.text.clone();
799                if peeked_text.len() == 8 && peeked_text.chars().all(|c| c.is_ascii_alphanumeric())
800                {
801                    let hash_tok = self.next()?;
802                    hash_pin = Some(hash_tok.text.clone());
803                }
804            }
805            Some(SpecRef {
806                name: from_name,
807                is_registry,
808                hash_pin,
809                effective: None,
810            })
811        } else {
812            None
813        };
814
815        // Parse arrow chain constraints
816        let mut commands = Vec::new();
817        while self.at(&TokenKind::Arrow)? {
818            self.next()?; // consume ->
819            let (cmd_name, cmd_args) = self.parse_command()?;
820            commands.push((cmd_name, cmd_args));
821        }
822
823        let constraints = if commands.is_empty() {
824            None
825        } else {
826            Some(commands)
827        };
828
829        Ok((base_name, from_spec, constraints))
830    }
831
832    fn parse_remaining_arrow_chain(&mut self) -> Result<TypeArrowChain, Error> {
833        let mut commands = Vec::new();
834        while self.at(&TokenKind::Arrow)? {
835            self.next()?; // consume ->
836            let (cmd_name, cmd_args) = self.parse_command()?;
837            commands.push((cmd_name, cmd_args));
838        }
839        let constraints = if commands.is_empty() {
840            None
841        } else {
842            Some(commands)
843        };
844        Ok((String::new(), None, constraints))
845    }
846
847    fn parse_command(&mut self) -> Result<(String, Vec<CommandArg>), Error> {
848        let name_tok = self.next()?;
849        if !can_be_label(&name_tok.kind) && !is_type_keyword(&name_tok.kind) {
850            return Err(self.error_at_token(
851                &name_tok,
852                format!("Expected a command name, found {}", name_tok.kind),
853            ));
854        }
855        let cmd_name = name_tok.text.clone();
856
857        let mut args = Vec::new();
858        loop {
859            // Command args: number, boolean, text, or label
860            // Stop at: ->, ], newlines (next keyword), EOF
861            if self.at(&TokenKind::Arrow)?
862                || self.at(&TokenKind::RBracket)?
863                || self.at(&TokenKind::Eof)?
864                || is_spec_body_keyword(&self.peek()?.kind)
865                || self.at(&TokenKind::Spec)?
866            {
867                break;
868            }
869
870            let peek_kind = self.peek()?.kind.clone();
871            match peek_kind {
872                TokenKind::NumberLit => {
873                    let tok = self.next()?;
874                    args.push(CommandArg::Number(tok.text));
875                }
876                TokenKind::Minus | TokenKind::Plus => {
877                    let second = self.lexer.peek_second()?.kind.clone();
878                    if second == TokenKind::NumberLit {
879                        let sign = self.next()?;
880                        let num = self.next()?;
881                        let text = format!("{}{}", sign.text, num.text);
882                        args.push(CommandArg::Number(text));
883                    } else {
884                        break;
885                    }
886                }
887                TokenKind::StringLit => {
888                    let tok = self.next()?;
889                    let content = unquote_string(&tok.text);
890                    args.push(CommandArg::Text(content));
891                }
892                ref k if is_boolean_keyword(k) => {
893                    let tok = self.next()?;
894                    args.push(CommandArg::Boolean(tok.text));
895                }
896                ref k if can_be_label(k) || is_type_keyword(k) => {
897                    let tok = self.next()?;
898                    args.push(CommandArg::Label(tok.text));
899                }
900                _ => break,
901            }
902        }
903
904        Ok((cmd_name, args))
905    }
906
907    // ========================================================================
908    // Meta parsing
909    // ========================================================================
910
911    fn parse_meta(&mut self) -> Result<MetaField, Error> {
912        let meta_tok = self.expect(&TokenKind::Meta)?;
913        let start_span = meta_tok.span.clone();
914
915        let key_tok = self.next()?;
916        let key = key_tok.text.clone();
917
918        self.expect(&TokenKind::Colon)?;
919
920        let value = self.parse_meta_value()?;
921
922        let end_span = self.peek()?.span.clone();
923        let span = self.span_covering(&start_span, &end_span);
924
925        Ok(MetaField {
926            key,
927            value,
928            source_location: self.make_source(span),
929        })
930    }
931
932    fn parse_meta_value(&mut self) -> Result<MetaValue, Error> {
933        // Try literal first (string, number, boolean, date)
934        let peeked = self.peek()?;
935        match &peeked.kind {
936            TokenKind::StringLit => {
937                let value = self.parse_literal_value()?;
938                return Ok(MetaValue::Literal(value));
939            }
940            TokenKind::NumberLit => {
941                let value = self.parse_literal_value()?;
942                return Ok(MetaValue::Literal(value));
943            }
944            k if is_boolean_keyword(k) => {
945                let value = self.parse_literal_value()?;
946                return Ok(MetaValue::Literal(value));
947            }
948            _ => {}
949        }
950
951        // Otherwise, consume as unquoted meta identifier
952        // meta_identifier: (ASCII_ALPHANUMERIC | "_" | "-" | "." | "/")+
953        let mut ident = String::new();
954        loop {
955            let peeked = self.peek()?;
956            match &peeked.kind {
957                k if k.is_identifier_like() => {
958                    let tok = self.next()?;
959                    ident.push_str(&tok.text);
960                }
961                TokenKind::Dot => {
962                    self.next()?;
963                    ident.push('.');
964                }
965                TokenKind::Slash => {
966                    self.next()?;
967                    ident.push('/');
968                }
969                TokenKind::Minus => {
970                    self.next()?;
971                    ident.push('-');
972                }
973                TokenKind::NumberLit => {
974                    let tok = self.next()?;
975                    ident.push_str(&tok.text);
976                }
977                _ => break,
978            }
979        }
980
981        if ident.is_empty() {
982            let tok = self.peek()?.clone();
983            return Err(self.error_at_token(&tok, "Expected a meta value"));
984        }
985
986        Ok(MetaValue::Unquoted(ident))
987    }
988
989    // ========================================================================
990    // Literal value parsing
991    // ========================================================================
992
993    fn parse_literal_value(&mut self) -> Result<Value, Error> {
994        let peeked = self.peek()?;
995        match &peeked.kind {
996            TokenKind::StringLit => {
997                let tok = self.next()?;
998                let content = unquote_string(&tok.text);
999                Ok(Value::Text(content))
1000            }
1001            k if is_boolean_keyword(k) => {
1002                let tok = self.next()?;
1003                let bv = parse_boolean_value(&tok.text);
1004                Ok(Value::Boolean(bv))
1005            }
1006            TokenKind::NumberLit => self.parse_number_literal(),
1007            TokenKind::Minus | TokenKind::Plus => self.parse_signed_number_literal(),
1008            _ => {
1009                let tok = self.next()?;
1010                Err(self.error_at_token(
1011                    &tok,
1012                    format!(
1013                        "Expected a value (number, text, boolean, date, etc.), found '{}'",
1014                        tok.text
1015                    ),
1016                ))
1017            }
1018        }
1019    }
1020
1021    fn parse_signed_number_literal(&mut self) -> Result<Value, Error> {
1022        let sign_tok = self.next()?;
1023        let sign_span = sign_tok.span.clone();
1024        let is_negative = sign_tok.kind == TokenKind::Minus;
1025
1026        if !self.at(&TokenKind::NumberLit)? {
1027            let tok = self.peek()?.clone();
1028            return Err(self.error_at_token(
1029                &tok,
1030                format!(
1031                    "Expected a number after '{}', found '{}'",
1032                    sign_tok.text, tok.text
1033                ),
1034            ));
1035        }
1036
1037        let value = self.parse_number_literal()?;
1038        if !is_negative {
1039            return Ok(value);
1040        }
1041        match value {
1042            Value::Number(d) => Ok(Value::Number(-d)),
1043            Value::Scale(d, unit) => Ok(Value::Scale(-d, unit)),
1044            Value::Duration(d, unit) => Ok(Value::Duration(-d, unit)),
1045            Value::Ratio(d, label) => Ok(Value::Ratio(-d, label)),
1046            other => Err(Error::parsing(
1047                format!("Cannot negate this value: {}", other),
1048                self.make_source(sign_span),
1049                None::<String>,
1050            )),
1051        }
1052    }
1053
1054    fn parse_number_literal(&mut self) -> Result<Value, Error> {
1055        let num_tok = self.next()?;
1056        let num_text = &num_tok.text;
1057        let num_span = num_tok.span.clone();
1058
1059        // Check if followed by - which could make it a date (YYYY-MM-DD)
1060        if num_text.len() == 4
1061            && num_text.chars().all(|c| c.is_ascii_digit())
1062            && self.at(&TokenKind::Minus)?
1063        {
1064            return self.parse_date_literal(num_text.clone(), num_span);
1065        }
1066
1067        // Check what follows the number
1068        let peeked = self.peek()?;
1069
1070        // Number followed by : could be a time literal (HH:MM:SS)
1071        if num_text.len() == 2
1072            && num_text.chars().all(|c| c.is_ascii_digit())
1073            && peeked.kind == TokenKind::Colon
1074        {
1075            // Only if we're in a fact value context... this is ambiguous.
1076            // Time literals look like: 14:30:00 or 14:30
1077            // But we might also have "rule x: expr" where : is assignment.
1078            // The grammar handles this at the grammar level. For us,
1079            // we need to check if the context is right.
1080            // Let's try to parse as time if the following pattern matches.
1081            return self.try_parse_time_literal(num_text.clone(), num_span);
1082        }
1083
1084        // Check for %% (permille) - must be before %
1085        if peeked.kind == TokenKind::PercentPercent {
1086            let pp_tok = self.next()?;
1087            // Check it's not followed by a digit
1088            if let Ok(next_peek) = self.peek() {
1089                if next_peek.kind == TokenKind::NumberLit {
1090                    return Err(self.error_at_token(
1091                        &pp_tok,
1092                        "Permille literal cannot be followed by a digit",
1093                    ));
1094                }
1095            }
1096            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1097            let ratio_value = decimal / Decimal::from(1000);
1098            return Ok(Value::Ratio(ratio_value, Some("permille".to_string())));
1099        }
1100
1101        // Check for % (percent)
1102        if peeked.kind == TokenKind::Percent {
1103            let pct_tok = self.next()?;
1104            // Check it's not followed by a digit or another %
1105            if let Ok(next_peek) = self.peek() {
1106                if next_peek.kind == TokenKind::NumberLit || next_peek.kind == TokenKind::Percent {
1107                    return Err(self.error_at_token(
1108                        &pct_tok,
1109                        "Percent literal cannot be followed by a digit",
1110                    ));
1111                }
1112            }
1113            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1114            let ratio_value = decimal / Decimal::from(100);
1115            return Ok(Value::Ratio(ratio_value, Some("percent".to_string())));
1116        }
1117
1118        // Check for "percent" keyword
1119        if peeked.kind == TokenKind::PercentKw {
1120            self.next()?; // consume "percent"
1121            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1122            let ratio_value = decimal / Decimal::from(100);
1123            return Ok(Value::Ratio(ratio_value, Some("percent".to_string())));
1124        }
1125
1126        // Check for "permille" keyword
1127        if peeked.kind == TokenKind::Permille {
1128            self.next()?; // consume "permille"
1129            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1130            let ratio_value = decimal / Decimal::from(1000);
1131            return Ok(Value::Ratio(ratio_value, Some("permille".to_string())));
1132        }
1133
1134        // Check for duration unit
1135        if is_duration_unit(&peeked.kind) && peeked.kind != TokenKind::PercentKw {
1136            let unit_tok = self.next()?;
1137            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1138            let duration_unit = parse_duration_unit_from_text(&unit_tok.text);
1139            return Ok(Value::Duration(decimal, duration_unit));
1140        }
1141
1142        // Check for user-defined unit (identifier after number)
1143        if can_be_label(&peeked.kind) {
1144            let unit_tok = self.next()?;
1145            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1146            return Ok(Value::Scale(decimal, unit_tok.text.clone()));
1147        }
1148
1149        // Plain number
1150        let decimal = parse_decimal_string(num_text, &num_span, self)?;
1151        Ok(Value::Number(decimal))
1152    }
1153
1154    fn parse_date_literal(&mut self, year_text: String, start_span: Span) -> Result<Value, Error> {
1155        let mut dt_str = year_text;
1156
1157        // Consume -MM
1158        self.expect(&TokenKind::Minus)?;
1159        dt_str.push('-');
1160        let month_tok = self.expect(&TokenKind::NumberLit)?;
1161        dt_str.push_str(&month_tok.text);
1162
1163        // Consume -DD
1164        self.expect(&TokenKind::Minus)?;
1165        dt_str.push('-');
1166        let day_tok = self.expect(&TokenKind::NumberLit)?;
1167        dt_str.push_str(&day_tok.text);
1168
1169        // Check for T (time component)
1170        if self.at(&TokenKind::Identifier)? {
1171            let peeked = self.peek()?;
1172            if peeked.text.len() >= 2
1173                && (peeked.text.starts_with('T') || peeked.text.starts_with('t'))
1174            {
1175                // The lexer may have tokenized T14 as a single identifier
1176                let t_tok = self.next()?;
1177                dt_str.push_str(&t_tok.text);
1178
1179                // Consume :MM
1180                if self.at(&TokenKind::Colon)? {
1181                    self.next()?;
1182                    dt_str.push(':');
1183                    let min_tok = self.next()?;
1184                    dt_str.push_str(&min_tok.text);
1185
1186                    // Consume :SS and optional fractional seconds
1187                    if self.at(&TokenKind::Colon)? {
1188                        self.next()?;
1189                        dt_str.push(':');
1190                        let sec_tok = self.next()?;
1191                        dt_str.push_str(&sec_tok.text);
1192
1193                        // Check for fractional seconds .NNNNNN
1194                        if self.at(&TokenKind::Dot)? {
1195                            self.next()?;
1196                            dt_str.push('.');
1197                            let frac_tok = self.expect(&TokenKind::NumberLit)?;
1198                            dt_str.push_str(&frac_tok.text);
1199                        }
1200                    }
1201                }
1202
1203                // Check for timezone
1204                self.try_consume_timezone(&mut dt_str)?;
1205            }
1206        }
1207
1208        if let Some(dtv) = crate::parsing::literals::parse_datetime_str(&dt_str) {
1209            return Ok(Value::Date(dtv));
1210        }
1211
1212        Err(Error::parsing(
1213            format!("Invalid date/time format: '{}'", dt_str),
1214            self.make_source(start_span),
1215            None::<String>,
1216        ))
1217    }
1218
1219    fn try_consume_timezone(&mut self, dt_str: &mut String) -> Result<(), Error> {
1220        // Z timezone
1221        if self.at(&TokenKind::Identifier)? {
1222            let peeked = self.peek()?;
1223            if peeked.text == "Z" || peeked.text == "z" {
1224                let z_tok = self.next()?;
1225                dt_str.push_str(&z_tok.text);
1226                return Ok(());
1227            }
1228        }
1229
1230        // +HH:MM or -HH:MM
1231        if self.at(&TokenKind::Plus)? || self.at(&TokenKind::Minus)? {
1232            let sign_tok = self.next()?;
1233            dt_str.push_str(&sign_tok.text);
1234            let hour_tok = self.expect(&TokenKind::NumberLit)?;
1235            dt_str.push_str(&hour_tok.text);
1236            if self.at(&TokenKind::Colon)? {
1237                self.next()?;
1238                dt_str.push(':');
1239                let min_tok = self.expect(&TokenKind::NumberLit)?;
1240                dt_str.push_str(&min_tok.text);
1241            }
1242        }
1243
1244        Ok(())
1245    }
1246
1247    fn try_parse_time_literal(
1248        &mut self,
1249        hour_text: String,
1250        start_span: Span,
1251    ) -> Result<Value, Error> {
1252        let mut time_str = hour_text;
1253
1254        // Consume :MM
1255        self.expect(&TokenKind::Colon)?;
1256        time_str.push(':');
1257        let min_tok = self.expect(&TokenKind::NumberLit)?;
1258        time_str.push_str(&min_tok.text);
1259
1260        // Optional :SS
1261        if self.at(&TokenKind::Colon)? {
1262            self.next()?;
1263            time_str.push(':');
1264            let sec_tok = self.expect(&TokenKind::NumberLit)?;
1265            time_str.push_str(&sec_tok.text);
1266        }
1267
1268        // Try timezone
1269        self.try_consume_timezone(&mut time_str)?;
1270
1271        if let Ok(t) = time_str.parse::<chrono::NaiveTime>() {
1272            use chrono::Timelike;
1273            return Ok(Value::Time(TimeValue {
1274                hour: t.hour() as u8,
1275                minute: t.minute() as u8,
1276                second: t.second() as u8,
1277                timezone: None,
1278            }));
1279        }
1280
1281        Err(Error::parsing(
1282            format!("Invalid time format: '{}'", time_str),
1283            self.make_source(start_span),
1284            None::<String>,
1285        ))
1286    }
1287
1288    // ========================================================================
1289    // Expression parsing (Pratt parser / precedence climbing)
1290    // ========================================================================
1291
1292    fn new_expression(
1293        &mut self,
1294        kind: ExpressionKind,
1295        source: Source,
1296    ) -> Result<Expression, Error> {
1297        self.expression_count += 1;
1298        if self.expression_count > self.max_expression_count {
1299            return Err(Error::resource_limit_exceeded(
1300                "max_expression_count",
1301                self.max_expression_count.to_string(),
1302                self.expression_count.to_string(),
1303                "Split logic into multiple rules to reduce expression count",
1304                Some(source),
1305            ));
1306        }
1307        Ok(Expression::new(kind, source))
1308    }
1309
1310    fn check_depth(&mut self) -> Result<(), Error> {
1311        if let Err(actual) = self.depth_tracker.push_depth() {
1312            let span = self.peek()?.span.clone();
1313            self.depth_tracker.pop_depth();
1314            return Err(Error::resource_limit_exceeded(
1315                "max_expression_depth",
1316                self.depth_tracker.max_depth().to_string(),
1317                actual.to_string(),
1318                "Simplify nested expressions or break into separate rules",
1319                Some(self.make_source(span)),
1320            ));
1321        }
1322        Ok(())
1323    }
1324
1325    fn parse_expression(&mut self) -> Result<Expression, Error> {
1326        self.check_depth()?;
1327        let result = self.parse_and_expression();
1328        self.depth_tracker.pop_depth();
1329        result
1330    }
1331
1332    fn parse_and_expression(&mut self) -> Result<Expression, Error> {
1333        let start_span = self.peek()?.span.clone();
1334        let mut left = self.parse_and_operand()?;
1335
1336        while self.at(&TokenKind::And)? {
1337            self.next()?; // consume 'and'
1338            let right = self.parse_and_operand()?;
1339            let span = self.span_covering(
1340                &start_span,
1341                &right
1342                    .source_location
1343                    .as_ref()
1344                    .map(|s| s.span.clone())
1345                    .unwrap_or_else(|| start_span.clone()),
1346            );
1347            left = self.new_expression(
1348                ExpressionKind::LogicalAnd(Arc::new(left), Arc::new(right)),
1349                self.make_source(span),
1350            )?;
1351        }
1352
1353        Ok(left)
1354    }
1355
1356    fn parse_and_operand(&mut self) -> Result<Expression, Error> {
1357        // not expression
1358        if self.at(&TokenKind::Not)? {
1359            return self.parse_not_expression();
1360        }
1361
1362        // base_with_suffix: base_expression followed by optional suffix
1363        self.parse_base_with_suffix()
1364    }
1365
1366    fn parse_not_expression(&mut self) -> Result<Expression, Error> {
1367        let not_tok = self.expect(&TokenKind::Not)?;
1368        let start_span = not_tok.span.clone();
1369
1370        self.check_depth()?;
1371        let operand = self.parse_and_operand()?;
1372        self.depth_tracker.pop_depth();
1373
1374        let end_span = operand
1375            .source_location
1376            .as_ref()
1377            .map(|s| s.span.clone())
1378            .unwrap_or_else(|| start_span.clone());
1379        let span = self.span_covering(&start_span, &end_span);
1380
1381        self.new_expression(
1382            ExpressionKind::LogicalNegation(Arc::new(operand), NegationType::Not),
1383            self.make_source(span),
1384        )
1385    }
1386
1387    fn parse_base_with_suffix(&mut self) -> Result<Expression, Error> {
1388        let start_span = self.peek()?.span.clone();
1389        let base = self.parse_base_expression()?;
1390
1391        // Check for suffixes
1392        let peeked = self.peek()?;
1393
1394        // Comparison suffix: >, <, >=, <=, ==, !=, is, is not
1395        if is_comparison_operator(&peeked.kind) {
1396            return self.parse_comparison_suffix(base, start_span);
1397        }
1398
1399        // "not in calendar <unit>" suffix: expr not in calendar year|month|week
1400        // After a base_expression, "not" must be this suffix (prefix "not" is only
1401        // at and_operand level, and "X and not Y" would have consumed "and" first).
1402        if peeked.kind == TokenKind::Not {
1403            return self.parse_not_in_calendar_suffix(base, start_span);
1404        }
1405
1406        // "in" suffix: conversion, date relative, date calendar
1407        if peeked.kind == TokenKind::In {
1408            return self.parse_in_suffix(base, start_span);
1409        }
1410
1411        Ok(base)
1412    }
1413
1414    fn parse_comparison_suffix(
1415        &mut self,
1416        left: Expression,
1417        start_span: Span,
1418    ) -> Result<Expression, Error> {
1419        let operator = self.parse_comparison_operator()?;
1420
1421        // Right side can be: not_expr | base_expression (optionally with "in unit")
1422        let right = if self.at(&TokenKind::Not)? {
1423            self.parse_not_expression()?
1424        } else {
1425            let rhs = self.parse_base_expression()?;
1426            // Check for "in unit" conversion on the rhs
1427            if self.at(&TokenKind::In)? {
1428                self.parse_in_suffix(rhs, start_span.clone())?
1429            } else {
1430                rhs
1431            }
1432        };
1433
1434        let end_span = right
1435            .source_location
1436            .as_ref()
1437            .map(|s| s.span.clone())
1438            .unwrap_or_else(|| start_span.clone());
1439        let span = self.span_covering(&start_span, &end_span);
1440
1441        self.new_expression(
1442            ExpressionKind::Comparison(Arc::new(left), operator, Arc::new(right)),
1443            self.make_source(span),
1444        )
1445    }
1446
1447    fn parse_comparison_operator(&mut self) -> Result<ComparisonComputation, Error> {
1448        let tok = self.next()?;
1449        match tok.kind {
1450            TokenKind::Gt => Ok(ComparisonComputation::GreaterThan),
1451            TokenKind::Lt => Ok(ComparisonComputation::LessThan),
1452            TokenKind::Gte => Ok(ComparisonComputation::GreaterThanOrEqual),
1453            TokenKind::Lte => Ok(ComparisonComputation::LessThanOrEqual),
1454            TokenKind::EqEq => Ok(ComparisonComputation::Equal),
1455            TokenKind::BangEq => Ok(ComparisonComputation::NotEqual),
1456            TokenKind::Is => {
1457                // Check for "is not"
1458                if self.at(&TokenKind::Not)? {
1459                    self.next()?; // consume 'not'
1460                    Ok(ComparisonComputation::IsNot)
1461                } else {
1462                    Ok(ComparisonComputation::Is)
1463                }
1464            }
1465            _ => Err(self.error_at_token(
1466                &tok,
1467                format!("Expected a comparison operator, found {}", tok.kind),
1468            )),
1469        }
1470    }
1471
1472    fn parse_not_in_calendar_suffix(
1473        &mut self,
1474        base: Expression,
1475        start_span: Span,
1476    ) -> Result<Expression, Error> {
1477        self.expect(&TokenKind::Not)?;
1478        self.expect(&TokenKind::In)?;
1479        self.expect(&TokenKind::Calendar)?;
1480        let unit = self.parse_calendar_unit()?;
1481        let end = self.peek()?.span.clone();
1482        let span = self.span_covering(&start_span, &end);
1483        self.new_expression(
1484            ExpressionKind::DateCalendar(DateCalendarKind::NotIn, unit, Arc::new(base)),
1485            self.make_source(span),
1486        )
1487    }
1488
1489    fn parse_in_suffix(&mut self, base: Expression, start_span: Span) -> Result<Expression, Error> {
1490        self.expect(&TokenKind::In)?;
1491
1492        let peeked = self.peek()?;
1493
1494        // "in past calendar <unit>" or "in future calendar <unit>"
1495        if peeked.kind == TokenKind::Past || peeked.kind == TokenKind::Future {
1496            let direction = self.next()?;
1497            let rel_kind = if direction.text.to_lowercase() == "past" {
1498                DateRelativeKind::InPast
1499            } else {
1500                DateRelativeKind::InFuture
1501            };
1502
1503            // Check for "calendar" keyword
1504            if self.at(&TokenKind::Calendar)? {
1505                self.next()?; // consume "calendar"
1506                let cal_kind = if direction.text.to_lowercase() == "past" {
1507                    DateCalendarKind::Past
1508                } else {
1509                    DateCalendarKind::Future
1510                };
1511                let unit = self.parse_calendar_unit()?;
1512                let end = self.peek()?.span.clone();
1513                let span = self.span_covering(&start_span, &end);
1514                return self.new_expression(
1515                    ExpressionKind::DateCalendar(cal_kind, unit, Arc::new(base)),
1516                    self.make_source(span),
1517                );
1518            }
1519
1520            // "in past [tolerance]" or "in future [tolerance]"
1521            let tolerance = if !self.at(&TokenKind::And)?
1522                && !self.at(&TokenKind::Unless)?
1523                && !self.at(&TokenKind::Then)?
1524                && !self.at(&TokenKind::Eof)?
1525                && !is_comparison_operator(&self.peek()?.kind)
1526            {
1527                let peek_kind = self.peek()?.kind.clone();
1528                if peek_kind == TokenKind::NumberLit
1529                    || peek_kind == TokenKind::LParen
1530                    || can_be_reference_segment(&peek_kind)
1531                    || is_math_function(&peek_kind)
1532                {
1533                    Some(Arc::new(self.parse_base_expression()?))
1534                } else {
1535                    None
1536                }
1537            } else {
1538                None
1539            };
1540
1541            let end = self.peek()?.span.clone();
1542            let span = self.span_covering(&start_span, &end);
1543            return self.new_expression(
1544                ExpressionKind::DateRelative(rel_kind, Arc::new(base), tolerance),
1545                self.make_source(span),
1546            );
1547        }
1548
1549        // "in calendar <unit>"
1550        if peeked.kind == TokenKind::Calendar {
1551            self.next()?; // consume "calendar"
1552            let unit = self.parse_calendar_unit()?;
1553            let end = self.peek()?.span.clone();
1554            let span = self.span_covering(&start_span, &end);
1555            return self.new_expression(
1556                ExpressionKind::DateCalendar(DateCalendarKind::Current, unit, Arc::new(base)),
1557                self.make_source(span),
1558            );
1559        }
1560
1561        // "in <unit>" — unit conversion
1562        let target_tok = self.next()?;
1563        let target_text = target_tok.text.clone();
1564        let target = parse_conversion_target(&target_text);
1565
1566        let converted = self.new_expression(
1567            ExpressionKind::UnitConversion(Arc::new(base), target),
1568            self.make_source(self.span_covering(&start_span, &target_tok.span)),
1569        )?;
1570
1571        // Check if followed by comparison operator
1572        if is_comparison_operator(&self.peek()?.kind) {
1573            return self.parse_comparison_suffix(converted, start_span);
1574        }
1575
1576        Ok(converted)
1577    }
1578
1579    fn parse_calendar_unit(&mut self) -> Result<CalendarUnit, Error> {
1580        let tok = self.next()?;
1581        match tok.text.to_lowercase().as_str() {
1582            "year" => Ok(CalendarUnit::Year),
1583            "month" => Ok(CalendarUnit::Month),
1584            "week" => Ok(CalendarUnit::Week),
1585            _ => Err(self.error_at_token(
1586                &tok,
1587                format!("Expected 'year', 'month', or 'week', found '{}'", tok.text),
1588            )),
1589        }
1590    }
1591
1592    // ========================================================================
1593    // Arithmetic expressions (precedence climbing)
1594    // ========================================================================
1595
1596    fn parse_base_expression(&mut self) -> Result<Expression, Error> {
1597        let start_span = self.peek()?.span.clone();
1598        let mut left = self.parse_term()?;
1599
1600        while self.at_any(&[TokenKind::Plus, TokenKind::Minus])? {
1601            // Check if this minus is really a binary operator or could be part of something else
1602            // In "X not in calendar year", we don't want to consume "not" as an operator
1603            let op_tok = self.next()?;
1604            let operation = match op_tok.kind {
1605                TokenKind::Plus => ArithmeticComputation::Add,
1606                TokenKind::Minus => ArithmeticComputation::Subtract,
1607                _ => unreachable!("BUG: only + and - should reach here"),
1608            };
1609
1610            let right = self.parse_term()?;
1611            let end_span = right
1612                .source_location
1613                .as_ref()
1614                .map(|s| s.span.clone())
1615                .unwrap_or_else(|| start_span.clone());
1616            let span = self.span_covering(&start_span, &end_span);
1617
1618            left = self.new_expression(
1619                ExpressionKind::Arithmetic(Arc::new(left), operation, Arc::new(right)),
1620                self.make_source(span),
1621            )?;
1622        }
1623
1624        Ok(left)
1625    }
1626
1627    fn parse_term(&mut self) -> Result<Expression, Error> {
1628        let start_span = self.peek()?.span.clone();
1629        let mut left = self.parse_power()?;
1630
1631        while self.at_any(&[TokenKind::Star, TokenKind::Slash, TokenKind::Percent])? {
1632            // Be careful: % could be a percent literal suffix (e.g. 50%)
1633            // But here in term context, it's modulo since we already parsed the number
1634            let op_tok = self.next()?;
1635            let operation = match op_tok.kind {
1636                TokenKind::Star => ArithmeticComputation::Multiply,
1637                TokenKind::Slash => ArithmeticComputation::Divide,
1638                TokenKind::Percent => ArithmeticComputation::Modulo,
1639                _ => unreachable!("BUG: only *, /, % should reach here"),
1640            };
1641
1642            let right = self.parse_power()?;
1643            let end_span = right
1644                .source_location
1645                .as_ref()
1646                .map(|s| s.span.clone())
1647                .unwrap_or_else(|| start_span.clone());
1648            let span = self.span_covering(&start_span, &end_span);
1649
1650            left = self.new_expression(
1651                ExpressionKind::Arithmetic(Arc::new(left), operation, Arc::new(right)),
1652                self.make_source(span),
1653            )?;
1654        }
1655
1656        Ok(left)
1657    }
1658
1659    fn parse_power(&mut self) -> Result<Expression, Error> {
1660        let start_span = self.peek()?.span.clone();
1661        let left = self.parse_factor()?;
1662
1663        if self.at(&TokenKind::Caret)? {
1664            self.next()?;
1665            self.check_depth()?;
1666            let right = self.parse_power()?;
1667            self.depth_tracker.pop_depth();
1668            let end_span = right
1669                .source_location
1670                .as_ref()
1671                .map(|s| s.span.clone())
1672                .unwrap_or_else(|| start_span.clone());
1673            let span = self.span_covering(&start_span, &end_span);
1674
1675            return self.new_expression(
1676                ExpressionKind::Arithmetic(
1677                    Arc::new(left),
1678                    ArithmeticComputation::Power,
1679                    Arc::new(right),
1680                ),
1681                self.make_source(span),
1682            );
1683        }
1684
1685        Ok(left)
1686    }
1687
1688    fn parse_factor(&mut self) -> Result<Expression, Error> {
1689        let peeked = self.peek()?;
1690        let start_span = peeked.span.clone();
1691
1692        if peeked.kind == TokenKind::Minus {
1693            self.next()?;
1694            let operand = self.parse_primary_or_math()?;
1695            let end_span = operand
1696                .source_location
1697                .as_ref()
1698                .map(|s| s.span.clone())
1699                .unwrap_or_else(|| start_span.clone());
1700            let span = self.span_covering(&start_span, &end_span);
1701
1702            let zero = self.new_expression(
1703                ExpressionKind::Literal(Value::Number(Decimal::ZERO)),
1704                self.make_source(start_span),
1705            )?;
1706            return self.new_expression(
1707                ExpressionKind::Arithmetic(
1708                    Arc::new(zero),
1709                    ArithmeticComputation::Subtract,
1710                    Arc::new(operand),
1711                ),
1712                self.make_source(span),
1713            );
1714        }
1715
1716        if peeked.kind == TokenKind::Plus {
1717            self.next()?;
1718            return self.parse_primary_or_math();
1719        }
1720
1721        self.parse_primary_or_math()
1722    }
1723
1724    fn parse_primary_or_math(&mut self) -> Result<Expression, Error> {
1725        let peeked = self.peek()?;
1726
1727        // Math functions
1728        if is_math_function(&peeked.kind) {
1729            return self.parse_math_function();
1730        }
1731
1732        self.parse_primary()
1733    }
1734
1735    fn parse_math_function(&mut self) -> Result<Expression, Error> {
1736        let func_tok = self.next()?;
1737        let start_span = func_tok.span.clone();
1738
1739        let operator = match func_tok.kind {
1740            TokenKind::Sqrt => MathematicalComputation::Sqrt,
1741            TokenKind::Sin => MathematicalComputation::Sin,
1742            TokenKind::Cos => MathematicalComputation::Cos,
1743            TokenKind::Tan => MathematicalComputation::Tan,
1744            TokenKind::Asin => MathematicalComputation::Asin,
1745            TokenKind::Acos => MathematicalComputation::Acos,
1746            TokenKind::Atan => MathematicalComputation::Atan,
1747            TokenKind::Log => MathematicalComputation::Log,
1748            TokenKind::Exp => MathematicalComputation::Exp,
1749            TokenKind::Abs => MathematicalComputation::Abs,
1750            TokenKind::Floor => MathematicalComputation::Floor,
1751            TokenKind::Ceil => MathematicalComputation::Ceil,
1752            TokenKind::Round => MathematicalComputation::Round,
1753            _ => unreachable!("BUG: only math functions should reach here"),
1754        };
1755
1756        self.check_depth()?;
1757        let operand = self.parse_base_expression()?;
1758        self.depth_tracker.pop_depth();
1759
1760        let end_span = operand
1761            .source_location
1762            .as_ref()
1763            .map(|s| s.span.clone())
1764            .unwrap_or_else(|| start_span.clone());
1765        let span = self.span_covering(&start_span, &end_span);
1766
1767        self.new_expression(
1768            ExpressionKind::MathematicalComputation(operator, Arc::new(operand)),
1769            self.make_source(span),
1770        )
1771    }
1772
1773    fn parse_primary(&mut self) -> Result<Expression, Error> {
1774        let peeked = self.peek()?;
1775        let start_span = peeked.span.clone();
1776
1777        match &peeked.kind {
1778            // Parenthesized expression
1779            TokenKind::LParen => {
1780                self.next()?; // consume (
1781                let inner = self.parse_expression()?;
1782                self.expect(&TokenKind::RParen)?;
1783                Ok(inner)
1784            }
1785
1786            // Now keyword
1787            TokenKind::Now => {
1788                let tok = self.next()?;
1789                self.new_expression(ExpressionKind::Now, self.make_source(tok.span))
1790            }
1791
1792            // String literal
1793            TokenKind::StringLit => {
1794                let tok = self.next()?;
1795                let content = unquote_string(&tok.text);
1796                self.new_expression(
1797                    ExpressionKind::Literal(Value::Text(content)),
1798                    self.make_source(tok.span),
1799                )
1800            }
1801
1802            // Boolean literals
1803            k if is_boolean_keyword(k) => {
1804                let tok = self.next()?;
1805                let bv = parse_boolean_value(&tok.text);
1806                self.new_expression(
1807                    ExpressionKind::Literal(Value::Boolean(bv)),
1808                    self.make_source(tok.span),
1809                )
1810            }
1811
1812            // Number literal (could be: plain number, date, time, duration, percent, unit)
1813            TokenKind::NumberLit => self.parse_number_expression(),
1814
1815            // Reference (identifier, type keyword)
1816            k if can_be_reference_segment(k) => {
1817                let reference = self.parse_expression_reference()?;
1818                let end_span = self.peek()?.span.clone();
1819                let span = self.span_covering(&start_span, &end_span);
1820                self.new_expression(ExpressionKind::Reference(reference), self.make_source(span))
1821            }
1822
1823            _ => {
1824                let tok = self.next()?;
1825                Err(self.error_at_token(
1826                    &tok,
1827                    format!("Expected an expression, found '{}'", tok.text),
1828                ))
1829            }
1830        }
1831    }
1832
1833    fn parse_number_expression(&mut self) -> Result<Expression, Error> {
1834        let num_tok = self.next()?;
1835        let num_text = num_tok.text.clone();
1836        let start_span = num_tok.span.clone();
1837
1838        // Check if this is a date literal (YYYY-MM-DD)
1839        if num_text.len() == 4
1840            && num_text.chars().all(|c| c.is_ascii_digit())
1841            && self.at(&TokenKind::Minus)?
1842        {
1843            // Peek further: if next-next is a number, this is likely a date
1844            // We need to be careful: "2024 - 5" is arithmetic, "2024-01-15" is a date
1845            // Date format requires: YYYY-MM-DD where MM and DD are 2 digits
1846            // This is ambiguous at the token level. Let's check if the pattern matches.
1847            // Since dates use -NN- pattern and arithmetic uses - N pattern (with spaces),
1848            // we can use the span positions to disambiguate.
1849            let minus_span = self.peek()?.span.clone();
1850            // If minus is immediately adjacent to the number (no space), it's a date
1851            if minus_span.start == start_span.end {
1852                let value = self.parse_date_literal(num_text, start_span.clone())?;
1853                return self
1854                    .new_expression(ExpressionKind::Literal(value), self.make_source(start_span));
1855            }
1856        }
1857
1858        // Check for time literal (HH:MM:SS)
1859        if num_text.len() == 2
1860            && num_text.chars().all(|c| c.is_ascii_digit())
1861            && self.at(&TokenKind::Colon)?
1862        {
1863            let colon_span = self.peek()?.span.clone();
1864            if colon_span.start == start_span.end {
1865                let value = self.try_parse_time_literal(num_text, start_span.clone())?;
1866                return self
1867                    .new_expression(ExpressionKind::Literal(value), self.make_source(start_span));
1868            }
1869        }
1870
1871        // Check for %% (permille)
1872        if self.at(&TokenKind::PercentPercent)? {
1873            let pp_tok = self.next()?;
1874            if let Ok(next_peek) = self.peek() {
1875                if next_peek.kind == TokenKind::NumberLit {
1876                    return Err(self.error_at_token(
1877                        &pp_tok,
1878                        "Permille literal cannot be followed by a digit",
1879                    ));
1880                }
1881            }
1882            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
1883            let ratio_value = decimal / Decimal::from(1000);
1884            return self.new_expression(
1885                ExpressionKind::Literal(Value::Ratio(ratio_value, Some("permille".to_string()))),
1886                self.make_source(start_span),
1887            );
1888        }
1889
1890        // Check for % (percent)
1891        if self.at(&TokenKind::Percent)? {
1892            let pct_span = self.peek()?.span.clone();
1893            // Only consume % if it's directly adjacent (no space) for the shorthand syntax
1894            // Or if it's "50 %" (space separated is also valid per the grammar)
1895            let pct_tok = self.next()?;
1896            if let Ok(next_peek) = self.peek() {
1897                if next_peek.kind == TokenKind::NumberLit || next_peek.kind == TokenKind::Percent {
1898                    return Err(self.error_at_token(
1899                        &pct_tok,
1900                        "Percent literal cannot be followed by a digit",
1901                    ));
1902                }
1903            }
1904            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
1905            let ratio_value = decimal / Decimal::from(100);
1906            return self.new_expression(
1907                ExpressionKind::Literal(Value::Ratio(ratio_value, Some("percent".to_string()))),
1908                self.make_source(self.span_covering(&start_span, &pct_span)),
1909            );
1910        }
1911
1912        // Check for "percent" keyword
1913        if self.at(&TokenKind::PercentKw)? {
1914            self.next()?;
1915            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
1916            let ratio_value = decimal / Decimal::from(100);
1917            return self.new_expression(
1918                ExpressionKind::Literal(Value::Ratio(ratio_value, Some("percent".to_string()))),
1919                self.make_source(start_span),
1920            );
1921        }
1922
1923        // Check for "permille" keyword
1924        if self.at(&TokenKind::Permille)? {
1925            self.next()?;
1926            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
1927            let ratio_value = decimal / Decimal::from(1000);
1928            return self.new_expression(
1929                ExpressionKind::Literal(Value::Ratio(ratio_value, Some("permille".to_string()))),
1930                self.make_source(start_span),
1931            );
1932        }
1933
1934        // Check for duration unit
1935        if is_duration_unit(&self.peek()?.kind) && self.peek()?.kind != TokenKind::PercentKw {
1936            let unit_tok = self.next()?;
1937            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
1938            let duration_unit = parse_duration_unit_from_text(&unit_tok.text);
1939            return self.new_expression(
1940                ExpressionKind::Literal(Value::Duration(decimal, duration_unit)),
1941                self.make_source(self.span_covering(&start_span, &unit_tok.span)),
1942            );
1943        }
1944
1945        // Check for user-defined unit (identifier after number)
1946        if can_be_label(&self.peek()?.kind) {
1947            let unit_tok = self.next()?;
1948            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
1949            return self.new_expression(
1950                ExpressionKind::UnresolvedUnitLiteral(decimal, unit_tok.text.clone()),
1951                self.make_source(self.span_covering(&start_span, &unit_tok.span)),
1952            );
1953        }
1954
1955        // Plain number
1956        let decimal = parse_decimal_string(&num_text, &start_span, self)?;
1957        self.new_expression(
1958            ExpressionKind::Literal(Value::Number(decimal)),
1959            self.make_source(start_span),
1960        )
1961    }
1962
1963    fn parse_expression_reference(&mut self) -> Result<Reference, Error> {
1964        let mut segments = Vec::new();
1965
1966        let first = self.next()?;
1967        segments.push(first.text.clone());
1968
1969        while self.at(&TokenKind::Dot)? {
1970            self.next()?; // consume .
1971            let seg = self.next()?;
1972            if !can_be_reference_segment(&seg.kind) {
1973                return Err(self.error_at_token(
1974                    &seg,
1975                    format!("Expected an identifier after '.', found {}", seg.kind),
1976                ));
1977            }
1978            segments.push(seg.text.clone());
1979        }
1980
1981        Ok(Reference::from_path(segments))
1982    }
1983}
1984
1985// ============================================================================
1986// Helper functions
1987// ============================================================================
1988
1989fn unquote_string(s: &str) -> String {
1990    if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') {
1991        s[1..s.len() - 1].to_string()
1992    } else {
1993        s.to_string()
1994    }
1995}
1996
1997fn parse_boolean_value(text: &str) -> BooleanValue {
1998    match text.to_lowercase().as_str() {
1999        "true" => BooleanValue::True,
2000        "false" => BooleanValue::False,
2001        "yes" => BooleanValue::Yes,
2002        "no" => BooleanValue::No,
2003        "accept" => BooleanValue::Accept,
2004        "reject" => BooleanValue::Reject,
2005        other => unreachable!("BUG: '{}' is not a boolean keyword", other),
2006    }
2007}
2008
2009fn parse_decimal_string(text: &str, span: &Span, parser: &Parser) -> Result<Decimal, Error> {
2010    let clean = text.replace(['_', ','], "");
2011    Decimal::from_str(&clean).map_err(|_| {
2012        Error::parsing(
2013            format!(
2014                "Invalid number: '{}'. Expected a valid decimal number (e.g., 42, 3.14, 1_000_000)",
2015                text
2016            ),
2017            parser.make_source(span.clone()),
2018            None::<String>,
2019        )
2020    })
2021}
2022
2023fn parse_conversion_target(unit_str: &str) -> ConversionTarget {
2024    let unit_lower = unit_str.to_lowercase();
2025    match unit_lower.as_str() {
2026        "year" | "years" => ConversionTarget::Duration(DurationUnit::Year),
2027        "month" | "months" => ConversionTarget::Duration(DurationUnit::Month),
2028        "week" | "weeks" => ConversionTarget::Duration(DurationUnit::Week),
2029        "day" | "days" => ConversionTarget::Duration(DurationUnit::Day),
2030        "hour" | "hours" => ConversionTarget::Duration(DurationUnit::Hour),
2031        "minute" | "minutes" => ConversionTarget::Duration(DurationUnit::Minute),
2032        "second" | "seconds" => ConversionTarget::Duration(DurationUnit::Second),
2033        "millisecond" | "milliseconds" => ConversionTarget::Duration(DurationUnit::Millisecond),
2034        "microsecond" | "microseconds" => ConversionTarget::Duration(DurationUnit::Microsecond),
2035        _ => ConversionTarget::Unit(unit_lower),
2036    }
2037}
2038
2039fn parse_duration_unit_from_text(text: &str) -> DurationUnit {
2040    match text.to_lowercase().as_str() {
2041        "year" | "years" => DurationUnit::Year,
2042        "month" | "months" => DurationUnit::Month,
2043        "week" | "weeks" => DurationUnit::Week,
2044        "day" | "days" => DurationUnit::Day,
2045        "hour" | "hours" => DurationUnit::Hour,
2046        "minute" | "minutes" => DurationUnit::Minute,
2047        "second" | "seconds" => DurationUnit::Second,
2048        "millisecond" | "milliseconds" => DurationUnit::Millisecond,
2049        "microsecond" | "microseconds" => DurationUnit::Microsecond,
2050        other => unreachable!("BUG: '{}' is not a duration unit", other),
2051    }
2052}
2053
2054fn is_comparison_operator(kind: &TokenKind) -> bool {
2055    matches!(
2056        kind,
2057        TokenKind::Gt
2058            | TokenKind::Lt
2059            | TokenKind::Gte
2060            | TokenKind::Lte
2061            | TokenKind::EqEq
2062            | TokenKind::BangEq
2063            | TokenKind::Is
2064    )
2065}
2066
2067// Helper trait for TokenKind
2068impl TokenKind {
2069    fn is_identifier_like(&self) -> bool {
2070        matches!(self, TokenKind::Identifier)
2071            || can_be_label(self)
2072            || is_type_keyword(self)
2073            || is_boolean_keyword(self)
2074            || is_duration_unit(self)
2075            || is_math_function(self)
2076    }
2077}