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 #[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 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 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(¤t_repo)).or_default();
225 continue;
226 }
227
228 if self.at(&TokenKind::Spec)? {
229 let spec = self.parse_spec()?;
230 map.entry(Arc::clone(¤t_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 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 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 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 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 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 if peeked_text.len() == 4 && peeked_text.chars().all(|c| c.is_ascii_digit()) {
581 let mut dt_str = String::new();
583 let num_tok = self.next()?; dt_str.push_str(&num_tok.text);
585
586 while self.at(&TokenKind::Minus)? {
588 self.next()?; dt_str.push('-');
590 let part = self.next()?;
591 dt_str.push_str(&part.text);
592 }
593
594 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 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 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 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 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 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 while self.at(&TokenKind::Dot)? {
721 self.next()?; 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 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 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 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 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 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 let expression = if self.at(&TokenKind::Veto)? {
871 self.parse_veto_expression()?
872 } else {
873 self.parse_expression()?
874 };
875
876 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 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()?; 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 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 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 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 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 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 let peeked = self.peek()?;
1219
1220 if num_text.len() == 2
1222 && num_text.chars().all(|c| c.is_ascii_digit())
1223 && peeked.kind == TokenKind::Colon
1224 {
1225 return self.try_parse_time_literal(num_text.clone(), num_span);
1232 }
1233
1234 if peeked.kind == TokenKind::PercentPercent {
1236 let pp_tok = self.next()?;
1237 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 if peeked.kind == TokenKind::Percent {
1253 let pct_tok = self.next()?;
1254 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 if peeked.kind == TokenKind::PercentKw {
1270 self.next()?; 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 if peeked.kind == TokenKind::Permille {
1278 self.next()?; 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 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 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 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 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 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 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 let t_tok = self.next()?;
1327 dt_str.push_str(&t_tok.text);
1328
1329 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 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 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 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 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 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 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 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 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 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()?; 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 if self.at(&TokenKind::Not)? {
1513 return self.parse_not_expression();
1514 }
1515
1516 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 let peeked = self.peek()?;
1547
1548 if is_comparison_operator(&peeked.kind) {
1550 return self.parse_comparison_suffix(repository, start_span);
1551 }
1552
1553 if peeked.kind == TokenKind::Not {
1557 return self.parse_not_in_calendar_suffix(repository, start_span);
1558 }
1559
1560 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 let right = if self.at(&TokenKind::Not)? {
1577 self.parse_not_expression()?
1578 } else {
1579 let rhs = self.parse_repository_expression()?;
1580 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 if self.at(&TokenKind::Not)? {
1611 self.next()?; 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 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 if self.at(&TokenKind::Calendar)? {
1661 self.next()?; 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 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 if peeked.kind == TokenKind::Calendar {
1707 self.next()?; 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 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 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 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 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 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 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 TokenKind::LParen => {
1933 self.next()?; let inner = self.parse_expression()?;
1935 self.expect(&TokenKind::RParen)?;
1936 Ok(inner)
1937 }
1938
1939 TokenKind::Now => {
1941 let tok = self.next()?;
1942 self.new_expression(ExpressionKind::Now, self.make_source(tok.span))
1943 }
1944
1945 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 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 TokenKind::NumberLit => self.parse_number_expression(),
1966
1967 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 if num_text.len() == 4
1991 && num_text.chars().all(|c| c.is_ascii_digit())
1992 && self.at(&TokenKind::Minus)?
1993 {
1994 let minus_span = self.peek()?.span.clone();
2001 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 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 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 if self.at(&TokenKind::Percent)? {
2043 let pct_span = self.peek()?.span.clone();
2044 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 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 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 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 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 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()?; 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
2136fn 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
2169impl 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}