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