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