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, can_be_repository_qualifier_segment,
6    conversion_target_from_token, is_boolean_keyword, is_math_function, is_spec_body_keyword,
7    is_structural_keyword, is_type_keyword, token_kind_to_boolean_value, token_kind_to_primitive,
8    Lexer, LexerCheckpoint, Token, TokenKind,
9};
10use crate::parsing::source::Source;
11use indexmap::IndexMap;
12use rust_decimal::Decimal;
13use std::str::FromStr;
14use std::sync::Arc;
15
16#[derive(Debug)]
17pub struct ParseResult {
18    pub repositories: IndexMap<Arc<LemmaRepository>, Vec<LemmaSpec>>,
19    pub expression_count: usize,
20}
21
22impl ParseResult {
23    /// Specs in parse order: repository groups follow declaration order; specs within each group follow source order.
24    #[must_use]
25    pub fn flatten_specs(&self) -> Vec<&LemmaSpec> {
26        self.repositories
27            .values()
28            .flat_map(|specs| specs.iter())
29            .collect()
30    }
31
32    #[must_use]
33    pub fn into_flattened_specs(self) -> Vec<LemmaSpec> {
34        self.repositories.into_values().flatten().collect()
35    }
36}
37
38pub fn parse(
39    content: &str,
40    source_type: crate::parsing::source::SourceType,
41    limits: &ResourceLimits,
42) -> Result<ParseResult, Error> {
43    if content.len() > limits.max_source_size_bytes {
44        return Err(Error::resource_limit_exceeded(
45            "max_source_size_bytes",
46            format!(
47                "{} bytes ({} MB)",
48                limits.max_source_size_bytes,
49                limits.max_source_size_bytes / (1024 * 1024)
50            ),
51            format!(
52                "{} bytes ({:.2} MB)",
53                content.len(),
54                content.len() as f64 / (1024.0 * 1024.0)
55            ),
56            "Reduce source size or split into multiple specs",
57            None,
58            None,
59            None,
60        ));
61    }
62
63    let mut parser = Parser::new(content, source_type, limits);
64    let repositories = parser.parse_file()?;
65    let mut result = ParseResult {
66        repositories,
67        expression_count: parser.expression_count,
68    };
69    canonicalize_parse_result(&mut result);
70    Ok(result)
71}
72
73fn canonicalize_parse_result(result: &mut ParseResult) {
74    let old = std::mem::take(&mut result.repositories);
75    let mut new_map: IndexMap<Arc<LemmaRepository>, Vec<LemmaSpec>> = IndexMap::new();
76    for (repo, mut specs) in old {
77        let mut canonical_repo = (*repo).clone();
78        canonicalize_repository(&mut canonical_repo);
79        for spec in &mut specs {
80            canonicalize_lemma_spec(spec);
81        }
82        new_map
83            .entry(Arc::new(canonical_repo))
84            .or_default()
85            .extend(specs);
86    }
87    result.repositories = new_map;
88}
89
90struct Parser {
91    lexer: Lexer,
92    source_type: crate::parsing::source::SourceType,
93    depth_tracker: DepthTracker,
94    expression_count: usize,
95    max_expression_count: usize,
96    max_spec_name_length: usize,
97    max_data_name_length: usize,
98    max_rule_name_length: usize,
99    last_span: Span,
100}
101
102impl Parser {
103    fn new(
104        content: &str,
105        source_type: crate::parsing::source::SourceType,
106        limits: &ResourceLimits,
107    ) -> Self {
108        Parser {
109            lexer: Lexer::new(content, &source_type),
110            source_type,
111            depth_tracker: DepthTracker::with_max_depth(limits.max_expression_depth),
112            expression_count: 0,
113            max_expression_count: limits.max_expression_count,
114            max_spec_name_length: crate::limits::MAX_SPEC_NAME_LENGTH,
115            max_data_name_length: crate::limits::MAX_DATA_NAME_LENGTH,
116            max_rule_name_length: crate::limits::MAX_RULE_NAME_LENGTH,
117            last_span: Span {
118                start: 0,
119                end: 0,
120                line: 1,
121                col: 0,
122            },
123        }
124    }
125
126    fn source_type(&self) -> crate::parsing::source::SourceType {
127        self.source_type.clone()
128    }
129
130    fn peek(&mut self) -> Result<&Token, Error> {
131        self.lexer.peek()
132    }
133
134    fn next(&mut self) -> Result<Token, Error> {
135        let token = self.lexer.next_token()?;
136        self.last_span = token.span.clone();
137        Ok(token)
138    }
139
140    fn at(&mut self, kind: &TokenKind) -> Result<bool, Error> {
141        Ok(&self.peek()?.kind == kind)
142    }
143
144    fn at_any(&mut self, kinds: &[TokenKind]) -> Result<bool, Error> {
145        let current = &self.peek()?.kind;
146        Ok(kinds.contains(current))
147    }
148
149    fn checkpoint(&self) -> (LexerCheckpoint, usize) {
150        (self.lexer.checkpoint(), self.expression_count)
151    }
152
153    fn restore(&mut self, checkpoint: (LexerCheckpoint, usize)) {
154        self.lexer.restore(checkpoint.0);
155        self.expression_count = checkpoint.1;
156    }
157
158    fn expect(&mut self, kind: &TokenKind) -> Result<Token, Error> {
159        let token = self.next()?;
160        if &token.kind == kind {
161            Ok(token)
162        } else {
163            Err(self.error_at_token(&token, format!("Expected {}, found {}", kind, token.kind)))
164        }
165    }
166
167    fn error_at_token(&self, token: &Token, message: impl Into<String>) -> Error {
168        Error::parsing(
169            message,
170            Source::new(self.source_type(), token.span.clone()),
171            None::<String>,
172        )
173    }
174
175    fn error_at_token_with_suggestion(
176        &self,
177        token: &Token,
178        message: impl Into<String>,
179        suggestion: impl Into<String>,
180    ) -> Error {
181        Error::parsing(
182            message,
183            Source::new(self.source_type(), token.span.clone()),
184            Some(suggestion),
185        )
186    }
187
188    fn parse_spec_ref_trailing_effective(&mut self) -> Result<Option<DateTimeValue>, Error> {
189        let mut effective = None;
190        if self.at(&TokenKind::NumberLit)? {
191            let peeked = self.peek()?;
192            if peeked.text.len() == 4 && peeked.text.chars().all(|c| c.is_ascii_digit()) {
193                effective = self.try_parse_effective_from()?;
194            }
195        }
196        Ok(effective)
197    }
198
199    fn make_source(&self, span: Span) -> Source {
200        Source::new(self.source_type(), span)
201    }
202
203    fn span_from(&self, start: &Span) -> Span {
204        // Create a span from start to the current lexer position.
205        // We peek to get the current position.
206        Span {
207            start: start.start,
208            end: start.end.max(start.start),
209            line: start.line,
210            col: start.col,
211        }
212    }
213
214    fn span_covering(&self, start: &Span, end: &Span) -> Span {
215        Span {
216            start: start.start,
217            end: end.end,
218            line: start.line,
219            col: start.col,
220        }
221    }
222
223    // ========================================================================
224    // Top-level: file and spec
225    // ========================================================================
226
227    fn parse_file(&mut self) -> Result<IndexMap<Arc<LemmaRepository>, Vec<LemmaSpec>>, Error> {
228        let mut map: IndexMap<Arc<LemmaRepository>, Vec<LemmaSpec>> = IndexMap::new();
229        let mut current_repo = Arc::new(LemmaRepository::new(None));
230
231        loop {
232            if self.at(&TokenKind::Eof)? {
233                break;
234            }
235
236            if self.at(&TokenKind::Repo)? {
237                let repo_token = self.expect(&TokenKind::Repo)?;
238                let start_line = repo_token.span.line;
239                let (qualifier, _) = self.parse_repository_qualifier()?;
240                crate::limits::check_max_length(
241                    &qualifier.name,
242                    self.max_spec_name_length,
243                    "repository name",
244                    Some(Source::new(self.source_type(), repo_token.span)),
245                )?;
246                current_repo = Arc::new(
247                    LemmaRepository::new(Some(qualifier.name)).with_start_line(start_line),
248                );
249                map.entry(Arc::clone(&current_repo)).or_default();
250                continue;
251            }
252
253            if self.at(&TokenKind::Spec)? {
254                let spec = self.parse_spec()?;
255                map.entry(Arc::clone(&current_repo)).or_default().push(spec);
256                continue;
257            }
258
259            let token = self.next()?;
260            return Err(self.error_at_token_with_suggestion(
261                &token,
262                format!(
263                    "Expected a top-level `repo` or `spec` declaration, found {}",
264                    token.kind
265                ),
266                "Each Lemma file is a sequence of optional `repo <name>` sections followed by `spec <name>` blocks",
267            ));
268        }
269
270        Ok(map)
271    }
272
273    fn parse_spec(&mut self) -> Result<LemmaSpec, Error> {
274        let spec_token = self.expect(&TokenKind::Spec)?;
275        let start_line = spec_token.span.line;
276
277        let (name, name_span) = self.parse_spec_name()?;
278        crate::limits::check_max_length(
279            &name,
280            self.max_spec_name_length,
281            "spec",
282            Some(Source::new(self.source_type(), name_span)),
283        )?;
284
285        let effective_from = self.try_parse_effective_from()?;
286
287        let commentary = self.try_parse_commentary()?;
288
289        let mut spec = LemmaSpec::new(name.clone())
290            .with_source_type(self.source_type())
291            .with_start_line(start_line);
292        spec.effective_from = crate::parsing::ast::EffectiveDate::from_option(effective_from);
293
294        if let Some(commentary_text) = commentary {
295            spec = spec.set_commentary(commentary_text);
296        }
297
298        // First pass: collect type definitions
299        // We need to peek and handle type definitions first, but since we consume tokens
300        // linearly, we'll collect all items in one pass.
301        let mut data = Vec::new();
302        let mut rules = Vec::new();
303        let mut meta_fields = Vec::new();
304
305        loop {
306            let peek_kind = self.peek()?.kind.clone();
307            match peek_kind {
308                TokenKind::Data => {
309                    let datum = self.parse_data()?;
310                    data.push(datum);
311                }
312                TokenKind::Fill => {
313                    let datum = self.parse_fill()?;
314                    data.push(datum);
315                }
316                TokenKind::Rule => {
317                    let rule = self.parse_rule()?;
318                    rules.push(rule);
319                }
320                TokenKind::Type => {
321                    let token = self.next()?;
322                    return Err(self.error_at_token_with_suggestion(
323                        &token,
324                        "'type' cannot start a declaration here",
325                        "Declare types with 'data', e.g. 'data age: number -> minimum 0'",
326                    ));
327                }
328                TokenKind::Meta => {
329                    let meta = self.parse_meta()?;
330                    meta_fields.push(meta);
331                }
332                TokenKind::Uses => {
333                    let uses_data = self.parse_uses_statement()?;
334                    data.extend(uses_data);
335                }
336                TokenKind::Spec | TokenKind::Repo | TokenKind::Eof => break,
337                _ => {
338                    let token = self.next()?;
339                    return Err(self.error_at_token_with_suggestion(
340                        &token,
341                        format!(
342                            "Expected 'data', 'fill', 'rule', 'meta', 'uses', or a new 'spec', found '{}'",
343                            token.text
344                        ),
345                        "Check the spelling or add the appropriate keyword",
346                    ));
347                }
348            }
349        }
350
351        for data in data {
352            spec = spec.add_data(data);
353        }
354        for rule in rules {
355            spec = spec.add_rule(rule);
356        }
357        for meta in meta_fields {
358            spec = spec.add_meta_field(meta);
359        }
360
361        Ok(spec)
362    }
363
364    /// Parse a spec name: identifier segments separated by `/`, `-`, or `.`.
365    ///
366    /// Allows: `my_spec`, `contracts/employment/jack`, `nl.tax.brackets`.
367    /// The `@` prefix is not allowed in spec names — it is valid in
368    /// repository names (`repo @org/name`) and qualifiers (`uses @org/name`).
369    fn parse_spec_name(&mut self) -> Result<(String, Span), Error> {
370        if self.at(&TokenKind::At)? {
371            let at_tok = self.next()?;
372            return Err(Error::parsing(
373                "'@' is not allowed in spec names; it is valid for repository names (`repo @org/name`) and qualifiers (`uses @org/name`)",
374                self.make_source(at_tok.span),
375                Some(
376                    "Write `spec my_spec`, then reference registry specs as `uses alias: @org/repo spec_name` or `data x: alias.TypeName` after importing with `uses`.",
377                ),
378            ));
379        }
380
381        let first = self.next()?;
382        if !first.kind.is_identifier_like() {
383            return Err(self.error_at_token(
384                &first,
385                format!("Expected a spec name, found {}", first.kind),
386            ));
387        }
388        let mut name = first.text.clone();
389        let start_span = first.span.clone();
390        let mut end_span = first.span.clone();
391
392        loop {
393            if self.at(&TokenKind::Slash)? {
394                self.next()?;
395                let seg = self.next()?;
396                if !seg.kind.is_identifier_like() {
397                    return Err(self.error_at_token(
398                        &seg,
399                        format!(
400                            "Expected identifier after '/' in spec name, found {}",
401                            seg.kind
402                        ),
403                    ));
404                }
405                name.push('/');
406                name.push_str(&seg.text);
407                end_span = seg.span.clone();
408            } else if self.at(&TokenKind::Dot)? {
409                self.next()?;
410                let seg = self.next()?;
411                if !seg.kind.is_identifier_like() {
412                    return Err(self.error_at_token(
413                        &seg,
414                        format!(
415                            "Expected identifier after '.' in spec name, found {}",
416                            seg.kind
417                        ),
418                    ));
419                }
420                name.push('.');
421                name.push_str(&seg.text);
422                end_span = seg.span.clone();
423            } else if self.at(&TokenKind::Minus)? {
424                let minus_span = self.peek()?.span.clone();
425                self.next()?;
426                let peeked = self.peek()?;
427                if !peeked.kind.is_identifier_like() {
428                    let span = self.span_covering(&start_span, &minus_span);
429                    return Err(Error::parsing(
430                        "Trailing '-' after spec name",
431                        self.make_source(span),
432                        None::<String>,
433                    ));
434                }
435                let seg = self.next()?;
436                name.push('-');
437                name.push_str(&seg.text);
438                end_span = seg.span.clone();
439            } else {
440                break;
441            }
442        }
443
444        let full_span = self.span_covering(&start_span, &end_span);
445        Ok((name, full_span))
446    }
447
448    /// Parse a repository qualifier: `[@] identifier ((Slash | Dot | Minus) identifier)*`.
449    ///
450    /// The `@` prefix, when present, is included in the name string (e.g. `"@org/repo"`).
451    /// Slashes, dots and minuses between segments are stitched into the name verbatim
452    /// so the qualifier round-trips exactly.
453    ///
454    /// Used in `repo` declarations and registry qualifiers (`uses`).
455    fn parse_repository_qualifier(&mut self) -> Result<(RepositoryQualifier, Span), Error> {
456        let has_at = self.at(&TokenKind::At)?;
457        let start_span = if has_at {
458            let at_tok = self.next()?;
459            at_tok.span.clone()
460        } else {
461            Span {
462                start: 0,
463                end: 0,
464                line: 0,
465                col: 0,
466            }
467        };
468
469        let first = self.next()?;
470        if !can_be_repository_qualifier_segment(&first.kind) {
471            return Err(self.error_at_token(
472                &first,
473                format!(
474                    "Expected a repository qualifier segment, found {}",
475                    first.kind
476                ),
477            ));
478        }
479        if !has_at && is_structural_keyword(&first.kind) {
480            return Err(self.error_at_token(
481                &first,
482                format!(
483                    "'{}' is a reserved keyword and cannot be used as a repository name",
484                    first.text
485                ),
486            ));
487        }
488        let start_span = if has_at {
489            start_span
490        } else {
491            first.span.clone()
492        };
493        let mut name = first.text.clone();
494
495        loop {
496            let next_kind = self.peek()?.kind.clone();
497            match next_kind {
498                TokenKind::Slash => {
499                    self.next()?;
500                    name.push('/');
501                    let seg = self.next()?;
502                    if !can_be_repository_qualifier_segment(&seg.kind) {
503                        return Err(self.error_at_token(
504                            &seg,
505                            format!(
506                                "Expected identifier after '/' in repository qualifier segment, found {}",
507                                seg.kind
508                            ),
509                        ));
510                    }
511                    name.push_str(&seg.text);
512                }
513                TokenKind::Dot => {
514                    self.next()?;
515                    name.push('.');
516                    let seg = self.next()?;
517                    if !can_be_repository_qualifier_segment(&seg.kind) {
518                        return Err(self.error_at_token(
519                            &seg,
520                            format!(
521                                "Expected identifier after '.' in repository qualifier segment, found {}",
522                                seg.kind
523                            ),
524                        ));
525                    }
526                    name.push_str(&seg.text);
527                }
528                TokenKind::Minus => {
529                    let minus_text_peek = self.lexer.peek_second()?;
530                    if !can_be_repository_qualifier_segment(&minus_text_peek.kind) {
531                        break;
532                    }
533                    self.next()?;
534                    name.push('-');
535                    let seg = self.next()?;
536                    name.push_str(&seg.text);
537                }
538                _ => break,
539            }
540        }
541
542        if has_at {
543            name.insert(0, '@');
544        }
545
546        let full_span = self.span_covering(&start_span, &self.last_span);
547        Ok((RepositoryQualifier { name }, full_span))
548    }
549
550    /// Parses `[<repository_qualifier>] <spec> [<effective>]`
551    pub fn parse_spec_ref_target(&mut self) -> Result<SpecRef, Error> {
552        let mut repository = None;
553        let mut repository_span = None;
554
555        if self.at(&TokenKind::At)? {
556            let (q, span) = self.parse_repository_qualifier()?;
557            repository = Some(q);
558            repository_span = Some(span);
559        } else {
560            let saved_state = self.lexer.clone();
561            if let Ok((potential_repository, span)) = self.parse_repository_qualifier() {
562                if let Ok(next_tok) = self.peek() {
563                    if next_tok.kind.is_identifier_like() {
564                        repository = Some(potential_repository);
565                        repository_span = Some(span);
566                    } else {
567                        self.lexer = saved_state;
568                    }
569                } else {
570                    self.lexer = saved_state;
571                }
572            } else {
573                self.lexer = saved_state;
574            }
575        }
576
577        let (spec_name, spec_name_span) = self.parse_spec_name()?;
578        let effective = self.parse_spec_ref_trailing_effective()?;
579        let target_span = self.span_covering(&spec_name_span, &self.last_span);
580
581        let has_repository = repository.is_some();
582        Ok(SpecRef {
583            name: spec_name,
584            repository,
585            effective,
586            repository_span: if has_repository {
587                repository_span
588            } else {
589                None
590            },
591            target_span: Some(target_span),
592        })
593    }
594
595    fn try_parse_effective_from(&mut self) -> Result<Option<DateTimeValue>, Error> {
596        // effective_from is a date/time token right after the spec name.
597        // It's tricky because it looks like a number (e.g. 2026-03-04).
598        // In the old grammar it was a special atomic rule.
599        // We'll check if the next token is a NumberLit that looks like a year.
600        if !self.at(&TokenKind::NumberLit)? {
601            return Ok(None);
602        }
603
604        let peeked = self.peek()?;
605        let peeked_text = peeked.text.clone();
606        let peeked_span = peeked.span.clone();
607
608        // Check if it could be a date: 4-digit number followed by -
609        if peeked_text.len() == 4 && peeked_text.chars().all(|c| c.is_ascii_digit()) {
610            // Collect the full datetime string by consuming tokens
611            let mut dt_str = String::new();
612            let num_tok = self.next()?; // consume the year number
613            dt_str.push_str(&num_tok.text);
614
615            // Try to consume -MM-DD and optional T... parts
616            while self.at(&TokenKind::Minus)? {
617                self.next()?; // consume -
618                dt_str.push('-');
619                let part = self.next()?;
620                dt_str.push_str(&part.text);
621            }
622
623            // Check for T (time part)
624            if self.at(&TokenKind::Identifier)? {
625                let peeked = self.peek()?;
626                if peeked.text.starts_with('T') || peeked.text.starts_with('t') {
627                    let time_part = self.next()?;
628                    dt_str.push_str(&time_part.text);
629                    // Consume any : separated parts
630                    while self.at(&TokenKind::Colon)? {
631                        self.next()?;
632                        dt_str.push(':');
633                        let part = self.next()?;
634                        dt_str.push_str(&part.text);
635                    }
636                    // Check for timezone (+ or Z)
637                    if self.at(&TokenKind::Plus)? {
638                        self.next()?;
639                        dt_str.push('+');
640                        let tz_part = self.next()?;
641                        dt_str.push_str(&tz_part.text);
642                        if self.at(&TokenKind::Colon)? {
643                            self.next()?;
644                            dt_str.push(':');
645                            let tz_min = self.next()?;
646                            dt_str.push_str(&tz_min.text);
647                        }
648                    }
649                }
650            }
651
652            // Try to parse as datetime
653            if let Ok(dtv) = dt_str.parse::<DateTimeValue>() {
654                return Ok(Some(dtv));
655            }
656
657            return Err(Error::parsing(
658                format!("Invalid date/time in spec declaration: '{}'", dt_str),
659                self.make_source(peeked_span),
660                None::<String>,
661            ));
662        }
663
664        Ok(None)
665    }
666
667    fn try_parse_commentary(&mut self) -> Result<Option<String>, Error> {
668        if !self.at(&TokenKind::Commentary)? {
669            return Ok(None);
670        }
671        let token = self.next()?;
672        let trimmed = token.text.trim().to_string();
673        if trimmed.is_empty() {
674            Ok(None)
675        } else {
676            Ok(Some(trimmed))
677        }
678    }
679
680    // ========================================================================
681    // Data parsing
682    // ========================================================================
683
684    fn parse_data(&mut self) -> Result<LemmaData, Error> {
685        let data_token = self.expect(&TokenKind::Data)?;
686        let start_span = data_token.span.clone();
687
688        let reference = self.parse_reference()?;
689        for segment in reference
690            .segments
691            .iter()
692            .chain(std::iter::once(&reference.name))
693        {
694            crate::limits::check_max_length(
695                segment,
696                self.max_data_name_length,
697                "data",
698                Some(Source::new(self.source_type(), start_span.clone())),
699            )?;
700        }
701
702        self.expect(&TokenKind::Colon)?;
703
704        if !reference.segments.is_empty() {
705            let tok = self.peek()?.clone();
706            return Err(self.error_at_token_with_suggestion(
707                &tok,
708                "Dotted paths require `fill`; `data` declares types and values on local names only.",
709                "Use `fill path.to.slot: <value or reference>` to assign on an imported or nested slot.",
710            ));
711        }
712
713        let value = self.parse_data_value()?;
714
715        let span = self.span_covering(&start_span, &self.last_span);
716        let source = self.make_source(span);
717
718        Ok(LemmaData::new(reference, value, source))
719    }
720
721    fn parse_fill(&mut self) -> Result<LemmaData, Error> {
722        let fill_token = self.expect(&TokenKind::Fill)?;
723        let start_span = fill_token.span.clone();
724
725        let reference = self.parse_reference()?;
726        for segment in reference
727            .segments
728            .iter()
729            .chain(std::iter::once(&reference.name))
730        {
731            crate::limits::check_max_length(
732                segment,
733                self.max_data_name_length,
734                "fill",
735                Some(Source::new(self.source_type(), start_span.clone())),
736            )?;
737        }
738
739        self.expect(&TokenKind::Colon)?;
740
741        let value = self.parse_fill_value()?;
742
743        let span = self.span_covering(&start_span, &self.last_span);
744        let source = self.make_source(span);
745
746        Ok(LemmaData::new(reference, value, source))
747    }
748
749    fn fill_rhs_starts_as_literal(&self, kind: &TokenKind) -> bool {
750        matches!(
751            kind,
752            TokenKind::StringLit | TokenKind::NumberLit | TokenKind::Minus | TokenKind::Plus
753        ) || is_boolean_keyword(kind)
754    }
755
756    fn parse_fill_value(&mut self) -> Result<DataValue, Error> {
757        let peek_kind = self.peek()?.kind.clone();
758
759        if self.fill_rhs_starts_as_literal(&peek_kind) {
760            let value = self.parse_literal_value()?;
761            return Ok(DataValue::Fill(FillRhs::Literal(value)));
762        }
763
764        if can_be_label(&peek_kind) || is_type_keyword(&peek_kind) {
765            let target = self.parse_reference()?;
766            if self.at(&TokenKind::Arrow)? {
767                let tok = self.peek()?.clone();
768                return Err(self.error_at_token_with_suggestion(
769                    &tok,
770                    "Constraint chains (`-> ...`) are not allowed on `fill`; use `data` to declare types and constraints.",
771                    "Use `data name: <type> -> ...` for constraints, then `fill name: <reference or literal>` to assign.",
772                ));
773            }
774            return Ok(DataValue::Fill(FillRhs::Reference { target }));
775        }
776
777        let tok = self.peek()?.clone();
778        Err(self.error_at_token(
779            &tok,
780            format!(
781                "Expected a reference or literal after `fill ...:`, found {}",
782                tok.kind
783            ),
784        ))
785    }
786
787    fn parse_reference(&mut self) -> Result<Reference, Error> {
788        let mut segments = Vec::new();
789
790        let first = self.next()?;
791        // Structural keywords (spec, data, rule, unless, ...) cannot be names.
792        // Type keywords (duration, number, date, ...) CAN be names per the grammar.
793        if is_structural_keyword(&first.kind) {
794            return Err(self.error_at_token_with_suggestion(
795                &first,
796                format!(
797                    "'{}' is a reserved keyword and cannot be used as a name",
798                    first.text
799                ),
800                "Choose a different name that is not a reserved keyword",
801            ));
802        }
803
804        if !can_be_reference_segment(&first.kind) {
805            return Err(self.error_at_token(
806                &first,
807                format!("Expected an identifier, found {}", first.kind),
808            ));
809        }
810
811        segments.push(first.text.clone());
812
813        // Consume . separated segments
814        while self.at(&TokenKind::Dot)? {
815            self.next()?; // consume .
816            let seg = self.next()?;
817            if !can_be_reference_segment(&seg.kind) {
818                return Err(self.error_at_token(
819                    &seg,
820                    format!("Expected an identifier after '.', found {}", seg.kind),
821                ));
822            }
823            segments.push(seg.text.clone());
824        }
825
826        Ok(Reference::from_path(segments))
827    }
828
829    fn parse_data_value(&mut self) -> Result<DataValue, Error> {
830        if self.at(&TokenKind::Spec)? {
831            let token = self.next()?;
832            return Err(self.error_at_token_with_suggestion(
833                &token,
834                "Cannot import a spec with `data`; use `uses`",
835                "Use `uses <spec_name>` or `uses <alias>: <spec_name>`",
836            ));
837        }
838
839        let peek_kind = self.peek()?.kind.clone();
840
841        if token_kind_to_primitive(&peek_kind).is_some() || can_be_label(&peek_kind) {
842            let (base, constraints) = self.parse_type_arrow_chain()?;
843            return Ok(DataValue::Definition {
844                base: Some(base),
845                constraints,
846                value: None,
847            });
848        }
849
850        // Otherwise, it's a literal value
851        let value = self.parse_literal_value()?;
852        Ok(DataValue::Definition {
853            base: None,
854            constraints: None,
855            value: Some(value),
856        })
857    }
858
859    /// Parse a single `uses` item: `[alias ':']` then [`Self::parse_spec_ref_target`] (optional
860    /// repository qualifier, spec name, optional effective date pin).
861    fn parse_uses_item(&mut self, start_span: &Span) -> Result<LemmaData, Error> {
862        let explicit_alias = if can_be_reference_segment(&self.peek()?.kind)
863            && self.lexer.peek_second()?.kind == TokenKind::Colon
864        {
865            let alias_tok = self.next()?;
866            self.expect(&TokenKind::Colon)?;
867            Some(alias_tok)
868        } else {
869            None
870        };
871
872        let spec_ref = self.parse_spec_ref_target()?;
873
874        let spec_name_source = spec_ref
875            .target_span
876            .as_ref()
877            .map(|sp| Source::new(self.source_type(), sp.clone()));
878
879        crate::limits::check_max_length(
880            &spec_ref.name,
881            self.max_spec_name_length,
882            "spec",
883            spec_name_source.clone(),
884        )?;
885
886        let alias = if let Some(ref alias_tok) = explicit_alias {
887            crate::limits::check_max_length(
888                &alias_tok.text,
889                self.max_data_name_length,
890                "data",
891                Some(Source::new(self.source_type(), alias_tok.span.clone())),
892            )?;
893            alias_tok.text.clone()
894        } else {
895            let implicit = spec_ref.name.clone();
896            crate::limits::check_max_length(
897                &implicit,
898                self.max_data_name_length,
899                "data",
900                spec_name_source,
901            )?;
902            implicit
903        };
904
905        let span = self.span_covering(start_span, &self.last_span);
906        Ok(LemmaData::new(
907            Reference::local(alias),
908            DataValue::Import(spec_ref),
909            self.make_source(span),
910        ))
911    }
912
913    fn parse_uses_statement(&mut self) -> Result<Vec<LemmaData>, Error> {
914        let uses_token = self.expect(&TokenKind::Uses)?;
915        let start_span = uses_token.span.clone();
916
917        let mut results = Vec::new();
918        results.push(self.parse_uses_item(&start_span)?);
919
920        while self.at(&TokenKind::Comma)? {
921            self.next()?;
922            results.push(self.parse_uses_item(&start_span)?);
923        }
924
925        Ok(results)
926    }
927
928    // ========================================================================
929    // Rule parsing
930    // ========================================================================
931
932    fn parse_rule(&mut self) -> Result<LemmaRule, Error> {
933        let rule_token = self.expect(&TokenKind::Rule)?;
934        let start_span = rule_token.span.clone();
935
936        let name_tok = self.next()?;
937        if is_structural_keyword(&name_tok.kind) {
938            return Err(self.error_at_token_with_suggestion(
939                &name_tok,
940                format!(
941                    "'{}' is a reserved keyword and cannot be used as a rule name",
942                    name_tok.text
943                ),
944                "Choose a different name that is not a reserved keyword",
945            ));
946        }
947        if !can_be_label(&name_tok.kind) && !is_type_keyword(&name_tok.kind) {
948            return Err(self.error_at_token(
949                &name_tok,
950                format!("Expected a rule name, found {}", name_tok.kind),
951            ));
952        }
953        let rule_name = name_tok.text.clone();
954        crate::limits::check_max_length(
955            &rule_name,
956            self.max_rule_name_length,
957            "rule",
958            Some(Source::new(self.source_type(), name_tok.span.clone())),
959        )?;
960
961        self.expect(&TokenKind::Colon)?;
962
963        // Parse the base expression or veto result (`veto "msg"`). `veto is …` is an expression.
964        let expression = if self.at(&TokenKind::Veto)? && !self.at_bare_veto_followed_by_is()? {
965            self.parse_veto_expression()?
966        } else {
967            self.parse_expression()?
968        };
969
970        // Parse unless clauses
971        let mut unless_clauses = Vec::new();
972        while self.at(&TokenKind::Unless)? {
973            unless_clauses.push(self.parse_unless_clause()?);
974        }
975
976        let end_span = if let Some(last_unless) = unless_clauses.last() {
977            last_unless.source_location.span.clone()
978        } else if let Some(ref loc) = expression.source_location {
979            loc.span.clone()
980        } else {
981            start_span.clone()
982        };
983
984        let span = self.span_covering(&start_span, &end_span);
985        Ok(LemmaRule {
986            name: rule_name,
987            expression,
988            unless_clauses,
989            source_location: self.make_source(span),
990        })
991    }
992
993    fn parse_veto_expression(&mut self) -> Result<Expression, Error> {
994        let veto_tok = self.expect(&TokenKind::Veto)?;
995        let start_span = veto_tok.span.clone();
996
997        let message = if self.at(&TokenKind::StringLit)? {
998            let str_tok = self.next()?;
999            let content = unquote_string(&str_tok.text);
1000            Some(content)
1001        } else {
1002            None
1003        };
1004
1005        let span = self.span_from(&start_span);
1006        self.new_expression(
1007            ExpressionKind::Veto(VetoExpression { message }),
1008            self.make_source(span),
1009        )
1010    }
1011
1012    fn parse_unless_clause(&mut self) -> Result<UnlessClause, Error> {
1013        let unless_tok = self.expect(&TokenKind::Unless)?;
1014        let start_span = unless_tok.span.clone();
1015
1016        let condition = self.parse_expression()?;
1017
1018        self.expect(&TokenKind::Then)?;
1019
1020        let result = if self.at(&TokenKind::Veto)? {
1021            self.parse_veto_expression()?
1022        } else {
1023            self.parse_expression()?
1024        };
1025
1026        let end_span = result
1027            .source_location
1028            .as_ref()
1029            .map(|s| s.span.clone())
1030            .unwrap_or_else(|| start_span.clone());
1031        let span = self.span_covering(&start_span, &end_span);
1032
1033        Ok(UnlessClause {
1034            condition,
1035            result,
1036            source_location: self.make_source(span),
1037        })
1038    }
1039
1040    fn parse_leaf_parent_type(&mut self) -> Result<ParentType, Error> {
1041        let name_tok = self.next()?;
1042        self.parse_leaf_parent_type_from_first_token(name_tok)
1043    }
1044
1045    fn parse_leaf_parent_type_from_first_token(
1046        &mut self,
1047        name_tok: Token,
1048    ) -> Result<ParentType, Error> {
1049        if let Some(kind) = token_kind_to_primitive(&name_tok.kind) {
1050            let primitive = if self.at(&TokenKind::Identifier)? && self.peek()?.text == "range" {
1051                let range_tok = self.peek()?.clone();
1052                match kind {
1053                    PrimitiveKind::Date => {
1054                        self.next()?;
1055                        PrimitiveKind::DateRange
1056                    }
1057                    PrimitiveKind::Number => {
1058                        self.next()?;
1059                        PrimitiveKind::NumberRange
1060                    }
1061                    PrimitiveKind::Quantity => {
1062                        self.next()?;
1063                        PrimitiveKind::QuantityRange
1064                    }
1065                    PrimitiveKind::Ratio => {
1066                        self.next()?;
1067                        PrimitiveKind::RatioRange
1068                    }
1069                    PrimitiveKind::Calendar => {
1070                        self.next()?;
1071                        PrimitiveKind::CalendarRange
1072                    }
1073                    _ => {
1074                        return Err(self.error_at_token(
1075                            &range_tok,
1076                            format!(
1077                                "'{} range' is not a valid type. Supported range types: calendar range, date range, number range, quantity range, ratio range",
1078                                name_tok.text
1079                            ),
1080                        ));
1081                    }
1082                }
1083            } else {
1084                kind
1085            };
1086            Ok(ParentType::Primitive { primitive })
1087        } else if can_be_label(&name_tok.kind) {
1088            Ok(ParentType::Custom {
1089                name: name_tok.text.clone(),
1090            })
1091        } else {
1092            Err(self.error_at_token(
1093                &name_tok,
1094                format!("Expected a type name, found {}", name_tok.kind),
1095            ))
1096        }
1097    }
1098
1099    /// Parse a type arrow chain: [`ParentType`] (`alias.type` allowed) followed by `(-> command)*`.
1100    fn parse_type_arrow_chain(&mut self) -> Result<(ParentType, Option<Vec<Constraint>>), Error> {
1101        let first = self.parse_leaf_parent_type()?;
1102
1103        let base = if let ParentType::Custom { name } = &first {
1104            if self.at(&TokenKind::Dot)? {
1105                self.next()?;
1106                let inner = self.parse_leaf_parent_type()?;
1107                ParentType::Qualified {
1108                    spec_alias: name.clone(),
1109                    inner: Box::new(inner),
1110                }
1111            } else {
1112                first
1113            }
1114        } else {
1115            if self.at(&TokenKind::Dot)? {
1116                let dot_tok = self.peek()?.clone();
1117                return Err(self.error_at_token_with_suggestion(
1118                    &dot_tok,
1119                    "A primitive type cannot be the left segment of a qualified parent path",
1120                    "Use `data name: alias.typename` where `alias` is the `uses` import name and `typename` is the parent type.",
1121                ));
1122            }
1123            first
1124        };
1125
1126        let constraints = self.parse_trailing_constraints()?;
1127
1128        Ok((base, constraints))
1129    }
1130
1131    fn parse_trailing_constraints(&mut self) -> Result<Option<Vec<Constraint>>, Error> {
1132        let mut commands = Vec::new();
1133        while self.at(&TokenKind::Arrow)? {
1134            self.next()?;
1135            let (cmd, cmd_args) = self.parse_command()?;
1136            commands.push((cmd, cmd_args));
1137        }
1138        let constraints = if commands.is_empty() {
1139            None
1140        } else {
1141            Some(commands)
1142        };
1143        Ok(constraints)
1144    }
1145
1146    fn parse_command(&mut self) -> Result<(TypeConstraintCommand, Vec<CommandArg>), Error> {
1147        let name_tok = self.next()?;
1148        if !can_be_label(&name_tok.kind) && !is_type_keyword(&name_tok.kind) {
1149            return Err(self.error_at_token(
1150                &name_tok,
1151                format!("Expected a command name, found {}", name_tok.kind),
1152            ));
1153        }
1154        let cmd = try_parse_type_constraint_command(&name_tok.text).ok_or_else(|| {
1155            self.error_at_token(
1156                &name_tok,
1157                format!(
1158                    "Unknown constraint command '{}'. Valid commands: help, default, unit, trait, minimum, maximum, decimals, option, options, length",
1159                    name_tok.text
1160                ),
1161            )
1162        })?;
1163
1164        let args = if cmd == TypeConstraintCommand::Unit {
1165            self.parse_unit_command_args()?
1166        } else {
1167            self.parse_generic_command_args()?
1168        };
1169
1170        Ok((cmd, args))
1171    }
1172
1173    /// Parse arguments for a generic (non-unit) constraint command.
1174    fn parse_generic_command_args(&mut self) -> Result<Vec<CommandArg>, Error> {
1175        let mut args = Vec::new();
1176        loop {
1177            if self.at(&TokenKind::Arrow)?
1178                || self.at(&TokenKind::Eof)?
1179                || is_spec_body_keyword(&self.peek()?.kind)
1180                || self.at(&TokenKind::Spec)?
1181            {
1182                break;
1183            }
1184
1185            let peek_kind = self.peek()?.kind.clone();
1186            match peek_kind {
1187                TokenKind::NumberLit
1188                | TokenKind::Minus
1189                | TokenKind::Plus
1190                | TokenKind::StringLit => {
1191                    let value = self.parse_literal_value()?;
1192                    args.push(CommandArg::Literal(value));
1193                }
1194                ref k if is_boolean_keyword(k) => {
1195                    let value = self.parse_literal_value()?;
1196                    args.push(CommandArg::Literal(value));
1197                }
1198                ref k if can_be_label(k) || is_type_keyword(k) => {
1199                    let tok = self.next()?;
1200                    args.push(CommandArg::Label(tok.text));
1201                }
1202                _ => break,
1203            }
1204        }
1205        Ok(args)
1206    }
1207
1208    fn parse_scalar_literal_value(&mut self) -> Result<Value, Error> {
1209        let peeked = self.peek()?;
1210        match &peeked.kind {
1211            TokenKind::StringLit => {
1212                let tok = self.next()?;
1213                let content = unquote_string(&tok.text);
1214                Ok(Value::Text(content))
1215            }
1216            k if is_boolean_keyword(k) => {
1217                let tok = self.next()?;
1218                Ok(Value::Boolean(token_kind_to_boolean_value(&tok.kind)))
1219            }
1220            TokenKind::NumberLit => self.parse_number_literal(),
1221            TokenKind::Minus | TokenKind::Plus => self.parse_signed_number_literal(),
1222            _ => {
1223                let tok = self.next()?;
1224                Err(self.error_at_token(
1225                    &tok,
1226                    format!(
1227                        "Expected a value (number, text, boolean, date, etc.), found '{}'",
1228                        tok.text
1229                    ),
1230                ))
1231            }
1232        }
1233    }
1234
1235    /// Returns true when the current token ends the current command argument list.
1236    fn at_command_terminator(&mut self) -> Result<bool, Error> {
1237        if self.at(&TokenKind::Arrow)? || self.at(&TokenKind::Eof)? || self.at(&TokenKind::Spec)? {
1238            return Ok(true);
1239        }
1240        Ok(is_spec_body_keyword(&self.peek()?.kind))
1241    }
1242
1243    /// Parse arguments for a `-> unit <name> ...` command.
1244    ///
1245    /// Produces `[CommandArg::Label(unit_name), CommandArg::UnitExpr(unit_arg)]` where
1246    /// `unit_arg` is either a simple `UnitArg::Factor` or a compound `UnitArg::Expr`.
1247    ///
1248    /// Grammar (after the `unit` keyword has been consumed):
1249    /// ```text
1250    /// unit_name_label  [numeric_prefix]  [unit_factor ('/' | ' ') unit_factor …]
1251    /// ```
1252    fn parse_unit_command_args(&mut self) -> Result<Vec<CommandArg>, Error> {
1253        if self.at_command_terminator()? {
1254            // No unit name — semantics will produce a meaningful error.
1255            return Ok(Vec::new());
1256        }
1257
1258        let peek_kind = self.peek()?.kind.clone();
1259        if !can_be_label(&peek_kind) && !is_type_keyword(&peek_kind) {
1260            // Not a label — let semantics produce the error.
1261            return Ok(Vec::new());
1262        }
1263
1264        let unit_name_tok = self.next()?;
1265        let unit_name_arg = CommandArg::Label(unit_name_tok.text.clone());
1266
1267        // Optional numeric prefix (e.g. the `1` in `-> unit meter 1` or the `3.6` in
1268        // `-> unit kmh 3.6 meter/second`).
1269        let numeric_prefix: Option<Decimal> = if self.at(&TokenKind::NumberLit)? {
1270            let num_tok = self.next()?;
1271            match Decimal::from_str(&num_tok.text) {
1272                Ok(d) => Some(d),
1273                Err(_) => {
1274                    return Err(self.error_at_token(
1275                        &num_tok,
1276                        format!(
1277                            "Invalid numeric factor '{}' in unit declaration",
1278                            num_tok.text
1279                        ),
1280                    ));
1281                }
1282            }
1283        } else {
1284            None
1285        };
1286
1287        // After an optional numeric prefix, check whether a compound unit expression follows
1288        // (starts with a label / duration-unit keyword).
1289        let peek_kind_after_prefix = self.peek()?.kind.clone();
1290        let has_compound_expr = (can_be_label(&peek_kind_after_prefix)
1291            || is_type_keyword(&peek_kind_after_prefix))
1292            && !self.at_command_terminator()?;
1293
1294        if has_compound_expr {
1295            let factors = self.parse_unit_factors()?;
1296            let prefix = numeric_prefix.unwrap_or(Decimal::ONE);
1297            let unit_arg = CommandArg::UnitExpr(UnitArg::Expr(prefix, factors));
1298            Ok(vec![unit_name_arg, unit_arg])
1299        } else if let Some(factor) = numeric_prefix {
1300            let unit_arg = CommandArg::UnitExpr(UnitArg::Factor(factor));
1301            Ok(vec![unit_name_arg, unit_arg])
1302        } else {
1303            // No factor and no compound expression.
1304            // Produce an arg list that semantics will reject with a clear error.
1305            Ok(vec![unit_name_arg])
1306        }
1307    }
1308
1309    /// Parse a sequence of `<quantity_ref>[^[-]<integer>]` terms joined by `*` or `/`.
1310    ///
1311    /// `*` is explicit multiplication and resets to numerator mode.
1312    /// `/` switches to denominator mode for all subsequent factors until the next `*`.
1313    /// Exponents in denominator mode are negated.
1314    /// An explicit `^<integer>` overrides the default (±1), still relative to the current mode.
1315    ///
1316    /// Space has no meaning in Lemma — `kg * m / s^2` and `kg*m/s^2` are identical.
1317    /// Space-adjacent labels without an intervening `*` or `/` end the expression;
1318    /// the bare label belongs to the next syntactic element.
1319    ///
1320    /// Examples:
1321    /// - `meter/second`            → `[{meter, +1}, {second, -1}]`
1322    /// - `meter/second^2`          → `[{meter, +1}, {second, -2}]`
1323    /// - `kg * meter / second^2`   → `[{kg, +1}, {meter, +1}, {second, -2}]`
1324    /// - `meter / second * kg`     → `[{meter, +1}, {second, -1}, {kg, +1}]`
1325    fn parse_unit_factors(&mut self) -> Result<Vec<UnitFactor>, Error> {
1326        let mut factors: Vec<UnitFactor> = Vec::new();
1327        let mut denominator_mode = false;
1328        // Tracks whether the loop is sitting immediately after an operator (* or /).
1329        // On the first iteration there is an implicit "we just started", so we allow
1330        // the first label without a preceding operator.
1331        let mut operator_just_consumed = true;
1332
1333        loop {
1334            if self.at_command_terminator()? {
1335                if !operator_just_consumed {
1336                    break;
1337                }
1338                // An operator was consumed but no label followed — that is a parse error
1339                // only if we already emitted at least one factor (dangling operator).
1340                // If factors is empty the caller will handle the missing expression.
1341                break;
1342            }
1343
1344            // `*` — explicit multiplication; reset to numerator mode.
1345            if self.at(&TokenKind::Star)? {
1346                if operator_just_consumed && !factors.is_empty() {
1347                    let bad_tok = self.next()?;
1348                    return Err(self.error_at_token(
1349                        &bad_tok,
1350                        "Unexpected '*' in unit expression: two consecutive operators".to_string(),
1351                    ));
1352                }
1353                self.next()?;
1354                denominator_mode = false;
1355                operator_just_consumed = true;
1356                continue;
1357            }
1358
1359            // `/` — switch to denominator mode.
1360            if self.at(&TokenKind::Slash)? {
1361                if operator_just_consumed && !factors.is_empty() {
1362                    let bad_tok = self.next()?;
1363                    return Err(self.error_at_token(
1364                        &bad_tok,
1365                        "Unexpected '/' in unit expression: two consecutive operators".to_string(),
1366                    ));
1367                }
1368                self.next()?;
1369                denominator_mode = true;
1370                operator_just_consumed = true;
1371                continue;
1372            }
1373
1374            // A label (or duration-unit keyword treated as a label) is a quantity reference.
1375            let peek_kind = self.peek()?.kind.clone();
1376            if !can_be_label(&peek_kind) && !is_type_keyword(&peek_kind) {
1377                break;
1378            }
1379
1380            // A label without a preceding operator ends the expression.
1381            // The first factor is permitted without an operator (operator_just_consumed starts true).
1382            if !operator_just_consumed {
1383                break;
1384            }
1385            operator_just_consumed = false;
1386
1387            let quantity_ref_tok = self.next()?;
1388            let quantity_ref = quantity_ref_tok.text.clone();
1389
1390            // Optional exponent: `^` followed by an optional `-` and an integer.
1391            let explicit_exp: Option<i32> = if self.at(&TokenKind::Caret)? {
1392                self.next()?; // consume `^`
1393
1394                let negative = if self.at(&TokenKind::Minus)? {
1395                    self.next()?; // consume `-`
1396                    true
1397                } else {
1398                    false
1399                };
1400
1401                if !self.at(&TokenKind::NumberLit)? {
1402                    let bad_tok = self.next()?;
1403                    return Err(self.error_at_token(
1404                        &bad_tok,
1405                        format!(
1406                            "Expected an integer exponent after '^' in unit expression, found {}",
1407                            bad_tok.kind
1408                        ),
1409                    ));
1410                }
1411
1412                let exp_tok = self.next()?;
1413                let raw: i32 = exp_tok.text.parse::<i32>().map_err(|_| {
1414                    self.error_at_token(
1415                        &exp_tok,
1416                        format!(
1417                            "Exponent '{}' is not a valid integer in unit expression",
1418                            exp_tok.text
1419                        ),
1420                    )
1421                })?;
1422
1423                if raw == 0 {
1424                    return Err(self.error_at_token(
1425                        &exp_tok,
1426                        "Exponent cannot be zero in a unit expression".to_string(),
1427                    ));
1428                }
1429
1430                Some(if negative { -raw } else { raw })
1431            } else {
1432                None
1433            };
1434
1435            // Apply denominator mode: negate the exponent (or its default) when in denominator.
1436            let final_exp = match (explicit_exp, denominator_mode) {
1437                (Some(e), true) => -e,
1438                (Some(e), false) => e,
1439                (None, true) => -1,
1440                (None, false) => 1,
1441            };
1442
1443            factors.push(UnitFactor {
1444                quantity_ref,
1445                exp: final_exp,
1446            });
1447        }
1448
1449        Ok(factors)
1450    }
1451
1452    // ========================================================================
1453    // Meta parsing
1454    // ========================================================================
1455
1456    fn parse_meta(&mut self) -> Result<MetaField, Error> {
1457        let meta_tok = self.expect(&TokenKind::Meta)?;
1458        let start_span = meta_tok.span.clone();
1459
1460        let key_tok = self.next()?;
1461        let key = key_tok.text.clone();
1462
1463        self.expect(&TokenKind::Colon)?;
1464
1465        let value = self.parse_meta_value()?;
1466
1467        let span = self.span_covering(&start_span, &self.last_span);
1468
1469        Ok(MetaField {
1470            key,
1471            value,
1472            source_location: self.make_source(span),
1473        })
1474    }
1475
1476    fn parse_meta_value(&mut self) -> Result<MetaValue, Error> {
1477        // Try literal first (string, number, boolean, date)
1478        let peeked = self.peek()?;
1479        match &peeked.kind {
1480            TokenKind::StringLit => {
1481                let value = self.parse_literal_value()?;
1482                return Ok(MetaValue::Literal(value));
1483            }
1484            TokenKind::NumberLit => {
1485                let value = self.parse_literal_value()?;
1486                return Ok(MetaValue::Literal(value));
1487            }
1488            k if is_boolean_keyword(k) => {
1489                let value = self.parse_literal_value()?;
1490                return Ok(MetaValue::Literal(value));
1491            }
1492            _ => {}
1493        }
1494
1495        // Otherwise, consume as unquoted meta identifier
1496        // meta_identifier: (ASCII_ALPHANUMERIC | "_" | "-" | "." | "/")+
1497        let mut ident = String::new();
1498        loop {
1499            let peeked = self.peek()?;
1500            match &peeked.kind {
1501                k if k.is_identifier_like() => {
1502                    let tok = self.next()?;
1503                    ident.push_str(&tok.text);
1504                }
1505                TokenKind::Dot => {
1506                    self.next()?;
1507                    ident.push('.');
1508                }
1509                TokenKind::Slash => {
1510                    self.next()?;
1511                    ident.push('/');
1512                }
1513                TokenKind::Minus => {
1514                    self.next()?;
1515                    ident.push('-');
1516                }
1517                TokenKind::NumberLit => {
1518                    let tok = self.next()?;
1519                    ident.push_str(&tok.text);
1520                }
1521                _ => break,
1522            }
1523        }
1524
1525        if ident.is_empty() {
1526            let tok = self.peek()?.clone();
1527            return Err(self.error_at_token(&tok, "Expected a meta value"));
1528        }
1529
1530        Ok(MetaValue::Unquoted(ident))
1531    }
1532
1533    // ========================================================================
1534    // Literal value parsing
1535    // ========================================================================
1536
1537    fn parse_literal_value(&mut self) -> Result<Value, Error> {
1538        let left = self.parse_scalar_literal_value()?;
1539        if self.at(&TokenKind::Ellipsis)? {
1540            self.next()?;
1541            let right = self.parse_scalar_literal_value()?;
1542            Ok(Value::Range(Box::new(left), Box::new(right)))
1543        } else {
1544            Ok(left)
1545        }
1546    }
1547
1548    fn parse_signed_number_literal(&mut self) -> Result<Value, Error> {
1549        let sign_tok = self.next()?;
1550        let sign_span = sign_tok.span.clone();
1551        let is_negative = sign_tok.kind == TokenKind::Minus;
1552
1553        if !self.at(&TokenKind::NumberLit)? {
1554            let tok = self.peek()?.clone();
1555            return Err(self.error_at_token(
1556                &tok,
1557                format!(
1558                    "Expected a number after '{}', found '{}'",
1559                    sign_tok.text, tok.text
1560                ),
1561            ));
1562        }
1563
1564        let value = self.parse_number_literal()?;
1565        if !is_negative {
1566            return Ok(value);
1567        }
1568        match value {
1569            Value::Number(d) => Ok(Value::Number(-d)),
1570            Value::NumberWithUnit(d, unit) => Ok(Value::NumberWithUnit(-d, unit)),
1571            Value::Calendar(d, unit) => Ok(Value::Calendar(-d, unit)),
1572            other => Err(Error::parsing(
1573                format!("Cannot negate this value: {}", other),
1574                self.make_source(sign_span),
1575                None::<String>,
1576            )),
1577        }
1578    }
1579
1580    fn parse_number_literal(&mut self) -> Result<Value, Error> {
1581        let num_tok = self.next()?;
1582        let num_text = &num_tok.text;
1583        let num_span = num_tok.span.clone();
1584
1585        // Check if followed by - which could make it a date (YYYY-MM-DD)
1586        if num_text.len() == 4
1587            && num_text.chars().all(|c| c.is_ascii_digit())
1588            && self.at(&TokenKind::Minus)?
1589        {
1590            return self.parse_date_literal(num_text.clone(), num_span);
1591        }
1592
1593        // Check what follows the number
1594        let peeked = self.peek()?;
1595
1596        // Number followed by : could be a time literal (HH:MM:SS)
1597        if num_text.len() == 2
1598            && num_text.chars().all(|c| c.is_ascii_digit())
1599            && peeked.kind == TokenKind::Colon
1600        {
1601            // Only if we're in a data value context... this is ambiguous.
1602            // Time literals look like: 14:30:00 or 14:30
1603            // But we might also have "rule x: expr" where : is assignment.
1604            // The grammar handles this at the grammar level. For us,
1605            // we need to check if the context is right.
1606            // Let's try to parse as time if the following pattern matches.
1607            return self.try_parse_time_literal(num_text.clone(), num_span);
1608        }
1609
1610        // Check for %% (permille) - must be before %
1611        if peeked.kind == TokenKind::PercentPercent {
1612            let pp_tok = self.next()?;
1613            // Check it's not followed by a digit
1614            if let Ok(next_peek) = self.peek() {
1615                if next_peek.kind == TokenKind::NumberLit {
1616                    return Err(self.error_at_token(
1617                        &pp_tok,
1618                        "Permille literal cannot be followed by a digit",
1619                    ));
1620                }
1621            }
1622            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1623            return Ok(Value::NumberWithUnit(decimal, "permille".to_string()));
1624        }
1625
1626        // Check for % (percent)
1627        if peeked.kind == TokenKind::Percent {
1628            let pct_tok = self.next()?;
1629            // Check it's not followed by a digit or another %
1630            if let Ok(next_peek) = self.peek() {
1631                if next_peek.kind == TokenKind::NumberLit || next_peek.kind == TokenKind::Percent {
1632                    return Err(self.error_at_token(
1633                        &pct_tok,
1634                        "Percent literal cannot be followed by a digit",
1635                    ));
1636                }
1637            }
1638            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1639            return Ok(Value::NumberWithUnit(decimal, "percent".to_string()));
1640        }
1641
1642        // Check for "percent" keyword
1643        if peeked.kind == TokenKind::PercentKw {
1644            self.next()?; // consume "percent"
1645            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1646            return Ok(Value::NumberWithUnit(decimal, "percent".to_string()));
1647        }
1648
1649        // Check for "permille" keyword
1650        if peeked.kind == TokenKind::Permille {
1651            self.next()?; // consume "permille"
1652            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1653            return Ok(Value::NumberWithUnit(decimal, "permille".to_string()));
1654        }
1655
1656        if can_be_label(&peeked.kind) {
1657            let unit_tok = self.next()?;
1658            let decimal = parse_decimal_string(num_text, &num_span, self)?;
1659            if let Some(calendar_unit) = CalendarUnit::from_keyword(&unit_tok.text) {
1660                return Ok(Value::Calendar(decimal, calendar_unit));
1661            }
1662            return Ok(Value::NumberWithUnit(decimal, unit_tok.text.clone()));
1663        }
1664
1665        // Plain number
1666        let decimal = parse_decimal_string(num_text, &num_span, self)?;
1667        Ok(Value::Number(decimal))
1668    }
1669
1670    fn parse_date_literal(&mut self, year_text: String, start_span: Span) -> Result<Value, Error> {
1671        let mut dt_str = year_text;
1672
1673        // Consume -MM
1674        self.expect(&TokenKind::Minus)?;
1675        dt_str.push('-');
1676        let month_tok = self.expect(&TokenKind::NumberLit)?;
1677        dt_str.push_str(&month_tok.text);
1678
1679        // Consume -DD
1680        self.expect(&TokenKind::Minus)?;
1681        dt_str.push('-');
1682        let day_tok = self.expect(&TokenKind::NumberLit)?;
1683        dt_str.push_str(&day_tok.text);
1684
1685        // Check for T (time component)
1686        if self.at(&TokenKind::Identifier)? {
1687            let peeked = self.peek()?;
1688            if peeked.text.len() >= 2
1689                && (peeked.text.starts_with('T') || peeked.text.starts_with('t'))
1690            {
1691                // The lexer may have tokenized T14 as a single identifier
1692                let t_tok = self.next()?;
1693                dt_str.push_str(&t_tok.text);
1694
1695                // Consume :MM
1696                if self.at(&TokenKind::Colon)? {
1697                    self.next()?;
1698                    dt_str.push(':');
1699                    let min_tok = self.next()?;
1700                    dt_str.push_str(&min_tok.text);
1701
1702                    // Consume :SS and optional fractional seconds
1703                    if self.at(&TokenKind::Colon)? {
1704                        self.next()?;
1705                        dt_str.push(':');
1706                        let sec_tok = self.next()?;
1707                        dt_str.push_str(&sec_tok.text);
1708
1709                        // Check for fractional seconds .NNNNNN
1710                        if self.at(&TokenKind::Dot)? {
1711                            self.next()?;
1712                            dt_str.push('.');
1713                            let frac_tok = self.expect(&TokenKind::NumberLit)?;
1714                            dt_str.push_str(&frac_tok.text);
1715                        }
1716                    }
1717                }
1718
1719                // Check for timezone
1720                self.try_consume_timezone(&mut dt_str)?;
1721            }
1722        }
1723
1724        if let Ok(dtv) = dt_str.parse::<crate::literals::DateTimeValue>() {
1725            return Ok(Value::Date(dtv));
1726        }
1727
1728        Err(Error::parsing(
1729            format!("Invalid date/time format: '{}'", dt_str),
1730            self.make_source(start_span),
1731            None::<String>,
1732        ))
1733    }
1734
1735    fn try_consume_timezone(&mut self, dt_str: &mut String) -> Result<(), Error> {
1736        // Z timezone
1737        if self.at(&TokenKind::Identifier)? {
1738            let peeked = self.peek()?;
1739            if (peeked.text == "Z" || peeked.text == "z") && peeked.span.start == self.last_span.end
1740            {
1741                let z_tok = self.next()?;
1742                dt_str.push_str(&z_tok.text);
1743                return Ok(());
1744            }
1745        }
1746
1747        // +HH:MM or -HH:MM, only when attached directly to the preceding token.
1748        if self.at(&TokenKind::Plus)? || self.at(&TokenKind::Minus)? {
1749            let mut lookahead = self.lexer.clone();
1750            let sign_tok = lookahead.next_token()?;
1751            let hour_tok = lookahead.next_token()?;
1752            let colon_tok = lookahead.next_token()?;
1753            let minute_tok = lookahead.next_token()?;
1754
1755            let attached = sign_tok.span.start == self.last_span.end;
1756            let is_timezone_shape = hour_tok.kind == TokenKind::NumberLit
1757                && colon_tok.kind == TokenKind::Colon
1758                && minute_tok.kind == TokenKind::NumberLit;
1759
1760            if attached && is_timezone_shape {
1761                let sign_tok = self.next()?;
1762                dt_str.push_str(&sign_tok.text);
1763                let hour_tok = self.expect(&TokenKind::NumberLit)?;
1764                dt_str.push_str(&hour_tok.text);
1765                self.expect(&TokenKind::Colon)?;
1766                dt_str.push(':');
1767                let min_tok = self.expect(&TokenKind::NumberLit)?;
1768                dt_str.push_str(&min_tok.text);
1769            }
1770        }
1771
1772        Ok(())
1773    }
1774
1775    fn try_parse_time_literal(
1776        &mut self,
1777        hour_text: String,
1778        start_span: Span,
1779    ) -> Result<Value, Error> {
1780        let mut time_str = hour_text;
1781
1782        // Consume :MM
1783        self.expect(&TokenKind::Colon)?;
1784        time_str.push(':');
1785        let min_tok = self.expect(&TokenKind::NumberLit)?;
1786        time_str.push_str(&min_tok.text);
1787
1788        // Optional :SS
1789        if self.at(&TokenKind::Colon)? {
1790            self.next()?;
1791            time_str.push(':');
1792            let sec_tok = self.expect(&TokenKind::NumberLit)?;
1793            time_str.push_str(&sec_tok.text);
1794
1795            // Optional fractional seconds .NNNNNN
1796            if self.at(&TokenKind::Dot)? {
1797                self.next()?;
1798                time_str.push('.');
1799                let frac_tok = self.expect(&TokenKind::NumberLit)?;
1800                time_str.push_str(&frac_tok.text);
1801            }
1802        }
1803
1804        // Try timezone
1805        self.try_consume_timezone(&mut time_str)?;
1806
1807        if let Ok(t) = time_str.parse::<TimeValue>() {
1808            return Ok(Value::Time(TimeValue {
1809                hour: t.hour,
1810                minute: t.minute,
1811                second: t.second,
1812                microsecond: t.microsecond,
1813                timezone: t.timezone,
1814            }));
1815        }
1816
1817        Err(Error::parsing(
1818            format!("Invalid time format: '{}'", time_str),
1819            self.make_source(start_span),
1820            None::<String>,
1821        ))
1822    }
1823
1824    // ========================================================================
1825    // Expression parsing (Pratt parser / precedence climbing)
1826    // ========================================================================
1827
1828    fn new_expression(
1829        &mut self,
1830        kind: ExpressionKind,
1831        source: Source,
1832    ) -> Result<Expression, Error> {
1833        self.expression_count += 1;
1834        if self.expression_count > self.max_expression_count {
1835            return Err(Error::resource_limit_exceeded(
1836                "max_expression_count",
1837                self.max_expression_count.to_string(),
1838                self.expression_count.to_string(),
1839                "Split logic into multiple rules to reduce expression count",
1840                Some(source),
1841                None,
1842                None,
1843            ));
1844        }
1845        Ok(Expression::new(kind, source))
1846    }
1847
1848    fn check_depth(&mut self) -> Result<(), Error> {
1849        if let Err(actual) = self.depth_tracker.push_depth() {
1850            let span = self.peek()?.span.clone();
1851            self.depth_tracker.pop_depth();
1852            return Err(Error::resource_limit_exceeded(
1853                "max_expression_depth",
1854                self.depth_tracker.max_depth().to_string(),
1855                actual.to_string(),
1856                "Simplify nested expressions or break into separate rules",
1857                Some(self.make_source(span)),
1858                None,
1859                None,
1860            ));
1861        }
1862        Ok(())
1863    }
1864
1865    fn parse_expression(&mut self) -> Result<Expression, Error> {
1866        self.check_depth()?;
1867        let result = self.parse_and_expression();
1868        self.depth_tracker.pop_depth();
1869        result
1870    }
1871
1872    fn parse_and_expression(&mut self) -> Result<Expression, Error> {
1873        let start_span = self.peek()?.span.clone();
1874        let mut left = self.parse_and_operand()?;
1875
1876        while self.at(&TokenKind::And)? {
1877            self.next()?; // consume 'and'
1878            let right = self.parse_and_operand()?;
1879            let span = self.span_covering(
1880                &start_span,
1881                &right
1882                    .source_location
1883                    .as_ref()
1884                    .map(|s| s.span.clone())
1885                    .unwrap_or_else(|| start_span.clone()),
1886            );
1887            left = self.new_expression(
1888                ExpressionKind::LogicalAnd(Arc::new(left), Arc::new(right)),
1889                self.make_source(span),
1890            )?;
1891        }
1892
1893        Ok(left)
1894    }
1895
1896    fn at_bare_veto_token(&mut self) -> Result<bool, Error> {
1897        if !self.at(&TokenKind::Veto)? {
1898            return Ok(false);
1899        }
1900        let checkpoint = self.checkpoint();
1901        self.next()?;
1902        let bare = !self.at(&TokenKind::StringLit)?;
1903        self.restore(checkpoint);
1904        Ok(bare)
1905    }
1906
1907    fn at_bare_veto_followed_by_is(&mut self) -> Result<bool, Error> {
1908        if !self.at_bare_veto_token()? {
1909            return Ok(false);
1910        }
1911        let checkpoint = self.checkpoint();
1912        self.next()?;
1913        let followed = self.at(&TokenKind::Is)?;
1914        self.restore(checkpoint);
1915        Ok(followed)
1916    }
1917
1918    fn at_not_bare_veto_followed_by_is(&mut self) -> Result<bool, Error> {
1919        if !self.at(&TokenKind::Not)? {
1920            return Ok(false);
1921        }
1922        let checkpoint = self.checkpoint();
1923        self.next()?;
1924        if !self.at(&TokenKind::Veto)? {
1925            self.restore(checkpoint);
1926            return Ok(false);
1927        }
1928        self.next()?;
1929        if self.at(&TokenKind::StringLit)? {
1930            self.restore(checkpoint);
1931            return Ok(false);
1932        }
1933        let followed = self.at(&TokenKind::Is)?;
1934        self.restore(checkpoint);
1935        Ok(followed)
1936    }
1937
1938    fn wrap_result_is_veto_expression(
1939        &mut self,
1940        operand: Expression,
1941        operator_is_not: bool,
1942        keyword_was_negated: bool,
1943        start_span: Span,
1944    ) -> Result<Expression, Error> {
1945        let negate = operator_is_not ^ keyword_was_negated;
1946        let end_span = operand
1947            .source_location
1948            .as_ref()
1949            .map(|source| source.span.clone())
1950            .unwrap_or_else(|| start_span.clone());
1951        let span = self.span_covering(&start_span, &end_span);
1952        let core = self.new_expression(
1953            ExpressionKind::ResultIsVeto(Arc::new(operand)),
1954            self.make_source(span.clone()),
1955        )?;
1956        if negate {
1957            self.new_expression(
1958                ExpressionKind::LogicalNegation(Arc::new(core), NegationType::Not),
1959                self.make_source(span),
1960            )
1961        } else {
1962            Ok(core)
1963        }
1964    }
1965
1966    fn parse_veto_status_lhs_is_comparison(&mut self) -> Result<Expression, Error> {
1967        let start_span = self.peek()?.span.clone();
1968        let keyword_was_negated = if self.at(&TokenKind::Not)? {
1969            self.next()?;
1970            true
1971        } else {
1972            false
1973        };
1974        self.expect(&TokenKind::Veto)?;
1975        if self.at(&TokenKind::StringLit)? {
1976            let tok = self.peek()?.clone();
1977            return Err(self.error_at_token(
1978                &tok,
1979                "veto with a message is only valid as a rule or unless result, not in `is veto` comparisons",
1980            ));
1981        }
1982        let operator = self.parse_comparison_operator()?;
1983        let operator_is_not = matches!(operator, ComparisonComputation::IsNot);
1984        if !matches!(
1985            operator,
1986            ComparisonComputation::Is | ComparisonComputation::IsNot
1987        ) {
1988            let tok = self.peek()?.clone();
1989            return Err(self.error_at_token(
1990                &tok,
1991                "Expected `is` or `is not` after `veto` in a veto-status comparison",
1992            ));
1993        }
1994        let operand = self.parse_range_expression()?;
1995        self.wrap_result_is_veto_expression(
1996            operand,
1997            operator_is_not,
1998            keyword_was_negated,
1999            start_span,
2000        )
2001    }
2002
2003    fn parse_and_operand(&mut self) -> Result<Expression, Error> {
2004        if self.at_not_bare_veto_followed_by_is()? || self.at_bare_veto_followed_by_is()? {
2005            return self.parse_veto_status_lhs_is_comparison();
2006        }
2007
2008        // not expression
2009        if self.at(&TokenKind::Not)? {
2010            return self.parse_not_expression();
2011        }
2012
2013        // repository_with_suffix: repository_expression followed by optional suffix
2014        self.parse_repository_with_suffix()
2015    }
2016
2017    fn parse_not_expression(&mut self) -> Result<Expression, Error> {
2018        let not_tok = self.expect(&TokenKind::Not)?;
2019        let start_span = not_tok.span.clone();
2020
2021        self.check_depth()?;
2022        let operand = self.parse_and_operand()?;
2023        self.depth_tracker.pop_depth();
2024
2025        let end_span = operand
2026            .source_location
2027            .as_ref()
2028            .map(|s| s.span.clone())
2029            .unwrap_or_else(|| start_span.clone());
2030        let span = self.span_covering(&start_span, &end_span);
2031
2032        self.new_expression(
2033            ExpressionKind::LogicalNegation(Arc::new(operand), NegationType::Not),
2034            self.make_source(span),
2035        )
2036    }
2037
2038    fn parse_repository_with_suffix(&mut self) -> Result<Expression, Error> {
2039        let start_span = self.peek()?.span.clone();
2040        let repository = self.parse_range_expression()?;
2041        self.continue_repository_operand(repository, start_span)
2042    }
2043
2044    /// Postfix suffixes on a completed repository/range expression (`in`, calendar, comparison).
2045    /// Unit conversion (`as`) is parsed at term level, not here.
2046    fn continue_repository_operand(
2047        &mut self,
2048        mut expr: Expression,
2049        start_span: Span,
2050    ) -> Result<Expression, Error> {
2051        loop {
2052            let peeked = self.peek()?;
2053
2054            if is_comparison_operator(&peeked.kind) {
2055                return self.parse_comparison_suffix(expr, start_span);
2056            }
2057
2058            if peeked.kind == TokenKind::Not {
2059                expr = self.parse_not_in_calendar_suffix(expr, start_span.clone())?;
2060                continue;
2061            }
2062
2063            if peeked.kind == TokenKind::In {
2064                expr = self.parse_in_suffix(expr, start_span.clone())?;
2065                continue;
2066            }
2067
2068            break;
2069        }
2070
2071        if self.at_expression_suffix_end()? {
2072            return Ok(expr);
2073        }
2074
2075        let tok = self.peek()?.clone();
2076        Err(self.error_at_token(
2077            &tok,
2078            format!("Unexpected token '{}' after expression", tok.text),
2079        ))
2080    }
2081
2082    fn parse_comparison_suffix(
2083        &mut self,
2084        left: Expression,
2085        start_span: Span,
2086    ) -> Result<Expression, Error> {
2087        let operator = self.parse_comparison_operator()?;
2088        let operator_is_not = matches!(operator, ComparisonComputation::IsNot);
2089
2090        if matches!(
2091            operator,
2092            ComparisonComputation::Is | ComparisonComputation::IsNot
2093        ) && self.at_bare_veto_token()?
2094        {
2095            self.expect(&TokenKind::Veto)?;
2096            if self.at(&TokenKind::StringLit)? {
2097                let tok = self.peek()?.clone();
2098                return Err(self.error_at_token(
2099                    &tok,
2100                    "veto with a message is only valid as a rule or unless result, not in `is veto` comparisons",
2101                ));
2102            }
2103            return self.wrap_result_is_veto_expression(left, operator_is_not, false, start_span);
2104        }
2105
2106        // Right side can be: not_expr | range/repository expression (term-level `as` included)
2107        let right = if self.at(&TokenKind::Not)? {
2108            self.parse_not_expression()?
2109        } else {
2110            self.parse_range_expression()?
2111        };
2112
2113        let end_span = right
2114            .source_location
2115            .as_ref()
2116            .map(|s| s.span.clone())
2117            .unwrap_or_else(|| start_span.clone());
2118        let span = self.span_covering(&start_span, &end_span);
2119
2120        self.new_expression(
2121            ExpressionKind::Comparison(Arc::new(left), operator, Arc::new(right)),
2122            self.make_source(span),
2123        )
2124    }
2125
2126    fn parse_comparison_operator(&mut self) -> Result<ComparisonComputation, Error> {
2127        let tok = self.next()?;
2128        match tok.kind {
2129            TokenKind::Gt => Ok(ComparisonComputation::GreaterThan),
2130            TokenKind::Lt => Ok(ComparisonComputation::LessThan),
2131            TokenKind::Gte => Ok(ComparisonComputation::GreaterThanOrEqual),
2132            TokenKind::Lte => Ok(ComparisonComputation::LessThanOrEqual),
2133            TokenKind::Is => {
2134                // Check for "is not"
2135                if self.at(&TokenKind::Not)? {
2136                    self.next()?; // consume 'not'
2137                    Ok(ComparisonComputation::IsNot)
2138                } else {
2139                    Ok(ComparisonComputation::Is)
2140                }
2141            }
2142            _ => Err(self.error_at_token(
2143                &tok,
2144                format!("Expected a comparison operator, found {}", tok.kind),
2145            )),
2146        }
2147    }
2148
2149    fn parse_not_in_calendar_suffix(
2150        &mut self,
2151        repository: Expression,
2152        start_span: Span,
2153    ) -> Result<Expression, Error> {
2154        self.expect(&TokenKind::Not)?;
2155        self.expect(&TokenKind::In)?;
2156        self.expect(&TokenKind::Calendar)?;
2157        let unit = self.parse_calendar_unit()?;
2158        let end = self.peek()?.span.clone();
2159        let span = self.span_covering(&start_span, &end);
2160        self.new_expression(
2161            ExpressionKind::DateCalendar(DateCalendarKind::NotIn, unit, Arc::new(repository)),
2162            self.make_source(span),
2163        )
2164    }
2165
2166    fn parse_in_suffix(
2167        &mut self,
2168        repository: Expression,
2169        start_span: Span,
2170    ) -> Result<Expression, Error> {
2171        self.expect(&TokenKind::In)?;
2172
2173        let peeked = self.peek()?;
2174
2175        // "in past calendar <unit>" or "in future calendar <unit>"
2176        if peeked.kind == TokenKind::Past || peeked.kind == TokenKind::Future {
2177            let direction = self.next()?;
2178            let rel_kind = if direction.kind == TokenKind::Past {
2179                DateRelativeKind::InPast
2180            } else {
2181                DateRelativeKind::InFuture
2182            };
2183
2184            // Check for "calendar" keyword
2185            if self.at(&TokenKind::Calendar)? {
2186                self.next()?; // consume "calendar"
2187                let cal_kind = if direction.kind == TokenKind::Past {
2188                    DateCalendarKind::Past
2189                } else {
2190                    DateCalendarKind::Future
2191                };
2192                let unit = self.parse_calendar_unit()?;
2193                let end = self.peek()?.span.clone();
2194                let span = self.span_covering(&start_span, &end);
2195                return self.new_expression(
2196                    ExpressionKind::DateCalendar(cal_kind, unit, Arc::new(repository)),
2197                    self.make_source(span),
2198                );
2199            }
2200
2201            if self.at(&TokenKind::And)?
2202                || self.at(&TokenKind::Unless)?
2203                || self.at(&TokenKind::Then)?
2204                || self.at(&TokenKind::RParen)?
2205                || self.at(&TokenKind::Eof)?
2206                || is_comparison_operator(&self.peek()?.kind)
2207            {
2208                let end = self.peek()?.span.clone();
2209                let span = self.span_covering(&start_span, &end);
2210                return self.new_expression(
2211                    ExpressionKind::DateRelative(rel_kind, Arc::new(repository)),
2212                    self.make_source(span),
2213                );
2214            }
2215
2216            let offset = self.parse_repository_expression()?;
2217            let offset_end_span = offset
2218                .source_location
2219                .as_ref()
2220                .map(|s| s.span.clone())
2221                .unwrap_or_else(|| start_span.clone());
2222            let range = self.new_expression(
2223                ExpressionKind::PastFutureRange(rel_kind, Arc::new(offset)),
2224                self.make_source(self.span_covering(&direction.span, &offset_end_span)),
2225            )?;
2226            let span = self.span_covering(&start_span, &offset_end_span);
2227            return self.new_expression(
2228                ExpressionKind::RangeContainment(Arc::new(repository), Arc::new(range)),
2229                self.make_source(span),
2230            );
2231        }
2232
2233        // "in calendar <unit>"
2234        if peeked.kind == TokenKind::Calendar {
2235            self.next()?; // consume "calendar"
2236            let unit = self.parse_calendar_unit()?;
2237            let end = self.peek()?.span.clone();
2238            let span = self.span_covering(&start_span, &end);
2239            return self.new_expression(
2240                ExpressionKind::DateCalendar(DateCalendarKind::Current, unit, Arc::new(repository)),
2241                self.make_source(span),
2242            );
2243        }
2244
2245        let range = self.parse_range_expression()?;
2246        let end_span = range
2247            .source_location
2248            .as_ref()
2249            .map(|s| s.span.clone())
2250            .unwrap_or_else(|| start_span.clone());
2251        let span = self.span_covering(&start_span, &end_span);
2252        self.new_expression(
2253            ExpressionKind::RangeContainment(Arc::new(repository), Arc::new(range)),
2254            self.make_source(span),
2255        )
2256    }
2257
2258    fn parse_as_chain(
2259        &mut self,
2260        mut expr: Expression,
2261        start_span: Span,
2262    ) -> Result<Expression, Error> {
2263        while self.at(&TokenKind::As)? {
2264            self.expect(&TokenKind::As)?;
2265            let target_tok = self.next()?;
2266            let target = conversion_target_from_token(&target_tok.kind, &target_tok.text);
2267            expr = self.new_expression(
2268                ExpressionKind::UnitConversion(Arc::new(expr), target),
2269                self.make_source(self.span_covering(&start_span, &target_tok.span)),
2270            )?;
2271        }
2272        Ok(expr)
2273    }
2274
2275    fn is_plain_number_literal(expr: &Expression) -> bool {
2276        matches!(expr.kind, ExpressionKind::Literal(Value::Number(_)))
2277    }
2278
2279    fn is_unit_conversion(expr: &Expression) -> bool {
2280        matches!(expr.kind, ExpressionKind::UnitConversion(..))
2281    }
2282
2283    /// True when the next token can follow a completed suffix expression (no further operands).
2284    ///
2285    /// Must include every token that can start the next spec-body item, a new `spec`/`repo`,
2286    /// or end the file. See `parse_unit_conversion_before_expression_boundaries` in `parsing/mod.rs`.
2287    fn at_expression_suffix_end(&mut self) -> Result<bool, Error> {
2288        Ok(self.at(&TokenKind::And)?
2289            || self.at(&TokenKind::Unless)?
2290            || self.at(&TokenKind::Then)?
2291            || self.at(&TokenKind::RParen)?
2292            || self.at(&TokenKind::Eof)?
2293            || self.at(&TokenKind::Spec)?
2294            || self.at(&TokenKind::Repo)?
2295            || self.at(&TokenKind::Uses)?
2296            || is_spec_body_keyword(&self.peek()?.kind))
2297    }
2298
2299    fn parse_calendar_unit(&mut self) -> Result<CalendarPeriodUnit, Error> {
2300        let tok = self.next()?;
2301        if let Some(unit) = CalendarPeriodUnit::from_keyword(&tok.text) {
2302            return Ok(unit);
2303        }
2304        Err(self.error_at_token(
2305            &tok,
2306            format!("Expected 'year', 'month', or 'week', found '{}'", tok.text),
2307        ))
2308    }
2309
2310    // ========================================================================
2311    // Arithmetic expressions (precedence climbing)
2312    // ========================================================================
2313
2314    fn parse_range_expression(&mut self) -> Result<Expression, Error> {
2315        self.parse_repository_expression()
2316    }
2317
2318    /// Atom or range-typed value: `...` binds before `^`, `*`, `/`, `%` on the same operand.
2319    /// Endpoints use [`Self::parse_range_ellipsis_bound`] (`+`/`-` only) so `now - 7 days...now` is valid
2320    /// and `rate * period_start...period_end` keeps `*` outside the range.
2321    fn parse_range_operand(&mut self) -> Result<Expression, Error> {
2322        let start_span = self.peek()?.span.clone();
2323        let checkpoint = self.checkpoint();
2324        let left = self.parse_range_ellipsis_bound()?;
2325        if !self.at(&TokenKind::Ellipsis)? {
2326            self.restore(checkpoint);
2327            return self.parse_factor();
2328        }
2329
2330        self.next()?;
2331        let right = self.parse_power_for_range_bound()?;
2332        let end_span = right
2333            .source_location
2334            .as_ref()
2335            .map(|s| s.span.clone())
2336            .unwrap_or_else(|| start_span.clone());
2337        let span = self.span_covering(&start_span, &end_span);
2338        self.new_expression(
2339            ExpressionKind::RangeLiteral(Arc::new(left), Arc::new(right)),
2340            self.make_source(span),
2341        )
2342    }
2343
2344    /// One side of `...`: `+`/`-` between powers only (no `*`/`/`/`%` — those bind outside the range).
2345    fn parse_range_ellipsis_bound(&mut self) -> Result<Expression, Error> {
2346        let start_span = self.peek()?.span.clone();
2347        let mut left = self.parse_power_for_range_bound()?;
2348
2349        while self.at_any(&[TokenKind::Plus, TokenKind::Minus])? {
2350            let op_tok = self.next()?;
2351            let operation = match op_tok.kind {
2352                TokenKind::Plus => ArithmeticComputation::Add,
2353                TokenKind::Minus => ArithmeticComputation::Subtract,
2354                _ => unreachable!("BUG: only + and - should reach here"),
2355            };
2356
2357            let right = self.parse_power_for_range_bound()?;
2358            let end_span = right
2359                .source_location
2360                .as_ref()
2361                .map(|s| s.span.clone())
2362                .unwrap_or_else(|| start_span.clone());
2363            let span = self.span_covering(&start_span, &end_span);
2364
2365            left = self.new_expression(
2366                ExpressionKind::Arithmetic(Arc::new(left), operation, Arc::new(right)),
2367                self.make_source(span),
2368            )?;
2369        }
2370
2371        Ok(left)
2372    }
2373
2374    fn parse_power_for_range_bound(&mut self) -> Result<Expression, Error> {
2375        let start_span = self.peek()?.span.clone();
2376        let left = self.parse_factor()?;
2377
2378        if self.at(&TokenKind::Caret)? {
2379            self.next()?;
2380            self.check_depth()?;
2381            let right = self.parse_power_for_range_bound()?;
2382            self.depth_tracker.pop_depth();
2383            let end_span = right
2384                .source_location
2385                .as_ref()
2386                .map(|s| s.span.clone())
2387                .unwrap_or_else(|| start_span.clone());
2388            let span = self.span_covering(&start_span, &end_span);
2389
2390            return self.new_expression(
2391                ExpressionKind::Arithmetic(
2392                    Arc::new(left),
2393                    ArithmeticComputation::Power,
2394                    Arc::new(right),
2395                ),
2396                self.make_source(span),
2397            );
2398        }
2399
2400        Ok(left)
2401    }
2402
2403    fn parse_repository_expression(&mut self) -> Result<Expression, Error> {
2404        let start_span = self.peek()?.span.clone();
2405        let mut left = self.parse_term()?;
2406
2407        while self.at_any(&[TokenKind::Plus, TokenKind::Minus])? {
2408            // Check if this minus is really a binary operator or could be part of something else
2409            // In "X not in calendar year", we don't want to consume "not" as an operator
2410            let op_tok = self.next()?;
2411            let operation = match op_tok.kind {
2412                TokenKind::Plus => ArithmeticComputation::Add,
2413                TokenKind::Minus => ArithmeticComputation::Subtract,
2414                _ => unreachable!("BUG: only + and - should reach here"),
2415            };
2416
2417            let right = self.parse_term()?;
2418            if Self::is_plain_number_literal(&left) && Self::is_unit_conversion(&right) {
2419                let source = right
2420                    .source_location
2421                    .clone()
2422                    .unwrap_or_else(|| self.make_source(start_span.clone()));
2423                return Err(Error::parsing(
2424                    "Cannot add a plain number to a converted value; convert each operand before \
2425                     '+' (e.g. '5 as usd + c as usd')",
2426                    source,
2427                    None::<String>,
2428                ));
2429            }
2430
2431            let end_span = right
2432                .source_location
2433                .as_ref()
2434                .map(|s| s.span.clone())
2435                .unwrap_or_else(|| start_span.clone());
2436            let span = self.span_covering(&start_span, &end_span);
2437
2438            left = self.new_expression(
2439                ExpressionKind::Arithmetic(Arc::new(left), operation, Arc::new(right)),
2440                self.make_source(span),
2441            )?;
2442        }
2443
2444        Ok(left)
2445    }
2446
2447    fn parse_term(&mut self) -> Result<Expression, Error> {
2448        self.parse_term_with_as(true)
2449    }
2450
2451    fn parse_term_with_as(&mut self, allow_as: bool) -> Result<Expression, Error> {
2452        let start_span = self.peek()?.span.clone();
2453        let mut left = self.parse_power()?;
2454        if allow_as {
2455            left = self.parse_as_chain(left, start_span.clone())?;
2456        }
2457
2458        while self.at_any(&[TokenKind::Star, TokenKind::Slash, TokenKind::Percent])? {
2459            // Be careful: % could be a percent literal suffix (e.g. 50%)
2460            // But here in term context, it's modulo since we already parsed the number
2461            let op_tok = self.next()?;
2462            let operation = match op_tok.kind {
2463                TokenKind::Star => ArithmeticComputation::Multiply,
2464                TokenKind::Slash => ArithmeticComputation::Divide,
2465                TokenKind::Percent => ArithmeticComputation::Modulo,
2466                _ => unreachable!("BUG: only *, /, % should reach here"),
2467            };
2468
2469            let right_start_span = self.peek()?.span.clone();
2470            let mut right = self.parse_power()?;
2471            if allow_as {
2472                right = self.parse_as_chain(right, right_start_span)?;
2473            }
2474            let end_span = right
2475                .source_location
2476                .as_ref()
2477                .map(|s| s.span.clone())
2478                .unwrap_or_else(|| start_span.clone());
2479            let span = self.span_covering(&start_span, &end_span);
2480
2481            left = self.new_expression(
2482                ExpressionKind::Arithmetic(Arc::new(left), operation, Arc::new(right)),
2483                self.make_source(span),
2484            )?;
2485        }
2486
2487        Ok(left)
2488    }
2489
2490    fn parse_power(&mut self) -> Result<Expression, Error> {
2491        let start_span = self.peek()?.span.clone();
2492        let left = self.parse_range_operand()?;
2493
2494        if self.at(&TokenKind::Caret)? {
2495            self.next()?;
2496            self.check_depth()?;
2497            let right = self.parse_power()?;
2498            self.depth_tracker.pop_depth();
2499            let end_span = right
2500                .source_location
2501                .as_ref()
2502                .map(|s| s.span.clone())
2503                .unwrap_or_else(|| start_span.clone());
2504            let span = self.span_covering(&start_span, &end_span);
2505
2506            return self.new_expression(
2507                ExpressionKind::Arithmetic(
2508                    Arc::new(left),
2509                    ArithmeticComputation::Power,
2510                    Arc::new(right),
2511                ),
2512                self.make_source(span),
2513            );
2514        }
2515
2516        Ok(left)
2517    }
2518
2519    fn parse_factor(&mut self) -> Result<Expression, Error> {
2520        let peeked = self.peek()?;
2521        let start_span = peeked.span.clone();
2522
2523        if peeked.kind == TokenKind::Minus {
2524            self.next()?;
2525            let operand = self.parse_primary_or_math()?;
2526            let end_span = operand
2527                .source_location
2528                .as_ref()
2529                .map(|s| s.span.clone())
2530                .unwrap_or_else(|| start_span.clone());
2531            let span = self.span_covering(&start_span, &end_span);
2532
2533            let zero = self.new_expression(
2534                ExpressionKind::Literal(Value::Number(Decimal::ZERO)),
2535                self.make_source(start_span),
2536            )?;
2537            return self.new_expression(
2538                ExpressionKind::Arithmetic(
2539                    Arc::new(zero),
2540                    ArithmeticComputation::Subtract,
2541                    Arc::new(operand),
2542                ),
2543                self.make_source(span),
2544            );
2545        }
2546
2547        if peeked.kind == TokenKind::Plus {
2548            self.next()?;
2549            return self.parse_primary_or_math();
2550        }
2551
2552        self.parse_primary_or_math()
2553    }
2554
2555    fn parse_primary_or_math(&mut self) -> Result<Expression, Error> {
2556        let peeked = self.peek()?;
2557
2558        // Math functions
2559        if is_math_function(&peeked.kind) {
2560            return self.parse_math_function();
2561        }
2562
2563        self.parse_primary()
2564    }
2565
2566    fn parse_math_function(&mut self) -> Result<Expression, Error> {
2567        let func_tok = self.next()?;
2568        let start_span = func_tok.span.clone();
2569
2570        let operator = match func_tok.kind {
2571            TokenKind::Sqrt => MathematicalComputation::Sqrt,
2572            TokenKind::Sin => MathematicalComputation::Sin,
2573            TokenKind::Cos => MathematicalComputation::Cos,
2574            TokenKind::Tan => MathematicalComputation::Tan,
2575            TokenKind::Asin => MathematicalComputation::Asin,
2576            TokenKind::Acos => MathematicalComputation::Acos,
2577            TokenKind::Atan => MathematicalComputation::Atan,
2578            TokenKind::Log => MathematicalComputation::Log,
2579            TokenKind::Exp => MathematicalComputation::Exp,
2580            TokenKind::Abs => MathematicalComputation::Abs,
2581            TokenKind::Floor => MathematicalComputation::Floor,
2582            TokenKind::Ceil => MathematicalComputation::Ceil,
2583            TokenKind::Round => MathematicalComputation::Round,
2584            _ => unreachable!("BUG: only math functions should reach here"),
2585        };
2586
2587        self.check_depth()?;
2588        let operand = self.parse_repository_expression()?;
2589        self.depth_tracker.pop_depth();
2590
2591        let end_span = operand
2592            .source_location
2593            .as_ref()
2594            .map(|s| s.span.clone())
2595            .unwrap_or_else(|| start_span.clone());
2596        let span = self.span_covering(&start_span, &end_span);
2597
2598        self.new_expression(
2599            ExpressionKind::MathematicalComputation(operator, Arc::new(operand)),
2600            self.make_source(span),
2601        )
2602    }
2603
2604    fn parse_primary(&mut self) -> Result<Expression, Error> {
2605        let peeked = self.peek()?;
2606        let start_span = peeked.span.clone();
2607
2608        match &peeked.kind {
2609            // Parenthesized expression
2610            TokenKind::LParen => {
2611                self.next()?; // consume (
2612                let inner = self.parse_expression()?;
2613                self.expect(&TokenKind::RParen)?;
2614                Ok(inner)
2615            }
2616
2617            // Now keyword
2618            TokenKind::Now => {
2619                let tok = self.next()?;
2620                self.new_expression(ExpressionKind::Now, self.make_source(tok.span))
2621            }
2622
2623            TokenKind::Past | TokenKind::Future => {
2624                let tok = self.next()?;
2625                let kind = if tok.kind == TokenKind::Past {
2626                    DateRelativeKind::InPast
2627                } else {
2628                    DateRelativeKind::InFuture
2629                };
2630                let offset = self.parse_repository_expression()?;
2631                let span = self.span_covering(
2632                    &start_span,
2633                    &offset
2634                        .source_location
2635                        .as_ref()
2636                        .map(|s| s.span.clone())
2637                        .unwrap_or(start_span.clone()),
2638                );
2639                self.new_expression(
2640                    ExpressionKind::PastFutureRange(kind, Arc::new(offset)),
2641                    self.make_source(span),
2642                )
2643            }
2644
2645            // String literal
2646            TokenKind::StringLit => {
2647                let tok = self.next()?;
2648                let content = unquote_string(&tok.text);
2649                self.new_expression(
2650                    ExpressionKind::Literal(Value::Text(content)),
2651                    self.make_source(tok.span),
2652                )
2653            }
2654
2655            // Boolean literals
2656            k if is_boolean_keyword(k) => {
2657                let tok = self.next()?;
2658                self.new_expression(
2659                    ExpressionKind::Literal(Value::Boolean(token_kind_to_boolean_value(&tok.kind))),
2660                    self.make_source(tok.span),
2661                )
2662            }
2663
2664            // Number literal (could be: plain number, date, time, duration, percent, unit)
2665            TokenKind::NumberLit => self.parse_number_expression(),
2666
2667            // Reference (identifier, type keyword)
2668            k if can_be_reference_segment(k) => {
2669                let reference = self.parse_expression_reference()?;
2670                let span = self.span_covering(&start_span, &self.last_span);
2671                self.new_expression(ExpressionKind::Reference(reference), self.make_source(span))
2672            }
2673
2674            _ => {
2675                let tok = self.next()?;
2676                Err(self.error_at_token(
2677                    &tok,
2678                    format!("Expected an expression, found '{}'", tok.text),
2679                ))
2680            }
2681        }
2682    }
2683
2684    fn parse_number_expression(&mut self) -> Result<Expression, Error> {
2685        let num_tok = self.next()?;
2686        let num_text = num_tok.text.clone();
2687        let start_span = num_tok.span.clone();
2688
2689        // Check if this is a date literal (YYYY-MM-DD)
2690        if num_text.len() == 4
2691            && num_text.chars().all(|c| c.is_ascii_digit())
2692            && self.at(&TokenKind::Minus)?
2693        {
2694            // Peek further: if next-next is a number, this is likely a date
2695            // We need to be careful: "2024 - 5" is arithmetic, "2024-01-15" is a date
2696            // Date format requires: YYYY-MM-DD where MM and DD are 2 digits
2697            // This is ambiguous at the token level. Let's check if the pattern matches.
2698            // Since dates use -NN- pattern and arithmetic uses - N pattern (with spaces),
2699            // we can use the span positions to disambiguate.
2700            let minus_span = self.peek()?.span.clone();
2701            // If minus is immediately adjacent to the number (no space), it's a date
2702            if minus_span.start == start_span.end {
2703                let value = self.parse_date_literal(num_text, start_span.clone())?;
2704                return self
2705                    .new_expression(ExpressionKind::Literal(value), self.make_source(start_span));
2706            }
2707        }
2708
2709        // Check for time literal (HH:MM:SS)
2710        if num_text.len() == 2
2711            && num_text.chars().all(|c| c.is_ascii_digit())
2712            && self.at(&TokenKind::Colon)?
2713        {
2714            let colon_span = self.peek()?.span.clone();
2715            if colon_span.start == start_span.end {
2716                let value = self.try_parse_time_literal(num_text, start_span.clone())?;
2717                return self
2718                    .new_expression(ExpressionKind::Literal(value), self.make_source(start_span));
2719            }
2720        }
2721
2722        // Check for %% (permille)
2723        if self.at(&TokenKind::PercentPercent)? {
2724            let pp_tok = self.next()?;
2725            if let Ok(next_peek) = self.peek() {
2726                if next_peek.kind == TokenKind::NumberLit {
2727                    return Err(self.error_at_token(
2728                        &pp_tok,
2729                        "Permille literal cannot be followed by a digit",
2730                    ));
2731                }
2732            }
2733            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
2734            return self.new_expression(
2735                ExpressionKind::Literal(Value::NumberWithUnit(decimal, "permille".to_string())),
2736                self.make_source(start_span),
2737            );
2738        }
2739
2740        // Check for % (percent)
2741        if self.at(&TokenKind::Percent)? {
2742            let pct_span = self.peek()?.span.clone();
2743            // Only consume % if it's directly adjacent (no space) for the shorthand syntax
2744            // Or if it's "50 %" (space separated is also valid per the grammar)
2745            let pct_tok = self.next()?;
2746            if let Ok(next_peek) = self.peek() {
2747                if next_peek.kind == TokenKind::NumberLit || next_peek.kind == TokenKind::Percent {
2748                    return Err(self.error_at_token(
2749                        &pct_tok,
2750                        "Percent literal cannot be followed by a digit",
2751                    ));
2752                }
2753            }
2754            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
2755            return self.new_expression(
2756                ExpressionKind::Literal(Value::NumberWithUnit(decimal, "percent".to_string())),
2757                self.make_source(self.span_covering(&start_span, &pct_span)),
2758            );
2759        }
2760
2761        // Check for "percent" keyword
2762        if self.at(&TokenKind::PercentKw)? {
2763            self.next()?;
2764            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
2765            return self.new_expression(
2766                ExpressionKind::Literal(Value::NumberWithUnit(decimal, "percent".to_string())),
2767                self.make_source(start_span),
2768            );
2769        }
2770
2771        // Check for "permille" keyword
2772        if self.at(&TokenKind::Permille)? {
2773            self.next()?;
2774            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
2775            return self.new_expression(
2776                ExpressionKind::Literal(Value::NumberWithUnit(decimal, "permille".to_string())),
2777                self.make_source(start_span),
2778            );
2779        }
2780
2781        if can_be_label(&self.peek()?.kind) {
2782            let unit_tok = self.next()?;
2783            let decimal = parse_decimal_string(&num_text, &start_span, self)?;
2784            if let Some(calendar_unit) = CalendarUnit::from_keyword(&unit_tok.text) {
2785                return self.new_expression(
2786                    ExpressionKind::Literal(Value::Calendar(decimal, calendar_unit)),
2787                    self.make_source(self.span_covering(&start_span, &unit_tok.span)),
2788                );
2789            }
2790            return self.new_expression(
2791                ExpressionKind::Literal(Value::NumberWithUnit(decimal, unit_tok.text.clone())),
2792                self.make_source(self.span_covering(&start_span, &unit_tok.span)),
2793            );
2794        }
2795
2796        // Plain number
2797        let decimal = parse_decimal_string(&num_text, &start_span, self)?;
2798        self.new_expression(
2799            ExpressionKind::Literal(Value::Number(decimal)),
2800            self.make_source(start_span),
2801        )
2802    }
2803
2804    fn parse_expression_reference(&mut self) -> Result<Reference, Error> {
2805        let mut segments = Vec::new();
2806
2807        let first = self.next()?;
2808        segments.push(first.text.clone());
2809
2810        while self.at(&TokenKind::Dot)? {
2811            self.next()?; // consume .
2812            let seg = self.next()?;
2813            if !can_be_reference_segment(&seg.kind) {
2814                return Err(self.error_at_token(
2815                    &seg,
2816                    format!("Expected an identifier after '.', found {}", seg.kind),
2817                ));
2818            }
2819            segments.push(seg.text.clone());
2820        }
2821
2822        Ok(Reference::from_path(segments))
2823    }
2824}
2825
2826// ============================================================================
2827// Helper functions
2828// ============================================================================
2829
2830fn unquote_string(s: &str) -> String {
2831    if s.len() >= 2 && s.starts_with('"') && s.ends_with('"') {
2832        s[1..s.len() - 1].to_string()
2833    } else {
2834        s.to_string()
2835    }
2836}
2837
2838fn parse_decimal_string(text: &str, span: &Span, parser: &Parser) -> Result<Decimal, Error> {
2839    let clean = text.replace(['_', ','], "");
2840    Decimal::from_str(&clean).map_err(|_| {
2841        Error::parsing(
2842            format!(
2843                "Invalid number: '{}'. Expected a valid decimal number (e.g., 42, 3.14, 1_000_000)",
2844                text
2845            ),
2846            parser.make_source(span.clone()),
2847            None::<String>,
2848        )
2849    })
2850}
2851
2852fn is_comparison_operator(kind: &TokenKind) -> bool {
2853    matches!(
2854        kind,
2855        TokenKind::Gt | TokenKind::Lt | TokenKind::Gte | TokenKind::Lte | TokenKind::Is
2856    )
2857}
2858
2859// Helper trait for TokenKind
2860impl TokenKind {
2861    fn is_identifier_like(&self) -> bool {
2862        matches!(self, TokenKind::Identifier)
2863            || can_be_label(self)
2864            || is_type_keyword(self)
2865            || is_boolean_keyword(self)
2866            || is_math_function(self)
2867    }
2868}