Skip to main content

lemma/parsing/
parser.rs

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