Skip to main content

lemma/parsing/
parser.rs

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