1use crate::ast::*;
8use crate::diagnostic::Diagnostic;
9use crate::lexer::{lex, SourceMap, Token, TokenKind};
10use crate::Span;
11
12#[derive(Debug)]
17pub struct ParseResult {
18 pub module: Module,
19 pub diagnostics: Vec<Diagnostic>,
20}
21
22pub fn parse(source: &str) -> ParseResult {
24 let tokens = lex(source);
25 let source_map = SourceMap::new(source);
26 let mut p = Parser {
27 source,
28 tokens,
29 pos: 0,
30 source_map,
31 diagnostics: Vec::new(),
32 };
33 let module = p.parse_module();
34 ParseResult {
35 module,
36 diagnostics: p.diagnostics,
37 }
38}
39
40struct Parser<'s> {
45 source: &'s str,
46 tokens: Vec<Token>,
47 pos: usize,
48 source_map: SourceMap,
49 diagnostics: Vec<Diagnostic>,
50}
51
52impl<'s> Parser<'s> {
57 fn peek(&self) -> Token {
58 self.tokens[self.pos]
59 }
60
61 fn peek_kind(&self) -> TokenKind {
62 self.tokens[self.pos].kind
63 }
64
65 fn peek_at(&self, offset: usize) -> Token {
66 let idx = (self.pos + offset).min(self.tokens.len() - 1);
67 self.tokens[idx]
68 }
69
70 fn advance(&mut self) -> Token {
71 let tok = self.tokens[self.pos];
72 if tok.kind != TokenKind::Eof {
73 self.pos += 1;
74 }
75 tok
76 }
77
78 fn at(&self, kind: TokenKind) -> bool {
79 self.peek_kind() == kind
80 }
81
82 fn at_eof(&self) -> bool {
83 self.at(TokenKind::Eof)
84 }
85
86 fn eat(&mut self, kind: TokenKind) -> Option<Token> {
87 if self.at(kind) {
88 Some(self.advance())
89 } else {
90 None
91 }
92 }
93
94 fn expect(&mut self, kind: TokenKind) -> Option<Token> {
95 if self.at(kind) {
96 Some(self.advance())
97 } else {
98 self.error(
99 self.peek().span,
100 format!("expected {kind}, found {}", self.peek_kind()),
101 );
102 None
103 }
104 }
105
106 fn text(&self, span: Span) -> &'s str {
107 &self.source[span.start..span.end]
108 }
109
110 fn line_of(&self, span: Span) -> u32 {
111 self.source_map.line_col(span.start).0
112 }
113
114 fn col_of(&self, span: Span) -> u32 {
115 self.source_map.line_col(span.start).1
116 }
117
118 fn error(&mut self, span: Span, msg: impl Into<String>) {
119 self.diagnostics.push(Diagnostic::error(span, msg));
120 }
121
122 fn parse_ident(&mut self) -> Option<Ident> {
124 self.parse_ident_in("identifier")
125 }
126
127 fn parse_ident_in(&mut self, context: &str) -> Option<Ident> {
129 let tok = self.peek();
130 if tok.kind.is_word() {
131 self.advance();
132 Some(Ident {
133 span: tok.span,
134 name: self.text(tok.span).to_string(),
135 })
136 } else {
137 self.error(
138 tok.span,
139 format!("expected {context}, found {}", tok.kind),
140 );
141 None
142 }
143 }
144
145 fn parse_string(&mut self) -> Option<StringLiteral> {
147 let tok = self.expect(TokenKind::String)?;
148 let raw = self.text(tok.span);
149 let inner = &raw[1..raw.len() - 1];
151 let parts = parse_string_parts(inner, tok.span.start + 1);
152 Some(StringLiteral {
153 span: tok.span,
154 parts,
155 })
156 }
157}
158
159fn parse_string_parts(inner: &str, base_offset: usize) -> Vec<StringPart> {
163 let mut parts = Vec::new();
164 let mut buf = String::new();
165 let bytes = inner.as_bytes();
166 let mut i = 0;
167 while i < bytes.len() {
168 if bytes[i] == b'\\' && i + 1 < bytes.len() {
169 buf.push(bytes[i + 1] as char);
170 i += 2;
171 } else if bytes[i] == b'{' {
172 if !buf.is_empty() {
173 parts.push(StringPart::Text(std::mem::take(&mut buf)));
174 }
175 i += 1; let start = i;
177 while i < bytes.len() && bytes[i] != b'}' {
178 i += 1;
179 }
180 let name = std::str::from_utf8(&bytes[start..i]).unwrap_or("").to_string();
181 let span_start = base_offset + start;
182 let span_end = base_offset + i;
183 parts.push(StringPart::Interpolation(Ident {
184 span: Span::new(span_start, span_end),
185 name,
186 }));
187 if i < bytes.len() {
188 i += 1; }
190 } else {
191 buf.push(bytes[i] as char);
192 i += 1;
193 }
194 }
195 if !buf.is_empty() {
196 parts.push(StringPart::Text(buf));
197 }
198 parts
199}
200
201fn is_clause_keyword(text: &str) -> bool {
208 matches!(
209 text,
210 "when"
211 | "requires"
212 | "ensures"
213 | "trigger"
214 | "facing"
215 | "context"
216 | "exposes"
217 | "provides"
218 | "related"
219 | "timeout"
220 | "guarantee"
221 | "guidance"
222 | "identified_by"
223 | "within"
224 | "tags"
225 | "invariant"
226 )
227}
228
229fn clause_allows_binding(keyword: &str) -> bool {
231 matches!(keyword, "when")
232}
233
234fn is_binding_clause_keyword(text: &str) -> bool {
237 matches!(text, "facing" | "context")
238}
239
240fn token_is_clause_keyword(kind: TokenKind) -> bool {
242 matches!(
243 kind,
244 TokenKind::When | TokenKind::Requires | TokenKind::Ensures | TokenKind::Within
245 )
246}
247
248impl<'s> Parser<'s> {
253 fn parse_module(&mut self) -> Module {
254 let start = self.peek().span;
255 let version = detect_version(self.source);
258
259 let mut decls = Vec::new();
260 while !self.at_eof() {
261 if let Some(d) = self.parse_decl() {
262 decls.push(d);
263 } else {
264 self.advance();
266 }
267 }
268 let end = self.peek().span;
269 Module {
270 span: start.merge(end),
271 version,
272 declarations: decls,
273 }
274 }
275}
276
277fn detect_version(source: &str) -> Option<u32> {
278 for line in source.lines() {
279 let trimmed = line.trim();
280 if trimmed.is_empty() {
281 continue;
282 }
283 if let Some(rest) = trimmed.strip_prefix("--") {
284 let rest = rest.trim();
285 if let Some(ver) = rest.strip_prefix("allium:") {
286 return ver.trim().parse().ok();
287 }
288 }
289 break; }
291 None
292}
293
294impl<'s> Parser<'s> {
299 fn parse_decl(&mut self) -> Option<Decl> {
300 match self.peek_kind() {
301 TokenKind::Use => self.parse_use_decl().map(Decl::Use),
302 TokenKind::Rule => self.parse_block(BlockKind::Rule).map(Decl::Block),
303 TokenKind::Entity => self.parse_block(BlockKind::Entity).map(Decl::Block),
304 TokenKind::External => {
305 let start = self.advance().span;
306 if self.at(TokenKind::Entity) {
307 self.parse_block_from(start, BlockKind::ExternalEntity)
308 .map(Decl::Block)
309 } else {
310 self.error(self.peek().span, "expected 'entity' after 'external'");
311 None
312 }
313 }
314 TokenKind::Value => self.parse_block(BlockKind::Value).map(Decl::Block),
315 TokenKind::Enum => self.parse_block(BlockKind::Enum).map(Decl::Block),
316 TokenKind::Given => self.parse_anonymous_block(BlockKind::Given).map(Decl::Block),
317 TokenKind::Config => self.parse_anonymous_block(BlockKind::Config).map(Decl::Block),
318 TokenKind::Surface => self.parse_block(BlockKind::Surface).map(Decl::Block),
319 TokenKind::Actor => self.parse_block(BlockKind::Actor).map(Decl::Block),
320 TokenKind::Default => self.parse_default_decl().map(Decl::Default),
321 TokenKind::Variant => self.parse_variant_decl().map(Decl::Variant),
322 TokenKind::Deferred => self.parse_deferred_decl().map(Decl::Deferred),
323 TokenKind::Open => self.parse_open_question_decl().map(Decl::OpenQuestion),
324 TokenKind::Module => self.parse_module_decl(),
325 TokenKind::Ident
327 if self.peek_at(1).kind == TokenKind::Slash
328 && self.text(self.peek_at(2).span) == "config" =>
329 {
330 self.parse_qualified_config().map(Decl::Block)
331 }
332 _ => {
333 self.error(
334 self.peek().span,
335 format!(
336 "expected declaration (entity, rule, enum, value, config, surface, actor, \
337 given, default, variant, deferred, use, open question), found {}",
338 self.peek_kind(),
339 ),
340 );
341 None
342 }
343 }
344 }
345
346 fn parse_module_decl(&mut self) -> Option<Decl> {
349 let start = self.expect(TokenKind::Module)?.span;
350 let name = self.parse_ident_in("module name")?;
351 Some(Decl::ModuleDecl(ModuleDecl {
352 span: start.merge(name.span),
353 name,
354 }))
355 }
356
357 fn parse_use_decl(&mut self) -> Option<UseDecl> {
360 let start = self.expect(TokenKind::Use)?.span;
361 let path = self.parse_string()?;
362 let alias = if self.eat(TokenKind::As).is_some() {
363 Some(self.parse_ident_in("import alias")?)
364 } else {
365 None
366 };
367 let end = alias
368 .as_ref()
369 .map(|a| a.span)
370 .unwrap_or(path.span);
371 Some(UseDecl {
372 span: start.merge(end),
373 path,
374 alias,
375 })
376 }
377
378 fn parse_block(&mut self, kind: BlockKind) -> Option<BlockDecl> {
381 let start = self.advance().span; self.parse_block_from(start, kind)
383 }
384
385 fn parse_block_from(&mut self, start: Span, kind: BlockKind) -> Option<BlockDecl> {
386 if kind == BlockKind::ExternalEntity {
389 self.expect(TokenKind::Entity)?;
390 }
391 let context = match kind {
392 BlockKind::Entity | BlockKind::ExternalEntity => "entity name",
393 BlockKind::Rule => "rule name",
394 BlockKind::Surface => "surface name",
395 BlockKind::Actor => "actor name",
396 BlockKind::Value => "value type name",
397 BlockKind::Enum => "enum name",
398 _ => "block name",
399 };
400 let name = Some(self.parse_ident_in(context)?);
401 self.expect(TokenKind::LBrace)?;
402 let items = if kind == BlockKind::Enum {
403 self.parse_enum_body()
404 } else {
405 self.parse_block_items()
406 };
407 let end = self.expect(TokenKind::RBrace)?.span;
408 Some(BlockDecl {
409 span: start.merge(end),
410 kind,
411 name,
412 items,
413 })
414 }
415
416 fn parse_enum_body(&mut self) -> Vec<BlockItem> {
421 let mut items = Vec::new();
422 while !self.at(TokenKind::RBrace) && !self.at_eof() {
423 if self.eat(TokenKind::Pipe).is_some() {
424 continue;
425 }
426 if let Some(ident) = self.parse_ident_in("enum variant") {
427 items.push(BlockItem {
428 span: ident.span,
429 kind: BlockItemKind::EnumVariant { name: ident },
430 });
431 } else {
432 self.advance(); }
434 }
435 items
436 }
437
438 fn parse_anonymous_block(&mut self, kind: BlockKind) -> Option<BlockDecl> {
439 let start = self.advance().span;
440 self.expect(TokenKind::LBrace)?;
441 let items = self.parse_block_items();
442 let end = self.expect(TokenKind::RBrace)?.span;
443 Some(BlockDecl {
444 span: start.merge(end),
445 kind,
446 name: None,
447 items,
448 })
449 }
450
451 fn parse_qualified_config(&mut self) -> Option<BlockDecl> {
454 let alias = self.parse_ident_in("config qualifier")?;
455 let start = alias.span;
456 self.expect(TokenKind::Slash)?;
457 self.advance(); self.expect(TokenKind::LBrace)?;
459 let items = self.parse_block_items();
460 let end = self.expect(TokenKind::RBrace)?.span;
461 Some(BlockDecl {
462 span: start.merge(end),
463 kind: BlockKind::Config,
464 name: Some(alias),
465 items,
466 })
467 }
468
469 fn parse_default_decl(&mut self) -> Option<DefaultDecl> {
472 let start = self.expect(TokenKind::Default)?.span;
473
474 let (type_name, name) = if self.peek_kind().is_word()
478 && self.peek_at(1).kind.is_word()
479 && self.peek_at(2).kind == TokenKind::Eq
480 {
481 let t = self.parse_ident_in("type name")?;
482 let n = self.parse_ident_in("default name")?;
483 (Some(t), n)
484 } else {
485 (None, self.parse_ident_in("default name")?)
486 };
487
488 self.expect(TokenKind::Eq)?;
489 let value = self.parse_expr(0)?;
490 Some(DefaultDecl {
491 span: start.merge(value.span()),
492 type_name,
493 name,
494 value,
495 })
496 }
497
498 fn parse_variant_decl(&mut self) -> Option<VariantDecl> {
501 let start = self.expect(TokenKind::Variant)?.span;
502 let name = self.parse_ident_in("variant name")?;
503 self.expect(TokenKind::Colon)?;
504 let base = self.parse_expr(0)?;
505
506 let items = if self.eat(TokenKind::LBrace).is_some() {
507 let items = self.parse_block_items();
508 self.expect(TokenKind::RBrace)?;
509 items
510 } else {
511 Vec::new()
512 };
513
514 let end = if let Some(last) = items.last() {
515 last.span
516 } else {
517 base.span()
518 };
519 Some(VariantDecl {
520 span: start.merge(end),
521 name,
522 base,
523 items,
524 })
525 }
526
527 fn parse_deferred_decl(&mut self) -> Option<DeferredDecl> {
530 let start = self.expect(TokenKind::Deferred)?.span;
531 let path = self.parse_expr(0)?;
532 Some(DeferredDecl {
533 span: start.merge(path.span()),
534 path,
535 })
536 }
537
538 fn parse_open_question_decl(&mut self) -> Option<OpenQuestionDecl> {
541 let start = self.expect(TokenKind::Open)?.span;
542 self.expect(TokenKind::Question)?;
543 let text = self.parse_string()?;
544 Some(OpenQuestionDecl {
545 span: start.merge(text.span),
546 text,
547 })
548 }
549}
550
551impl<'s> Parser<'s> {
556 fn parse_block_items(&mut self) -> Vec<BlockItem> {
557 let mut items = Vec::new();
558 while !self.at(TokenKind::RBrace) && !self.at_eof() {
559 if let Some(item) = self.parse_block_item() {
560 items.push(item);
561 } else {
562 self.advance();
564 }
565 }
566 items
567 }
568
569 fn parse_block_item(&mut self) -> Option<BlockItem> {
570 let start = self.peek().span;
571
572 if self.at(TokenKind::Let) {
574 return self.parse_let_item(start);
575 }
576
577 if self.at(TokenKind::For) {
579 return self.parse_for_block_item(start);
580 }
581
582 if self.at(TokenKind::Open) && self.peek_at(1).kind == TokenKind::Question {
584 self.advance(); self.advance(); let text = self.parse_string()?;
587 return Some(BlockItem {
588 span: start.merge(text.span),
589 kind: BlockItemKind::OpenQuestion { text },
590 });
591 }
592
593 if self.peek_kind().is_word() {
596 if is_binding_clause_keyword(self.text(self.peek().span))
599 && self.peek_at(1).kind.is_word()
600 && self.peek_at(2).kind == TokenKind::Colon
601 {
602 return self.parse_binding_clause_item(start);
603 }
604
605 if self.peek_at(1).kind == TokenKind::LParen {
607 return self.parse_param_or_clause_item(start);
608 }
609
610 if self.peek_at(1).kind == TokenKind::Colon {
612 return self.parse_assign_or_clause_item(start);
613 }
614 }
615
616 if token_is_clause_keyword(self.peek_kind()) && self.peek_at(1).kind == TokenKind::Colon {
618 return self.parse_assign_or_clause_item(start);
619 }
620
621 self.error(
622 start,
623 format!(
624 "expected block item (name: value, let name = value, when:/requires:/ensures: clause, \
625 for ... in ...:, or open question), found {}",
626 self.peek_kind(),
627 ),
628 );
629 None
630 }
631
632 fn parse_let_item(&mut self, start: Span) -> Option<BlockItem> {
633 self.advance(); let name = self.parse_ident_in("binding name")?;
635 self.expect(TokenKind::Eq)?;
636 let value = self.parse_clause_value(start)?;
637 Some(BlockItem {
638 span: start.merge(value.span()),
639 kind: BlockItemKind::Let { name, value },
640 })
641 }
642
643 fn parse_binding_clause_item(&mut self, start: Span) -> Option<BlockItem> {
646 let keyword_tok = self.advance(); let keyword = self.text(keyword_tok.span).to_string();
648 let binding_name = self.parse_ident_in(&format!("{keyword} binding name"))?;
649 self.advance(); let type_expr = self.parse_clause_value(start)?;
651 let value_span = type_expr.span();
652 let value = Expr::Binding {
653 span: binding_name.span.merge(value_span),
654 name: binding_name,
655 value: Box::new(type_expr),
656 };
657 Some(BlockItem {
658 span: start.merge(value_span),
659 kind: BlockItemKind::Clause { keyword, value },
660 })
661 }
662
663 fn parse_for_block_item(&mut self, start: Span) -> Option<BlockItem> {
666 self.advance(); let binding = self.parse_ident_in("loop variable")?;
668 self.expect(TokenKind::In)?;
669
670 let collection = self.parse_expr(BP_WITH_WHERE + 1)?;
671
672 let filter = if self.eat(TokenKind::Where).is_some() {
673 Some(self.parse_expr(0)?)
676 } else {
677 None
678 };
679
680 self.expect(TokenKind::Colon)?;
681
682 let for_line = self.line_of(start);
684 let next_line = self.line_of(self.peek().span);
685
686 let items = if next_line > for_line {
687 let base_col = self.col_of(self.peek().span);
688 self.parse_indented_block_items(base_col)
689 } else {
690 let mut items = Vec::new();
692 if let Some(item) = self.parse_block_item() {
693 items.push(item);
694 }
695 items
696 };
697
698 let end = items
699 .last()
700 .map(|i| i.span)
701 .unwrap_or(start);
702
703 Some(BlockItem {
704 span: start.merge(end),
705 kind: BlockItemKind::ForBlock {
706 binding,
707 collection,
708 filter,
709 items,
710 },
711 })
712 }
713
714 fn parse_indented_block_items(&mut self, base_col: u32) -> Vec<BlockItem> {
716 let mut items = Vec::new();
717 while !self.at_eof()
718 && !self.at(TokenKind::RBrace)
719 && self.col_of(self.peek().span) >= base_col
720 {
721 if let Some(item) = self.parse_block_item() {
722 items.push(item);
723 } else {
724 self.advance();
725 break;
726 }
727 }
728 items
729 }
730
731 fn parse_assign_or_clause_item(&mut self, start: Span) -> Option<BlockItem> {
732 let name_tok = self.advance(); let name_text = self.text(name_tok.span).to_string();
734 self.advance(); let allows_binding = clause_allows_binding(&name_text);
737 let value = self.parse_clause_value_maybe_binding(start, allows_binding)?;
738 let value_span = value.span();
739
740 let kind = if is_clause_keyword(&name_text) {
741 BlockItemKind::Clause {
742 keyword: name_text,
743 value,
744 }
745 } else {
746 BlockItemKind::Assignment {
747 name: Ident {
748 span: name_tok.span,
749 name: name_text,
750 },
751 value,
752 }
753 };
754
755 Some(BlockItem {
756 span: start.merge(value_span),
757 kind,
758 })
759 }
760
761 fn parse_param_or_clause_item(&mut self, start: Span) -> Option<BlockItem> {
762 let saved_pos = self.pos;
766 let _name_tok = self.advance();
767 self.advance(); let mut depth = 1u32;
771 while !self.at_eof() && depth > 0 {
772 match self.peek_kind() {
773 TokenKind::LParen => {
774 depth += 1;
775 self.advance();
776 }
777 TokenKind::RParen => {
778 depth -= 1;
779 self.advance();
780 }
781 _ => {
782 self.advance();
783 }
784 }
785 }
786
787 if self.at(TokenKind::Colon) {
788 self.pos = saved_pos;
790 let name = self.parse_ident_in("derived value name")?;
791 self.expect(TokenKind::LParen)?;
792 let params = self.parse_ident_list()?;
793 self.expect(TokenKind::RParen)?;
794 self.expect(TokenKind::Colon)?;
795 let value = self.parse_clause_value(start)?;
796 Some(BlockItem {
797 span: start.merge(value.span()),
798 kind: BlockItemKind::ParamAssignment {
799 name,
800 params,
801 value,
802 },
803 })
804 } else {
805 self.pos = saved_pos;
807 if self.peek_at(1).kind == TokenKind::Colon {
809 }
811 self.parse_assign_or_clause_item(start)
813 }
814 }
815
816 fn parse_ident_list(&mut self) -> Option<Vec<Ident>> {
817 let mut params = Vec::new();
818 if !self.at(TokenKind::RParen) {
819 params.push(self.parse_ident_in("parameter name")?);
820 while self.eat(TokenKind::Comma).is_some() {
821 params.push(self.parse_ident_in("parameter name")?);
822 }
823 }
824 Some(params)
825 }
826
827 fn parse_clause_value_maybe_binding(
831 &mut self,
832 clause_start: Span,
833 allow_binding: bool,
834 ) -> Option<Expr> {
835 if allow_binding
836 && self.peek_kind().is_word()
837 && self.peek_at(1).kind == TokenKind::Colon
838 {
839 let clause_line = self.line_of(clause_start);
842 let next_line = self.line_of(self.peek().span);
843 let colon_is_block_item = next_line > clause_line
844 && self.peek_at(2).kind != TokenKind::Eof
845 && self.line_of(self.peek_at(2).span) == next_line;
846
847 if next_line == clause_line || colon_is_block_item {
848 let name = self.parse_ident_in("binding name")?;
849 self.advance(); let inner = self.parse_clause_value(clause_start)?;
851 return Some(Expr::Binding {
852 span: name.span.merge(inner.span()),
853 name,
854 value: Box::new(inner),
855 });
856 }
857 }
858 self.parse_clause_value(clause_start)
859 }
860
861 fn parse_clause_value(&mut self, clause_start: Span) -> Option<Expr> {
864 let clause_line = self.line_of(clause_start);
865 let next = self.peek();
866 let next_line = self.line_of(next.span);
867
868 if next_line > clause_line {
869 let base_col = self.col_of(next.span);
871 self.parse_indented_block(base_col)
872 } else {
873 let expr = self.parse_expr(0)?;
875 self.maybe_wrap_suffix_predicate(expr, clause_line)
876 }
877 }
878
879 fn maybe_wrap_suffix_predicate(&mut self, expr: Expr, clause_line: u32) -> Option<Expr> {
882 if self.at_eof()
883 || self.at(TokenKind::RBrace)
884 || self.line_of(self.peek().span) != clause_line
885 {
886 return Some(expr);
887 }
888
889 let mut tail = Vec::new();
890 while !self.at_eof()
891 && !self.at(TokenKind::RBrace)
892 && self.line_of(self.peek().span) == clause_line
893 {
894 if let Some(e) = self.try_parse_expr(0) {
895 tail.push(e);
896 } else {
897 let tok = self.advance();
899 tail.push(Expr::Ident(Ident {
900 span: tok.span,
901 name: self.text(tok.span).to_string(),
902 }));
903 }
904 }
905
906 if tail.is_empty() {
907 Some(expr)
908 } else {
909 let end = tail.last().unwrap().span();
910 Some(Expr::Predicate {
911 span: expr.span().merge(end),
912 subject: Box::new(expr),
913 tail,
914 })
915 }
916 }
917
918 fn parse_indented_block(&mut self, base_col: u32) -> Option<Expr> {
921 let start = self.peek().span;
922 let mut items = Vec::new();
923
924 while !self.at_eof()
925 && !self.at(TokenKind::RBrace)
926 && self.col_of(self.peek().span) >= base_col
927 {
928 if self.at(TokenKind::Let) {
930 let let_start = self.advance().span;
931 if let Some(name) = self.parse_ident_in("binding name") {
932 if self.expect(TokenKind::Eq).is_some() {
933 if let Some(value) = self.parse_expr(0) {
934 items.push(Expr::LetExpr {
935 span: let_start.merge(value.span()),
936 name,
937 value: Box::new(value),
938 });
939 continue;
940 }
941 }
942 }
943 break;
944 }
945
946 if let Some(expr) = self.parse_expr(0) {
947 items.push(expr);
948 } else {
949 self.advance();
950 break;
951 }
952 }
953
954 if items.len() == 1 {
955 Some(items.pop().unwrap())
956 } else {
957 let end = items.last().map(|e| e.span()).unwrap_or(start);
958 Some(Expr::Block {
959 span: start.merge(end),
960 items,
961 })
962 }
963 }
964}
965
966const BP_LAMBDA: u8 = 4;
972const BP_WHEN_GUARD: u8 = 5;
973const BP_OR: u8 = 10;
974const BP_AND: u8 = 20;
975const BP_COMPARE: u8 = 30;
976const BP_TRANSITION: u8 = 32;
977const BP_WITH_WHERE: u8 = 35;
978const BP_PROJECTION: u8 = 37;
979const BP_NULL_COALESCE: u8 = 40;
980const BP_ADD: u8 = 50;
981const BP_MUL: u8 = 60;
982const BP_PIPE: u8 = 65;
983const BP_PREFIX: u8 = 70;
984const BP_POSTFIX: u8 = 80;
985
986impl<'s> Parser<'s> {
987 pub fn parse_expr(&mut self, min_bp: u8) -> Option<Expr> {
988 let mut lhs = self.parse_prefix()?;
989
990 loop {
991 if let Some((l_bp, r_bp)) = self.infix_bp() {
992 if l_bp < min_bp {
993 break;
994 }
995 lhs = self.parse_infix(lhs, r_bp)?;
996 } else if let Some(l_bp) = self.postfix_bp() {
997 if l_bp < min_bp {
998 break;
999 }
1000 lhs = self.parse_postfix(lhs)?;
1001 } else {
1002 break;
1003 }
1004 }
1005
1006 Some(lhs)
1007 }
1008
1009 fn try_parse_expr(&mut self, min_bp: u8) -> Option<Expr> {
1012 let saved_pos = self.pos;
1013 let saved_diag_count = self.diagnostics.len();
1014 match self.parse_expr(min_bp) {
1015 Some(expr) => Some(expr),
1016 None => {
1017 self.pos = saved_pos;
1018 self.diagnostics.truncate(saved_diag_count);
1019 None
1020 }
1021 }
1022 }
1023
1024 fn parse_prefix(&mut self) -> Option<Expr> {
1027 match self.peek_kind() {
1028 TokenKind::Not => {
1029 let start = self.advance().span;
1030 if self.at(TokenKind::Exists) {
1031 self.advance();
1032 let operand = self.parse_expr(BP_PREFIX)?;
1033 Some(Expr::NotExists {
1034 span: start.merge(operand.span()),
1035 operand: Box::new(operand),
1036 })
1037 } else {
1038 let operand = self.parse_expr(BP_PREFIX)?;
1039 Some(Expr::Not {
1040 span: start.merge(operand.span()),
1041 operand: Box::new(operand),
1042 })
1043 }
1044 }
1045 TokenKind::Exists => {
1046 let next = self.peek_at(1).kind;
1049 if matches!(
1050 next,
1051 TokenKind::RParen
1052 | TokenKind::RBrace
1053 | TokenKind::RBracket
1054 | TokenKind::Comma
1055 | TokenKind::Eof
1056 ) {
1057 let id = self.parse_ident()?;
1058 return Some(Expr::Ident(id));
1059 }
1060 let start = self.advance().span;
1061 let operand = self.parse_expr(BP_PREFIX)?;
1062 Some(Expr::Exists {
1063 span: start.merge(operand.span()),
1064 operand: Box::new(operand),
1065 })
1066 }
1067 TokenKind::If => self.parse_if_expr(),
1068 TokenKind::For => self.parse_for_expr(),
1069 TokenKind::LBrace => self.parse_brace_expr(),
1070 TokenKind::LBracket => self.parse_list_literal(),
1071 TokenKind::LParen => self.parse_paren_expr(),
1072 TokenKind::Number => {
1073 let t = self.advance();
1074 Some(Expr::NumberLiteral {
1075 span: t.span,
1076 value: self.text(t.span).to_string(),
1077 })
1078 }
1079 TokenKind::Duration => {
1080 let t = self.advance();
1081 Some(Expr::DurationLiteral {
1082 span: t.span,
1083 value: self.text(t.span).to_string(),
1084 })
1085 }
1086 TokenKind::String => {
1087 let sl = self.parse_string()?;
1088 Some(Expr::StringLiteral(sl))
1089 }
1090 TokenKind::True => {
1091 let t = self.advance();
1092 Some(Expr::BoolLiteral {
1093 span: t.span,
1094 value: true,
1095 })
1096 }
1097 TokenKind::False => {
1098 let t = self.advance();
1099 Some(Expr::BoolLiteral {
1100 span: t.span,
1101 value: false,
1102 })
1103 }
1104 TokenKind::Null => {
1105 let t = self.advance();
1106 Some(Expr::Null { span: t.span })
1107 }
1108 TokenKind::Now => {
1109 let t = self.advance();
1110 Some(Expr::Now { span: t.span })
1111 }
1112 TokenKind::This => {
1113 let t = self.advance();
1114 Some(Expr::This { span: t.span })
1115 }
1116 TokenKind::Within => {
1117 let t = self.advance();
1118 Some(Expr::Within { span: t.span })
1119 }
1120 k if k.is_word() => {
1121 let id = self.parse_ident()?;
1122 Some(Expr::Ident(id))
1123 }
1124 TokenKind::Minus => {
1125 let start = self.advance().span;
1127 let operand = self.parse_expr(BP_PREFIX)?;
1128 Some(Expr::BinaryOp {
1129 span: start.merge(operand.span()),
1130 left: Box::new(Expr::NumberLiteral {
1131 span: start,
1132 value: "0".into(),
1133 }),
1134 op: BinaryOp::Sub,
1135 right: Box::new(operand),
1136 })
1137 }
1138 _ => {
1139 self.error(
1140 self.peek().span,
1141 format!(
1142 "expected expression (identifier, number, string, true/false, null, \
1143 if/for/not/exists, '(', '{{', '['), found {}",
1144 self.peek_kind(),
1145 ),
1146 );
1147 None
1148 }
1149 }
1150 }
1151
1152 fn infix_bp(&self) -> Option<(u8, u8)> {
1155 match self.peek_kind() {
1156 TokenKind::FatArrow => Some((BP_LAMBDA, BP_LAMBDA - 1)), TokenKind::When => Some((BP_WHEN_GUARD, BP_WHEN_GUARD + 1)),
1159 TokenKind::Pipe => Some((BP_PIPE, BP_PIPE + 1)),
1160 TokenKind::Or => Some((BP_OR, BP_OR + 1)),
1161 TokenKind::And => Some((BP_AND, BP_AND + 1)),
1162 TokenKind::Eq | TokenKind::EqEq | TokenKind::BangEq => {
1163 Some((BP_COMPARE, BP_COMPARE + 1))
1164 }
1165 TokenKind::Lt => {
1166 if self.pos > 0 {
1169 let prev = self.tokens[self.pos - 1];
1170 if prev.span.end == self.peek().span.start && prev.kind.is_word() {
1171 return None;
1172 }
1173 }
1174 Some((BP_COMPARE, BP_COMPARE + 1))
1175 }
1176 TokenKind::LtEq | TokenKind::Gt | TokenKind::GtEq => {
1177 Some((BP_COMPARE, BP_COMPARE + 1))
1178 }
1179 TokenKind::In => Some((BP_COMPARE, BP_COMPARE + 1)),
1180 TokenKind::Not if self.peek_at(1).kind == TokenKind::In => {
1182 Some((BP_COMPARE, BP_COMPARE + 1))
1183 }
1184 TokenKind::TransitionsTo => Some((BP_TRANSITION, BP_TRANSITION + 1)),
1185 TokenKind::Becomes => Some((BP_TRANSITION, BP_TRANSITION + 1)),
1186 TokenKind::Includes => Some((BP_COMPARE, BP_COMPARE + 1)),
1187 TokenKind::Excludes => Some((BP_COMPARE, BP_COMPARE + 1)),
1188 TokenKind::Where => Some((BP_WITH_WHERE, BP_WITH_WHERE + 1)),
1189 TokenKind::With => Some((BP_WITH_WHERE, BP_WITH_WHERE + 1)),
1190 TokenKind::ThinArrow => Some((BP_PROJECTION, BP_PROJECTION + 1)),
1191 TokenKind::QuestionQuestion => Some((BP_NULL_COALESCE, BP_NULL_COALESCE + 1)),
1192 TokenKind::DotDot => Some((BP_ADD + 1, BP_ADD + 2)), TokenKind::Plus | TokenKind::Minus => Some((BP_ADD, BP_ADD + 1)),
1194 TokenKind::Star | TokenKind::Slash => Some((BP_MUL, BP_MUL + 1)),
1195 _ => None,
1196 }
1197 }
1198
1199 fn parse_infix(&mut self, lhs: Expr, r_bp: u8) -> Option<Expr> {
1200 let op_tok = self.advance();
1201 match op_tok.kind {
1202 TokenKind::FatArrow => {
1203 let body = self.parse_expr(r_bp)?;
1204 Some(Expr::Lambda {
1205 span: lhs.span().merge(body.span()),
1206 param: Box::new(lhs),
1207 body: Box::new(body),
1208 })
1209 }
1210 TokenKind::Pipe => {
1211 let rhs = self.parse_expr(r_bp)?;
1212 Some(Expr::Pipe {
1213 span: lhs.span().merge(rhs.span()),
1214 left: Box::new(lhs),
1215 right: Box::new(rhs),
1216 })
1217 }
1218 TokenKind::Or => {
1219 let rhs = self.parse_expr(r_bp)?;
1220 Some(Expr::LogicalOp {
1221 span: lhs.span().merge(rhs.span()),
1222 left: Box::new(lhs),
1223 op: LogicalOp::Or,
1224 right: Box::new(rhs),
1225 })
1226 }
1227 TokenKind::And => {
1228 let rhs = self.parse_expr(r_bp)?;
1229 Some(Expr::LogicalOp {
1230 span: lhs.span().merge(rhs.span()),
1231 left: Box::new(lhs),
1232 op: LogicalOp::And,
1233 right: Box::new(rhs),
1234 })
1235 }
1236 TokenKind::Eq | TokenKind::EqEq => {
1237 let rhs = self.parse_expr(r_bp)?;
1238 Some(Expr::Comparison {
1239 span: lhs.span().merge(rhs.span()),
1240 left: Box::new(lhs),
1241 op: ComparisonOp::Eq,
1242 right: Box::new(rhs),
1243 })
1244 }
1245 TokenKind::BangEq => {
1246 let rhs = self.parse_expr(r_bp)?;
1247 Some(Expr::Comparison {
1248 span: lhs.span().merge(rhs.span()),
1249 left: Box::new(lhs),
1250 op: ComparisonOp::NotEq,
1251 right: Box::new(rhs),
1252 })
1253 }
1254 TokenKind::Lt => {
1255 let rhs = self.parse_expr(r_bp)?;
1256 Some(Expr::Comparison {
1257 span: lhs.span().merge(rhs.span()),
1258 left: Box::new(lhs),
1259 op: ComparisonOp::Lt,
1260 right: Box::new(rhs),
1261 })
1262 }
1263 TokenKind::LtEq => {
1264 let rhs = self.parse_expr(r_bp)?;
1265 Some(Expr::Comparison {
1266 span: lhs.span().merge(rhs.span()),
1267 left: Box::new(lhs),
1268 op: ComparisonOp::LtEq,
1269 right: Box::new(rhs),
1270 })
1271 }
1272 TokenKind::Gt => {
1273 let rhs = self.parse_expr(r_bp)?;
1274 Some(Expr::Comparison {
1275 span: lhs.span().merge(rhs.span()),
1276 left: Box::new(lhs),
1277 op: ComparisonOp::Gt,
1278 right: Box::new(rhs),
1279 })
1280 }
1281 TokenKind::GtEq => {
1282 let rhs = self.parse_expr(r_bp)?;
1283 Some(Expr::Comparison {
1284 span: lhs.span().merge(rhs.span()),
1285 left: Box::new(lhs),
1286 op: ComparisonOp::GtEq,
1287 right: Box::new(rhs),
1288 })
1289 }
1290 TokenKind::In => {
1291 let rhs = self.parse_expr(r_bp)?;
1292 Some(Expr::In {
1293 span: lhs.span().merge(rhs.span()),
1294 element: Box::new(lhs),
1295 collection: Box::new(rhs),
1296 })
1297 }
1298 TokenKind::Not => {
1299 self.expect(TokenKind::In)?;
1301 let rhs = self.parse_expr(r_bp)?;
1302 Some(Expr::NotIn {
1303 span: lhs.span().merge(rhs.span()),
1304 element: Box::new(lhs),
1305 collection: Box::new(rhs),
1306 })
1307 }
1308 TokenKind::Where => {
1309 let rhs = self.parse_expr(r_bp)?;
1310 Some(Expr::Where {
1311 span: lhs.span().merge(rhs.span()),
1312 source: Box::new(lhs),
1313 condition: Box::new(rhs),
1314 })
1315 }
1316 TokenKind::With => {
1317 let rhs = self.parse_expr(r_bp)?;
1318 Some(Expr::With {
1319 span: lhs.span().merge(rhs.span()),
1320 source: Box::new(lhs),
1321 predicate: Box::new(rhs),
1322 })
1323 }
1324 TokenKind::QuestionQuestion => {
1325 let rhs = self.parse_expr(r_bp)?;
1326 Some(Expr::NullCoalesce {
1327 span: lhs.span().merge(rhs.span()),
1328 left: Box::new(lhs),
1329 right: Box::new(rhs),
1330 })
1331 }
1332 TokenKind::Plus => {
1333 let rhs = self.parse_expr(r_bp)?;
1334 Some(Expr::BinaryOp {
1335 span: lhs.span().merge(rhs.span()),
1336 left: Box::new(lhs),
1337 op: BinaryOp::Add,
1338 right: Box::new(rhs),
1339 })
1340 }
1341 TokenKind::Minus => {
1342 let rhs = self.parse_expr(r_bp)?;
1343 Some(Expr::BinaryOp {
1344 span: lhs.span().merge(rhs.span()),
1345 left: Box::new(lhs),
1346 op: BinaryOp::Sub,
1347 right: Box::new(rhs),
1348 })
1349 }
1350 TokenKind::Star => {
1351 let rhs = self.parse_expr(r_bp)?;
1352 Some(Expr::BinaryOp {
1353 span: lhs.span().merge(rhs.span()),
1354 left: Box::new(lhs),
1355 op: BinaryOp::Mul,
1356 right: Box::new(rhs),
1357 })
1358 }
1359 TokenKind::Slash => {
1360 if let Expr::Ident(ref id) = lhs {
1365 if self.peek_kind().is_word() {
1366 let next_text = self.text(self.peek().span);
1367 let is_qualified = next_text
1368 .chars()
1369 .next()
1370 .is_some_and(|c| c.is_uppercase())
1371 || matches!(
1372 self.peek_kind(),
1373 TokenKind::Config | TokenKind::Entity | TokenKind::Value
1374 );
1375 if is_qualified {
1376 let name_tok = self.advance();
1377 return Some(Expr::QualifiedName(QualifiedName {
1378 span: lhs.span().merge(name_tok.span),
1379 qualifier: Some(id.name.clone()),
1380 name: self.text(name_tok.span).to_string(),
1381 }));
1382 }
1383 }
1384 }
1385 let rhs = self.parse_expr(r_bp)?;
1386 Some(Expr::BinaryOp {
1387 span: lhs.span().merge(rhs.span()),
1388 left: Box::new(lhs),
1389 op: BinaryOp::Div,
1390 right: Box::new(rhs),
1391 })
1392 }
1393 TokenKind::ThinArrow => {
1394 let field = self.parse_ident_in("projection field")?;
1395 Some(Expr::ProjectionMap {
1396 span: lhs.span().merge(field.span),
1397 source: Box::new(lhs),
1398 field,
1399 })
1400 }
1401 TokenKind::TransitionsTo => {
1402 let rhs = self.parse_expr(r_bp)?;
1403 Some(Expr::TransitionsTo {
1404 span: lhs.span().merge(rhs.span()),
1405 subject: Box::new(lhs),
1406 new_state: Box::new(rhs),
1407 })
1408 }
1409 TokenKind::Becomes => {
1410 let rhs = self.parse_expr(r_bp)?;
1411 Some(Expr::Becomes {
1412 span: lhs.span().merge(rhs.span()),
1413 subject: Box::new(lhs),
1414 new_state: Box::new(rhs),
1415 })
1416 }
1417 TokenKind::Includes => {
1418 let rhs = self.parse_expr(r_bp)?;
1419 Some(Expr::Includes {
1420 span: lhs.span().merge(rhs.span()),
1421 collection: Box::new(lhs),
1422 element: Box::new(rhs),
1423 })
1424 }
1425 TokenKind::Excludes => {
1426 let rhs = self.parse_expr(r_bp)?;
1427 Some(Expr::Excludes {
1428 span: lhs.span().merge(rhs.span()),
1429 collection: Box::new(lhs),
1430 element: Box::new(rhs),
1431 })
1432 }
1433 TokenKind::When => {
1434 let rhs = self.parse_expr(r_bp)?;
1436 Some(Expr::WhenGuard {
1437 span: lhs.span().merge(rhs.span()),
1438 action: Box::new(lhs),
1439 condition: Box::new(rhs),
1440 })
1441 }
1442 TokenKind::DotDot => {
1443 let rhs = self.parse_expr(r_bp)?;
1444 Some(Expr::Range {
1445 span: lhs.span().merge(rhs.span()),
1446 start: Box::new(lhs),
1447 end: Box::new(rhs),
1448 })
1449 }
1450 _ => {
1451 self.error(
1452 op_tok.span,
1453 format!("unexpected infix operator {}", op_tok.kind),
1454 );
1455 None
1456 }
1457 }
1458 }
1459
1460 fn postfix_bp(&self) -> Option<u8> {
1463 match self.peek_kind() {
1464 TokenKind::Dot | TokenKind::QuestionDot => Some(BP_POSTFIX),
1465 TokenKind::QuestionMark => Some(BP_POSTFIX),
1466 TokenKind::Lt => {
1469 if self.pos > 0 {
1470 let prev = self.tokens[self.pos - 1];
1471 if prev.span.end == self.peek().span.start && prev.kind.is_word() {
1474 return Some(BP_POSTFIX);
1475 }
1476 }
1477 None
1478 }
1479 TokenKind::LParen => Some(BP_POSTFIX),
1480 TokenKind::LBrace => {
1481 let next = self.peek();
1487 let prev_end = if self.pos > 0 {
1488 self.tokens[self.pos - 1].span.end
1489 } else {
1490 0
1491 };
1492 if self.line_of(Span::new(prev_end, prev_end))
1494 == self.line_of(next.span)
1495 {
1496 Some(BP_POSTFIX)
1497 } else {
1498 None
1499 }
1500 }
1501 _ => None,
1502 }
1503 }
1504
1505 fn parse_postfix(&mut self, lhs: Expr) -> Option<Expr> {
1506 match self.peek_kind() {
1507 TokenKind::QuestionMark => {
1508 let end = self.advance().span;
1509 Some(Expr::TypeOptional {
1510 span: lhs.span().merge(end),
1511 inner: Box::new(lhs),
1512 })
1513 }
1514 TokenKind::Lt => {
1515 self.advance(); let mut args = Vec::new();
1518 while !self.at(TokenKind::Gt) && !self.at_eof() {
1520 args.push(self.parse_expr(BP_COMPARE + 1)?);
1521 self.eat(TokenKind::Comma);
1522 }
1523 let end = self.expect(TokenKind::Gt)?.span;
1524 Some(Expr::GenericType {
1525 span: lhs.span().merge(end),
1526 name: Box::new(lhs),
1527 args,
1528 })
1529 }
1530 TokenKind::Dot => {
1531 self.advance();
1532 let field = self.parse_ident_in("field name")?;
1533 Some(Expr::MemberAccess {
1534 span: lhs.span().merge(field.span),
1535 object: Box::new(lhs),
1536 field,
1537 })
1538 }
1539 TokenKind::QuestionDot => {
1540 self.advance();
1541 let field = self.parse_ident_in("field name")?;
1542 Some(Expr::OptionalAccess {
1543 span: lhs.span().merge(field.span),
1544 object: Box::new(lhs),
1545 field,
1546 })
1547 }
1548 TokenKind::LParen => {
1549 self.advance();
1550 let args = self.parse_call_args()?;
1551 let end = self.expect(TokenKind::RParen)?.span;
1552 Some(Expr::Call {
1553 span: lhs.span().merge(end),
1554 function: Box::new(lhs),
1555 args,
1556 })
1557 }
1558 TokenKind::LBrace => {
1559 self.advance();
1560 let fields = self.parse_join_fields()?;
1561 let end = self.expect(TokenKind::RBrace)?.span;
1562 Some(Expr::JoinLookup {
1563 span: lhs.span().merge(end),
1564 entity: Box::new(lhs),
1565 fields,
1566 })
1567 }
1568 _ => None,
1569 }
1570 }
1571
1572 fn parse_call_args(&mut self) -> Option<Vec<CallArg>> {
1575 let mut args = Vec::new();
1576 while !self.at(TokenKind::RParen) && !self.at_eof() {
1577 if self.peek_kind().is_word() && self.peek_at(1).kind == TokenKind::Colon {
1579 let name = self.parse_ident_in("argument name")?;
1580 self.advance(); let value = self.parse_expr(0)?;
1582 args.push(CallArg::Named(NamedArg {
1583 span: name.span.merge(value.span()),
1584 name,
1585 value,
1586 }));
1587 } else {
1588 let expr = self.parse_expr(0)?;
1589 args.push(CallArg::Positional(expr));
1590 }
1591 self.eat(TokenKind::Comma);
1592 }
1593 Some(args)
1594 }
1595
1596 fn parse_join_fields(&mut self) -> Option<Vec<JoinField>> {
1599 let mut fields = Vec::new();
1600 while !self.at(TokenKind::RBrace) && !self.at_eof() {
1601 let field = self.parse_ident_in("join field name")?;
1602 let value = if self.eat(TokenKind::Colon).is_some() {
1603 Some(self.parse_expr(0)?)
1604 } else {
1605 None
1606 };
1607 fields.push(JoinField {
1608 span: field.span.merge(
1609 value
1610 .as_ref()
1611 .map(|v| v.span())
1612 .unwrap_or(field.span),
1613 ),
1614 field,
1615 value,
1616 });
1617 self.eat(TokenKind::Comma);
1618 }
1619 Some(fields)
1620 }
1621
1622 fn parse_if_expr(&mut self) -> Option<Expr> {
1625 let start = self.advance().span; let mut branches = Vec::new();
1627
1628 let condition = self.parse_expr(0)?;
1630 self.expect(TokenKind::Colon)?;
1631 let body = self.parse_branch_body(start)?;
1632 branches.push(CondBranch {
1633 span: start.merge(body.span()),
1634 condition,
1635 body,
1636 });
1637
1638 let mut else_body = None;
1640 while self.at(TokenKind::Else) {
1641 let else_tok = self.advance();
1642 if self.at(TokenKind::If) {
1643 let if_start = self.advance().span;
1644 let cond = self.parse_expr(0)?;
1645 self.expect(TokenKind::Colon)?;
1646 let body = self.parse_branch_body(else_tok.span)?;
1647 branches.push(CondBranch {
1648 span: if_start.merge(body.span()),
1649 condition: cond,
1650 body,
1651 });
1652 } else {
1653 self.expect(TokenKind::Colon)?;
1654 let body = self.parse_branch_body(else_tok.span)?;
1655 else_body = Some(Box::new(body));
1656 break;
1657 }
1658 }
1659
1660 let end = else_body
1661 .as_ref()
1662 .map(|b| b.span())
1663 .or_else(|| branches.last().map(|b| b.body.span()))
1664 .unwrap_or(start);
1665
1666 Some(Expr::Conditional {
1667 span: start.merge(end),
1668 branches,
1669 else_body,
1670 })
1671 }
1672
1673 fn parse_branch_body(&mut self, keyword_span: Span) -> Option<Expr> {
1674 let keyword_line = self.line_of(keyword_span);
1675 let next_line = self.line_of(self.peek().span);
1676
1677 if next_line > keyword_line {
1678 let base_col = self.col_of(self.peek().span);
1679 self.parse_indented_block(base_col)
1680 } else {
1681 self.parse_expr(0)
1682 }
1683 }
1684
1685 fn parse_for_expr(&mut self) -> Option<Expr> {
1688 let start = self.advance().span; let binding = self.parse_ident_in("loop variable")?;
1690 self.expect(TokenKind::In)?;
1691
1692 let collection = self.parse_expr(BP_WITH_WHERE + 1)?;
1694
1695 let filter = if self.eat(TokenKind::Where).is_some() {
1696 Some(Box::new(self.parse_expr(0)?))
1698 } else {
1699 None
1700 };
1701
1702 self.expect(TokenKind::Colon)?;
1703 let body = self.parse_branch_body(start)?;
1704
1705 Some(Expr::For {
1706 span: start.merge(body.span()),
1707 binding,
1708 collection: Box::new(collection),
1709 filter,
1710 body: Box::new(body),
1711 })
1712 }
1713
1714 fn parse_brace_expr(&mut self) -> Option<Expr> {
1717 let start = self.advance().span; if self.at(TokenKind::RBrace) {
1720 let end = self.advance().span;
1721 return Some(Expr::SetLiteral {
1722 span: start.merge(end),
1723 elements: Vec::new(),
1724 });
1725 }
1726
1727 if self.peek_kind().is_word() && self.peek_at(1).kind == TokenKind::Colon {
1729 return self.parse_object_literal(start);
1730 }
1731
1732 self.parse_set_literal(start)
1734 }
1735
1736 fn parse_list_literal(&mut self) -> Option<Expr> {
1737 let start = self.advance().span; let mut elements = Vec::new();
1739 while !self.at(TokenKind::RBracket) && !self.at_eof() {
1740 elements.push(self.parse_expr(0)?);
1741 self.eat(TokenKind::Comma);
1742 }
1743 let end = self.expect(TokenKind::RBracket)?.span;
1744 Some(Expr::ListLiteral {
1745 span: start.merge(end),
1746 elements,
1747 })
1748 }
1749
1750 fn parse_object_literal(&mut self, start: Span) -> Option<Expr> {
1751 let mut fields = Vec::new();
1752 while !self.at(TokenKind::RBrace) && !self.at_eof() {
1753 let name = self.parse_ident_in("field name")?;
1754 self.expect(TokenKind::Colon)?;
1755 let value = self.parse_expr(0)?;
1756 fields.push(NamedArg {
1757 span: name.span.merge(value.span()),
1758 name,
1759 value,
1760 });
1761 self.eat(TokenKind::Comma);
1762 }
1763 let end = self.expect(TokenKind::RBrace)?.span;
1764 Some(Expr::ObjectLiteral {
1765 span: start.merge(end),
1766 fields,
1767 })
1768 }
1769
1770 fn parse_set_literal(&mut self, start: Span) -> Option<Expr> {
1771 let mut elements = Vec::new();
1772 while !self.at(TokenKind::RBrace) && !self.at_eof() {
1773 elements.push(self.parse_expr(0)?);
1774 self.eat(TokenKind::Comma);
1775 }
1776 let end = self.expect(TokenKind::RBrace)?.span;
1777 Some(Expr::SetLiteral {
1778 span: start.merge(end),
1779 elements,
1780 })
1781 }
1782
1783 fn parse_paren_expr(&mut self) -> Option<Expr> {
1786 self.advance(); let expr = self.parse_expr(0)?;
1788 self.expect(TokenKind::RParen)?;
1789 Some(expr)
1790 }
1791}
1792
1793#[cfg(test)]
1798mod tests {
1799 use super::*;
1800
1801 fn parse_ok(src: &str) -> ParseResult {
1802 let result = parse(src);
1803 if !result.diagnostics.is_empty() {
1804 for d in &result.diagnostics {
1805 eprintln!(
1806 " [{:?}] {} ({}..{})",
1807 d.severity, d.message, d.span.start, d.span.end
1808 );
1809 }
1810 }
1811 result
1812 }
1813
1814 #[test]
1815 fn version_marker() {
1816 let r = parse_ok("-- allium: 1\n");
1817 assert_eq!(r.module.version, Some(1));
1818 }
1819
1820 #[test]
1821 fn empty_entity() {
1822 let r = parse_ok("entity User {}");
1823 assert_eq!(r.diagnostics.len(), 0);
1824 assert_eq!(r.module.declarations.len(), 1);
1825 match &r.module.declarations[0] {
1826 Decl::Block(b) => {
1827 assert_eq!(b.kind, BlockKind::Entity);
1828 assert_eq!(b.name.as_ref().unwrap().name, "User");
1829 }
1830 other => panic!("expected Block, got {other:?}"),
1831 }
1832 }
1833
1834 #[test]
1835 fn entity_with_fields() {
1836 let src = r#"entity Order {
1837 customer: Customer
1838 status: pending | active | completed
1839 total: Decimal
1840}"#;
1841 let r = parse_ok(src);
1842 assert_eq!(r.diagnostics.len(), 0);
1843 match &r.module.declarations[0] {
1844 Decl::Block(b) => {
1845 assert_eq!(b.items.len(), 3);
1846 }
1847 other => panic!("expected Block, got {other:?}"),
1848 }
1849 }
1850
1851 #[test]
1852 fn use_declaration() {
1853 let r = parse_ok(r#"use "github.com/specs/oauth/abc123" as oauth"#);
1854 assert_eq!(r.diagnostics.len(), 0);
1855 match &r.module.declarations[0] {
1856 Decl::Use(u) => {
1857 assert_eq!(u.alias.as_ref().unwrap().name, "oauth");
1858 }
1859 other => panic!("expected Use, got {other:?}"),
1860 }
1861 }
1862
1863 #[test]
1864 fn enum_declaration() {
1865 let src = "enum OrderStatus { pending | shipped | delivered }";
1866 let r = parse_ok(src);
1867 assert_eq!(r.diagnostics.len(), 0);
1868 }
1869
1870 #[test]
1871 fn config_block() {
1872 let src = r#"config {
1873 max_retries: Integer = 3
1874 timeout: Duration = 24.hours
1875}"#;
1876 let r = parse_ok(src);
1881 assert_eq!(r.diagnostics.len(), 0);
1882 }
1883
1884 #[test]
1885 fn rule_declaration() {
1886 let src = r#"rule PlaceOrder {
1887 when: CustomerPlacesOrder(customer, items, total)
1888 requires: total > 0
1889 ensures: Order.created(customer: customer, status: pending, total: total)
1890}"#;
1891 let r = parse_ok(src);
1892 assert_eq!(r.diagnostics.len(), 0);
1893 match &r.module.declarations[0] {
1894 Decl::Block(b) => {
1895 assert_eq!(b.kind, BlockKind::Rule);
1896 assert_eq!(b.items.len(), 3);
1897 }
1898 other => panic!("expected Block, got {other:?}"),
1899 }
1900 }
1901
1902 #[test]
1903 fn expression_precedence() {
1904 let r = parse_ok("rule T { v: a + b * c }");
1905 match &r.module.declarations[0] {
1907 Decl::Block(b) => match &b.items[0].kind {
1908 BlockItemKind::Assignment { value, .. } => match value {
1909 Expr::BinaryOp { op, right, .. } => {
1910 assert_eq!(*op, BinaryOp::Add);
1911 assert!(matches!(**right, Expr::BinaryOp { op: BinaryOp::Mul, .. }));
1912 }
1913 other => panic!("expected BinaryOp, got {other:?}"),
1914 },
1915 other => panic!("expected Assignment, got {other:?}"),
1916 },
1917 other => panic!("expected Block, got {other:?}"),
1918 }
1919 }
1920
1921 #[test]
1922 fn default_declaration() {
1923 let src = r#"default Role admin = { name: "admin", permissions: { "read" } }"#;
1924 let r = parse_ok(src);
1925 assert_eq!(r.diagnostics.len(), 0);
1926 }
1927
1928 #[test]
1929 fn open_question() {
1930 let src = r#"open question "Should admins be role-specific?""#;
1931 let r = parse_ok(src);
1932 assert_eq!(r.diagnostics.len(), 0);
1933 }
1934
1935 #[test]
1936 fn external_entity() {
1937 let src = "external entity Customer { email: String }";
1938 let r = parse_ok(src);
1939 assert_eq!(r.diagnostics.len(), 0);
1940 match &r.module.declarations[0] {
1941 Decl::Block(b) => assert_eq!(b.kind, BlockKind::ExternalEntity),
1942 other => panic!("expected Block, got {other:?}"),
1943 }
1944 }
1945
1946 #[test]
1947 fn where_expression() {
1948 let src = "entity E { active: items where status = active }";
1949 let r = parse_ok(src);
1950 assert_eq!(r.diagnostics.len(), 0);
1951 }
1952
1953 #[test]
1954 fn with_expression() {
1955 let src = "entity E { slots: InterviewSlot with candidacy = this }";
1956 let r = parse_ok(src);
1957 assert_eq!(r.diagnostics.len(), 0);
1958 }
1959
1960 #[test]
1961 fn lambda_expression() {
1962 let src = "entity E { v: items.any(i => i.active) }";
1963 let r = parse_ok(src);
1964 assert_eq!(r.diagnostics.len(), 0);
1965 }
1966
1967 #[test]
1968 fn deferred() {
1969 let src = "deferred InterviewerMatching.suggest";
1970 let r = parse_ok(src);
1971 assert_eq!(r.diagnostics.len(), 0);
1972 }
1973
1974 #[test]
1975 fn variant_declaration() {
1976 let src = "variant Email : Notification { subject: String }";
1977 let r = parse_ok(src);
1978 assert_eq!(r.diagnostics.len(), 0);
1979 }
1980
1981 #[test]
1984 fn projection_arrow() {
1985 let src = "entity E { confirmed: confirmations where status = confirmed -> interviewer }";
1986 let r = parse_ok(src);
1987 assert_eq!(r.diagnostics.len(), 0);
1988 }
1989
1990 #[test]
1993 fn transitions_to_trigger() {
1994 let src = "rule R { when: Interview.status transitions_to scheduled\n ensures: Notification.created() }";
1995 let r = parse_ok(src);
1996 assert_eq!(r.diagnostics.len(), 0);
1997 }
1998
1999 #[test]
2000 fn becomes_trigger() {
2001 let src = "rule R { when: Interview.status becomes scheduled\n ensures: Notification.created() }";
2002 let r = parse_ok(src);
2003 assert_eq!(r.diagnostics.len(), 0);
2004 }
2005
2006 #[test]
2009 fn when_binding() {
2010 let src = "rule R {\n when: interview: Interview.status transitions_to scheduled\n ensures: Notification.created()\n}";
2011 let r = parse_ok(src);
2012 assert_eq!(r.diagnostics.len(), 0);
2013 let decl = &r.module.declarations[0];
2015 if let Decl::Block(b) = decl {
2016 if let BlockItemKind::Clause { keyword, value } = &b.items[0].kind {
2017 assert_eq!(keyword, "when");
2018 assert!(matches!(value, Expr::Binding { .. }));
2019 } else {
2020 panic!("expected clause");
2021 }
2022 } else {
2023 panic!("expected block decl");
2024 }
2025 }
2026
2027 #[test]
2028 fn when_binding_temporal() {
2029 let src = "rule R {\n when: invitation: Invitation.expires_at <= now\n ensures: Invitation.expired()\n}";
2030 let r = parse_ok(src);
2031 assert_eq!(r.diagnostics.len(), 0);
2032 }
2033
2034 #[test]
2035 fn when_binding_created() {
2036 let src = "rule R {\n when: batch: DigestBatch.created\n ensures: Email.created()\n}";
2037 let r = parse_ok(src);
2038 assert_eq!(r.diagnostics.len(), 0);
2039 }
2040
2041 #[test]
2042 fn facing_binding() {
2043 let src = "surface S {\n facing viewer: Interviewer\n exposes: InterviewList\n}";
2044 let r = parse_ok(src);
2045 assert_eq!(r.diagnostics.len(), 0);
2046 }
2047
2048 #[test]
2049 fn context_binding() {
2050 let src = "surface S {\n facing viewer: Interviewer\n context assignment: SlotConfirmation where interviewer = viewer\n}";
2051 let r = parse_ok(src);
2052 assert_eq!(r.diagnostics.len(), 0);
2053 }
2054
2055 #[test]
2058 fn rule_level_for() {
2059 let src = r#"rule ProcessDigests {
2060 when: schedule: DigestSchedule.next_run_at <= now
2061 for user in Users where notification_setting.digest_enabled:
2062 ensures: DigestBatch.created(user: user)
2063}"#;
2064 let r = parse_ok(src);
2065 assert_eq!(r.diagnostics.len(), 0);
2066 if let Decl::Block(b) = &r.module.declarations[0] {
2067 assert!(b.items.len() >= 2);
2069 assert!(matches!(b.items[1].kind, BlockItemKind::ForBlock { .. }));
2070 } else {
2071 panic!("expected block decl");
2072 }
2073 }
2074
2075 #[test]
2078 fn let_in_ensures_block() {
2079 let src = r#"rule R {
2080 when: ScheduleInterview(candidacy, time, interviewers)
2081 ensures:
2082 let slot = InterviewSlot.created(time: time, candidacy: candidacy)
2083 for interviewer in interviewers:
2084 SlotConfirmation.created(slot: slot, interviewer: interviewer)
2085}"#;
2086 let r = parse_ok(src);
2087 assert_eq!(r.diagnostics.len(), 0);
2088 }
2089
2090 #[test]
2093 fn provides_when_guard() {
2094 let src = "surface S {\n facing viewer: Interviewer\n provides: ConfirmSlot(viewer, slot) when slot.status = pending\n}";
2095 let r = parse_ok(src);
2096 assert_eq!(r.diagnostics.len(), 0);
2097 }
2098
2099 #[test]
2102 fn optional_type_suffix() {
2103 let src = "entity E { locked_until: Timestamp? }";
2104 let r = parse_ok(src);
2105 assert_eq!(r.diagnostics.len(), 0);
2106 }
2107
2108 #[test]
2109 fn optional_trigger_param() {
2110 let src = "rule R { when: Report(interviewer, interview, reason, details?)\n ensures: Done() }";
2111 let r = parse_ok(src);
2112 assert_eq!(r.diagnostics.len(), 0);
2113 }
2114
2115 #[test]
2118 fn qualified_config_access() {
2119 let src = "entity E { duration: oauth/config.session_duration }";
2120 let r = parse_ok(src);
2121 assert_eq!(r.diagnostics.len(), 0);
2122 }
2123
2124 #[test]
2127 fn realistic_spec() {
2128 let src = r#"-- allium: 1
2129
2130enum OrderStatus { pending | shipped | delivered }
2131
2132external entity Customer {
2133 email: String
2134 name: String
2135}
2136
2137entity Order {
2138 customer: Customer
2139 status: OrderStatus
2140 total: Decimal
2141 items: OrderItem with order = this
2142 shipped_items: items where status = shipped
2143 confirmed_items: items where status = confirmed -> item
2144 is_complete: status = delivered
2145 locked_until: Timestamp?
2146}
2147
2148config {
2149 max_retries: Integer = 3
2150 timeout: Duration = 24.hours
2151}
2152
2153rule PlaceOrder {
2154 when: CustomerPlacesOrder(customer, items, total)
2155 requires: total > 0
2156 ensures: Order.created(customer: customer, status: pending, total: total)
2157}
2158
2159rule ShipOrder {
2160 when: order: Order.status transitions_to shipped
2161 ensures: Email.created(to: order.customer.email, template: order_shipped)
2162}
2163
2164open question "How do we handle partial shipments?"
2165"#;
2166 let r = parse_ok(src);
2167 assert_eq!(r.diagnostics.len(), 0, "expected no errors");
2168 assert_eq!(r.module.version, Some(1));
2169 assert_eq!(r.module.declarations.len(), 7);
2170 }
2171
2172 #[test]
2173 fn extension_behaviour_excerpt() {
2174 let src = r#"value Document {
2177 uri: String
2178 text: String
2179}
2180
2181entity Finding {
2182 code: String
2183 severity: error | warning | info
2184 range: FindingRange
2185}
2186
2187entity DiagnosticsMode {
2188 value: strict | relaxed
2189}
2190
2191config {
2192 duplicateKey: String = "allium.config.duplicateKey"
2193}
2194
2195rule RefreshDiagnostics {
2196 when: DocumentOpened(document) or DocumentChanged(document)
2197 requires: document.language_id = "allium"
2198 ensures: FindingsComputed(document)
2199}
2200
2201surface DiagnosticsDashboard {
2202 facing viewer: Developer
2203 context doc: Document where viewer.active_document = doc
2204 provides: RunChecks(viewer) when doc.language_id = "allium"
2205 exposes: FindingList
2206}
2207
2208rule ProcessDigests {
2209 when: schedule: DigestSchedule.next_run_at <= now
2210 for user in Users where notification_setting.digest_enabled:
2211 let settings = user.notification_setting
2212 ensures: DigestBatch.created(user: user)
2213}
2214"#;
2215 let r = parse_ok(src);
2216 assert_eq!(r.diagnostics.len(), 0, "expected no errors");
2217 assert_eq!(r.module.declarations.len(), 7);
2219 }
2220
2221 #[test]
2222 fn suffix_predicate_simple() {
2223 let src = r#"rule R {
2224 when: RenameRequested(symbol)
2225 requires: symbol resolves_to_single_definition
2226 ensures: RenameApplied()
2227}"#;
2228 let r = parse_ok(src);
2229 assert_eq!(r.diagnostics.len(), 0);
2230 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2231 assert_eq!(b.items.len(), 3);
2233 let BlockItemKind::Clause { keyword, value } = &b.items[1].kind else { panic!() };
2235 assert_eq!(keyword, "requires");
2236 assert!(matches!(value, Expr::Predicate { .. }));
2237 }
2238
2239 #[test]
2240 fn suffix_predicate_with_arg() {
2241 let src = r#"rule R {
2242 when: X()
2243 requires: finding.code starts_with "allium."
2244}"#;
2245 let r = parse_ok(src);
2246 assert_eq!(r.diagnostics.len(), 0);
2247 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2248 let BlockItemKind::Clause { value, .. } = &b.items[1].kind else { panic!() };
2249 if let Expr::Predicate { subject, tail, .. } = value {
2250 assert!(matches!(subject.as_ref(), Expr::MemberAccess { .. }));
2251 assert_eq!(tail.len(), 2); } else {
2253 panic!("expected Predicate, got {:?}", value);
2254 }
2255 }
2256
2257 #[test]
2258 fn suffix_predicate_with_comparison() {
2259 let src = r#"rule R {
2260 when: X()
2261 requires: clause compares_literals = true
2262}"#;
2263 let r = parse_ok(src);
2264 assert_eq!(r.diagnostics.len(), 0);
2265 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2266 let BlockItemKind::Clause { value, .. } = &b.items[1].kind else { panic!() };
2267 if let Expr::Predicate { tail, .. } = value {
2269 assert_eq!(tail.len(), 1);
2270 assert!(matches!(tail[0], Expr::Comparison { .. }));
2271 } else {
2272 panic!("expected Predicate");
2273 }
2274 }
2275
2276 #[test]
2277 fn range_literal_in_list() {
2278 let src = r#"rule R {
2279 when: X()
2280 requires: width in [1..8]
2281}"#;
2282 let r = parse_ok(src);
2283 assert_eq!(r.diagnostics.len(), 0);
2284 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2285 let BlockItemKind::Clause { value, .. } = &b.items[1].kind else { panic!() };
2286 if let Expr::In { collection, .. } = value {
2288 if let Expr::ListLiteral { elements, .. } = collection.as_ref() {
2289 assert_eq!(elements.len(), 1);
2290 assert!(matches!(elements[0], Expr::Range { .. }));
2291 } else {
2292 panic!("expected ListLiteral");
2293 }
2294 } else {
2295 panic!("expected In");
2296 }
2297 }
2298
2299 #[test]
2300 fn exists_as_identifier() {
2301 let src = r#"rule R {
2302 when: X()
2303 ensures: CompletionItemAvailable(label: exists)
2304}"#;
2305 let r = parse_ok(src);
2306 assert_eq!(r.diagnostics.len(), 0);
2307 }
2308
2309 #[test]
2312 fn pipe_binds_tighter_than_or() {
2313 let src = "entity E { v: a or b | c }";
2315 let r = parse_ok(src);
2316 assert_eq!(r.diagnostics.len(), 0);
2317 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2318 let BlockItemKind::Assignment { value, .. } = &b.items[0].kind else { panic!() };
2319 let Expr::LogicalOp { op, right, .. } = value else {
2321 panic!("expected LogicalOp, got {value:?}");
2322 };
2323 assert_eq!(*op, LogicalOp::Or);
2324 assert!(matches!(right.as_ref(), Expr::Pipe { .. }));
2326 }
2327
2328 #[test]
2331 fn variant_with_pipe_base() {
2332 let src = "variant Mixed : TypeA | TypeB";
2333 let r = parse_ok(src);
2334 assert_eq!(r.diagnostics.len(), 0);
2335 let Decl::Variant(v) = &r.module.declarations[0] else { panic!() };
2336 assert!(matches!(v.base, Expr::Pipe { .. }));
2337 }
2338
2339 #[test]
2342 fn trigger_clause() {
2343 let src = "rule R { trigger: ExternalEvent(data)\n ensures: Done() }";
2344 let r = parse_ok(src);
2345 assert_eq!(r.diagnostics.len(), 0);
2346 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2347 let BlockItemKind::Clause { keyword, .. } = &b.items[0].kind else { panic!() };
2348 assert_eq!(keyword, "trigger");
2349 }
2350
2351 #[test]
2354 fn for_block_where_comparison() {
2355 let src = r#"rule R {
2356 when: X()
2357 for item in Items where item.status = active:
2358 ensures: Processed(item: item)
2359}"#;
2360 let r = parse_ok(src);
2361 assert_eq!(r.diagnostics.len(), 0);
2362 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2363 let BlockItemKind::ForBlock { filter, .. } = &b.items[1].kind else { panic!() };
2364 assert!(filter.is_some());
2365 assert!(matches!(filter.as_ref().unwrap(), Expr::Comparison { .. }));
2366 }
2367
2368 #[test]
2371 fn for_expr_where_comparison() {
2372 let src = r#"rule R {
2373 when: X()
2374 ensures:
2375 for item in Items where item.active = true:
2376 Processed(item: item)
2377}"#;
2378 let r = parse_ok(src);
2379 assert_eq!(r.diagnostics.len(), 0);
2380 }
2381
2382 #[test]
2385 fn if_else_if_else() {
2386 let src = r#"rule R {
2387 when: X(v)
2388 ensures:
2389 if v < 10: Small()
2390 else if v < 100: Medium()
2391 else: Large()
2392}"#;
2393 let r = parse_ok(src);
2394 assert_eq!(r.diagnostics.len(), 0);
2395 }
2396
2397 #[test]
2400 fn null_coalesce_and_optional_chain() {
2401 let src = "entity E { v: a?.b ?? fallback }";
2402 let r = parse_ok(src);
2403 assert_eq!(r.diagnostics.len(), 0);
2404 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2405 let BlockItemKind::Assignment { value, .. } = &b.items[0].kind else { panic!() };
2406 assert!(matches!(value, Expr::NullCoalesce { .. }));
2408 }
2409
2410 #[test]
2413 fn generic_type_nested() {
2414 let src = "entity E { v: List<Set<String>> }";
2415 let r = parse_ok(src);
2416 assert_eq!(r.diagnostics.len(), 0);
2417 }
2418
2419 #[test]
2422 fn collection_literals() {
2423 let src = r#"rule R {
2424 when: X()
2425 ensures:
2426 let s = {a, b, c}
2427 let l = [1, 2, 3]
2428 let o = {name: "test", count: 42}
2429 Done()
2430}"#;
2431 let r = parse_ok(src);
2432 assert_eq!(r.diagnostics.len(), 0);
2433 }
2434
2435 #[test]
2438 fn given_block() {
2439 let src = "given { viewer: User\n time: Timestamp }";
2440 let r = parse_ok(src);
2441 assert_eq!(r.diagnostics.len(), 0);
2442 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2443 assert_eq!(b.kind, BlockKind::Given);
2444 assert!(b.name.is_none());
2445 }
2446
2447 #[test]
2450 fn actor_block() {
2451 let src = "actor Admin { identified_by: User where role = admin }";
2452 let r = parse_ok(src);
2453 assert_eq!(r.diagnostics.len(), 0);
2454 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2455 assert_eq!(b.kind, BlockKind::Actor);
2456 }
2457
2458 #[test]
2461 fn join_lookup() {
2462 let src = "entity E { match: Other{field_a, field_b: value} }";
2463 let r = parse_ok(src);
2464 assert_eq!(r.diagnostics.len(), 0);
2465 let Decl::Block(b) = &r.module.declarations[0] else { panic!() };
2466 let BlockItemKind::Assignment { value, .. } = &b.items[0].kind else { panic!() };
2467 assert!(matches!(value, Expr::JoinLookup { .. }));
2468 }
2469
2470 #[test]
2473 fn includes_excludes() {
2474 let src = r#"rule R {
2475 when: X(a, b)
2476 requires: a.items includes b
2477 requires: a.items excludes b
2478 ensures: Done()
2479}"#;
2480 let r = parse_ok(src);
2481 assert_eq!(r.diagnostics.len(), 0);
2482 }
2483
2484 #[test]
2487 fn in_not_in_set() {
2488 let src = r#"rule R {
2489 when: X(s)
2490 requires: s in {a, b, c}
2491 requires: s not in {d, e}
2492 ensures: Done()
2493}"#;
2494 let r = parse_ok(src);
2495 assert_eq!(r.diagnostics.len(), 0);
2496 }
2497
2498 #[test]
2501 fn comprehensive_fixture() {
2502 let src = include_str!("../tests/fixtures/comprehensive-edge-cases.allium");
2503 let r = parse(src);
2504 assert_eq!(
2505 r.diagnostics.len(),
2506 0,
2507 "expected no errors in comprehensive fixture, got: {:?}",
2508 r.diagnostics.iter().map(|d| &d.message).collect::<Vec<_>>(),
2509 );
2510 assert!(r.module.declarations.len() > 30, "expected many declarations");
2511 }
2512
2513 #[test]
2516 fn error_expected_declaration() {
2517 let r = parse("+ invalid");
2518 assert!(r.diagnostics.len() >= 1);
2519 let msg = &r.diagnostics[0].message;
2520 assert!(msg.contains("expected declaration"), "got: {msg}");
2521 assert!(msg.contains("entity"), "should list valid options, got: {msg}");
2522 assert!(msg.contains("rule"), "should list valid options, got: {msg}");
2523 }
2524
2525 #[test]
2526 fn error_expected_expression() {
2527 let r = parse("entity E { v: }");
2528 assert!(r.diagnostics.len() >= 1);
2529 let msg = &r.diagnostics[0].message;
2530 assert!(msg.contains("expected expression"), "got: {msg}");
2531 assert!(msg.contains("identifier"), "should list valid starters, got: {msg}");
2532 }
2533
2534 #[test]
2535 fn error_expected_block_item() {
2536 let r = parse("entity E { + }");
2537 assert!(r.diagnostics.len() >= 1);
2538 let msg = &r.diagnostics[0].message;
2539 assert!(msg.contains("expected block item"), "got: {msg}");
2540 }
2541
2542 #[test]
2543 fn error_expected_identifier() {
2544 let r = parse("entity 123 {}");
2545 assert!(r.diagnostics.len() >= 1);
2546 let msg = &r.diagnostics[0].message;
2547 assert!(msg.contains("expected entity name"), "got: {msg}");
2549 assert!(msg.contains("number"), "should say what was found, got: {msg}");
2551 }
2552
2553 #[test]
2554 fn error_missing_brace() {
2555 let r = parse("entity E {");
2556 assert!(r.diagnostics.len() >= 1);
2557 let msg = &r.diagnostics[0].message;
2558 assert!(msg.contains("expected"), "got: {msg}");
2559 }
2560
2561 #[test]
2562 fn error_recovery_multiple() {
2563 let r = parse("entity E { + }\nentity F { - }");
2565 assert!(r.diagnostics.len() >= 2, "expected at least 2 errors, got {}", r.diagnostics.len());
2566 }
2567}