Skip to main content

lemma/parsing/
parser.rs

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